• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

skeema / mybase / 15280734891

27 May 2025 04:31PM UTC coverage: 71.845% (+0.8%) from 71.055%
15280734891

push

github

evanelias
go.mod, ci: bump to Go 1.23

This project supports the same Go versions as the Go authors, and Go 1.23 is
currently the older of the two supported Go versions.

985 of 1371 relevant lines covered (71.85%)

41.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.47
/cli.go
1
package mybase
2

3
import (
4
        "errors"
5
        "fmt"
6
        "strings"
7
)
8

9
// CommandLine stores state relating to executing an application.
10
type CommandLine struct {
11
        InvokedAs    string            // How the bin was invoked; e.g. os.Args[0]
12
        Command      *Command          // Which command (or subcommand) is being executed
13
        OptionValues map[string]string // Option values parsed from the command-line
14
        ArgValues    []string          // Positional arg values (does not include InvokedAs or Command.Name)
15
}
16

17
// OptionValue returns the value for the requested option if it was specified
18
// on the command-line. This is satisfies the OptionValuer interface, allowing
19
// Config to use the command-line as the highest-priority option provider.
20
func (cli *CommandLine) OptionValue(optionName string) (string, bool) {
281✔
21
        value, ok := cli.OptionValues[optionName]
281✔
22
        return value, ok
281✔
23
}
281✔
24

25
// DeprecationWarnings returns a slice of warning messages for usage of
26
// deprecated options on the command-line. This satisfies the DeprecationWarner
27
// interface.
28
func (cli *CommandLine) DeprecationWarnings() []string {
5✔
29
        var warnings []string
5✔
30
        optionMap := cli.Command.Options()
5✔
31
        for name := range cli.OptionValues {
15✔
32
                if opt := optionMap[name]; opt.Deprecated() {
15✔
33
                        warnings = append(warnings, "Option --"+name+" is deprecated. "+opt.deprecationDetails)
5✔
34
                }
5✔
35
        }
36
        return warnings
5✔
37
}
38

39
func (cli *CommandLine) parseLongArg(arg string, args *[]string, longOptionIndex map[string]*Option) error {
15✔
40
        key, value, hasValue, loose := NormalizeOptionToken(arg)
15✔
41
        opt, found := longOptionIndex[key]
15✔
42
        if !found {
15✔
43
                if loose {
×
44
                        return nil
×
45
                }
×
46
                return OptionNotDefinedError{key, "CLI"}
×
47
        }
48

49
        // Use returned hasValue boolean instead of comparing value to "", since "" may
50
        // be set explicitly (--some-opt='') or implicitly (--skip-some-bool-opt) and
51
        // both of those cases treat hasValue=true
52
        if !hasValue {
18✔
53
                if opt.RequireValue {
4✔
54
                        // Value required: slurp next arg to allow format "--foo bar" in addition to "--foo=bar"
1✔
55
                        if len(*args) == 0 || strings.HasPrefix((*args)[0], "-") {
1✔
56
                                return OptionMissingValueError{opt.Name, "CLI"}
×
57
                        }
×
58
                        value = (*args)[0]
1✔
59
                        *args = (*args)[1:]
1✔
60
                } else if opt.Type == OptionTypeBool {
3✔
61
                        // Boolean without value is treated as true
1✔
62
                        value = "1"
1✔
63
                }
1✔
64
        } else if value == "" && opt.Type == OptionTypeString {
15✔
65
                // Convert empty strings into quote-wrapped empty strings, so that callers
3✔
66
                // may differentiate between bare "--foo" vs "--foo=" if desired, by using
3✔
67
                // Config.GetRaw(). Meanwhile Config.Get and most other getters strip
3✔
68
                // surrounding quotes, so this does not break anything.
3✔
69
                value = "''"
3✔
70
        }
3✔
71

72
        cli.OptionValues[opt.Name] = value
15✔
73
        return nil
15✔
74
}
75

76
func (cli *CommandLine) parseShortArgs(arg string, args *[]string, shortOptionIndex map[rune]*Option) error {
14✔
77
        runeList := []rune(arg)
14✔
78
        var done bool
14✔
79
        for len(runeList) > 0 && !done {
28✔
80
                short := runeList[0]
14✔
81
                runeList = runeList[1:]
14✔
82
                var value string
14✔
83
                opt, found := shortOptionIndex[short]
14✔
84
                if !found {
14✔
85
                        return OptionNotDefinedError{string(short), "CLI"}
×
86
                }
×
87

88
                // Consume value. Depending on the option, value may be supplied as chars immediately following
89
                // this one, or after a space as next arg on CLI.
90
                if len(runeList) > 0 && opt.Type != OptionTypeBool { // "-xvalue", only supported for non-bools
15✔
91
                        value = string(runeList)
1✔
92
                        done = true
1✔
93
                } else if opt.RequireValue { // "-x value", only supported if opt requires a value
24✔
94
                        if len(*args) > 0 && !strings.HasPrefix((*args)[0], "-") {
20✔
95
                                value = (*args)[0]
10✔
96
                                *args = (*args)[1:]
10✔
97
                        } else {
10✔
98
                                return OptionMissingValueError{opt.Name, "CLI"}
×
99
                        }
×
100
                } else { // "-xyz", parse x as a valueless option and loop again to parse y (and possibly z) as separate shorthand options
3✔
101
                        if opt.Type == OptionTypeBool {
4✔
102
                                value = "1" // booleans handle lack of value as being true, whereas other types keep it as empty string
1✔
103
                        }
1✔
104
                }
105

106
                cli.OptionValues[opt.Name] = value
14✔
107
        }
108
        return nil
14✔
109
}
110

