Binder manages mapping of keys to handlers, auto-discovery from struct tags,
before/after hooks, and optional JSON backup.
AddBool overrides the default handler for key with a custom bool→error function.
{
b.handlers[key] = wrapBool(fn)
}
AddInt overrides the default handler for key with a custom int→error function.
{
b.handlers[key] = wrapInt(fn)
}
AddDuration overrides the default handler for key with a custom duration→error function.
{
b.handlers[key] = wrapDuration(fn)
}
AddEnum overrides the default handler for key with a custom enum handler enforcing choices.
{
b.handlers[key] = wrapEnum(choices, fn)
}
AddStrings overrides the default handler for key with a custom []string→error function.
{
b.handlers[key] = fn
}
BeforeHook registers a hook called before setting a field.
{
b.hooks.addBefore(key, fn)
}
AfterHook registers a hook called after setting a field.
{
b.hooks.addAfter(key, fn)
}
Run applies handler for key with args, invoking before and after hooks.
{
b.hooks.runBefore(key, args)
h, ok := b.handlers[key]
if !ok {
return fmt.Errorf("no handler for key %s", key)
}
err := h(args)
b.hooks.runAfter(key, args)
return err
}
RunAll applies multiple key→args mappings in sequence.
{
for k, args := range batch {
if err := b.Run(k, args); err != nil {
return err
}
}
return nil
}
HandlerFunc processes raw args for a field.
func(args []string) error
HookFunc defines a hook signature.
func(key string, args []string)
hooks stores before/after hook functions.
addBefore registers a before-hook for a key.
{
h.before[key] = append(h.before[key], fn)
}
addAfter registers an after-hook for a key.
{
h.after[key] = append(h.after[key], fn)
}
runBefore executes all before-hooks for a key.
{
for _, fn := range h.before[key] {
fn(key, args)
}
}
runAfter executes all after-hooks for a key.
{
for _, fn := range h.after[key] {
fn(key, args)
}
}
NewBinder creates a Binder for dst and optionally writes a JSON backup to backupDir.
{
if autobackup {
if err := os.MkdirAll(backupDir, 0o755); err != nil {
return nil, err
}
backupPath := filepath.Join(backupDir, fmt.Sprintf("backup-%d.json", time.Now().Unix()))
f, err := os.Create(backupPath)
if err != nil {
return nil, err
}
defer f.Close()
if err := json.NewEncoder(f).Encode(dst); err != nil {
return nil, err
}
}
b := &Binder{
dst: dst,
handlers: make(map[string]HandlerFunc),
hooks: newHooks(),
}
autoDiscover(b)
return b, nil
}
parseTag splits a tag like "socketX11,bool" or "mode,enum,dev|prod" into tagMeta.
{
parts := strings.Split(tag, ",")
meta := tagMeta{Name: parts[0], Type: parts[1]}
if meta.Type == "enum" && len(parts) > 2 {
meta.Choices = strings.Split(parts[2], "|")
}
return meta
}
autoDiscover inspects dst struct tags and registers default handlers.
{
v := reflect.ValueOf(b.dst).Elem()
t := v.Type()
for i := range t.NumField() {
f := t.Field(i)
tag := f.Tag.Get("flag")
if tag == "" {
continue
}
meta := parseTag(tag)
field := v.Field(i)
switch meta.Type {
case "bool":
b.handlers[meta.Name] = defaultBool(field)
case "int":
b.handlers[meta.Name] = defaultInt(field)
case "duration":
b.handlers[meta.Name] = defaultDuration(field)
case "enum":
b.handlers[meta.Name] = defaultEnum(field, meta.Choices)
case "strings":
b.handlers[meta.Name] = defaultStrings(field)
}
}
}
wrapBool adapts fn to HandlerFunc.
{
return func(args []string) error {
v, err := strconv.ParseBool(args[0])
if err != nil {
return err
}
return fn(v)
}
}
defaultBool returns a HandlerFunc that sets a reflect.Bool field.
{
return func(args []string) error {
v, err := strconv.ParseBool(args[0])
if err != nil {
return err
}
field.SetBool(v)
return nil
}
}
wrapInt adapts fn to HandlerFunc.
{
return func(args []string) error {
v, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return err
}
return fn(v)
}
}
defaultInt returns a HandlerFunc that sets a reflect.Int field.
{
return func(args []string) error {
v, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return err
}
field.SetInt(v)
return nil
}
}
wrapDuration adapts fn to HandlerFunc.
{
return func(args []string) error {
d, err := time.ParseDuration(args[0])
if err != nil {
return err
}
return fn(d)
}
}
defaultDuration returns a HandlerFunc that sets a duration-stored-as-int64 field.
{
return func(args []string) error {
d, err := time.ParseDuration(args[0])
if err != nil {
return err
}
field.SetInt(int64(d))
return nil
}
}
wrapEnum enforces allowed choices then calls fn.
{
return func(args []string) error {
v := args[0]
for _, c := range choices {
if v == c {
return fn(v)
}
}
return fmt.Errorf("invalid value %q, must be one of %v", v, choices)
}
}
defaultEnum returns a HandlerFunc that sets a string field with validation.
{
return func(args []string) error {
v := args[0]
for _, c := range choices {
if v == c {
field.SetString(v)
return nil
}
}
return fmt.Errorf("invalid value %q, must be one of %v", v, choices)
}
}
defaultStrings returns a HandlerFunc that sets a []string field.
{
return func(args []string) error {
var parts []string
if len(args) == 1 {
parts = strings.Split(args[0], ":")
} else {
parts = args
}
slice := reflect.MakeSlice(field.Type(), len(parts), len(parts))
for i, s := range parts {
slice.Index(i).SetString(s)
}
field.Set(slice)
return nil
}
}
newHooks creates an empty hooks registry.
{
return &hooks{
before: make(map[string][]HookFunc),
after: make(map[string][]HookFunc),
}
}
import "encoding/json"
import "fmt"
import "os"
import "path/filepath"
import "time"
import "reflect"
import "strings"
import "fmt"
import "reflect"
import "strconv"
import "strings"
import "time"