// Package cli turns command-line arguments and config files into a resolved set // of options. The whole design is one flat table of option descriptors; help // text, defaults, parsing and validation all derive from it. The whole option // model is plain data rather than a class hierarchy. package cli // Kind is the value type of an option. It drives parsing and how the value is // later read back. type Kind int const ( Bool Kind = iota // bare flag means true; --flag=false also accepted Int // integer, optionally bounded by Min/Max Size // byte size with optional K/M/G suffix Float // floating point Str // free-form string Enum // one of Opt.Enum List // repeatable; values accumulate, newline-joined ) // Tag groups options for --help. type Tag int const ( Basic Tag = iota HTTP BitTorrent Advanced ) func (t Tag) String() string { switch t { case Basic: return "basic" case HTTP: return "http" case BitTorrent: return "bittorrent" case Advanced: return "advanced" default: return "?" } } // Opt describes one option: its names, value kind, default and help text. type Opt struct { Long string // long name without dashes, e.g. "max-concurrent-downloads" Short byte // short flag byte, e.g. 'x'; 0 if none 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 // floor of 0 (negative inputs are rejected). Min int64 // Max is the inclusive upper Int/Size bound; the zero value means no upper // bound. For Size options Min/Max are byte counts. Max int64 } // options is the single source of truth: a focused, practical subset. The Tag // decides which --help group an option shows under; the Basic group is the bare // `got --help` and is kept to the handful of flags that change WHAT happens, not // how fast. Every other flag still works — it just lives behind --help=. var options = []Opt{ // --- basic --- the outcome-changing core shown by a bare `got --help`. {Long: "dir", Short: 'd', Kind: Str, Help: "directory to store downloaded files", Tag: Basic}, {Long: "out", Short: 'o', Kind: Str, Help: "output filename for the download", Tag: Basic}, {Long: "input-file", Short: 'i', Kind: Str, Help: "read URIs line by line from FILE (- for stdin)", Tag: Basic}, {Long: "max-concurrent-downloads", Short: 'j', Kind: Int, Default: "5", Min: 1, Help: "how many files to download at once (not connections within one file)", Tag: Basic}, {Long: "continue", Short: 'c', Kind: Bool, Default: "false", Help: "resume a partially downloaded file", Tag: Basic}, {Long: "split", Short: 's', Kind: Int, Default: "5", Min: 1, Help: "connections per download — the speed dial (default 5; e.g. -s16 for 16)", Tag: Basic}, {Long: "max-overall-download-limit", Kind: Size, Default: "0", Help: "global download speed limit (0 = unlimited)", Tag: Basic}, {Long: "checksum", Kind: Str, Help: "verify the finished file: TYPE=DIGEST, e.g. sha-256= (md5, sha-1, sha-224, sha-256, sha-384, sha-512)", Tag: Basic}, {Long: "show-files", Short: 'S', Kind: Bool, Default: "false", Help: "list files in a torrent and exit", Tag: Basic}, {Long: "quiet", Short: 'q', Kind: Bool, Default: "false", Help: "suppress the progress readout", Tag: Basic}, // --- http --- {Long: "force-sequential", Short: 'Z', Kind: Bool, Default: "false", Help: "download each command-line URI as its own file instead of mirroring them", Tag: HTTP}, {Long: "max-tries", Short: 'm', Kind: Int, Default: "5", Min: 0, Help: "max retries per segment (0 = unlimited)", Tag: HTTP}, {Long: "timeout", Short: 't', Kind: Int, Default: "60", Min: 1, Help: "connection/idle timeout in seconds", Tag: HTTP}, {Long: "header", Kind: List, Help: "append an extra HTTP header (repeatable), e.g. 'Authorization: Bearer …'", Tag: HTTP}, // --- bittorrent --- {Long: "check-integrity", Short: 'V', Kind: Bool, Default: "false", Help: "re-verify torrent data against piece hashes", 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: "listen-port", Kind: Str, Default: "6881-6999", Help: "TCP port (range) for incoming peers", Tag: BitTorrent}, {Long: "enable-dht", Kind: Bool, Default: "true", Help: "enable the BitTorrent DHT (turn off for private torrents)", Tag: BitTorrent}, {Long: "max-overall-upload-limit", Kind: Size, Default: "0", Help: "global upload speed limit (0 = unlimited)", Tag: BitTorrent}, {Long: "select-file", Kind: Str, Help: "download only these file indexes, e.g. 1,3-5", Tag: BitTorrent}, // --- advanced --- {Long: "allow-overwrite", Kind: Bool, Default: "false", Help: "overwrite an existing file instead of renaming the new one", Tag: Advanced}, {Long: "auto-file-renaming", Kind: Bool, Default: "true", Help: "rename file (.1, .2, ...) if it already exists", Tag: Advanced}, {Long: "file-allocation", Short: 'a', Kind: Enum, Default: "prealloc", Enum: []string{"none", "prealloc", "trunc", "falloc"}, Help: "how to reserve disk space (prealloc and falloc are identical here)", Tag: Advanced}, {Long: "dry-run", Kind: Bool, Default: "false", Help: "check that the file is available but do not download it", Tag: Advanced}, {Long: "save-session", Kind: Str, Help: "on exit, write unfinished downloads to FILE (reload with -i)", Tag: Advanced}, {Long: "conf-path", Kind: Str, Help: "path to the config file", Tag: Advanced}, {Long: "no-conf", Kind: Bool, Default: "false", Help: "do not read a config file", Tag: Advanced}, } // byLong and byShort index the table. Built once at init. var ( byLong = map[string]*Opt{} byShort = map[byte]*Opt{} ) func init() { for i := range options { o := &options[i] byLong[o.Long] = o if o.Short != 0 { byShort[o.Short] = o } } }