111
func (cli *CommandLine) String() string {
×
112
        // Don't reveal the actual command-line value, since it may contain something
×
113
        // sensitive (even though it shouldn't!)
×
114
        return "command line"
×
115
}
×
116

117
// ParseCLI parses the command-line to generate a CommandLine, which
118
// stores which (sub)command was used, named option values, and positional arg
119
// values. The CommandLine will then be wrapped in a Config for returning.
120
//
121
// The supplied cmd should typically be a root Command (one with nil
122
// ParentCommand), but this is not a requirement.
123
//
124
// The supplied args should match format of os.Args; i.e. args[0]
125
// should contain the program name.
126
func ParseCLI(cmd *Command, args []string) (*Config, error) {
15✔
127
        if len(args) == 0 {
15✔
128
                return nil, errors.New("ParseCLI: No command-line supplied")
×
129
        }
×
130

131
        cli := &CommandLine{
15✔
132
                Command:      cmd,
15✔
133
                InvokedAs:    args[0],
15✔
134
                OptionValues: make(map[string]string),
15✔
135
                ArgValues:    make([]string, 0),
15✔
136
        }
15✔
137
        args = args[1:]
15✔
138

15✔
139
        // Index options by shorthand
15✔
140
        longOptionIndex := cmd.Options()
15✔
141
        shortOptionIndex := make(map[rune]*Option, len(longOptionIndex))
15✔
142
        for name, opt := range longOptionIndex {
126✔
143
                if opt.Shorthand != 0 {
186✔
144
                        if _, already := shortOptionIndex[opt.Shorthand]; already {
75✔
145
                                panic(fmt.Errorf("Command %s defines multiple conflicting options with short-form -%c", cmd.Name, opt.Shorthand))
×
146
                        }
147
                        shortOptionIndex[opt.Shorthand] = longOptionIndex[name]
75✔
148
                }
149
        }
150

151
        var noMoreOptions bool
15✔
152

15✔
153
        // Iterate over the cli args and process each in turn
15✔
154
        for len(args) > 0 {
51✔
155
                arg := args[0]
36✔
156
                args = args[1:]
36✔
157
                switch {
36✔
158
                // option terminator
159
                case arg == "--":
×
160
                        noMoreOptions = true
×
161

162
                // long option
163
                case len(arg) > 2 && arg[0:2] == "--" && !noMoreOptions:
15✔
164
                        if err := cli.parseLongArg(arg[2:], &args, longOptionIndex); err != nil {
15✔
165
                                return nil, err
×
166
                        }
×
167

168
                // short option(s) -- multiple bools may be combined into one
169
                case len(arg) > 1 && arg[0] == '-' && !noMoreOptions:
14✔
170
                        if err := cli.parseShortArgs(arg[1:], &args, shortOptionIndex); err != nil {
14✔
171
                                return nil, err
×
172
                        }
×
173

174
                // first positional arg is command name if the current command is a command suite
175
                case len(cli.Command.SubCommands) > 0:
×
176
                        command, validCommand := cli.Command.SubCommands[arg]
×
177
                        if !validCommand {
×
178
                                return nil, fmt.Errorf("Unknown command \"%s\"", arg)
×
179
                        }
×
180
                        cli.Command = command
×
181

×
182
                        // Add the options of the new command into our maps. Any name conflicts
×
183
                        // intentionally override parent versions.
×
184
                        for name, opt := range command.options {
×
185
                                longOptionIndex[name] = command.options[name]
×
186
                                if opt.Shorthand != 0 {
×
187
                                        shortOptionIndex[opt.Shorthand] = command.options[name]
×
188
                                }
×
189
                        }
190

191
                // supplying help or version as first positional arg to a non-command-suite:
192
                // treat as if supplied as option instead
193
                case len(cli.ArgValues) == 0 && (arg == "help" || arg == "version"):
×
194
                        if err := cli.parseLongArg(arg, &args, longOptionIndex); err != nil {
×
195
                                return nil, err
×
196
                        }
×
197

198
                // superfluous positional arg
199
                case len(cli.ArgValues) >= len(cli.Command.args):
×
200
                        return nil, fmt.Errorf("Extra command-line arg \"%s\" supplied; command %s takes a max of %d args", arg, cli.Command.Name, len(cli.Command.args))
×
201

202
                // positional arg
203
                default:
7✔
204
                        cli.ArgValues = append(cli.ArgValues, arg)
7✔
205
                }
206
        }
207

208
        if _, helpWanted := cli.OptionValues["help"]; !helpWanted && len(cli.ArgValues) < cli.Command.minArgs() {
15✔
209
                return nil, fmt.Errorf("Too few positional args supplied on command line; command %s requires at least %d args", cli.Command.Name, cli.Command.minArgs())
×
210
        }
×
211

212
        // If no command supplied on a command suite, redirect to help subcommand
213
        if len(cli.Command.SubCommands) > 0 {
15✔
214
                cli.Command = cli.Command.SubCommands["help"]
×
215
        }
×
216

217
        return NewConfig(cli), nil
15✔
218
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc