initial commit
This commit is contained in:
227
cli_args.go
Normal file
227
cli_args.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A parsed command-line argument.
|
||||
type Argument struct {
|
||||
name string
|
||||
suffix string
|
||||
value string
|
||||
isFlag bool
|
||||
mayBeCommand bool
|
||||
hasValue bool
|
||||
wasUsed bool
|
||||
err error
|
||||
}
|
||||
|
||||
// Turn an argument into an Argument struct.
|
||||
func parseArgument(arg string, into Args, acceptFlags bool) Args {
|
||||
return append(into, Argument{value: arg, mayBeCommand: acceptFlags})
|
||||
}
|
||||
|
||||
// Parse a "long" flag such as --name.
|
||||
func parseLongFlag(arg string, into Args) Args {
|
||||
var (
|
||||
flag = Argument{isFlag: true}
|
||||
name string
|
||||
hasSuffix bool
|
||||
)
|
||||
|
||||
name, flag.value, flag.hasValue = strings.Cut(arg[2:], "=")
|
||||
flag.name, flag.suffix, hasSuffix = strings.Cut(name, ":")
|
||||
|
||||
if len(flag.name) == 0 {
|
||||
flag.err = fmt.Errorf("Flag \"%s\" has no name", arg)
|
||||
}
|
||||
if hasSuffix && len(flag.suffix) == 0 {
|
||||
flag.err = fmt.Errorf("Flag \"%s\" has a colon in its name but no suffix", arg)
|
||||
}
|
||||
|
||||
return append(into, flag)
|
||||
}
|
||||
|
||||
// Parse a "short" flag such as -n.
|
||||
// -name will be parsed as four boolean flags -n, -a, -m and -e.
|
||||
func parseShortFlag(arg string, into Args) Args {
|
||||
name, value, hasValue := strings.Cut(arg[1:], "=")
|
||||
prefix, suffix, hasSuffix := strings.Cut(name, ":")
|
||||
prefixes := strings.Split(prefix, "")
|
||||
|
||||
if len(prefixes) == 0 {
|
||||
flag := Argument{
|
||||
isFlag: true, name: prefix, suffix: suffix, hasValue: hasValue, value: value,
|
||||
err: fmt.Errorf("Flag \"%s\" has no name", arg),
|
||||
}
|
||||
return append(into, flag)
|
||||
}
|
||||
|
||||
for i, p := range prefixes {
|
||||
flag := Argument{isFlag: true, name: p}
|
||||
if i == len(prefixes)-1 {
|
||||
flag.suffix = suffix
|
||||
flag.hasValue = hasValue
|
||||
flag.value = value
|
||||
if hasSuffix && len(suffix) == 0 {
|
||||
flag.err = fmt.Errorf("Flag group \"%s\" contains a colon but no suffix", arg)
|
||||
}
|
||||
}
|
||||
into = append(into, flag)
|
||||
}
|
||||
|
||||
return into
|
||||
}
|
||||
|
||||
type Args []Argument
|
||||
|
||||
// Parse all command-line arguments from os.Args.
|
||||
func ParseArgs() (arglist Args) {
|
||||
var acceptFlags bool = true
|
||||
|
||||
for i, arg := range os.Args {
|
||||
if i == 0 {
|
||||
// skip program name
|
||||
continue
|
||||
}
|
||||
|
||||
if acceptFlags && strings.HasPrefix(arg, "--") {
|
||||
if len(arg) > 2 {
|
||||
arglist = parseLongFlag(arg, arglist)
|
||||
} else {
|
||||
// everything after the first double dash "--" is an argument
|
||||
acceptFlags = false
|
||||
}
|
||||
} else if acceptFlags && strings.HasPrefix(arg, "-") {
|
||||
if len(arg) > 1 {
|
||||
arglist = parseShortFlag(arg, arglist)
|
||||
} else {
|
||||
// a single dash "-" has no special meaning
|
||||
arglist = parseArgument(arg, arglist, false)
|
||||
}
|
||||
} else {
|
||||
arglist = parseArgument(arg, arglist, acceptFlags)
|
||||
}
|
||||
}
|
||||
|
||||
return arglist
|
||||
}
|
||||
|
||||
// Print an Argument as a flag.
|
||||
func sprintFlag(flag Argument) string {
|
||||
if utf8.RuneCountInString(flag.name) < 2 {
|
||||
return fmt.Sprintf("\"-%s\"", flag.name)
|
||||
} else {
|
||||
return fmt.Sprintf("\"--%s\"", flag.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Print a flag given its long and short form.
|
||||
func sprintFlagName(name string, shorthand string) string {
|
||||
if shorthand == "" {
|
||||
return fmt.Sprintf("\"--%s\"", name)
|
||||
} else {
|
||||
return fmt.Sprintf("\"-%s\" or \"--%s\"", shorthand, name)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the flag named [name] or [shorthand], or <nil> if none if found.
|
||||
// Fails if more than one flag is found.
|
||||
func (args *Args) findSingleFlag(name string, shorthand string) (*Argument, error) {
|
||||
var (
|
||||
found *Argument
|
||||
err error
|
||||
)
|
||||
for i := range *args {
|
||||
arg := &(*args)[i]
|
||||
if !arg.wasUsed && arg.isFlag && (arg.name == name || arg.name == shorthand) {
|
||||
if found == nil {
|
||||
found = arg
|
||||
} else {
|
||||
err = fmt.Errorf("Flag %s can only be used once", sprintFlagName(name, shorthand))
|
||||
}
|
||||
arg.wasUsed = true
|
||||
}
|
||||
}
|
||||
return found, err
|
||||
}
|
||||
|
||||
// Return wether an argument is considered a help flag. -h, --help and windows-style /? are accepted.
|
||||
func (arg *Argument) isHelpFlag() bool {
|
||||
if arg.isFlag {
|
||||
return arg.name == "h" || arg.name == "help"
|
||||
} else if arg.mayBeCommand {
|
||||
return arg.value == "/?"
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Return wether a help flag is present in [args].
|
||||
func (args *Args) HelpFlag() bool {
|
||||
var (
|
||||
result bool = false
|
||||
)
|
||||
for _, arg := range *args {
|
||||
if !arg.wasUsed && arg.isHelpFlag() {
|
||||
arg.wasUsed = true
|
||||
result = true
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Read a boolean flag. The return value will be [value] if the flag is set.
|
||||
func (args *Args) BoolFlag(name string, shorthand string, value bool) (bool, error) {
|
||||
var result bool = !value
|
||||
flag, err := args.findSingleFlag(name, shorthand)
|
||||
|
||||
if flag != nil {
|
||||
if flag.suffix != "" {
|
||||
err = fmt.Errorf("Flag %s cannot have a suffix", sprintFlagName(name, shorthand))
|
||||
} else if flag.hasValue {
|
||||
err = fmt.Errorf("Flag %s cannot have a value", sprintFlagName(name, shorthand))
|
||||
} else {
|
||||
result = value
|
||||
}
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Read the next argument as a command.
|
||||
func (args *Args) Command() (cmd string) {
|
||||
for i := range *args {
|
||||
arg := &(*args)[i]
|
||||
if !arg.wasUsed && arg.mayBeCommand {
|
||||
cmd = arg.value
|
||||
arg.wasUsed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Verify that all arguments have been used.
|
||||
func (args *Args) Done() {
|
||||
var errs []error
|
||||
for _, arg := range *args {
|
||||
if !arg.wasUsed {
|
||||
if arg.isFlag {
|
||||
errs = append(errs, fmt.Errorf("Unexpected flag %s", sprintFlag(arg)))
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("Unexpected argument \"%s\"", arg.value))
|
||||
}
|
||||
} else if arg.err != nil {
|
||||
errs = append(errs, arg.err)
|
||||
}
|
||||
}
|
||||
if errs != nil {
|
||||
InvalidArgs(errs...)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user