diff --git a/action.go b/action.go index 4e1b593..5645dc1 100644 --- a/action.go +++ b/action.go @@ -37,7 +37,7 @@ func (value) JSONSchema() *jsonschema.Schema { examples := make([]interface{}, 0, len(macros)) for _, name := range sortedNames { - examples = append(examples, fmt.Sprintf("$%v(%v)", name, macros[name].Signature())) + examples = append(examples, fmt.Sprintf("$%v(%v)", name, macros[name].macro.Signature())) } return &jsonschema.Schema{ Type: "string", @@ -59,7 +59,7 @@ func ActionMacro(s string) carapace.Action { if m, ok := macros[matches["macro"]]; !ok { return carapace.ActionMessage("unknown macro: %#v", s) } else { - return m.f(matches["arg"]) + return m.Parse(s) } }) } diff --git a/macro.go b/macro.go index f7599b7..c80f9f5 100644 --- a/macro.go +++ b/macro.go @@ -1,31 +1,28 @@ package spec import ( - "fmt" - "reflect" - "regexp" - "strings" - "github.com/rsteube/carapace" - "gopkg.in/yaml.v3" + "github.com/rsteube/carapace-spec/pkg/macro" ) type Macro struct { - f func(string) carapace.Action - s func() string + macro macro.Macro[carapace.Action] disableFlagParsing bool } -func (m Macro) parse(s string) carapace.Action { - r := regexp.MustCompile(`^\$(?P[^(]*)(\((?P.*)\))?$`) - matches := r.FindStringSubmatch(s) - if matches == nil { - return carapace.ActionMessage("malformed macro: '%v'", s) +func (m Macro) Parse(s string) carapace.Action { + a, err := m.macro.Parse(s) + if err != nil { + return carapace.ActionMessage(err.Error()) } - return m.f(matches[3]) + return *a +} + +func (m Macro) Signature() string { + return m.macro.Signature() } -func (m Macro) Signature() string { return m.s() } -func (m Macro) NoFlag() Macro { m.disableFlagParsing = true; return m } + +func (m Macro) NoFlag() Macro { m.disableFlagParsing = true; return m } var macros = make(map[string]Macro) @@ -38,81 +35,29 @@ func AddMacro(s string, m Macro) { macros["_"+s] = m } -// MacroN creates a macro without an argument func MacroN(f func() carapace.Action) Macro { return Macro{ - f: func(s string) carapace.Action { - return f() - }, - s: func() string { return "" }, + macro: macro.MacroN[carapace.Action](func() (*carapace.Action, error) { + a := f() + return &a, nil + }), } } -// MacroI creates a macro with an argument func MacroI[T any](f func(t T) carapace.Action) Macro { return Macro{ - f: func(s string) carapace.Action { - var t T - if reflect.TypeOf(t).Kind() == reflect.String { - reflect.ValueOf(&t).Elem().SetString(s) - } else { - if err := yaml.Unmarshal([]byte(s), &t); err != nil { - return carapace.ActionMessage(err.Error()) - } - - if s == "" { - if m := reflect.ValueOf(&t).MethodByName("Default"); m.IsValid() && m.Type().NumIn() == 0 { - values := m.Call([]reflect.Value{}) // TODO check if needs args - if len(values) > 0 && values[0].Type().AssignableTo(reflect.TypeOf(t)) { - reflect.ValueOf(&t).Elem().Set(values[0]) - } - } - - } - } - return f(t) - }, - s: func() string { return signature(new(T)) }, + macro: macro.MacroI[T, carapace.Action](func(t T) (*carapace.Action, error) { + a := f(t) + return &a, nil + }), } } -// MacroV creates a macro with a variable argument -func MacroV[T any](f func(s ...T) carapace.Action) Macro { +func MacroV[T any](f func(t ...T) carapace.Action) Macro { return Macro{ - f: func(s string) carapace.Action { - if s == "" { - return f() - } - - var t []T - if err := yaml.Unmarshal([]byte(s), &t); err != nil { - return carapace.ActionMessage("malformed macro arg: '%v', expected '%v'", s, reflect.TypeOf(t)) - } - return f(t...) - }, - s: func() string { return fmt.Sprintf("[%v]", signature(new(T))) }, - } -} - -func signature(i interface{}) string { - elem := reflect.ValueOf(i).Elem() - switch elem.Kind() { - case reflect.Struct: - out, err := yaml.Marshal(i) - if err != nil { - return err.Error() - } - lines := strings.Split(string(out), "\n") - return fmt.Sprintf("{%v}", strings.Join(lines[:len(lines)-1], ", ")) - - case reflect.Slice: - ptr := reflect.New(elem.Type().Elem()).Interface() - return fmt.Sprintf("[%v]", signature(ptr)) - - case reflect.String: - return `""` - - default: - return fmt.Sprintf("%v", reflect.ValueOf(i).Elem()) + macro: macro.MacroV[T, carapace.Action](func(t ...T) (*carapace.Action, error) { + a := f(t...) + return &a, nil + }), } } diff --git a/macro_test.go b/macro_test.go index e77255b..d437b2c 100644 --- a/macro_test.go +++ b/macro_test.go @@ -17,32 +17,32 @@ func (a Arg) Default() Arg { } func TestSignature(t *testing.T) { - signature := MacroI(func(a Arg) carapace.Action { return carapace.ActionValues() }).Signature() + signature := MacroI(func(a Arg) carapace.Action { return carapace.ActionValues() }).macro.Signature() if expected := `{name: "", option: false}`; signature != expected { t.Errorf("should be: %v", expected) } - signature = MacroI(func(a []Arg) carapace.Action { return carapace.ActionValues() }).Signature() + signature = MacroI(func(a []Arg) carapace.Action { return carapace.ActionValues() }).macro.Signature() if expected := `[{name: "", option: false}]`; signature != expected { t.Errorf("should be: %v", expected) } - signature = MacroI(func(b bool) carapace.Action { return carapace.ActionValues() }).Signature() + signature = MacroI(func(b bool) carapace.Action { return carapace.ActionValues() }).macro.Signature() if expected := `false`; signature != expected { t.Errorf("should be: %v", expected) } - signature = MacroV(func(a ...Arg) carapace.Action { return carapace.ActionValues() }).Signature() + signature = MacroV(func(a ...Arg) carapace.Action { return carapace.ActionValues() }).macro.Signature() if expected := `[{name: "", option: false}]`; signature != expected { t.Errorf("should be: %v", expected) } - signature = MacroV(func(b ...bool) carapace.Action { return carapace.ActionValues() }).Signature() + signature = MacroV(func(b ...bool) carapace.Action { return carapace.ActionValues() }).macro.Signature() if expected := `[false]`; signature != expected { t.Errorf("should be: %v", expected) } - signature = MacroI(func(s string) carapace.Action { return carapace.ActionValues() }).Signature() + signature = MacroI(func(s string) carapace.Action { return carapace.ActionValues() }).macro.Signature() if expected := `""`; signature != expected { t.Errorf("should be: %v", expected) } @@ -52,11 +52,11 @@ func TestDefault(t *testing.T) { var actual Arg m := MacroI(func(a Arg) carapace.Action { actual = a; return carapace.ActionValues() }) - if m.f(""); !actual.Option { + if m.Parse("$default"); !actual.Option { t.Error("should be true (default)") } - if m.f("{option: false}"); actual.Option { + if m.Parse("$default({option: false})"); actual.Option { t.Error("should be false") } } diff --git a/modifier.go b/modifier.go index d663318..d35fd39 100644 --- a/modifier.go +++ b/modifier.go @@ -42,7 +42,7 @@ func (m modifier) Parse(s string) carapace.Action { } if modifier, ok := modifiers[strings.SplitN(s, "(", 2)[0]]; ok { - return modifier.parse(s) + return modifier.Parse(s) } return carapace.ActionMessage("unknown macro: %#v", s) }) @@ -65,7 +65,7 @@ func (m modifier) chdir(s string) carapace.Action { "$xdgconfighome": MacroN(func() carapace.Action { return m.Action.ChdirF(traverse.XdgConfigHome) }), } if modifier, ok := traverse[strings.SplitN(s, "(", 2)[0]]; ok { - return modifier.parse(s) + return modifier.Parse(s) } return carapace.ActionMessage("unknown macro: %#v", s) } diff --git a/pkg/macro/macro.go b/pkg/macro/macro.go new file mode 100644 index 0000000..44fd40b --- /dev/null +++ b/pkg/macro/macro.go @@ -0,0 +1,106 @@ +package macro + +import ( + "fmt" + "reflect" + "regexp" + "strings" + + "gopkg.in/yaml.v3" +) + +type Macro[T any] struct { + f func(string) (*T, error) + s func() string +} + +type Default[T any] interface { + Default() T +} + +func (m Macro[T]) Parse(s string) (*T, error) { + r := regexp.MustCompile(`^\$(?P[^(]*)(\((?P.*)\))?$`) + matches := r.FindStringSubmatch(s) + if matches == nil { + return nil, fmt.Errorf("malformed macro: '%v'", s) + } + return m.f(matches[3]) +} + +func (m Macro[T]) Signature() string { return m.s() } + +// MacroN creates a macro without an argument +func MacroN[T any](f func() (*T, error)) Macro[T] { + return Macro[T]{ + f: func(s string) (*T, error) { + return f() + }, + s: func() string { return "" }, + } +} + +// MacroI creates a macro with an argument +func MacroI[A, T any](f func(arg A) (*T, error)) Macro[T] { + return Macro[T]{ + f: func(s string) (*T, error) { + var arg A + switch reflect.TypeOf(arg).Kind() { + case reflect.String: + reflect.ValueOf(&arg).Elem().SetString(s) + + default: + if err := yaml.Unmarshal([]byte(s), &arg); err != nil { + return nil, err + } + if s == "" { + if v, ok := interface{}(arg).(Default[A]); ok { + arg = v.Default() + } + } + } + return f(arg) + }, + s: func() string { return signature(new(A)) }, + } +} + +// MacroV creates a macro with a variable argument +func MacroV[A, T any](f func(args ...A) (*T, error)) Macro[T] { + return Macro[T]{ + f: func(s string) (*T, error) { + if s == "" { + return f() + } + + var args []A + if err := yaml.Unmarshal([]byte(s), &args); err != nil { + return nil, fmt.Errorf("malformed macro arg: '%v', expected '%v'", s, reflect.TypeOf(args)) + } + return f(args...) + }, + s: func() string { return fmt.Sprintf("[%v]", signature(new(A))) }, + } +} + +func signature(i interface{}) string { + elem := reflect.ValueOf(i).Elem() + switch elem.Kind() { + case reflect.Struct: + out, err := yaml.Marshal(i) + if err != nil { + return err.Error() + } + lines := strings.Split(string(out), "\n") + return fmt.Sprintf("{%v}", strings.Join(lines[:len(lines)-1], ", ")) + + case reflect.Slice: + ptr := reflect.New(elem.Type().Elem()).Interface() + return fmt.Sprintf("[%v]", signature(ptr)) + + case reflect.String: + return `""` + + default: + return fmt.Sprintf("%v", reflect.ValueOf(i).Elem()) + } +}