mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-12 17:27:50 +08:00
Break builtin_fn.go into many files.
This commit is contained in:
parent
ea2d5635ef
commit
cbe45b815d
1169
eval/builtin_fn.go
1169
eval/builtin_fn.go
File diff suppressed because it is too large
Load Diff
82
eval/builtin_fn_cmd.go
Normal file
82
eval/builtin_fn_cmd.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Command and process control.
|
||||
|
||||
var ErrNotInSameGroup = errors.New("not in the same process group")
|
||||
|
||||
func init() {
|
||||
addToBuiltinFns([]*BuiltinFn{
|
||||
// Command resolution
|
||||
{"resolve", resolveFn},
|
||||
{"has-external", hasExternal},
|
||||
{"search-external", searchExternal},
|
||||
|
||||
// Process control
|
||||
{"fg", fg},
|
||||
{"exec", execFn},
|
||||
{"exit", exit},
|
||||
})
|
||||
}
|
||||
|
||||
func resolveFn(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var cmd String
|
||||
ScanArgs(args, &cmd)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
out <- resolve(string(cmd), ec)
|
||||
}
|
||||
|
||||
func hasExternal(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var cmd String
|
||||
ScanArgs(args, &cmd)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
_, err := exec.LookPath(string(cmd))
|
||||
ec.OutputChan() <- Bool(err == nil)
|
||||
}
|
||||
|
||||
func searchExternal(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var cmd String
|
||||
ScanArgs(args, &cmd)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
path, err := exec.LookPath(string(cmd))
|
||||
maybeThrow(err)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
out <- String(path)
|
||||
}
|
||||
|
||||
func exit(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var codes []int
|
||||
ScanArgsVariadic(args, &codes)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
doexit := func(i int) {
|
||||
preExit(ec)
|
||||
os.Exit(i)
|
||||
}
|
||||
switch len(codes) {
|
||||
case 0:
|
||||
doexit(0)
|
||||
case 1:
|
||||
doexit(codes[0])
|
||||
default:
|
||||
throw(ErrArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func preExit(ec *EvalCtx) {
|
||||
err := ec.Daemon.Close()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
}
|
5
eval/builtin_fn_cmd_test.go
Normal file
5
eval/builtin_fn_cmd_test.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package eval
|
||||
|
||||
func init() {
|
||||
addToEvalTests([]evalTest{})
|
||||
}
|
250
eval/builtin_fn_container.go
Normal file
250
eval/builtin_fn_container.go
Normal file
|
@ -0,0 +1,250 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
||||
// Sequence, list and maps.
|
||||
|
||||
func init() {
|
||||
addToBuiltinFns([]*BuiltinFn{
|
||||
{"range", rangeFn},
|
||||
{"repeat", repeat},
|
||||
{"explode", explode},
|
||||
|
||||
{"assoc", assoc},
|
||||
{"dissoc", dissoc},
|
||||
|
||||
{"all", all},
|
||||
{"take", take},
|
||||
{"drop", drop},
|
||||
|
||||
{"has-key", hasKey},
|
||||
{"has-value", hasValue},
|
||||
|
||||
{"count", count},
|
||||
|
||||
{"keys", keys},
|
||||
})
|
||||
}
|
||||
|
||||
func rangeFn(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var step float64
|
||||
ScanOpts(opts, OptToScan{"step", &step, String("1")})
|
||||
|
||||
var lower, upper float64
|
||||
var err error
|
||||
|
||||
switch len(args) {
|
||||
case 1:
|
||||
upper, err = toFloat(args[0])
|
||||
maybeThrow(err)
|
||||
case 2:
|
||||
lower, err = toFloat(args[0])
|
||||
maybeThrow(err)
|
||||
upper, err = toFloat(args[1])
|
||||
maybeThrow(err)
|
||||
default:
|
||||
throw(ErrArgs)
|
||||
}
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
for f := lower; f < upper; f += step {
|
||||
out <- floatToString(f)
|
||||
}
|
||||
}
|
||||
|
||||
func repeat(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var (
|
||||
n int
|
||||
v Value
|
||||
)
|
||||
ScanArgs(args, &n, &v)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.OutputChan()
|
||||
for i := 0; i < n; i++ {
|
||||
out <- v
|
||||
}
|
||||
}
|
||||
|
||||
// explode puts each element of the argument.
|
||||
func explode(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var v IterableValue
|
||||
ScanArgs(args, &v)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
v.Iterate(func(e Value) bool {
|
||||
out <- e
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func assoc(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var (
|
||||
a Assocer
|
||||
k, v Value
|
||||
)
|
||||
ScanArgs(args, &a, &k, &v)
|
||||
TakeNoOpt(opts)
|
||||
ec.OutputChan() <- a.Assoc(k, v)
|
||||
}
|
||||
|
||||
func dissoc(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var (
|
||||
a Dissocer
|
||||
k Value
|
||||
)
|
||||
ScanArgs(args, &a, &k)
|
||||
TakeNoOpt(opts)
|
||||
ec.OutputChan() <- a.Dissoc(k)
|
||||
}
|
||||
|
||||
const allBufferSize = 4096
|
||||
|
||||
func all(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
valuesDone := make(chan struct{})
|
||||
go func() {
|
||||
for input := range ec.ports[0].Chan {
|
||||
ec.ports[1].Chan <- input
|
||||
}
|
||||
close(valuesDone)
|
||||
}()
|
||||
_, err := io.Copy(ec.ports[1].File, ec.ports[0].File)
|
||||
<-valuesDone
|
||||
if err != nil {
|
||||
throwf("cannot copy byte input: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func take(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var n int
|
||||
iterate := ScanArgsOptionalInput(ec, args, &n)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
i := 0
|
||||
iterate(func(v Value) {
|
||||
if i < n {
|
||||
out <- v
|
||||
}
|
||||
i++
|
||||
})
|
||||
}
|
||||
|
||||
func drop(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var n int
|
||||
iterate := ScanArgsOptionalInput(ec, args, &n)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
i := 0
|
||||
iterate(func(v Value) {
|
||||
if i >= n {
|
||||
out <- v
|
||||
}
|
||||
i++
|
||||
})
|
||||
}
|
||||
|
||||
func hasValue(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
|
||||
var container, value Value
|
||||
var found bool
|
||||
|
||||
ScanArgs(args, &container, &value)
|
||||
|
||||
switch container := container.(type) {
|
||||
case Iterable:
|
||||
container.Iterate(func(v Value) bool {
|
||||
found = (v == value)
|
||||
return !found
|
||||
})
|
||||
case MapLike:
|
||||
container.IterateKey(func(v Value) bool {
|
||||
found = (container.IndexOne(v) == value)
|
||||
return !found
|
||||
})
|
||||
default:
|
||||
throw(fmt.Errorf("argument of type '%s' is not iterable", container.Kind()))
|
||||
}
|
||||
|
||||
ec.ports[1].Chan <- Bool(found)
|
||||
}
|
||||
|
||||
func hasKey(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
|
||||
var container, key Value
|
||||
var found bool
|
||||
|
||||
ScanArgs(args, &container, &key)
|
||||
|
||||
switch container := container.(type) {
|
||||
case HasKeyer:
|
||||
found = container.HasKey(key)
|
||||
case Lener:
|
||||
// XXX(xiaq): Not all types that implement Lener have numerical indices
|
||||
err := util.PCall(func() {
|
||||
ParseAndFixListIndex(ToString(key), container.Len())
|
||||
})
|
||||
found = (err == nil)
|
||||
default:
|
||||
throw(fmt.Errorf("couldn't get key or index of type '%s'", container.Kind()))
|
||||
}
|
||||
|
||||
ec.ports[1].Chan <- Bool(found)
|
||||
}
|
||||
|
||||
func count(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
|
||||
var n int
|
||||
switch len(args) {
|
||||
case 0:
|
||||
// Count inputs.
|
||||
ec.IterateInputs(func(Value) {
|
||||
n++
|
||||
})
|
||||
case 1:
|
||||
// Get length of argument.
|
||||
v := args[0]
|
||||
if lener, ok := v.(Lener); ok {
|
||||
n = lener.Len()
|
||||
} else if iterator, ok := v.(Iterable); ok {
|
||||
iterator.Iterate(func(Value) bool {
|
||||
n++
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
throw(fmt.Errorf("cannot get length of a %s", v.Kind()))
|
||||
}
|
||||
default:
|
||||
throw(errors.New("want 0 or 1 argument"))
|
||||
}
|
||||
ec.ports[1].Chan <- String(strconv.Itoa(n))
|
||||
}
|
||||
|
||||
func keys(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
|
||||
var iter IterateKeyer
|
||||
ScanArgs(args, &iter)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
|
||||
iter.IterateKey(func(v Value) bool {
|
||||
out <- v
|
||||
return true
|
||||
})
|
||||
}
|
44
eval/builtin_fn_container_test.go
Normal file
44
eval/builtin_fn_container_test.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package eval
|
||||
|
||||
func init() {
|
||||
addToEvalTests([]evalTest{
|
||||
{`range 3`, want{out: strs("0", "1", "2")}},
|
||||
{`range 1 3`, want{out: strs("1", "2")}},
|
||||
{`range 0 10 &step=3`, want{out: strs("0", "3", "6", "9")}},
|
||||
{`repeat 4 foo`, want{out: strs("foo", "foo", "foo", "foo")}},
|
||||
{`explode [foo bar]`, want{out: strs("foo", "bar")}},
|
||||
|
||||
{`put (assoc [0] 0 zero)[0]`, want{out: strs("zero")}},
|
||||
{`put (assoc [&] k v)[k]`, want{out: strs("v")}},
|
||||
{`put (assoc [&k=v] k v2)[k]`, want{out: strs("v2")}},
|
||||
{`has-key (dissoc [&k=v] k) k`, want{out: bools(false)}},
|
||||
|
||||
{`put foo bar | all`, want{out: strs("foo", "bar")}},
|
||||
{`echo foobar | all`, want{bytesOut: []byte("foobar\n")}},
|
||||
{`{ put foo bar; echo foobar } | all`,
|
||||
want{out: strs("foo", "bar"), bytesOut: []byte("foobar\n")}},
|
||||
{`range 100 | take 2`, want{out: strs("0", "1")}},
|
||||
{`range 100 | drop 98`, want{out: strs("98", "99")}},
|
||||
|
||||
{`has-key [foo bar] 0`, want{out: bools(true)}},
|
||||
{`has-key [foo bar] 0:1`, want{out: bools(true)}},
|
||||
{`has-key [foo bar] 0:20`, want{out: bools(false)}},
|
||||
{`has-key [&lorem=ipsum &foo=bar] lorem`, want{out: bools(true)}},
|
||||
{`has-key [&lorem=ipsum &foo=bar] loremwsq`, want{out: bools(false)}},
|
||||
{`has-value [&lorem=ipsum &foo=bar] lorem`, want{out: bools(false)}},
|
||||
{`has-value [&lorem=ipsum &foo=bar] bar`, want{out: bools(true)}},
|
||||
{`has-value [foo bar] bar`, want{out: bools(true)}},
|
||||
{`has-value [foo bar] badehose`, want{out: bools(false)}},
|
||||
{`has-value "foo" o`, want{out: bools(true)}},
|
||||
{`has-value "foo" d`, want{out: bools(false)}},
|
||||
|
||||
{`range 100 | count`, want{out: strs("100")}},
|
||||
{`count [(range 100)]`, want{out: strs("100")}},
|
||||
|
||||
{`keys [&]`, wantNothing},
|
||||
{`keys [&a=foo]`, want{out: strs("a")}},
|
||||
// Windows does not have an external sort command. Disabled until we have a
|
||||
// builtin sort command.
|
||||
// {`keys [&a=foo &b=bar] | each echo | sort | each put`, want{out: strs("a", "b")}},
|
||||
})
|
||||
}
|
154
eval/builtin_fn_flow.go
Normal file
154
eval/builtin_fn_flow.go
Normal file
|
@ -0,0 +1,154 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Flow control.
|
||||
|
||||
func init() {
|
||||
addToBuiltinFns([]*BuiltinFn{
|
||||
{"run-parallel", runParallel},
|
||||
|
||||
// Iterations.
|
||||
{"each", each},
|
||||
{"peach", peach},
|
||||
|
||||
// Exception and control
|
||||
{"fail", fail},
|
||||
{"multi-error", multiErrorFn},
|
||||
{"return", returnFn},
|
||||
{"break", breakFn},
|
||||
{"continue", continueFn},
|
||||
})
|
||||
}
|
||||
|
||||
func runParallel(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var functions []CallableValue
|
||||
ScanArgsVariadic(args, &functions)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
var waitg sync.WaitGroup
|
||||
waitg.Add(len(functions))
|
||||
exceptions := make([]*Exception, len(functions))
|
||||
for i, function := range functions {
|
||||
go func(ec *EvalCtx, function CallableValue, exception **Exception) {
|
||||
err := ec.PCall(function, NoArgs, NoOpts)
|
||||
if err != nil {
|
||||
*exception = err.(*Exception)
|
||||
}
|
||||
waitg.Done()
|
||||
}(ec.fork("[run-parallel function]"), function, &exceptions[i])
|
||||
}
|
||||
|
||||
waitg.Wait()
|
||||
maybeThrow(ComposeExceptionsFromPipeline(exceptions))
|
||||
}
|
||||
|
||||
// each takes a single closure and applies it to all input values.
|
||||
func each(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var f CallableValue
|
||||
iterate := ScanArgsOptionalInput(ec, args, &f)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
broken := false
|
||||
iterate(func(v Value) {
|
||||
if broken {
|
||||
return
|
||||
}
|
||||
// NOTE We don't have the position range of the closure in the source.
|
||||
// Ideally, it should be kept in the Closure itself.
|
||||
newec := ec.fork("closure of each")
|
||||
newec.ports[0] = DevNullClosedChan
|
||||
ex := newec.PCall(f, []Value{v}, NoOpts)
|
||||
ClosePorts(newec.ports)
|
||||
|
||||
if ex != nil {
|
||||
switch ex.(*Exception).Cause {
|
||||
case nil, Continue:
|
||||
// nop
|
||||
case Break:
|
||||
broken = true
|
||||
default:
|
||||
throw(ex)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// peach takes a single closure and applies it to all input values in parallel.
|
||||
func peach(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var f CallableValue
|
||||
iterate := ScanArgsOptionalInput(ec, args, &f)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
var w sync.WaitGroup
|
||||
broken := false
|
||||
var err error
|
||||
iterate(func(v Value) {
|
||||
if broken || err != nil {
|
||||
return
|
||||
}
|
||||
w.Add(1)
|
||||
go func() {
|
||||
// NOTE We don't have the position range of the closure in the source.
|
||||
// Ideally, it should be kept in the Closure itself.
|
||||
newec := ec.fork("closure of each")
|
||||
newec.ports[0] = DevNullClosedChan
|
||||
ex := newec.PCall(f, []Value{v}, NoOpts)
|
||||
ClosePorts(newec.ports)
|
||||
|
||||
if ex != nil {
|
||||
switch ex.(*Exception).Cause {
|
||||
case nil, Continue:
|
||||
// nop
|
||||
case Break:
|
||||
broken = true
|
||||
default:
|
||||
err = ex
|
||||
}
|
||||
}
|
||||
w.Done()
|
||||
}()
|
||||
})
|
||||
w.Wait()
|
||||
maybeThrow(err)
|
||||
}
|
||||
|
||||
func fail(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var msg String
|
||||
ScanArgs(args, &msg)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
throw(errors.New(string(msg)))
|
||||
}
|
||||
|
||||
func multiErrorFn(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var excs []*Exception
|
||||
ScanArgsVariadic(args, &excs)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
throw(PipelineError{excs})
|
||||
}
|
||||
|
||||
func returnFn(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
throw(Return)
|
||||
}
|
||||
|
||||
func breakFn(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
throw(Break)
|
||||
}
|
||||
|
||||
func continueFn(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
throw(Continue)
|
||||
}
|
20
eval/builtin_fn_flow_test.go
Normal file
20
eval/builtin_fn_flow_test.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package eval
|
||||
|
||||
func init() {
|
||||
addToEvalTests([]evalTest{
|
||||
{`run-parallel { put lorem } { echo ipsum }`,
|
||||
want{out: strs("lorem"), bytesOut: []byte("ipsum\n")}},
|
||||
|
||||
{`put 1 233 | each put`, want{out: strs("1", "233")}},
|
||||
{`echo "1\n233" | each put`, want{out: strs("1", "233")}},
|
||||
{`each put [1 233]`, want{out: strs("1", "233")}},
|
||||
{`range 10 | each [x]{ if (== $x 4) { break }; put $x }`,
|
||||
want{out: strs("0", "1", "2", "3")}},
|
||||
{`range 10 | each [x]{ if (== $x 4) { fail haha }; put $x }`,
|
||||
want{out: strs("0", "1", "2", "3"), err: errAny}},
|
||||
// TODO: test peach
|
||||
|
||||
{`fail haha`, want{err: errAny}},
|
||||
{`return`, want{err: Return}},
|
||||
})
|
||||
}
|
130
eval/builtin_fn_fs.go
Normal file
130
eval/builtin_fn_fs.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/elves/elvish/store/storedefs"
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
||||
// Filesystem.
|
||||
|
||||
var ErrStoreNotConnected = errors.New("store not connected")
|
||||
|
||||
func init() {
|
||||
addToBuiltinFns([]*BuiltinFn{
|
||||
// Directory
|
||||
{"cd", cd},
|
||||
{"dir-history", dirs},
|
||||
|
||||
// Path
|
||||
{"path-abs", WrapStringToStringError(filepath.Abs)},
|
||||
{"path-base", WrapStringToString(filepath.Base)},
|
||||
{"path-clean", WrapStringToString(filepath.Clean)},
|
||||
{"path-dir", WrapStringToString(filepath.Dir)},
|
||||
{"path-ext", WrapStringToString(filepath.Ext)},
|
||||
{"eval-symlinks", WrapStringToStringError(filepath.EvalSymlinks)},
|
||||
{"tilde-abbr", tildeAbbr},
|
||||
|
||||
// File types
|
||||
{"-is-dir", isDir},
|
||||
})
|
||||
}
|
||||
|
||||
func WrapStringToString(f func(string) string) BuiltinFnImpl {
|
||||
return func(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
s := mustGetOneString(args)
|
||||
ec.ports[1].Chan <- String(f(s))
|
||||
}
|
||||
}
|
||||
|
||||
func WrapStringToStringError(f func(string) (string, error)) BuiltinFnImpl {
|
||||
return func(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
s := mustGetOneString(args)
|
||||
result, err := f(s)
|
||||
maybeThrow(err)
|
||||
ec.ports[1].Chan <- String(result)
|
||||
}
|
||||
}
|
||||
|
||||
var errMustBeOneString = errors.New("must be one string argument")
|
||||
|
||||
func mustGetOneString(args []Value) string {
|
||||
if len(args) != 1 {
|
||||
throw(errMustBeOneString)
|
||||
}
|
||||
s, ok := args[0].(String)
|
||||
if !ok {
|
||||
throw(errMustBeOneString)
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func cd(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
|
||||
var dir string
|
||||
if len(args) == 0 {
|
||||
dir = mustGetHome("")
|
||||
} else if len(args) == 1 {
|
||||
dir = ToString(args[0])
|
||||
} else {
|
||||
throw(ErrArgs)
|
||||
}
|
||||
|
||||
cdInner(dir, ec)
|
||||
}
|
||||
|
||||
func cdInner(dir string, ec *EvalCtx) {
|
||||
maybeThrow(Chdir(dir, ec.Daemon))
|
||||
}
|
||||
|
||||
var dirDescriptor = NewStructDescriptor("path", "score")
|
||||
|
||||
func dirs(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
if ec.Daemon == nil {
|
||||
throw(ErrStoreNotConnected)
|
||||
}
|
||||
dirs, err := ec.Daemon.Dirs(storedefs.NoBlacklist)
|
||||
if err != nil {
|
||||
throw(errors.New("store error: " + err.Error()))
|
||||
}
|
||||
out := ec.ports[1].Chan
|
||||
for _, dir := range dirs {
|
||||
out <- &Struct{dirDescriptor, []Value{
|
||||
String(dir.Path),
|
||||
floatToString(dir.Score),
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
func tildeAbbr(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var pathv String
|
||||
ScanArgs(args, &pathv)
|
||||
path := string(pathv)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
out <- String(util.TildeAbbr(path))
|
||||
}
|
||||
|
||||
func isDir(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var pathv String
|
||||
ScanArgs(args, &pathv)
|
||||
path := string(pathv)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
ec.OutputChan() <- Bool(isDirInner(path))
|
||||
}
|
||||
|
||||
func isDirInner(path string) bool {
|
||||
fi, err := os.Stat(path)
|
||||
return err == nil && fi.Mode().IsDir()
|
||||
}
|
7
eval/builtin_fn_fs_test.go
Normal file
7
eval/builtin_fn_fs_test.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package eval
|
||||
|
||||
func init() {
|
||||
addToEvalTests([]evalTest{
|
||||
{`path-base a/b/c.png`, want{out: strs("c.png")}},
|
||||
})
|
||||
}
|
204
eval/builtin_fn_io.go
Normal file
204
eval/builtin_fn_io.go
Normal file
|
@ -0,0 +1,204 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Input and output.
|
||||
|
||||
func init() {
|
||||
addToBuiltinFns([]*BuiltinFn{
|
||||
// Value output
|
||||
{"put", put},
|
||||
|
||||
// Bytes output
|
||||
{"print", print},
|
||||
{"echo", echo},
|
||||
{"pprint", pprint},
|
||||
{"repr", repr},
|
||||
|
||||
// Bytes to value
|
||||
{"slurp", slurp},
|
||||
{"from-lines", fromLines},
|
||||
{"from-json", fromJSON},
|
||||
|
||||
// Value to bytes
|
||||
{"to-lines", toLines},
|
||||
{"to-json", toJSON},
|
||||
|
||||
// File and pipe
|
||||
{"fopen", fopen},
|
||||
{"fclose", fclose},
|
||||
{"pipe", pipe},
|
||||
{"prclose", prclose},
|
||||
{"pwclose", pwclose},
|
||||
})
|
||||
}
|
||||
|
||||
func put(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
out := ec.ports[1].Chan
|
||||
for _, a := range args {
|
||||
out <- a
|
||||
}
|
||||
}
|
||||
|
||||
func print(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var sepv String
|
||||
ScanOpts(opts, OptToScan{"sep", &sepv, String(" ")})
|
||||
|
||||
out := ec.ports[1].File
|
||||
sep := string(sepv)
|
||||
for i, arg := range args {
|
||||
if i > 0 {
|
||||
out.WriteString(sep)
|
||||
}
|
||||
out.WriteString(ToString(arg))
|
||||
}
|
||||
}
|
||||
|
||||
func echo(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
print(ec, args, opts)
|
||||
ec.ports[1].File.WriteString("\n")
|
||||
}
|
||||
|
||||
func pprint(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
out := ec.ports[1].File
|
||||
for _, arg := range args {
|
||||
out.WriteString(arg.Repr(0))
|
||||
out.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func repr(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
out := ec.ports[1].File
|
||||
for i, arg := range args {
|
||||
if i > 0 {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
out.WriteString(arg.Repr(NoPretty))
|
||||
}
|
||||
out.WriteString("\n")
|
||||
}
|
||||
|
||||
func slurp(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
in := ec.ports[0].File
|
||||
out := ec.ports[1].Chan
|
||||
|
||||
all, err := ioutil.ReadAll(in)
|
||||
maybeThrow(err)
|
||||
out <- String(string(all))
|
||||
}
|
||||
|
||||
func fromLines(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
in := ec.ports[0].File
|
||||
out := ec.ports[1].Chan
|
||||
|
||||
linesToChan(in, out)
|
||||
}
|
||||
|
||||
// fromJSON parses a stream of JSON data into Value's.
|
||||
func fromJSON(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
in := ec.ports[0].File
|
||||
out := ec.ports[1].Chan
|
||||
|
||||
dec := json.NewDecoder(in)
|
||||
var v interface{}
|
||||
for {
|
||||
err := dec.Decode(&v)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
throw(err)
|
||||
}
|
||||
out <- FromJSONInterface(v)
|
||||
}
|
||||
}
|
||||
|
||||
func toLines(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
iterate := ScanArgsOptionalInput(ec, args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].File
|
||||
|
||||
iterate(func(v Value) {
|
||||
fmt.Fprintln(out, ToString(v))
|
||||
})
|
||||
}
|
||||
|
||||
// toJSON converts a stream of Value's to JSON data.
|
||||
func toJSON(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
iterate := ScanArgsOptionalInput(ec, args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].File
|
||||
|
||||
enc := json.NewEncoder(out)
|
||||
iterate(func(v Value) {
|
||||
err := enc.Encode(v)
|
||||
maybeThrow(err)
|
||||
})
|
||||
}
|
||||
|
||||
func fopen(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var namev String
|
||||
ScanArgs(args, &namev)
|
||||
name := string(namev)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
// TODO support opening files for writing etc as well.
|
||||
out := ec.ports[1].Chan
|
||||
f, err := os.Open(name)
|
||||
maybeThrow(err)
|
||||
out <- File{f}
|
||||
}
|
||||
|
||||
func fclose(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var f File
|
||||
ScanArgs(args, &f)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
maybeThrow(f.inner.Close())
|
||||
}
|
||||
|
||||
func pipe(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
out := ec.ports[1].Chan
|
||||
maybeThrow(err)
|
||||
out <- Pipe{r, w}
|
||||
}
|
||||
|
||||
func prclose(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var p Pipe
|
||||
ScanArgs(args, &p)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
maybeThrow(p.r.Close())
|
||||
}
|
||||
|
||||
func pwclose(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var p Pipe
|
||||
ScanArgs(args, &p)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
maybeThrow(p.w.Close())
|
||||
}
|
30
eval/builtin_fn_io_test.go
Normal file
30
eval/builtin_fn_io_test.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package eval
|
||||
|
||||
func init() {
|
||||
addToEvalTests([]evalTest{
|
||||
{`put foo bar`, want{out: strs("foo", "bar")}},
|
||||
|
||||
{`print [foo bar]`, want{bytesOut: []byte("[foo bar]")}},
|
||||
{`echo [foo bar]`, want{bytesOut: []byte("[foo bar]\n")}},
|
||||
{`pprint [foo bar]`, want{bytesOut: []byte("[\n foo\n bar\n]\n")}},
|
||||
|
||||
{`print "a\nb" | slurp`, want{out: strs("a\nb")}},
|
||||
{`print "a\nb" | from-lines`, want{out: strs("a", "b")}},
|
||||
{`print "a\nb\n" | from-lines`, want{out: strs("a", "b")}},
|
||||
{`echo '{"k": "v", "a": [1, 2]}' '"foo"' | from-json`,
|
||||
want{out: []Value{
|
||||
ConvertToMap(map[Value]Value{
|
||||
String("k"): String("v"),
|
||||
String("a"): NewList(strs("1", "2")...)}),
|
||||
String("foo"),
|
||||
}}},
|
||||
{`echo 'invalid' | from-json`, want{err: errAny}},
|
||||
|
||||
{`put "l\norem" ipsum | to-lines`,
|
||||
want{bytesOut: []byte("l\norem\nipsum\n")}},
|
||||
{`put [&k=v &a=[1 2]] foo | to-json`,
|
||||
want{bytesOut: []byte(`{"a":["1","2"],"k":"v"}
|
||||
"foo"
|
||||
`)}},
|
||||
})
|
||||
}
|
170
eval/builtin_fn_num.go
Normal file
170
eval/builtin_fn_num.go
Normal file
|
@ -0,0 +1,170 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Numerical operations.
|
||||
|
||||
func init() {
|
||||
addToBuiltinFns([]*BuiltinFn{
|
||||
// Comparison
|
||||
{"<",
|
||||
wrapNumCompare(func(a, b float64) bool { return a < b })},
|
||||
{"<=",
|
||||
wrapNumCompare(func(a, b float64) bool { return a <= b })},
|
||||
{"==",
|
||||
wrapNumCompare(func(a, b float64) bool { return a == b })},
|
||||
{"!=",
|
||||
wrapNumCompare(func(a, b float64) bool { return a != b })},
|
||||
{">",
|
||||
wrapNumCompare(func(a, b float64) bool { return a > b })},
|
||||
{">=",
|
||||
wrapNumCompare(func(a, b float64) bool { return a >= b })},
|
||||
|
||||
// Arithmetics
|
||||
{"+", plus},
|
||||
{"-", minus},
|
||||
{"*", times},
|
||||
{"/", slash},
|
||||
{"^", pow},
|
||||
{"%", mod},
|
||||
|
||||
// Random
|
||||
{"rand", randFn},
|
||||
{"randint", randint},
|
||||
})
|
||||
}
|
||||
|
||||
func wrapNumCompare(cmp func(a, b float64) bool) BuiltinFnImpl {
|
||||
return func(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
floats := make([]float64, len(args))
|
||||
for i, a := range args {
|
||||
f, err := toFloat(a)
|
||||
maybeThrow(err)
|
||||
floats[i] = f
|
||||
}
|
||||
result := true
|
||||
for i := 0; i < len(floats)-1; i++ {
|
||||
if !cmp(floats[i], floats[i+1]) {
|
||||
result = false
|
||||
break
|
||||
}
|
||||
}
|
||||
ec.OutputChan() <- Bool(result)
|
||||
}
|
||||
}
|
||||
|
||||
func plus(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var nums []float64
|
||||
ScanArgsVariadic(args, &nums)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
sum := 0.0
|
||||
for _, f := range nums {
|
||||
sum += f
|
||||
}
|
||||
out <- floatToString(sum)
|
||||
}
|
||||
|
||||
func minus(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var (
|
||||
sum float64
|
||||
nums []float64
|
||||
)
|
||||
ScanArgsVariadic(args, &sum, &nums)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
if len(nums) == 0 {
|
||||
// Unary -
|
||||
sum = -sum
|
||||
} else {
|
||||
for _, f := range nums {
|
||||
sum -= f
|
||||
}
|
||||
}
|
||||
out <- floatToString(sum)
|
||||
}
|
||||
|
||||
func times(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var nums []float64
|
||||
ScanArgsVariadic(args, &nums)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
prod := 1.0
|
||||
for _, f := range nums {
|
||||
prod *= f
|
||||
}
|
||||
out <- floatToString(prod)
|
||||
}
|
||||
|
||||
func slash(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
if len(args) == 0 {
|
||||
// cd /
|
||||
cdInner("/", ec)
|
||||
return
|
||||
}
|
||||
// Division
|
||||
divide(ec, args, opts)
|
||||
}
|
||||
|
||||
func divide(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var (
|
||||
prod float64
|
||||
nums []float64
|
||||
)
|
||||
ScanArgsVariadic(args, &prod, &nums)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
for _, f := range nums {
|
||||
prod /= f
|
||||
}
|
||||
out <- floatToString(prod)
|
||||
}
|
||||
|
||||
func pow(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var b, p float64
|
||||
ScanArgs(args, &b, &p)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
out <- floatToString(math.Pow(b, p))
|
||||
}
|
||||
|
||||
func mod(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var a, b int
|
||||
ScanArgs(args, &a, &b)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
out <- String(strconv.Itoa(a % b))
|
||||
}
|
||||
|
||||
func randFn(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoArg(args)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
out <- floatToString(rand.Float64())
|
||||
}
|
||||
|
||||
func randint(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var low, high int
|
||||
ScanArgs(args, &low, &high)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
if low >= high {
|
||||
throw(ErrArgs)
|
||||
}
|
||||
out := ec.ports[1].Chan
|
||||
i := low + rand.Intn(high-low)
|
||||
out <- String(strconv.Itoa(i))
|
||||
}
|
20
eval/builtin_fn_num_test.go
Normal file
20
eval/builtin_fn_num_test.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package eval
|
||||
|
||||
func init() {
|
||||
addToEvalTests([]evalTest{
|
||||
{`== 1 1.0`, want{out: bools(true)}},
|
||||
{`== 10 0xa`, want{out: bools(true)}},
|
||||
{`== a a`, want{err: errAny}},
|
||||
{`> 0x10 1`, want{out: bools(true)}},
|
||||
|
||||
// TODO test more edge cases
|
||||
{"+ 233100 233", want{out: strs("233333")}},
|
||||
{"- 233333 233100", want{out: strs("233")}},
|
||||
{"- 233", want{out: strs("-233")}},
|
||||
{"* 353 661", want{out: strs("233333")}},
|
||||
{"/ 233333 353", want{out: strs("661")}},
|
||||
{"/ 1 0", want{out: strs("+Inf")}},
|
||||
{"^ 16 2", want{out: strs("256")}},
|
||||
{"% 23 7", want{out: strs("2")}},
|
||||
})
|
||||
}
|
239
eval/builtin_fn_str.go
Normal file
239
eval/builtin_fn_str.go
Normal file
|
@ -0,0 +1,239 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
||||
// String operations.
|
||||
|
||||
var ErrInput = errors.New("input error")
|
||||
|
||||
func init() {
|
||||
addToBuiltinFns([]*BuiltinFn{
|
||||
{"<s",
|
||||
wrapStrCompare(func(a, b string) bool { return a < b })},
|
||||
{"<=s",
|
||||
wrapStrCompare(func(a, b string) bool { return a <= b })},
|
||||
{"==s",
|
||||
wrapStrCompare(func(a, b string) bool { return a == b })},
|
||||
{"!=s",
|
||||
wrapStrCompare(func(a, b string) bool { return a != b })},
|
||||
{">s",
|
||||
wrapStrCompare(func(a, b string) bool { return a > b })},
|
||||
{">=s",
|
||||
wrapStrCompare(func(a, b string) bool { return a >= b })},
|
||||
|
||||
{"to-string", toString},
|
||||
|
||||
{"joins", joins},
|
||||
{"splits", splits},
|
||||
{"replaces", replaces},
|
||||
|
||||
{"ord", ord},
|
||||
{"base", base},
|
||||
|
||||
{"wcswidth", wcswidth},
|
||||
{"-override-wcwidth", overrideWcwidth},
|
||||
|
||||
{"has-prefix", hasPrefix},
|
||||
{"has-suffix", hasSuffix},
|
||||
|
||||
{"eawk", eawk},
|
||||
})
|
||||
}
|
||||
|
||||
func wrapStrCompare(cmp func(a, b string) bool) BuiltinFnImpl {
|
||||
return func(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
for _, a := range args {
|
||||
if _, ok := a.(String); !ok {
|
||||
throw(ErrArgs)
|
||||
}
|
||||
}
|
||||
result := true
|
||||
for i := 0; i < len(args)-1; i++ {
|
||||
if !cmp(string(args[i].(String)), string(args[i+1].(String))) {
|
||||
result = false
|
||||
break
|
||||
}
|
||||
}
|
||||
ec.OutputChan() <- Bool(result)
|
||||
}
|
||||
}
|
||||
|
||||
// toString converts all arguments to strings.
|
||||
func toString(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
TakeNoOpt(opts)
|
||||
out := ec.OutputChan()
|
||||
for _, a := range args {
|
||||
out <- String(ToString(a))
|
||||
}
|
||||
}
|
||||
|
||||
// joins joins all input strings with a delimiter.
|
||||
func joins(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var sepv String
|
||||
iterate := ScanArgsOptionalInput(ec, args, &sepv)
|
||||
sep := string(sepv)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
var buf bytes.Buffer
|
||||
iterate(func(v Value) {
|
||||
if s, ok := v.(String); ok {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteString(sep)
|
||||
}
|
||||
buf.WriteString(string(s))
|
||||
} else {
|
||||
throwf("join wants string input, got %s", v.Kind())
|
||||
}
|
||||
})
|
||||
out := ec.ports[1].Chan
|
||||
out <- String(buf.String())
|
||||
}
|
||||
|
||||
// splits splits an argument strings by a delimiter and writes all pieces.
|
||||
func splits(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var s, sep String
|
||||
ScanArgs(args, &sep, &s)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
parts := strings.Split(string(s), string(sep))
|
||||
for _, p := range parts {
|
||||
out <- String(p)
|
||||
}
|
||||
}
|
||||
|
||||
func replaces(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var (
|
||||
old, repl, s String
|
||||
optMax int
|
||||
)
|
||||
ScanArgs(args, &old, &repl, &s)
|
||||
ScanOpts(opts, OptToScan{"max", &optMax, String("-1")})
|
||||
|
||||
ec.ports[1].Chan <- String(strings.Replace(string(s), string(old), string(repl), optMax))
|
||||
}
|
||||
|
||||
func ord(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var s String
|
||||
ScanArgs(args, &s)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
for _, r := range s {
|
||||
out <- String("0x" + strconv.FormatInt(int64(r), 16))
|
||||
}
|
||||
}
|
||||
|
||||
// ErrBadBase is thrown by the "base" builtin if the base is smaller than 2 or
|
||||
// greater than 36.
|
||||
var ErrBadBase = errors.New("bad base")
|
||||
|
||||
func base(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var (
|
||||
b int
|
||||
nums []int
|
||||
)
|
||||
ScanArgsVariadic(args, &b, &nums)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
if b < 2 || b > 36 {
|
||||
throw(ErrBadBase)
|
||||
}
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
|
||||
for _, num := range nums {
|
||||
out <- String(strconv.FormatInt(int64(num), b))
|
||||
}
|
||||
}
|
||||
|
||||
func wcswidth(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var s String
|
||||
ScanArgs(args, &s)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
out := ec.ports[1].Chan
|
||||
out <- String(strconv.Itoa(util.Wcswidth(string(s))))
|
||||
}
|
||||
|
||||
func overrideWcwidth(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var (
|
||||
s String
|
||||
w int
|
||||
)
|
||||
ScanArgs(args, &s, &w)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
r, err := toRune(s)
|
||||
maybeThrow(err)
|
||||
util.OverrideWcwidth(r, w)
|
||||
}
|
||||
|
||||
func hasPrefix(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var s, prefix String
|
||||
ScanArgs(args, &s, &prefix)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
ec.OutputChan() <- Bool(strings.HasPrefix(string(s), string(prefix)))
|
||||
}
|
||||
|
||||
func hasSuffix(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var s, suffix String
|
||||
ScanArgs(args, &s, &suffix)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
ec.OutputChan() <- Bool(strings.HasSuffix(string(s), string(suffix)))
|
||||
}
|
||||
|
||||
var eawkWordSep = regexp.MustCompile("[ \t]+")
|
||||
|
||||
// eawk takes a function. For each line in the input stream, it calls the
|
||||
// function with the line and the words in the line. The words are found by
|
||||
// stripping the line and splitting the line by whitespaces. The function may
|
||||
// call break and continue. Overall this provides a similar functionality to
|
||||
// awk, hence the name.
|
||||
func eawk(ec *EvalCtx, args []Value, opts map[string]Value) {
|
||||
var f CallableValue
|
||||
iterate := ScanArgsOptionalInput(ec, args, &f)
|
||||
TakeNoOpt(opts)
|
||||
|
||||
broken := false
|
||||
iterate(func(v Value) {
|
||||
if broken {
|
||||
return
|
||||
}
|
||||
line, ok := v.(String)
|
||||
if !ok {
|
||||
throw(ErrInput)
|
||||
}
|
||||
args := []Value{line}
|
||||
for _, field := range eawkWordSep.Split(strings.Trim(string(line), " \t"), -1) {
|
||||
args = append(args, String(field))
|
||||
}
|
||||
|
||||
newec := ec.fork("fn of eawk")
|
||||
// TODO: Close port 0 of newec.
|
||||
ex := newec.PCall(f, args, NoOpts)
|
||||
ClosePorts(newec.ports)
|
||||
|
||||
if ex != nil {
|
||||
switch ex.(*Exception).Cause {
|
||||
case nil, Continue:
|
||||
// nop
|
||||
case Break:
|
||||
broken = true
|
||||
default:
|
||||
throw(ex)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
26
eval/builtin_fn_str_test.go
Normal file
26
eval/builtin_fn_str_test.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package eval
|
||||
|
||||
func init() {
|
||||
addToEvalTests([]evalTest{
|
||||
{`==s haha haha`, want{out: bools(true)}},
|
||||
{`==s 10 10.0`, want{out: bools(false)}},
|
||||
{`<s a b`, want{out: bools(true)}},
|
||||
{`<s 2 10`, want{out: bools(false)}},
|
||||
|
||||
{`joins : [/usr /bin /tmp]`, want{out: strs("/usr:/bin:/tmp")}},
|
||||
{`splits : /usr:/bin:/tmp`, want{out: strs("/usr", "/bin", "/tmp")}},
|
||||
{`replaces : / ":usr:bin:tmp"`, want{out: strs("/usr/bin/tmp")}},
|
||||
{`replaces &max=2 : / :usr:bin:tmp`, want{out: strs("/usr/bin:tmp")}},
|
||||
|
||||
{`ord a`, want{out: strs("0x61")}},
|
||||
{`base 16 42 233`, want{out: strs("2a", "e9")}},
|
||||
{`wcswidth 你好`, want{out: strs("4")}},
|
||||
|
||||
{`has-prefix golang go`, want{out: bools(true)}},
|
||||
{`has-prefix golang x`, want{out: bools(false)}},
|
||||
{`has-suffix golang x`, want{out: bools(false)}},
|
||||
|
||||
{`echo " ax by cz \n11\t22 33" | eawk [@a]{ put $a[-1] }`,
|
||||
want{out: strs("cz", "33")}},
|
||||
})
|
||||
}
|
|
@ -7,126 +7,14 @@ var builtinFnTests = []evalTest{
|
|||
{"kind-of bare 'str' [] [&] []{ }",
|
||||
want{out: strs("string", "string", "list", "map", "fn")}},
|
||||
|
||||
{`put foo bar`, want{out: strs("foo", "bar")}},
|
||||
{`explode [foo bar]`, want{out: strs("foo", "bar")}},
|
||||
|
||||
{`print [foo bar]`, want{bytesOut: []byte("[foo bar]")}},
|
||||
{`echo [foo bar]`, want{bytesOut: []byte("[foo bar]\n")}},
|
||||
{`pprint [foo bar]`, want{bytesOut: []byte("[\n foo\n bar\n]\n")}},
|
||||
|
||||
{`print "a\nb" | slurp`, want{out: strs("a\nb")}},
|
||||
{`print "a\nb" | from-lines`, want{out: strs("a", "b")}},
|
||||
{`print "a\nb\n" | from-lines`, want{out: strs("a", "b")}},
|
||||
{`echo '{"k": "v", "a": [1, 2]}' '"foo"' | from-json`,
|
||||
want{out: []Value{
|
||||
ConvertToMap(map[Value]Value{
|
||||
String("k"): String("v"),
|
||||
String("a"): NewList(strs("1", "2")...)}),
|
||||
String("foo"),
|
||||
}}},
|
||||
{`echo 'invalid' | from-json`, want{err: errAny}},
|
||||
|
||||
{`put "l\norem" ipsum | to-lines`,
|
||||
want{bytesOut: []byte("l\norem\nipsum\n")}},
|
||||
{`put [&k=v &a=[1 2]] foo | to-json`,
|
||||
want{bytesOut: []byte(`{"a":["1","2"],"k":"v"}
|
||||
"foo"
|
||||
`)}},
|
||||
|
||||
{`joins : [/usr /bin /tmp]`, want{out: strs("/usr:/bin:/tmp")}},
|
||||
{`splits : /usr:/bin:/tmp`, want{out: strs("/usr", "/bin", "/tmp")}},
|
||||
{`replaces : / ":usr:bin:tmp"`, want{out: strs("/usr/bin/tmp")}},
|
||||
{`replaces &max=2 : / :usr:bin:tmp`, want{out: strs("/usr/bin:tmp")}},
|
||||
{`has-prefix golang go`, want{out: bools(true)}},
|
||||
{`has-prefix golang x`, want{out: bools(false)}},
|
||||
{`has-suffix golang x`, want{out: bools(false)}},
|
||||
|
||||
{`keys [&]`, wantNothing},
|
||||
{`keys [&a=foo]`, want{out: strs("a")}},
|
||||
// Windows does not have an external sort command. Disabled until we have a
|
||||
// builtin sort command.
|
||||
// {`keys [&a=foo &b=bar] | each echo | sort | each put`, want{out: strs("a", "b")}},
|
||||
|
||||
{`==s haha haha`, want{out: bools(true)}},
|
||||
{`==s 10 10.0`, want{out: bools(false)}},
|
||||
{`<s a b`, want{out: bools(true)}},
|
||||
{`<s 2 10`, want{out: bools(false)}},
|
||||
|
||||
{`run-parallel { put lorem } { echo ipsum }`,
|
||||
want{out: strs("lorem"), bytesOut: []byte("ipsum\n")}},
|
||||
|
||||
{`fail haha`, want{err: errAny}},
|
||||
{`return`, want{err: Return}},
|
||||
|
||||
{`f=(constantly foo); $f; $f`, want{out: strs("foo", "foo")}},
|
||||
{`(constantly foo) bad`, want{err: errAny}},
|
||||
{`put 1 233 | each put`, want{out: strs("1", "233")}},
|
||||
{`echo "1\n233" | each put`, want{out: strs("1", "233")}},
|
||||
{`each put [1 233]`, want{out: strs("1", "233")}},
|
||||
{`range 10 | each [x]{ if (== $x 4) { break }; put $x }`,
|
||||
want{out: strs("0", "1", "2", "3")}},
|
||||
{`range 10 | each [x]{ if (== $x 4) { fail haha }; put $x }`,
|
||||
want{out: strs("0", "1", "2", "3"), err: errAny}},
|
||||
{`repeat 4 foo`, want{out: strs("foo", "foo", "foo", "foo")}},
|
||||
// TODO: test peach
|
||||
|
||||
{`range 3`, want{out: strs("0", "1", "2")}},
|
||||
{`range 1 3`, want{out: strs("1", "2")}},
|
||||
{`range 0 10 &step=3`, want{out: strs("0", "3", "6", "9")}},
|
||||
{`put foo bar | all`, want{out: strs("foo", "bar")}},
|
||||
{`echo foobar | all`, want{bytesOut: []byte("foobar\n")}},
|
||||
{`{ put foo bar; echo foobar } | all`,
|
||||
want{out: strs("foo", "bar"), bytesOut: []byte("foobar\n")}},
|
||||
{`range 100 | take 2`, want{out: strs("0", "1")}},
|
||||
{`range 100 | drop 98`, want{out: strs("98", "99")}},
|
||||
{`range 100 | count`, want{out: strs("100")}},
|
||||
{`count [(range 100)]`, want{out: strs("100")}},
|
||||
|
||||
{`echo " ax by cz \n11\t22 33" | eawk [@a]{ put $a[-1] }`,
|
||||
want{out: strs("cz", "33")}},
|
||||
|
||||
{`path-base a/b/c.png`, want{out: strs("c.png")}},
|
||||
|
||||
// TODO test more edge cases
|
||||
{"+ 233100 233", want{out: strs("233333")}},
|
||||
{"- 233333 233100", want{out: strs("233")}},
|
||||
{"- 233", want{out: strs("-233")}},
|
||||
{"* 353 661", want{out: strs("233333")}},
|
||||
{"/ 233333 353", want{out: strs("661")}},
|
||||
{"/ 1 0", want{out: strs("+Inf")}},
|
||||
{"^ 16 2", want{out: strs("256")}},
|
||||
{"% 23 7", want{out: strs("2")}},
|
||||
|
||||
{`== 1 1.0`, want{out: bools(true)}},
|
||||
{`== 10 0xa`, want{out: bools(true)}},
|
||||
{`== a a`, want{err: errAny}},
|
||||
{`> 0x10 1`, want{out: bools(true)}},
|
||||
|
||||
{`is 1 1`, want{out: bools(true)}},
|
||||
{`is [] []`, want{out: bools(true)}},
|
||||
{`is [1] [1]`, want{out: bools(false)}},
|
||||
{`eq 1 1`, want{out: bools(true)}},
|
||||
{`eq [] []`, want{out: bools(true)}},
|
||||
|
||||
{`ord a`, want{out: strs("0x61")}},
|
||||
{`base 16 42 233`, want{out: strs("2a", "e9")}},
|
||||
{`wcswidth 你好`, want{out: strs("4")}},
|
||||
{`has-key [foo bar] 0`, want{out: bools(true)}},
|
||||
{`has-key [foo bar] 0:1`, want{out: bools(true)}},
|
||||
{`has-key [foo bar] 0:20`, want{out: bools(false)}},
|
||||
{`has-key [&lorem=ipsum &foo=bar] lorem`, want{out: bools(true)}},
|
||||
{`has-key [&lorem=ipsum &foo=bar] loremwsq`, want{out: bools(false)}},
|
||||
{`has-value [&lorem=ipsum &foo=bar] lorem`, want{out: bools(false)}},
|
||||
{`has-value [&lorem=ipsum &foo=bar] bar`, want{out: bools(true)}},
|
||||
{`has-value [foo bar] bar`, want{out: bools(true)}},
|
||||
{`has-value [foo bar] badehose`, want{out: bools(false)}},
|
||||
{`has-value "foo" o`, want{out: bools(true)}},
|
||||
{`has-value "foo" d`, want{out: bools(false)}},
|
||||
|
||||
{`put (assoc [0] 0 zero)[0]`, want{out: strs("zero")}},
|
||||
{`put (assoc [&] k v)[k]`, want{out: strs("v")}},
|
||||
{`put (assoc [&k=v] k v2)[k]`, want{out: strs("v2")}},
|
||||
{`has-key (dissoc [&k=v] k) k`, want{out: bools(false)}},
|
||||
{`f=(constantly foo); $f; $f`, want{out: strs("foo", "foo")}},
|
||||
{`(constantly foo) bad`, want{err: errAny}},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user