mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
eval: Remove ScanArgs and friends.
This commit is contained in:
parent
f3501bbeba
commit
37e491d31c
149
eval/scan.go
149
eval/scan.go
|
@ -1,149 +0,0 @@
|
|||
package eval
|
||||
|
||||
// This file implements facilities for "scanning" arguments and options.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/elves/elvish/eval/vals"
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
||||
// ScanArgs scans arguments into pointers to supported argument types. If the
|
||||
// arguments cannot be scanned, an error is thrown.
|
||||
func ScanArgs(src []interface{}, dstPtrs ...interface{}) {
|
||||
if len(src) != len(dstPtrs) {
|
||||
throwf("arity mistmatch: want %d arguments, got %d", len(dstPtrs), len(src))
|
||||
}
|
||||
for i, value := range src {
|
||||
mustScanToGo(value, dstPtrs[i])
|
||||
}
|
||||
}
|
||||
|
||||
// ScanArgsVariadic is like ScanArgs, but the last element of args should be a
|
||||
// pointer to a slice, and the rest of arguments will be scanned into it.
|
||||
func ScanArgsVariadic(src []interface{}, dstPtrs ...interface{}) {
|
||||
if len(src) < len(dstPtrs)-1 {
|
||||
throwf("arity mistmatch: want at least %d arguments, got %d", len(dstPtrs)-1, len(src))
|
||||
}
|
||||
ScanArgs(src[:len(dstPtrs)-1], dstPtrs[:len(dstPtrs)-1]...)
|
||||
|
||||
// Scan the rest of arguments into a slice.
|
||||
rest := src[len(dstPtrs)-1:]
|
||||
restDst := reflect.ValueOf(dstPtrs[len(dstPtrs)-1])
|
||||
if restDst.Kind() != reflect.Ptr || restDst.Elem().Kind() != reflect.Slice {
|
||||
throwf("internal bug: %T to ScanArgsVariadic, need pointer to slice", dstPtrs[len(dstPtrs)-1])
|
||||
}
|
||||
scanned := reflect.MakeSlice(restDst.Elem().Type(), len(rest), len(rest))
|
||||
for i, value := range rest {
|
||||
mustScanToGo(value, scanned.Index(i).Addr().Interface())
|
||||
}
|
||||
reflect.Indirect(restDst).Set(scanned)
|
||||
}
|
||||
|
||||
// ScanArgsOptionalInput is like ScanArgs, but the argument can contain an
|
||||
// optional iterable value at the end containing inputs to the function. The
|
||||
// return value is a function that iterates the iterable value if it exists, or
|
||||
// the input otherwise.
|
||||
func ScanArgsOptionalInput(ec *Frame, src []interface{}, dstArgs ...interface{}) func(func(interface{})) {
|
||||
switch len(src) {
|
||||
case len(dstArgs):
|
||||
ScanArgs(src, dstArgs...)
|
||||
return ec.IterateInputs
|
||||
case len(dstArgs) + 1:
|
||||
ScanArgs(src[:len(dstArgs)], dstArgs...)
|
||||
value := src[len(dstArgs)]
|
||||
return func(f func(interface{})) {
|
||||
err := vals.Iterate(value, func(v interface{}) bool {
|
||||
f(v)
|
||||
return true
|
||||
})
|
||||
maybeThrow(err)
|
||||
}
|
||||
default:
|
||||
throwf("arity mistmatch: want %d or %d arguments, got %d", len(dstArgs), len(dstArgs)+1, len(src))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptToScan is a data structure for an option that is intended to be used in
|
||||
// ScanOpts.
|
||||
type OptToScan struct {
|
||||
Name string
|
||||
Ptr interface{}
|
||||
Default interface{}
|
||||
}
|
||||
|
||||
// ScanOpts scans options from a map.
|
||||
func ScanOpts(m map[string]interface{}, opts ...OptToScan) {
|
||||
scanned := make(map[string]bool)
|
||||
for _, opt := range opts {
|
||||
a := opt.Ptr
|
||||
value, ok := m[opt.Name]
|
||||
if !ok {
|
||||
value = opt.Default
|
||||
}
|
||||
mustScanToGo(value, a)
|
||||
scanned[opt.Name] = true
|
||||
}
|
||||
for key := range m {
|
||||
if !scanned[key] {
|
||||
throwf("unknown option %s", parse.Quote(key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ScanOptsToStruct scan options from a map like ScanOpts except the destination
|
||||
// is a struct whose fields correspond to the options to be parsed. A field
|
||||
// named FieldName corresponds to the option named field-name, unless the field
|
||||
// has a explicit "name" tag.
|
||||
func ScanOptsToStruct(m map[string]interface{}, structPtr interface{}) {
|
||||
ptrValue := reflect.ValueOf(structPtr)
|
||||
if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct {
|
||||
throwf("internal bug: need struct ptr for ScanOptsToStruct, got %T", structPtr)
|
||||
}
|
||||
struc := ptrValue.Elem()
|
||||
|
||||
// fieldIdxForOpt maps option name to the index of field in struc.
|
||||
fieldIdxForOpt := make(map[string]int)
|
||||
for i := 0; i < struc.Type().NumField(); i++ {
|
||||
// ignore unexported fields
|
||||
if !struc.Field(i).CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
f := struc.Type().Field(i)
|
||||
optName := f.Tag.Get("name")
|
||||
if optName == "" {
|
||||
optName = util.CamelToDashed(f.Name)
|
||||
}
|
||||
fieldIdxForOpt[optName] = i
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
fieldIdx, ok := fieldIdxForOpt[k]
|
||||
if !ok {
|
||||
throwf("unknown option %s", parse.Quote(k))
|
||||
}
|
||||
mustScanToGo(v, struc.Field(fieldIdx).Addr().Interface())
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNoArgAccepted = errors.New("no argument accepted")
|
||||
ErrNoOptAccepted = errors.New("no option accepted")
|
||||
)
|
||||
|
||||
func TakeNoArg(args []interface{}) {
|
||||
if len(args) > 0 {
|
||||
throw(ErrNoArgAccepted)
|
||||
}
|
||||
}
|
||||
|
||||
func TakeNoOpt(opts map[string]interface{}) {
|
||||
if len(opts) > 0 {
|
||||
throw(ErrNoOptAccepted)
|
||||
}
|
||||
}
|
82
eval/scan_opts.go
Normal file
82
eval/scan_opts.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package eval
|
||||
|
||||
// This file implements facilities for "scanning" arguments and options.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
||||
// OptToScan is a data structure for an option that is intended to be used in
|
||||
// ScanOpts.
|
||||
type OptToScan struct {
|
||||
Name string
|
||||
Ptr interface{}
|
||||
Default interface{}
|
||||
}
|
||||
|
||||
// ScanOpts scans options from a map.
|
||||
func ScanOpts(m map[string]interface{}, opts ...OptToScan) {
|
||||
scanned := make(map[string]bool)
|
||||
for _, opt := range opts {
|
||||
a := opt.Ptr
|
||||
value, ok := m[opt.Name]
|
||||
if !ok {
|
||||
value = opt.Default
|
||||
}
|
||||
mustScanToGo(value, a)
|
||||
scanned[opt.Name] = true
|
||||
}
|
||||
for key := range m {
|
||||
if !scanned[key] {
|
||||
throwf("unknown option %s", parse.Quote(key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ScanOptsToStruct scan options from a map like ScanOpts except the destination
|
||||
// is a struct whose fields correspond to the options to be parsed. A field
|
||||
// named FieldName corresponds to the option named field-name, unless the field
|
||||
// has a explicit "name" tag.
|
||||
func ScanOptsToStruct(m map[string]interface{}, structPtr interface{}) {
|
||||
ptrValue := reflect.ValueOf(structPtr)
|
||||
if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct {
|
||||
throwf("internal bug: need struct ptr for ScanOptsToStruct, got %T", structPtr)
|
||||
}
|
||||
struc := ptrValue.Elem()
|
||||
|
||||
// fieldIdxForOpt maps option name to the index of field in struc.
|
||||
fieldIdxForOpt := make(map[string]int)
|
||||
for i := 0; i < struc.Type().NumField(); i++ {
|
||||
// ignore unexported fields
|
||||
if !struc.Field(i).CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
f := struc.Type().Field(i)
|
||||
optName := f.Tag.Get("name")
|
||||
if optName == "" {
|
||||
optName = util.CamelToDashed(f.Name)
|
||||
}
|
||||
fieldIdxForOpt[optName] = i
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
fieldIdx, ok := fieldIdxForOpt[k]
|
||||
if !ok {
|
||||
throwf("unknown option %s", parse.Quote(k))
|
||||
}
|
||||
mustScanToGo(v, struc.Field(fieldIdx).Addr().Interface())
|
||||
}
|
||||
}
|
||||
|
||||
var ErrNoOptAccepted = errors.New("no option accepted")
|
||||
|
||||
func TakeNoOpt(opts map[string]interface{}) {
|
||||
if len(opts) > 0 {
|
||||
throw(ErrNoOptAccepted)
|
||||
}
|
||||
}
|
87
eval/scan_opts_test.go
Normal file
87
eval/scan_opts_test.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
||||
// These are used as arguments to scanArg, since Go does not allow taking
|
||||
// address of literals.
|
||||
|
||||
func intPtr() *int { var x int; return &x }
|
||||
func intsPtr() *[]int { var x []int; return &x }
|
||||
func float64Ptr() *float64 { var x float64; return &x }
|
||||
func stringPtr() *string { var x string; return &x }
|
||||
|
||||
var scanOptsTestCases = []struct {
|
||||
bad bool
|
||||
src map[string]interface{}
|
||||
opts []OptToScan
|
||||
want []interface{}
|
||||
}{
|
||||
{
|
||||
src: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
opts: []OptToScan{
|
||||
{"foo", stringPtr(), "haha"},
|
||||
},
|
||||
want: []interface{}{
|
||||
"bar",
|
||||
},
|
||||
},
|
||||
// Default values.
|
||||
{
|
||||
src: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
opts: []OptToScan{
|
||||
{"foo", stringPtr(), "haha"},
|
||||
{"lorem", stringPtr(), "ipsum"},
|
||||
},
|
||||
want: []interface{}{
|
||||
"bar",
|
||||
"ipsum",
|
||||
},
|
||||
},
|
||||
|
||||
// Unknown option
|
||||
{
|
||||
bad: true,
|
||||
src: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
opts: []OptToScan{
|
||||
{"lorem", stringPtr(), "ipsum"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestScanOpts(t *testing.T) {
|
||||
for _, tc := range scanOptsTestCases {
|
||||
if tc.bad {
|
||||
if !util.ThrowsAny(func() { ScanOpts(tc.src, tc.opts...) }) {
|
||||
t.Errorf("ScanOpts(%v, %v...) should throw an error",
|
||||
tc.src, tc.opts)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ScanOpts(tc.src, tc.opts...)
|
||||
dsts := make([]interface{}, len(tc.opts))
|
||||
for i, opt := range tc.opts {
|
||||
dsts[i] = indirect(opt.Ptr)
|
||||
}
|
||||
err := compareSlice(tc.want, dsts)
|
||||
if err != nil {
|
||||
t.Errorf("ScanOpts(%v, %v) got %v, want %v", tc.src, tc.opts,
|
||||
dsts, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func indirect(i interface{}) interface{} {
|
||||
return reflect.Indirect(reflect.ValueOf(i)).Interface()
|
||||
}
|
|
@ -1,213 +0,0 @@
|
|||
package eval
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/elves/elvish/eval/vals"
|
||||
"github.com/elves/elvish/util"
|
||||
)
|
||||
|
||||
// These are used as arguments to scanArg, since Go does not allow taking
|
||||
// address of literals.
|
||||
|
||||
func intPtr() *int { var x int; return &x }
|
||||
func intsPtr() *[]int { var x []int; return &x }
|
||||
func float64Ptr() *float64 { var x float64; return &x }
|
||||
func stringPtr() *string { var x string; return &x }
|
||||
|
||||
var scanArgsTestCases = []struct {
|
||||
variadic bool
|
||||
src []interface{}
|
||||
dstPtrs []interface{}
|
||||
want []interface{}
|
||||
bad bool
|
||||
}{
|
||||
// Scanning an int and a String
|
||||
{
|
||||
src: []interface{}{"20", "20"},
|
||||
dstPtrs: []interface{}{intPtr(), stringPtr()},
|
||||
want: []interface{}{20, "20"},
|
||||
},
|
||||
// Scanning a String and any number of ints (here 2)
|
||||
{
|
||||
variadic: true,
|
||||
src: []interface{}{"a", "1", "2"},
|
||||
dstPtrs: []interface{}{stringPtr(), intsPtr()},
|
||||
want: []interface{}{"a", []int{1, 2}},
|
||||
},
|
||||
// Scanning a String and any number of ints (here 0)
|
||||
{
|
||||
variadic: true,
|
||||
src: []interface{}{"a"},
|
||||
dstPtrs: []interface{}{stringPtr(), intsPtr()},
|
||||
want: []interface{}{"a", []int{}},
|
||||
},
|
||||
|
||||
// Arity mismatch: too few arguments (non-variadic)
|
||||
{
|
||||
bad: true,
|
||||
src: []interface{}{},
|
||||
dstPtrs: []interface{}{stringPtr()},
|
||||
},
|
||||
// Arity mismatch: too few arguments (non-variadic)
|
||||
{
|
||||
bad: true,
|
||||
src: []interface{}{""},
|
||||
dstPtrs: []interface{}{stringPtr(), intPtr()},
|
||||
},
|
||||
// Arity mismatch: too many arguments (non-variadic)
|
||||
{
|
||||
bad: true,
|
||||
src: []interface{}{"1", "2"},
|
||||
dstPtrs: []interface{}{stringPtr()},
|
||||
},
|
||||
// Type mismatch (nonvariadic)
|
||||
{
|
||||
bad: true,
|
||||
src: []interface{}{"x"},
|
||||
dstPtrs: []interface{}{intPtr()},
|
||||
},
|
||||
|
||||
// Arity mismatch: too few arguments (variadic)
|
||||
{
|
||||
bad: true,
|
||||
src: []interface{}{},
|
||||
dstPtrs: []interface{}{stringPtr(), intsPtr()},
|
||||
variadic: true,
|
||||
},
|
||||
// Type mismatch within rest arg
|
||||
{
|
||||
bad: true,
|
||||
src: []interface{}{"a", "1", "lorem"},
|
||||
dstPtrs: []interface{}{stringPtr(), intsPtr()},
|
||||
variadic: true,
|
||||
},
|
||||
}
|
||||
|
||||
func TestScanArgs(t *testing.T) {
|
||||
for _, tc := range scanArgsTestCases {
|
||||
funcToTest := ScanArgs
|
||||
if tc.variadic {
|
||||
funcToTest = ScanArgsVariadic
|
||||
}
|
||||
|
||||
if tc.bad {
|
||||
if !util.ThrowsAny(func() { funcToTest(tc.src, tc.dstPtrs...) }) {
|
||||
t.Errorf("ScanArgs(%v, %v) should throw an error", tc.src, tc.dstPtrs)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
funcToTest(tc.src, tc.dstPtrs...)
|
||||
dsts := make([]interface{}, len(tc.dstPtrs))
|
||||
for i, ptr := range tc.dstPtrs {
|
||||
dsts[i] = indirect(ptr)
|
||||
}
|
||||
err := compareSlice(tc.want, dsts)
|
||||
if err != nil {
|
||||
t.Errorf("ScanArgs(%v) got %v, want %v", tc.src, dsts, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scanArgsBadTestCases = []struct {
|
||||
variadic bool
|
||||
src []interface{}
|
||||
dstPtrs []interface{}
|
||||
}{}
|
||||
|
||||
var scanOptsTestCases = []struct {
|
||||
bad bool
|
||||
src map[string]interface{}
|
||||
opts []OptToScan
|
||||
want []interface{}
|
||||
}{
|
||||
{
|
||||
src: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
opts: []OptToScan{
|
||||
{"foo", stringPtr(), "haha"},
|
||||
},
|
||||
want: []interface{}{
|
||||
"bar",
|
||||
},
|
||||
},
|
||||
// Default values.
|
||||
{
|
||||
src: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
opts: []OptToScan{
|
||||
{"foo", stringPtr(), "haha"},
|
||||
{"lorem", stringPtr(), "ipsum"},
|
||||
},
|
||||
want: []interface{}{
|
||||
"bar",
|
||||
"ipsum",
|
||||
},
|
||||
},
|
||||
|
||||
// Unknown option
|
||||
{
|
||||
bad: true,
|
||||
src: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
opts: []OptToScan{
|
||||
{"lorem", stringPtr(), "ipsum"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestScanOpts(t *testing.T) {
|
||||
for _, tc := range scanOptsTestCases {
|
||||
if tc.bad {
|
||||
if !util.ThrowsAny(func() { ScanOpts(tc.src, tc.opts...) }) {
|
||||
t.Errorf("ScanOpts(%v, %v...) should throw an error",
|
||||
tc.src, tc.opts)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ScanOpts(tc.src, tc.opts...)
|
||||
dsts := make([]interface{}, len(tc.opts))
|
||||
for i, opt := range tc.opts {
|
||||
dsts[i] = indirect(opt.Ptr)
|
||||
}
|
||||
err := compareSlice(tc.want, dsts)
|
||||
if err != nil {
|
||||
t.Errorf("ScanOpts(%v, %v) got %v, want %v", tc.src, tc.opts,
|
||||
dsts, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scanArgTestCases = []struct {
|
||||
source interface{}
|
||||
destPtr interface{}
|
||||
want interface{}
|
||||
}{
|
||||
{"20", intPtr(), 20},
|
||||
{"0x20", intPtr(), 0x20},
|
||||
{"20", float64Ptr(), 20.0},
|
||||
{"23.33", float64Ptr(), 23.33},
|
||||
{"", stringPtr(), ""},
|
||||
{"1", stringPtr(), "1"},
|
||||
{"2.33", stringPtr(), "2.33"},
|
||||
}
|
||||
|
||||
func TestScanArg(t *testing.T) {
|
||||
for _, tc := range scanArgTestCases {
|
||||
mustScanToGo(tc.source, tc.destPtr)
|
||||
if !vals.Equal(indirect(tc.destPtr), tc.want) {
|
||||
t.Errorf("scanArg(%s) got %q, want %v", tc.source,
|
||||
indirect(tc.destPtr), tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func indirect(i interface{}) interface{} {
|
||||
return reflect.Indirect(reflect.ValueOf(i)).Interface()
|
||||
}
|
Loading…
Reference in New Issue
Block a user