elvish/pkg/eval/external_cmd.go

116 lines
2.7 KiB
Go
Raw Normal View History

2016-02-19 05:52:05 +08:00
package eval
import (
"errors"
"io/ioutil"
2016-02-19 05:52:05 +08:00
"os"
"os/exec"
2016-02-19 05:52:05 +08:00
"syscall"
2019-12-24 04:00:59 +08:00
"github.com/elves/elvish/pkg/eval/vals"
"github.com/elves/elvish/pkg/parse"
"github.com/elves/elvish/pkg/util"
2017-08-31 02:52:27 +08:00
"github.com/xiaq/persistent/hash"
2016-02-19 05:52:05 +08:00
)
var (
2019-04-19 05:15:34 +08:00
// ErrExternalCmdOpts is thrown when an external command is passed Elvish
// options.
//
// TODO: Catch this kind of errors at compilation time.
ErrExternalCmdOpts = errors.New("external commands don't accept elvish options")
2019-04-19 05:15:34 +08:00
// ErrImplicitCdNoArg is thrown when an implicit cd form is passed arguments.
ErrImplicitCdNoArg = errors.New("implicit cd accepts no arguments")
)
2016-02-19 05:52:05 +08:00
// ExternalCmd is an external command.
type ExternalCmd struct {
Name string
}
func (ExternalCmd) Kind() string {
return "fn"
}
2017-08-31 01:47:50 +08:00
func (e ExternalCmd) Equal(a interface{}) bool {
return e == a
}
2017-08-31 02:52:27 +08:00
func (e ExternalCmd) Hash() uint32 {
return hash.String(e.Name)
}
func (e ExternalCmd) Repr(int) string {
return "<external " + parse.Quote(e.Name) + ">"
2016-02-19 05:52:05 +08:00
}
// Call calls an external command.
func (e ExternalCmd) Call(fm *Frame, argVals []interface{}, opts map[string]interface{}) error {
if len(opts) > 0 {
return ErrExternalCmdOpts
}
if util.DontSearch(e.Name) {
2016-02-19 05:52:05 +08:00
stat, err := os.Stat(e.Name)
if err == nil && stat.IsDir() {
// implicit cd
if len(argVals) > 0 {
2019-04-19 05:15:34 +08:00
return ErrImplicitCdNoArg
2016-02-19 05:52:05 +08:00
}
return fm.Chdir(e.Name)
2016-02-19 05:52:05 +08:00
}
}
files := make([]*os.File, len(fm.ports))
for i, port := range fm.ports {
if port != nil {
files[i] = port.File
}
2016-02-19 05:52:05 +08:00
}
args := make([]string, len(argVals)+1)
for i, a := range argVals {
// TODO: Maybe we should enforce string arguments instead of coercing
// all args to strings.
2018-02-15 17:14:05 +08:00
args[i+1] = vals.ToString(a)
2016-02-19 05:52:05 +08:00
}
path, err := exec.LookPath(e.Name)
2016-02-19 05:52:05 +08:00
if err != nil {
return err
2016-02-19 05:52:05 +08:00
}
args[0] = path
sys := makeSysProcAttr(fm.background)
proc, err := os.StartProcess(path, args, &os.ProcAttr{Files: files, Sys: sys})
2016-02-19 05:52:05 +08:00
if err != nil {
return err
2016-02-19 05:52:05 +08:00
}
state, err := proc.Wait()
2016-02-19 05:52:05 +08:00
if err != nil {
// This should be a can't happen situation. Nonetheless, treat it as a
// soft error rather than panicing since the Go documentation is not
// explicit that this can only happen if we make a mistake. Such as
// calling `Wait` twice on a particular process object.
return err
2016-02-19 05:52:05 +08:00
}
return NewExternalCmdExit(e.Name, state.Sys().(syscall.WaitStatus), proc.Pid)
2016-02-19 05:52:05 +08:00
}
// EachExternal calls f for each name that can resolve to an external command.
//
// TODO(xiaq): Windows support. See https://golang.org/pkg/os/#Chmod for why
// this doesn't work on Windows as currently written.
func EachExternal(f func(string)) {
for _, dir := range searchPaths() {
// TODO(xiaq): Ignore error.
infos, _ := ioutil.ReadDir(dir)
for _, info := range infos {
if !info.IsDir() && (info.Mode()&0111 != 0) {
f(info.Name())
}
}
}
}