github.com/switchupcb/yaegi@v0.10.2/cmd/yaegi/run.go (about)

     1  package main
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"go/build"
     7  	"io/ioutil"
     8  	"os"
     9  	"reflect"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/switchupcb/yaegi/interp"
    14  	"github.com/switchupcb/yaegi/stdlib"
    15  	"github.com/switchupcb/yaegi/stdlib/syscall"
    16  	"github.com/switchupcb/yaegi/stdlib/unrestricted"
    17  	"github.com/switchupcb/yaegi/stdlib/unsafe"
    18  )
    19  
    20  func run(arg []string) error {
    21  	var interactive bool
    22  	var noAutoImport bool
    23  	var tags string
    24  	var cmd string
    25  	var err error
    26  
    27  	// The following flags are initialized from environment.
    28  	useSyscall, _ := strconv.ParseBool(os.Getenv("YAEGI_SYSCALL"))
    29  	useUnrestricted, _ := strconv.ParseBool(os.Getenv("YAEGI_UNRESTRICTED"))
    30  	useUnsafe, _ := strconv.ParseBool(os.Getenv("YAEGI_UNSAFE"))
    31  
    32  	rflag := flag.NewFlagSet("run", flag.ContinueOnError)
    33  	rflag.BoolVar(&interactive, "i", false, "start an interactive REPL")
    34  	rflag.BoolVar(&useSyscall, "syscall", useSyscall, "include syscall symbols")
    35  	rflag.BoolVar(&useUnrestricted, "unrestricted", useUnrestricted, "include unrestricted symbols")
    36  	rflag.StringVar(&tags, "tags", "", "set a list of build tags")
    37  	rflag.BoolVar(&useUnsafe, "unsafe", useUnsafe, "include unsafe symbols")
    38  	rflag.BoolVar(&noAutoImport, "noautoimport", false, "do not auto import pre-compiled packages. Import names that would result in collisions (e.g. rand from crypto/rand and rand from math/rand) are automatically renamed (crypto_rand and math_rand)")
    39  	rflag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
    40  	rflag.Usage = func() {
    41  		fmt.Println("Usage: yaegi run [options] [path] [args]")
    42  		fmt.Println("Options:")
    43  		rflag.PrintDefaults()
    44  	}
    45  	if err = rflag.Parse(arg); err != nil {
    46  		return err
    47  	}
    48  	args := rflag.Args()
    49  
    50  	i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
    51  	if err := i.Use(stdlib.Symbols); err != nil {
    52  		return err
    53  	}
    54  	if err := i.Use(interp.Symbols); err != nil {
    55  		return err
    56  	}
    57  	if useSyscall {
    58  		if err := i.Use(syscall.Symbols); err != nil {
    59  			return err
    60  		}
    61  		// Using a environment var allows a nested interpreter to import the syscall package.
    62  		if err := os.Setenv("YAEGI_SYSCALL", "1"); err != nil {
    63  			return err
    64  		}
    65  	}
    66  	if useUnsafe {
    67  		if err := i.Use(unsafe.Symbols); err != nil {
    68  			return err
    69  		}
    70  		if err := os.Setenv("YAEGI_UNSAFE", "1"); err != nil {
    71  			return err
    72  		}
    73  	}
    74  	if useUnrestricted {
    75  		// Use of unrestricted symbols should always follow stdlib and syscall symbols, to update them.
    76  		if err := i.Use(unrestricted.Symbols); err != nil {
    77  			return err
    78  		}
    79  		if err := os.Setenv("YAEGI_UNRESTRICTED", "1"); err != nil {
    80  			return err
    81  		}
    82  	}
    83  
    84  	if cmd != "" {
    85  		if !noAutoImport {
    86  			i.ImportUsed()
    87  		}
    88  		var v reflect.Value
    89  		v, err = i.Eval(cmd)
    90  		if len(args) == 0 && v.IsValid() {
    91  			fmt.Println(v)
    92  		}
    93  	}
    94  
    95  	if len(args) == 0 {
    96  		if cmd == "" || interactive {
    97  			showError(err)
    98  			if !noAutoImport {
    99  				i.ImportUsed()
   100  			}
   101  			_, err = i.REPL()
   102  		}
   103  		return err
   104  	}
   105  
   106  	// Skip first os arg to set command line as expected by interpreted main.
   107  	path := args[0]
   108  	os.Args = arg
   109  	flag.CommandLine = flag.NewFlagSet(path, flag.ExitOnError)
   110  
   111  	if isFile(path) {
   112  		err = runFile(i, path, noAutoImport)
   113  	} else {
   114  		_, err = i.EvalPath(path)
   115  	}
   116  
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	if interactive {
   122  		_, err = i.REPL()
   123  	}
   124  	return err
   125  }
   126  
   127  func isFile(path string) bool {
   128  	fi, err := os.Stat(path)
   129  	return err == nil && fi.Mode().IsRegular()
   130  }
   131  
   132  func runFile(i *interp.Interpreter, path string, noAutoImport bool) error {
   133  	b, err := ioutil.ReadFile(path)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	if s := string(b); strings.HasPrefix(s, "#!") {
   139  		// Allow executable go scripts, Have the same behavior as in interactive mode.
   140  		s = strings.Replace(s, "#!", "//", 1)
   141  		if !noAutoImport {
   142  			i.ImportUsed()
   143  		}
   144  		_, err = i.Eval(s)
   145  		return err
   146  	}
   147  
   148  	// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
   149  	_, err = i.EvalPath(path)
   150  	return err
   151  }
   152  
   153  func showError(err error) {
   154  	if err == nil {
   155  		return
   156  	}
   157  	fmt.Fprintln(os.Stderr, err)
   158  	if p, ok := err.(interp.Panic); ok {
   159  		fmt.Fprintln(os.Stderr, string(p.Stack))
   160  	}
   161  }