feature: command line provide sub-commands
The Yaegi command line has been changed to provide subcommands. The following sub-commands are provided: - extract (formerly goexports) - help - run - test The previous behaviour is now implemented in run command which is the default, so the change should be transparent. In run command, prepare the ability to run a package or a directory in addition to a file. Not implemented yet The test command is not implemented yet. The extract command is meant to generate wrappers to non stdlib packages. Fixes #639
This commit is contained in:
107
cmd/yaegi/extract.go
Normal file
107
cmd/yaegi/extract.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/yaegi/extract"
|
||||
)
|
||||
|
||||
func extractCmd(arg []string) error {
|
||||
var licensePath string
|
||||
var importPath string
|
||||
|
||||
eflag := flag.NewFlagSet("run", flag.ContinueOnError)
|
||||
eflag.StringVar(&licensePath, "license", "", "path to a LICENSE file")
|
||||
eflag.StringVar(&importPath, "import_path", "", "the namespace for the extracted symbols")
|
||||
eflag.Usage = func() {
|
||||
fmt.Println("Usage: yaegi extract [options] packages...")
|
||||
fmt.Println("Options:")
|
||||
eflag.PrintDefaults()
|
||||
}
|
||||
|
||||
if err := eflag.Parse(arg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := eflag.Args()
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("missing package")
|
||||
}
|
||||
|
||||
license, err := genLicense(licensePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ext := extract.Extractor{
|
||||
Dest: path.Base(wd),
|
||||
License: license,
|
||||
}
|
||||
|
||||
for _, pkgIdent := range args {
|
||||
var buf bytes.Buffer
|
||||
importPath, err := ext.Extract(pkgIdent, importPath, &buf)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
oFile := strings.Replace(importPath, "/", "_", -1) + ".go"
|
||||
f, err := os.Create(oFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(f, &buf); err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// genLicense generates the correct LICENSE header text from the provided
|
||||
// path to a LICENSE file.
|
||||
func genLicense(fname string) (string, error) {
|
||||
if fname == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not open LICENSE file: %v", err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
license := new(strings.Builder)
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
txt := sc.Text()
|
||||
if txt != "" {
|
||||
txt = " " + txt
|
||||
}
|
||||
license.WriteString("//" + txt + "\n")
|
||||
}
|
||||
if sc.Err() != nil {
|
||||
return "", fmt.Errorf("could not scan LICENSE file: %v", err)
|
||||
}
|
||||
|
||||
return license.String(), nil
|
||||
}
|
||||
43
cmd/yaegi/help.go
Normal file
43
cmd/yaegi/help.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const usage = `Yaegi is a Go interpreter.
|
||||
|
||||
Usage:
|
||||
|
||||
yaegi [command] [arguments]
|
||||
|
||||
The commands are:
|
||||
|
||||
extract generate a wrapper file from a source package
|
||||
help print usage information
|
||||
run execute a Go program from source
|
||||
test execute test functions in a Go package
|
||||
|
||||
Use "yaegi help <command>" for more information about a command.
|
||||
|
||||
If no command is given or if the first argument is not a command, then
|
||||
the run command is assumed.
|
||||
`
|
||||
|
||||
func help(arg []string) error {
|
||||
var cmd string
|
||||
if len(arg) > 0 {
|
||||
cmd = arg[0]
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case Extract:
|
||||
return extractCmd([]string{"-h"})
|
||||
case Help, "", "-h", "--help":
|
||||
fmt.Print(usage)
|
||||
return nil
|
||||
case Run:
|
||||
return run([]string{"-h"})
|
||||
case Test:
|
||||
return fmt.Errorf("help: test not implemented")
|
||||
default:
|
||||
return fmt.Errorf("help: invalid yaegi command: %v", cmd)
|
||||
}
|
||||
}
|
||||
132
cmd/yaegi/run.go
Normal file
132
cmd/yaegi/run.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/yaegi/interp"
|
||||
"github.com/containous/yaegi/stdlib"
|
||||
"github.com/containous/yaegi/stdlib/syscall"
|
||||
"github.com/containous/yaegi/stdlib/unrestricted"
|
||||
"github.com/containous/yaegi/stdlib/unsafe"
|
||||
)
|
||||
|
||||
func run(arg []string) error {
|
||||
var interactive bool
|
||||
var useSyscall bool
|
||||
var useUnrestricted bool
|
||||
var useUnsafe bool
|
||||
var tags string
|
||||
var cmd string
|
||||
var err error
|
||||
|
||||
rflag := flag.NewFlagSet("run", flag.ContinueOnError)
|
||||
rflag.BoolVar(&interactive, "i", false, "start an interactive REPL")
|
||||
rflag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
|
||||
rflag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
|
||||
rflag.StringVar(&tags, "tags", "", "set a list of build tags")
|
||||
rflag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
|
||||
rflag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
|
||||
rflag.Usage = func() {
|
||||
fmt.Println("Usage: yaegi run [options] [path] [args]")
|
||||
fmt.Println("Options:")
|
||||
rflag.PrintDefaults()
|
||||
}
|
||||
if err = rflag.Parse(arg); err != nil {
|
||||
return err
|
||||
}
|
||||
args := rflag.Args()
|
||||
|
||||
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
|
||||
i.Use(stdlib.Symbols)
|
||||
i.Use(interp.Symbols)
|
||||
if useSyscall {
|
||||
i.Use(syscall.Symbols)
|
||||
}
|
||||
if useUnsafe {
|
||||
i.Use(unsafe.Symbols)
|
||||
}
|
||||
if useUnrestricted {
|
||||
// Use of unrestricted symbols should always follow use of stdlib symbols, to update them.
|
||||
i.Use(unrestricted.Symbols)
|
||||
}
|
||||
|
||||
if cmd != "" {
|
||||
i.REPL(strings.NewReader(cmd), os.Stderr)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
if interactive || cmd == "" {
|
||||
i.REPL(os.Stdin, os.Stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip first os arg to set command line as expected by interpreted main
|
||||
path := args[0]
|
||||
os.Args = arg[1:]
|
||||
flag.CommandLine = flag.NewFlagSet(path, flag.ExitOnError)
|
||||
|
||||
if isPackageName(path) {
|
||||
err = runPackage(i, path)
|
||||
} else {
|
||||
if isDir(path) {
|
||||
err = runDir(i, path)
|
||||
} else {
|
||||
err = runFile(i, path)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if interactive {
|
||||
i.REPL(os.Stdin, os.Stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isPackageName(path string) bool {
|
||||
return !strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "./") && !strings.HasPrefix(path, "../")
|
||||
}
|
||||
|
||||
func isDir(path string) bool {
|
||||
fi, err := os.Lstat(path)
|
||||
return err == nil && fi.IsDir()
|
||||
}
|
||||
|
||||
func runPackage(i *interp.Interpreter, path string) error {
|
||||
return fmt.Errorf("runPackage not implemented")
|
||||
}
|
||||
|
||||
func runDir(i *interp.Interpreter, path string) error {
|
||||
return fmt.Errorf("runDir not implemented")
|
||||
}
|
||||
|
||||
func runFile(i *interp.Interpreter, path string) error {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s := string(b); strings.HasPrefix(s, "#!") {
|
||||
// Allow executable go scripts, Have the same behavior as in interactive mode.
|
||||
s = strings.Replace(s, "#!", "//", 1)
|
||||
i.REPL(strings.NewReader(s), os.Stdout)
|
||||
} else {
|
||||
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
|
||||
i.Name = path
|
||||
_, err := i.Eval(s)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if p, ok := err.(interp.Panic); ok {
|
||||
fmt.Println(string(p.Stack))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -85,94 +85,53 @@ Debugging support (may be removed at any time):
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
"github.com/containous/yaegi/interp"
|
||||
"github.com/containous/yaegi/stdlib"
|
||||
"github.com/containous/yaegi/stdlib/syscall"
|
||||
"github.com/containous/yaegi/stdlib/unrestricted"
|
||||
"github.com/containous/yaegi/stdlib/unsafe"
|
||||
const (
|
||||
Extract = "extract"
|
||||
Help = "help"
|
||||
Run = "run"
|
||||
Test = "test"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var interactive bool
|
||||
var useSyscall bool
|
||||
var useUnrestricted bool
|
||||
var useUnsafe bool
|
||||
var tags string
|
||||
var cmd string
|
||||
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
|
||||
flag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
|
||||
flag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
|
||||
flag.StringVar(&tags, "tags", "", "set a list of build tags")
|
||||
flag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
|
||||
flag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage:", os.Args[0], "[options] [script] [args]")
|
||||
fmt.Println("Options:")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
log.SetFlags(log.Lshortfile)
|
||||
var err error
|
||||
var exitCode int
|
||||
|
||||
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
|
||||
i.Use(stdlib.Symbols)
|
||||
i.Use(interp.Symbols)
|
||||
if useSyscall {
|
||||
i.Use(syscall.Symbols)
|
||||
}
|
||||
if useUnsafe {
|
||||
i.Use(unsafe.Symbols)
|
||||
}
|
||||
if useUnrestricted {
|
||||
// Use of unrestricted symbols should always follow use of stdlib symbols, to update them.
|
||||
i.Use(unrestricted.Symbols)
|
||||
log.SetFlags(log.Lshortfile) // Ease debugging.
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
cmd = os.Args[1]
|
||||
}
|
||||
|
||||
if cmd != `` {
|
||||
i.REPL(strings.NewReader(cmd), os.Stderr)
|
||||
switch cmd {
|
||||
case Extract:
|
||||
err = extractCmd(os.Args[2:])
|
||||
case Help, "-h", "--help":
|
||||
err = help(os.Args[2:])
|
||||
case Run:
|
||||
err = run(os.Args[2:])
|
||||
case Test:
|
||||
err = fmt.Errorf("test not implemented")
|
||||
default:
|
||||
// If no command is given, fallback to default "run" command.
|
||||
// This allows scripts starting with "#!/usr/bin/env yaegi",
|
||||
// as passing more than 1 argument to #! executable may be not supported
|
||||
// on all platforms.
|
||||
cmd = Run
|
||||
err = run(os.Args[1:])
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
if interactive || cmd == `` {
|
||||
i.REPL(os.Stdin, os.Stdout)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Skip first os arg to set command line as expected by interpreted main
|
||||
os.Args = os.Args[1:]
|
||||
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||
|
||||
b, err := ioutil.ReadFile(args[0])
|
||||
if err != nil {
|
||||
log.Fatal("Could not read file: ", args[0])
|
||||
}
|
||||
|
||||
if s := string(b); strings.HasPrefix(s, "#!") {
|
||||
// Allow executable go scripts, Have the same behavior as in interactive mode.
|
||||
s = strings.Replace(s, "#!", "//", 1)
|
||||
i.REPL(strings.NewReader(s), os.Stdout)
|
||||
} else {
|
||||
// Files not starting with "#!" are supposed to be pure Go, directly Evaled.
|
||||
i.Name = args[0]
|
||||
_, err := i.Eval(s)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if p, ok := err.(interp.Panic); ok {
|
||||
fmt.Println(string(p.Stack))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if interactive {
|
||||
i.REPL(os.Stdin, os.Stdout)
|
||||
if err != nil && !errors.Is(err, flag.ErrHelp) {
|
||||
err = fmt.Errorf("%s: %w", cmd, err)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
exitCode = 1
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user