Comments (and one test comment) now describe the rules on their own terms instead of by comparison to another downloader. No behavior change.
116 lines
3.6 KiB
Go
116 lines
3.6 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Options is the resolved configuration: a flat name->value map plus a record
|
|
// of which names were explicitly set (so callers can tell a default from a
|
|
// chosen value). Values are kept as strings end-to-end and converted only at
|
|
// the point of use by the typed getters below.
|
|
type Options struct {
|
|
vals map[string]string
|
|
set map[string]bool
|
|
}
|
|
|
|
func newOptions() *Options {
|
|
return &Options{vals: map[string]string{}, set: map[string]bool{}}
|
|
}
|
|
|
|
// IsSet reports whether name was given on the command line or in the config
|
|
// (as opposed to coming from the built-in default).
|
|
func (o *Options) IsSet(name string) bool { return o.set[name] }
|
|
|
|
// Str returns the raw string value (empty if unset and no default).
|
|
func (o *Options) Str(name string) string { return o.vals[name] }
|
|
|
|
// boolWords is the accepted vocabulary for boolean options: exactly true and
|
|
// false, nothing else. boolWord is the single consult point — the Bool reader,
|
|
// validate(), and the no-conf bootstrap all go through it — so exactly the words
|
|
// that validate are honoured.
|
|
var boolWords = map[string]bool{
|
|
"true": true,
|
|
"false": false,
|
|
}
|
|
|
|
// boolWord reports a value's truth and whether it is a recognised boolean word.
|
|
// Whitespace is trimmed, but case is significant: "True"/"TRUE" are rejected, so
|
|
// the match is against exactly "true"/"false".
|
|
func boolWord(s string) (val, ok bool) {
|
|
val, ok = boolWords[strings.TrimSpace(s)]
|
|
return val, ok
|
|
}
|
|
|
|
// Bool reports whether the value is a truthy boolean word.
|
|
func (o *Options) Bool(name string) bool {
|
|
val, _ := boolWord(o.vals[name])
|
|
return val
|
|
}
|
|
|
|
// 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 int(n)
|
|
}
|
|
|
|
// Float returns the value as a float64, or 0 if empty/invalid.
|
|
func (o *Options) Float(name string) float64 {
|
|
f, _ := strconv.ParseFloat(strings.TrimSpace(o.vals[name]), 64)
|
|
return f
|
|
}
|
|
|
|
// Size returns a byte count parsed from a value like "20M" or "512K".
|
|
func (o *Options) Size(name string) int64 {
|
|
n, _ := parseSize(o.vals[name])
|
|
return n
|
|
}
|
|
|
|
// List returns a repeatable option's accumulated values.
|
|
func (o *Options) List(name string) []string {
|
|
v := o.vals[name]
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
return strings.Split(v, "\n")
|
|
}
|
|
|
|
// parseSize converts a size string into a byte count: the first 'K'/'k' or
|
|
// 'M'/'m' in the string selects the multiplier (1024 or 1024*1024) and
|
|
// everything from that byte on is discarded; with no such unit the whole string
|
|
// is the byte count. There is no gigabyte unit, so "1G" is rejected; "1Mi" and
|
|
// "10MB" are 1M and 10M (the trailing bytes are dropped). An empty string is 0
|
|
// bytes; a negative value is rejected.
|
|
func parseSize(s string) (int64, error) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return 0, nil
|
|
}
|
|
mult := int64(1)
|
|
if i := strings.IndexAny(s, "KkMm"); i >= 0 {
|
|
if c := s[i]; c == 'M' || c == 'm' {
|
|
mult = 1 << 20
|
|
} else {
|
|
mult = 1 << 10
|
|
}
|
|
s = s[:i]
|
|
}
|
|
n, err := strconv.ParseInt(strings.TrimSpace(s), 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("bad size %q", s)
|
|
}
|
|
// Size parsing rejects negative sizes outright.
|
|
if n < 0 {
|
|
return 0, fmt.Errorf("negative size %q", s)
|
|
}
|
|
// Reject a value whose unit multiply would overflow int64 and silently wrap to
|
|
// a bogus (positive or negative) byte count.
|
|
if mult > 1 && n > math.MaxInt64/mult {
|
|
return 0, fmt.Errorf("size %q too large", s)
|
|
}
|
|
return n * mult, nil
|
|
}
|