fixes
This commit is contained in:
parent
7e5e7e97f8
commit
3ff038c9d3
@ -12,8 +12,9 @@ func main() {
|
|||||||
man := Command("manpages", tools.Man)
|
man := Command("manpages", tools.Man)
|
||||||
md := Command("markdown", tools.Markdown)
|
md := Command("markdown", tools.Markdown)
|
||||||
exec := Command("exec", tools.Exec)
|
exec := Command("exec", tools.Exec)
|
||||||
wand := Command("wand", nil, docreflect, man, md, Default(exec))
|
wand := Group("wand", docreflect, man, md, Default(exec))
|
||||||
wand = Version(wand, version)
|
wand = Version(wand, version)
|
||||||
|
wand = ShortForm(wand, "f", "no-cache", "c", "clear-cache", "d", "cache-dir")
|
||||||
conf := MergeConfig(Etc(), UserConfig())
|
conf := MergeConfig(Etc(), UserConfig())
|
||||||
Exec(wand, conf)
|
Exec(wand, conf)
|
||||||
}
|
}
|
||||||
|
|||||||
12
command.go
12
command.go
@ -10,14 +10,6 @@ import (
|
|||||||
|
|
||||||
var commandNameExpression = regexp.MustCompile("^[a-zA-Z_][a-zA-Z_0-9]*$")
|
var commandNameExpression = regexp.MustCompile("^[a-zA-Z_][a-zA-Z_0-9]*$")
|
||||||
|
|
||||||
func command(name string, impl any, subcmds ...Cmd) Cmd {
|
|
||||||
return Cmd{
|
|
||||||
name: name,
|
|
||||||
impl: impl,
|
|
||||||
subcommands: subcmds,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrap(impl any) Cmd {
|
func wrap(impl any) Cmd {
|
||||||
cmd, ok := impl.(Cmd)
|
cmd, ok := impl.(Cmd)
|
||||||
if ok {
|
if ok {
|
||||||
@ -219,6 +211,10 @@ func validateCommandTree(cmd Cmd, conf Config, assignedShortForms map[string]str
|
|||||||
return fmt.Errorf("command name is not a valid symbol: '%s'", cmd.name)
|
return fmt.Errorf("command name is not a valid symbol: '%s'", cmd.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cmd.impl == nil && !cmd.group {
|
||||||
|
return fmt.Errorf("command does not have an implementation: %s", cmd.name)
|
||||||
|
}
|
||||||
|
|
||||||
if cmd.impl != nil {
|
if cmd.impl != nil {
|
||||||
if err := validateImpl(cmd, conf); err != nil {
|
if err := validateImpl(cmd, conf); err != nil {
|
||||||
return fmt.Errorf("%s: %w", cmd.name, err)
|
return fmt.Errorf("%s: %w", cmd.name, err)
|
||||||
|
|||||||
@ -46,8 +46,8 @@ docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Docreflect", "\
|
|||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Exec", "\nfunc(o, function, args)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Exec", "\nfunc(o, function, args)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.CacheDir", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.CacheDir", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.ClearCache", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.NoCache", "")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.NoCache", "")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.PurgeCache", "")
|
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Man", "\nfunc(out, commandDir)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Man", "\nfunc(out, commandDir)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Markdown", "\nfunc(out, commandDir)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Markdown", "\nfunc(out, commandDir)")
|
||||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.copyGomod", "\nfunc(mn, dst, src)")
|
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.copyGomod", "\nfunc(mn, dst, src)")
|
||||||
|
|||||||
10
exec_test.go
10
exec_test.go
@ -9,10 +9,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
impl any
|
impl any
|
||||||
stdin string
|
stdin string
|
||||||
conf string
|
conf string
|
||||||
env string
|
env string
|
||||||
command string
|
command string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ func testExec(test testCase, err string, expect ...string) func(*testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output := stdout.String()
|
output := stdout.String()
|
||||||
if output[len(output) - 1] != '\n' {
|
if output[len(output)-1] != '\n' {
|
||||||
output = output + "\n"
|
output = output + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
help.go
8
help.go
@ -14,7 +14,7 @@ type (
|
|||||||
argumentSet struct {
|
argumentSet struct {
|
||||||
count int
|
count int
|
||||||
names []string
|
names []string
|
||||||
types []string
|
types []string
|
||||||
variadic bool
|
variadic bool
|
||||||
usesStdin bool
|
usesStdin bool
|
||||||
usesStdout bool
|
usesStdout bool
|
||||||
@ -31,7 +31,7 @@ type (
|
|||||||
|
|
||||||
docOption struct {
|
docOption struct {
|
||||||
name string
|
name string
|
||||||
typ string
|
typ string
|
||||||
description string
|
description string
|
||||||
shortNames []string
|
shortNames []string
|
||||||
isBool bool
|
isBool bool
|
||||||
@ -189,7 +189,7 @@ func constructArguments(cmd Cmd) argumentSet {
|
|||||||
return argumentSet{
|
return argumentSet{
|
||||||
count: count,
|
count: count,
|
||||||
names: names,
|
names: names,
|
||||||
types: types,
|
types: types,
|
||||||
variadic: t.IsVariadic(),
|
variadic: t.IsVariadic(),
|
||||||
usesStdin: len(ior) > 0,
|
usesStdin: len(ior) > 0,
|
||||||
usesStdout: len(iow) > 0,
|
usesStdout: len(iow) > 0,
|
||||||
@ -245,7 +245,7 @@ func constructOptions(cmd Cmd, hasConfigFromOption bool) []docOption {
|
|||||||
for name, fi := range f {
|
for name, fi := range f {
|
||||||
opt := docOption{
|
opt := docOption{
|
||||||
name: name,
|
name: name,
|
||||||
typ: strings.ToLower(fmt.Sprint(fi[0].typ.Kind())),
|
typ: strings.ToLower(fmt.Sprint(fi[0].typ.Kind())),
|
||||||
description: d[name],
|
description: d[name],
|
||||||
shortNames: sf[name],
|
shortNames: sf[name],
|
||||||
isBool: fi[0].typ.Kind() == reflect.Bool,
|
isBool: fi[0].typ.Kind() == reflect.Bool,
|
||||||
|
|||||||
13
notes.txt
13
notes.txt
@ -1,10 +1,19 @@
|
|||||||
speed up wand exec with smarter caching
|
speed up wand exec with smarter caching
|
||||||
test starting from the most referenced file to the least referenced one
|
|
||||||
run only the related test when testing a file
|
|
||||||
fix notation: .build/wand net/http.Get https://squareroundforest.org | less
|
fix notation: .build/wand net/http.Get https://squareroundforest.org | less
|
||||||
fix wand exec: .build/wand -- 'func(a struct{Foo string}) string { return a.Foo }' --foo bar
|
fix wand exec: .build/wand -- 'func(a struct{Foo string}) string { return a.Foo }' --foo bar
|
||||||
allow in wand exec: .build/wand 'regexp.MustCompile("a").MatchString' aaa
|
allow in wand exec: .build/wand 'regexp.MustCompile("a").MatchString' aaa
|
||||||
|
the exec command could have an --import option for the packages, for cases when it's not possible to infer
|
||||||
considering that the possible input comes from limited sources, it may make sense to not escape just every dot in markdown
|
considering that the possible input comes from limited sources, it may make sense to not escape just every dot in markdown
|
||||||
|
fix short form: program error: exec: exec: short form shadowing field name: no-cache
|
||||||
|
fix error reporting: program error: exec: exec: short form shadowing field name: no-cache
|
||||||
|
header file for the docreflect generated code
|
||||||
|
switch to wand docreflect for the docreflect generated code
|
||||||
|
rename wandgenerate env var to _wandgenerate
|
||||||
|
support discard in the same config file
|
||||||
|
trim input strings
|
||||||
|
improve validation messages
|
||||||
|
test starting from the most referenced file to the least referenced one
|
||||||
|
run only the related test when testing a file
|
||||||
|
|
||||||
reflect
|
reflect
|
||||||
command
|
command
|
||||||
|
|||||||
@ -358,7 +358,7 @@ func acceptsMultiple(t reflect.Type) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case reflect.Pointer, reflect.Slice:
|
case reflect.Pointer:
|
||||||
return acceptsMultiple(t.Elem())
|
return acceptsMultiple(t.Elem())
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package wand
|
package wand
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
"io"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReflect(t *testing.T) {
|
func TestReflect(t *testing.T) {
|
||||||
@ -28,7 +28,7 @@ func TestReflect(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("struct param", func(t *testing.T) {
|
t.Run("struct param", func(t *testing.T) {
|
||||||
f := func(s struct{Bar int}) int { return s.Bar }
|
f := func(s struct{ Bar int }) int { return s.Bar }
|
||||||
t.Run("basic", testExec(testCase{impl: f, command: "foo --bar 42"}, "", "42"))
|
t.Run("basic", testExec(testCase{impl: f, command: "foo --bar 42"}, "", "42"))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ func TestReflect(t *testing.T) {
|
|||||||
t.Run("interface", func(t *testing.T) {
|
t.Run("interface", func(t *testing.T) {
|
||||||
f := func(a any) any { return a }
|
f := func(a any) any { return a }
|
||||||
t.Run("any", testExec(testCase{impl: f, command: "foo bar"}, "", "bar"))
|
t.Run("any", testExec(testCase{impl: f, command: "foo bar"}, "", "bar"))
|
||||||
type i interface{Foo()}
|
type i interface{ Foo() }
|
||||||
g := func(a i) any { return a }
|
g := func(a i) any { return a }
|
||||||
t.Run("unscannable", testExec(testCase{impl: g, command: "foo bar"}, "non-empty interface", ""))
|
t.Run("unscannable", testExec(testCase{impl: g, command: "foo bar"}, "non-empty interface", ""))
|
||||||
})
|
})
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
type ExecOptions struct {
|
type ExecOptions struct {
|
||||||
NoCache bool
|
NoCache bool
|
||||||
PurgeCache bool
|
ClearCache bool
|
||||||
CacheDir string
|
CacheDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ func Exec(o ExecOptions, function string, args ...string) error {
|
|||||||
functionDir = path.Join(cacheDir, "tmp", functionHash)
|
functionDir = path.Join(cacheDir, "tmp", functionHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.NoCache || o.PurgeCache {
|
if o.NoCache || o.ClearCache {
|
||||||
if err := os.RemoveAll(functionDir); err != nil {
|
if err := os.RemoveAll(functionDir); err != nil {
|
||||||
return fmt.Errorf("failed to clean cache: %w", err)
|
return fmt.Errorf("failed to clean cache: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
38
wand.go
38
wand.go
@ -17,12 +17,12 @@ type Config struct {
|
|||||||
type Cmd struct {
|
type Cmd struct {
|
||||||
name string
|
name string
|
||||||
impl any
|
impl any
|
||||||
|
group bool
|
||||||
subcommands []Cmd
|
subcommands []Cmd
|
||||||
isDefault bool
|
isDefault bool
|
||||||
minPositional int
|
minPositional int
|
||||||
maxPositional int
|
maxPositional int
|
||||||
shortForms []string
|
shortForms []string
|
||||||
description string
|
|
||||||
isHelp bool
|
isHelp bool
|
||||||
version string
|
version string
|
||||||
}
|
}
|
||||||
@ -30,7 +30,11 @@ type Cmd struct {
|
|||||||
// name needs to be valid symbol. The application name should also be a valid symbol,
|
// name needs to be valid symbol. The application name should also be a valid symbol,
|
||||||
// though not mandatory. If it is not, the environment variables may not work properly.
|
// though not mandatory. If it is not, the environment variables may not work properly.
|
||||||
func Command(name string, impl any, subcmds ...Cmd) Cmd {
|
func Command(name string, impl any, subcmds ...Cmd) Cmd {
|
||||||
return command(name, impl, subcmds...)
|
return Cmd{name: name, impl: impl, subcommands: subcmds}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Group(name string, subcmds ...Cmd) Cmd {
|
||||||
|
return Cmd{name: name, group: true, subcommands: subcmds}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Default(cmd Cmd) Cmd {
|
func Default(cmd Cmd) Cmd {
|
||||||
@ -44,10 +48,10 @@ func Args(cmd Cmd, min, max int) Cmd {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func ShortFormOptions(cmd Cmd, f ...string) Cmd {
|
func ShortForm(cmd Cmd, f ...string) Cmd {
|
||||||
cmd.shortForms = append(cmd.shortForms, f...)
|
cmd.shortForms = append(cmd.shortForms, f...)
|
||||||
for i := range cmd.subcommands {
|
for i := range cmd.subcommands {
|
||||||
cmd.subcommands[i] = ShortFormOptions(
|
cmd.subcommands[i] = ShortForm(
|
||||||
cmd.subcommands[i],
|
cmd.subcommands[i],
|
||||||
f...,
|
f...,
|
||||||
)
|
)
|
||||||
@ -89,22 +93,20 @@ func Etc() Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserConfig() Config {
|
func UserConfig() Config {
|
||||||
return OptionalConfig(MergeConfig(
|
return OptionalConfig(
|
||||||
Config{
|
MergeConfig(
|
||||||
file: func(cmd Cmd) *file {
|
Config{
|
||||||
return fileReader(
|
file: func(cmd Cmd) *file {
|
||||||
path.Join(os.Getenv("HOME"), fmt.Sprintf(".%s", cmd.name), "config"),
|
return fileReader(path.Join(os.Getenv("HOME"), fmt.Sprintf(".%s", cmd.name), "config"))
|
||||||
)
|
},
|
||||||
},
|
},
|
||||||
},
|
Config{
|
||||||
Config{
|
file: func(cmd Cmd) *file {
|
||||||
file: func(cmd Cmd) *file {
|
return fileReader(path.Join(os.Getenv("HOME"), ".config", cmd.name, "config"))
|
||||||
return fileReader(
|
},
|
||||||
path.Join(os.Getenv("HOME"), ".config", cmd.name, "config"),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
),
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfigFromOption() Config {
|
func ConfigFromOption() Config {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user