diff --git a/cli/help.go b/cli/help.go index 3fd94b0..c82c34f 100644 --- a/cli/help.go +++ b/cli/help.go @@ -89,24 +89,31 @@ func flagLabel(o *Opt) string { b.WriteString(" ") } b.WriteString("--" + o.Long) - switch o.Kind { - case Bool: - // no argument shown - case Size: - b.WriteString("=SIZE") - case Int: - b.WriteString("=N") - case Float: - // seed-time is a count of minutes; only seed-ratio is an actual ratio. - if o.Long == "seed-time" { - b.WriteString("=MIN") - } else { - b.WriteString("=RATIO") - } - case Enum: - b.WriteString("=" + strings.Join(o.Enum, "|")) - default: - b.WriteString("=VAL") + if m := metavar(o); m != "" { + b.WriteString("=" + m) } return b.String() } + +// metavar is the placeholder shown after a value-taking flag, e.g. the "N" in +// "--split=N". An explicit Opt.Metavar wins; otherwise it derives from the Kind. +// Bool takes no value, so its metavar is empty and no "=ARG" is printed. +func metavar(o *Opt) string { + if o.Metavar != "" { + return o.Metavar + } + switch o.Kind { + case Bool: + return "" + case Size: + return "SIZE" + case Int: + return "N" + case Float: + return "RATIO" + case Enum: + return strings.Join(o.Enum, "|") + default: + return "VAL" + } +} diff --git a/cli/option.go b/cli/option.go index afd2ddc..1a1d744 100644 --- a/cli/option.go +++ b/cli/option.go @@ -50,6 +50,10 @@ type Opt struct { Kind Kind Default string Help string + // Metavar overrides the "=PLACEHOLDER" shown after the flag in --help. Empty + // means derive it from Kind (N, SIZE, RATIO, ...). Set it only when the + // Kind-derived default would mislead, e.g. seed-time is minutes, not a ratio. + Metavar string Tag Tag Enum []string // valid values when Kind == Enum // Min is the inclusive lower Int/Size bound. The zero value enforces a @@ -103,7 +107,7 @@ var options = []Opt{ // --- bittorrent --- {Long: "torrent-file", Short: 'T', Kind: Str, Help: "path to a .torrent file", Tag: BitTorrent}, - {Long: "seed-time", Kind: Float, Help: "minutes to seed after completion (0 = no seeding)", Tag: BitTorrent}, + {Long: "seed-time", Kind: Float, Metavar: "MIN", Help: "minutes to seed after completion (0 = no seeding)", Tag: BitTorrent}, {Long: "seed-ratio", Kind: Float, Default: "1.0", Help: "stop seeding at this share ratio (0 = unlimited)", Tag: BitTorrent}, {Long: "bt-stop-timeout", Kind: Int, Default: "0", Min: 0, Help: "stop a torrent with no download progress for N seconds (0 = off)", Tag: BitTorrent}, {Long: "bt-metadata-timeout", Kind: Int, Default: "60", Min: 0, Help: "stop a magnet that can't fetch metadata in N seconds (0 = off)", Tag: BitTorrent}, diff --git a/cli/options.go b/cli/options.go index db0229f..243ce10 100644 --- a/cli/options.go +++ b/cli/options.go @@ -48,13 +48,11 @@ func (o *Options) Bool(name string) bool { return val } -// Int returns the value as an int, or 0 if empty/invalid. -func (o *Options) Int(name string) int { return int(o.Int64(name)) } - -// Int64 returns the value as an int64, or 0 if empty/invalid. -func (o *Options) Int64(name string) int64 { +// Int returns the value as an int, or 0 if empty/invalid. The value has already +// passed validate(), so a parse failure here is unreachable in normal flow. +func (o *Options) Int(name string) int { n, _ := strconv.ParseInt(strings.TrimSpace(o.vals[name]), 10, 64) - return n + return int(n) } // Float returns the value as a float64, or 0 if empty/invalid. diff --git a/cli/parse.go b/cli/parse.go index 8530c44..e4e6519 100644 --- a/cli/parse.go +++ b/cli/parse.go @@ -41,7 +41,7 @@ func Parse(args []string) (*Result, error) { opts := newOptions() applyDefaults(opts) - if !truthy(cmdVals["no-conf"]) { + if noConf, _ := boolWord(cmdVals["no-conf"]); !noConf { explicit := cmdVals["conf-path"] path, fromUser := confPath(explicit) if err := applyConfig(opts, path, fromUser); err != nil { @@ -138,14 +138,23 @@ func parseArgs(args []string) (vals map[string]string, uris []string, action Act // accumulate stores a value, joining repeatable List options with newlines. func accumulate(vals map[string]string, o *Opt, val string) { if o.Kind == List { - if prev, ok := vals[o.Long]; ok { - vals[o.Long] = prev + "\n" + val - return - } + vals[o.Long] = appendList(vals[o.Long], val) + return } vals[o.Long] = val } +// appendList is the single home of the List newline-join rule, shared by the +// raw-parse layer (accumulate) and the resolved layer (set). An empty prev (the +// option not yet seen) yields val unchanged, so the first value carries no +// leading newline. +func appendList(prev, val string) string { + if prev == "" { + return val + } + return prev + "\n" + val +} + func applyDefaults(o *Options) { for i := range options { opt := &options[i] @@ -215,10 +224,8 @@ func set(o *Options, name, val string) error { if err := validate(opt, val); err != nil { return err } - if opt.Kind == List { - if prev, ok := o.vals[name]; ok && o.set[name] { - val = prev + "\n" + val - } + if opt.Kind == List && o.set[name] { + val = appendList(o.vals[name], val) } o.vals[name] = val o.set[name] = true @@ -304,8 +311,3 @@ func confPath(explicit string) (path string, fromUser bool) { } return filepath.Join(home, ".config", "got", "got.conf"), false } - -func truthy(s string) bool { - val, _ := boolWord(s) - return val -}