Accept --root after the subcommand
`money serve --root DIR` failed because --root was only read before the subcommand; after it, the flag reached serve's own flag set, which knows only --addr, and was rejected. It is too natural a thing to type to refuse, so run now lifts --root/--root=DIR out of any subcommand's arguments before they are parsed. A bare `--` still ends it, and given on both sides, the one after the command wins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ build changes its shape, delete it and run `money import` again.
|
|||||||
|
|
||||||
Checked in order, first one wins:
|
Checked in order, first one wins:
|
||||||
|
|
||||||
1. `--root DIR` on the command line
|
1. `--root DIR` on the command line, before or after the command
|
||||||
2. `$MONEY_ROOT`
|
2. `$MONEY_ROOT`
|
||||||
3. the `root` key in `$XDG_CONFIG_HOME/money/config.toml`, which is
|
3. the `root` key in `$XDG_CONFIG_HOME/money/config.toml`, which is
|
||||||
`~/.config/money/config.toml` unless `XDG_CONFIG_HOME` says otherwise
|
`~/.config/money/config.toml` unless `XDG_CONFIG_HOME` says otherwise
|
||||||
|
|||||||
+44
-1
@@ -34,7 +34,7 @@ import (
|
|||||||
|
|
||||||
const usage = `money - statement-driven personal finance tracker
|
const usage = `money - statement-driven personal finance tracker
|
||||||
|
|
||||||
usage: money [--root DIR] <command> [flags]
|
usage: money [--root DIR] <command> [flags] (--root may also follow the command)
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
tui browse transactions, build the rules that tag them and the
|
tui browse transactions, build the rules that tag them and the
|
||||||
@@ -88,6 +88,17 @@ func run(args []string) error {
|
|||||||
cmd, args = args[0], args[1:]
|
cmd, args = args[0], args[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `money serve --root DIR` is too natural to reject, so --root is also
|
||||||
|
// accepted after the subcommand; each subcommand's own flag set never
|
||||||
|
// sees it.
|
||||||
|
after, args, err := takeRoot(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if after != "" {
|
||||||
|
root = after
|
||||||
|
}
|
||||||
|
|
||||||
abs, source, err := config.ResolveRoot(root)
|
abs, source, err := config.ResolveRoot(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -486,6 +497,38 @@ func cmdTUI(root string, _ []string) error {
|
|||||||
return tui.Run(root, o.db, o.accounts, o.engine, o.links)
|
return tui.Run(root, o.db, o.accounts, o.engine, o.links)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// takeRoot removes --root/-root (as `--root DIR` or `--root=DIR`) from a
|
||||||
|
// subcommand's arguments, returning the last value given. It stops at `--`,
|
||||||
|
// after which everything is positional.
|
||||||
|
func takeRoot(args []string) (root string, rest []string, err error) {
|
||||||
|
rest = make([]string, 0, len(args))
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
a := args[i]
|
||||||
|
if a == "--" {
|
||||||
|
return root, append(rest, args[i:]...), nil
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(a, "-") {
|
||||||
|
rest = append(rest, a)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, inline, hasInline := strings.Cut(strings.TrimLeft(a, "-"), "=")
|
||||||
|
if name != "root" {
|
||||||
|
rest = append(rest, a)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasInline {
|
||||||
|
root = inline
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i+1 >= len(args) {
|
||||||
|
return "", nil, fmt.Errorf("--root needs a directory")
|
||||||
|
}
|
||||||
|
root = args[i+1]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return root, rest, nil
|
||||||
|
}
|
||||||
|
|
||||||
// cmdServe serves the TUI's screens over HTTP until interrupted. There is no
|
// cmdServe serves the TUI's screens over HTTP until interrupted. There is no
|
||||||
// authentication, so it listens on loopback unless told otherwise: anyone who
|
// authentication, so it listens on loopback unless told otherwise: anyone who
|
||||||
// can reach the port can rewrite rules.toml and start an import.
|
// can reach the port can rewrite rules.toml and start an import.
|
||||||
|
|||||||
@@ -84,3 +84,27 @@ func TestUniqLimitReportsWhatItHeldBack(t *testing.T) {
|
|||||||
t.Errorf("want the first row alphabetically:\n%s", out)
|
t.Errorf("want the first row alphabetically:\n%s", out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRootAfterTheSubcommand(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
args []string
|
||||||
|
root string
|
||||||
|
rest []string
|
||||||
|
}{
|
||||||
|
{[]string{"--root", "/r", "--addr", ":9"}, "/r", []string{"--addr", ":9"}},
|
||||||
|
{[]string{"--addr", ":9", "-root=/r"}, "/r", []string{"--addr", ":9"}},
|
||||||
|
{[]string{"--untagged"}, "", []string{"--untagged"}},
|
||||||
|
{[]string{"--", "--root", "/r"}, "", []string{"--", "--root", "/r"}},
|
||||||
|
} {
|
||||||
|
root, rest, err := takeRoot(tc.args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%q: %v", tc.args, err)
|
||||||
|
}
|
||||||
|
if root != tc.root || strings.Join(rest, " ") != strings.Join(tc.rest, " ") {
|
||||||
|
t.Errorf("%q: got %q %q, want %q %q", tc.args, root, rest, tc.root, tc.rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, _, err := takeRoot([]string{"--root"}); err == nil {
|
||||||
|
t.Error("--root without a directory was accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user