This commit is contained in:
Arpad Ryszka 2025-08-26 14:12:18 +02:00
parent 7e5e7e97f8
commit 3ff038c9d3
10 changed files with 54 additions and 46 deletions

View File

@ -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)
} }

View File

@ -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)

View File

@ -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)")

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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)
} }

28
wand.go
View File

@ -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(
MergeConfig(
Config{ Config{
file: func(cmd Cmd) *file { file: func(cmd Cmd) *file {
return fileReader( return fileReader(path.Join(os.Getenv("HOME"), fmt.Sprintf(".%s", cmd.name), "config"))
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( return fileReader(path.Join(os.Getenv("HOME"), ".config", cmd.name, "config"))
path.Join(os.Getenv("HOME"), ".config", cmd.name, "config"), },
},
),
) )
},
},
))
} }
func ConfigFromOption() Config { func ConfigFromOption() Config {