github.com/traefik/yaegi@v0.15.1/cmd/yaegi/run.go (about)

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