pax_global_header00006660000000000000000000000064135757007400014522gustar00rootroot0000000000000052 comment=7cd400d892f1e9788a3f1f7d1cf9063328460c12 golang-github-integrii-flaggy-1.4.0/000077500000000000000000000000001357570074000173325ustar00rootroot00000000000000golang-github-integrii-flaggy-1.4.0/.gitignore000066400000000000000000000004331357570074000213220ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ .idea/ golang-github-integrii-flaggy-1.4.0/.travis.yml000066400000000000000000000001701357570074000214410ustar00rootroot00000000000000{ "language": "go", "os": "linux", "group": "stable", "dist": "trusty", "script": "go get -v && go test -v" } golang-github-integrii-flaggy-1.4.0/LICENSE000066400000000000000000000022721357570074000203420ustar00rootroot00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to golang-github-integrii-flaggy-1.4.0/README.md000066400000000000000000000226051357570074000206160ustar00rootroot00000000000000


Sensible and _fast_ command-line flag parsing with excellent support for **subcommands** and **positional values**. Flags can be at any position. Flaggy has no required project or package layout like [Cobra requires](https://github.com/spf13/cobra/issues/641), and **no external dependencies**! Check out the [godoc](http://godoc.org/github.com/integrii/flaggy), [examples directory](https://github.com/integrii/flaggy/tree/master/examples), and [examples in this readme](https://github.com/integrii/flaggy#super-simple-example) to get started quickly. You can also read the Flaggy introduction post with helpful examples [on my weblog](https://ericgreer.info/post/a-better-flags-package-for-go/). # Installation `go get -u github.com/integrii/flaggy` # Key Features - Very easy to use ([see examples below](https://github.com/integrii/flaggy#super-simple-example)) - 35 different flag types supported - Any flag can be at any position - Pretty and readable help output by default - Positional subcommands - Positional parameters - Suggested subcommands when a subcommand is typo'd - Nested subcommands - Both global and subcommand specific flags - Both global and subcommand specific positional parameters - [Customizable help templates for both the global command and subcommands](https://github.com/integrii/flaggy/blob/master/examples/customTemplate/main.go) - Customizable appended/prepended help messages for both the global command and subcommands - Simple function that displays help followed by a custom message string - Flags and subcommands may have both a short and long name - Unlimited trailing arguments after a `--` - Flags can use a single dash or double dash (`--flag`, `-flag`, `-f`, `--f`) - Flags can have `=` assignment operators, or use a space (`--flag=value`, `--flag value`) - Flags support single quote globs with spaces (`--flag 'this is all one value'`) - Flags of slice types can be passed multiple times (`-f one -f two -f three`) - Optional but default version output with `--version` - Optional but default help output with `-h` or `--help` - Optional but default help output when any invalid or unknown parameter is passed - It's _fast_. All flag and subcommand parsing takes less than `1ms` in most programs. # Example Help Output ``` testCommand - Description goes here. Get more information at http://flaggy. This is a prepend for help Usage: testCommand [subcommandA|subcommandB|subcommandC] [testPositionalA] [testPositionalB] Positional Variables: testPositionalA Test positional A does some things with a positional value. (Required) testPositionalB Test positional B does some less than serious things with a positional value. Subcommands: subcommandA (a) Subcommand A is a command that does stuff subcommandB (b) Subcommand B is a command that does other stuff subcommandC (c) Subcommand C is a command that does SERIOUS stuff Flags: --version Displays the program version string. -h --help Displays help with available flag, subcommand, and positional value parameters. -s --stringFlag This is a test string flag that does some stringy string stuff. -i --intFlg This is a test int flag that does some interesting int stuff. (default: 5) -b --boolFlag This is a test bool flag that does some booly bool stuff. (default: true) -d --durationFlag This is a test duration flag that does some untimely stuff. (default: 1h23s) This is an append for help This is a help add-on message ``` # Super Simple Example `./yourApp -f test` ```go // Declare variables and their defaults var stringFlag = "defaultValue" // Add a flag flaggy.String(&stringFlag, "f", "flag", "A test string flag") // Parse the flag flaggy.Parse() // Use the flag print(stringFlag) ``` # Example with Subcommand `./yourApp subcommandExample -f test` ```go // Declare variables and their defaults var stringFlag = "defaultValue" // Create the subcommand subcommand := flaggy.NewSubcommand("subcommandExample") // Add a flag to the subcommand subcommand.String(&stringFlag, "f", "flag", "A test string flag") // Add the subcommand to the parser at position 1 flaggy.AttachSubcommand(subcommand, 1) // Parse the subcommand and all flags flaggy.Parse() // Use the flag print(stringFlag) ``` # Example with Nested Subcommands, Various Flags and Trailing Arguments `./yourApp subcommandExample --flag=5 nestedSubcommand -t test -y -- trailingArg` ```go // Declare variables and their defaults var stringFlagF = "defaultValueF" var intFlagT = 3 var boolFlagB bool // Create the subcommands subcommandExample := flaggy.NewSubcommand("subcommandExample") nestedSubcommand := flaggy.NewSubcommand("nestedSubcommand") // Add a flag to both subcommands subcommandExample.String(&stringFlagF, "t", "testFlag", "A test string flag") nestedSubcommand.Int(&intFlagT, "f", "flag", "A test int flag") // add a global bool flag for fun flaggy.Bool(&boolFlagB, "y", "yes", "A sample boolean flag") // attach the nested subcommand to the parent subcommand at position 1 subcommandExample.AttachSubcommand(nestedSubcommand, 1) // attach the base subcommand to the parser at position 1 flaggy.AttachSubcommand(subcommandExample, 1) // Parse everything, then use the flags and trailing arguments flaggy.Parse() print(stringFlagF) print(intFlagT) print(boolFlagB) print(flaggy.TrailingArguments[0]) ``` # Supported Flag Types Flaggy has specific flag types for all basic types included in go as well as a slice of any of those types. This includes all of the following types: - string and []string - bool and []bool - all int types and all []int types - all float types and all []float types - all uint types and all []uint types Other more specific types can also be used as flag types. They will be automatically parsed using the standard parsing functions included with those types in those packages. This includes: - net.IP - []net.IP - net.HardwareAddr - []net.HardwareAddr - net.IPMask - []net.IPMask - time.Duration - []time.Duration # An Example Program Best practice when using flaggy includes setting your program's name, description, and version (at build time) as shown in this example program. ```go package main import "github.com/integrii/flaggy" // Make a variable for the version which will be set at build time. var version = "unknown" // Keep subcommands as globals so you can easily check if they were used later on. var mySubcommand *flaggy.Subcommand // Setup the variables you want your incoming flags to set. var testVar string // If you would like an environment variable as the default for a value, just populate the flag // with the value of the environment by default. If the flag corresponding to this value is not // used, then it will not be changed. var myVar = os.Getenv("MY_VAR") func init() { // Set your program's name and description. These appear in help output. flaggy.SetName("Test Program") flaggy.SetDescription("A little example program") // You can disable various things by changing bools on the default parser // (or your own parser if you have created one). flaggy.DefaultParser.ShowHelpOnUnexpected = false // You can set a help prepend or append on the default parser. flaggy.DefaultParser.AdditionalHelpPrepend = "http://github.com/integrii/flaggy" // Add a flag to the main program (this will be available in all subcommands as well). flaggy.String(&testVar, "tv", "testVariable", "A variable just for testing things!") // Create any subcommands and set their parameters. mySubcommand = flaggy.NewSubcommand("mySubcommand") mySubcommand.Description = "My great subcommand!" // Add a flag to the subcommand. mySubcommand.String(&myVar, "mv", "myVariable", "A variable just for me!") // Set the version and parse all inputs into variables. flaggy.SetVersion(version) flaggy.Parse() } func main(){ if mySubcommand.Used { ... } } ``` Then, you can use the following build command to set the `version` variable in the above program at build time. ```bash # build your app and set the version string $ go build -ldflags='-X main.version=1.0.3-a3db3' $ ./yourApp version Version: 1.0.3-a3db3 $ ./yourApp --help Test Program - A little example program http://github.com/integrii/flaggy ``` # Contributions Please feel free to open an issue if you find any bugs or see any features that make sense. Pull requests will be reviewed and accepted if they make sense, but it is always wise to submit a proposal issue before any major changes. golang-github-integrii-flaggy-1.4.0/argumentParser.go000066400000000000000000000015051357570074000226610ustar00rootroot00000000000000package flaggy // setValueForParsers sets the value for a specified key in the // specified parsers (which normally include a Parser and Subcommand). // The return values represent the key being set, and any errors // returned when setting the key, such as failures to convert the string // into the appropriate flag value. We stop assigning values as soon // as we find a any parser that accepts it. func setValueForParsers(key string, value string, parsers ...ArgumentParser) (bool, error) { for _, p := range parsers { valueWasSet, err := p.SetValueForKey(key, value) if err != nil { return valueWasSet, err } if valueWasSet { return true, nil } } return false, nil } // ArgumentParser represents a parser or subcommand type ArgumentParser interface { SetValueForKey(key string, value string) (bool, error) } golang-github-integrii-flaggy-1.4.0/examples/000077500000000000000000000000001357570074000211505ustar00rootroot00000000000000golang-github-integrii-flaggy-1.4.0/examples/.gitignore000066400000000000000000000001561357570074000231420ustar00rootroot00000000000000complex/complex customParser/customParser positionalValue/positionalValue simple/simple subcommand/subcommand golang-github-integrii-flaggy-1.4.0/examples/complex/000077500000000000000000000000001357570074000226175ustar00rootroot00000000000000golang-github-integrii-flaggy-1.4.0/examples/complex/main.go000066400000000000000000000021711357570074000240730ustar00rootroot00000000000000package main import ( "fmt" "github.com/integrii/flaggy" ) func main() { // Declare variables and their defaults var stringFlagF = "defaultValueF" var intFlagT = 3 var boolFlagB bool // Create the subcommand subcommandExample := flaggy.NewSubcommand("subcommandExample") nestedSubcommand := flaggy.NewSubcommand("nestedSubcommand") // Add a flag to the subcommand subcommandExample.String(&stringFlagF, "t", "testFlag", "A test string flag") nestedSubcommand.Int(&intFlagT, "f", "flag", "A test int flag") // add a global bool flag for fun flaggy.Bool(&boolFlagB, "y", "yes", "A sample boolean flag") // the nested subcommand to the parent subcommand at position 1 subcommandExample.AttachSubcommand(nestedSubcommand, 1) // the base subcommand to the parser at position 1 flaggy.AttachSubcommand(subcommandExample, 1) // Parse the subcommand and all flags flaggy.Parse() // Use the flags and trailing arguments fmt.Println(stringFlagF) fmt.Println(intFlagT) // we can check if a subcommand was used easily if nestedSubcommand.Used { fmt.Println(boolFlagB) } fmt.Println(flaggy.TrailingArguments[0:]) } golang-github-integrii-flaggy-1.4.0/examples/customParser/000077500000000000000000000000001357570074000236375ustar00rootroot00000000000000golang-github-integrii-flaggy-1.4.0/examples/customParser/main.go000066400000000000000000000025241357570074000251150ustar00rootroot00000000000000package main import ( "fmt" "github.com/integrii/flaggy" ) // Declare variables and their defaults var positionalValue = "defaultString" var intFlagT = 3 var boolFlagB bool func main() { // set a description, name, and version for our parser p := flaggy.NewParser("myAppName") p.Description = "This parser just shows you how to make a parser." p.Version = "1.3.5" // display some before and after text for all help outputs p.AdditionalHelpPrepend = "I hope you like this program!" p.AdditionalHelpAppend = "This command has no warranty." // add a positional value at position 1 p.AddPositionalValue(&positionalValue, "testPositional", 1, true, "This is a test positional value that is required") // create a subcommand at position 2 // you don't have to finish the subcommand before adding it to the parser subCmd := flaggy.NewSubcommand("subCmd") subCmd.Description = "Description of subcommand" p.AttachSubcommand(subCmd, 2) // add a flag to the subcomand subCmd.Int(&intFlagT, "i", "testInt", "This is a test int flag") // add a bool flag to the root command p.Bool(&boolFlagB, "b", "boolTest", "This is a test boolean flag") p.Parse() fmt.Println(positionalValue, intFlagT, boolFlagB) // Imagine the following command line: // ./customParser positionalHere subCmd -i 33 -b // It would produce: // positionalHere 33 true } golang-github-integrii-flaggy-1.4.0/examples/customTemplate/000077500000000000000000000000001357570074000241565ustar00rootroot00000000000000golang-github-integrii-flaggy-1.4.0/examples/customTemplate/main.go000066400000000000000000000031451357570074000254340ustar00rootroot00000000000000package main import "github.com/integrii/flaggy" // The custom help message template. // For rendering text/template will be used: https://godoc.org/text/template // Object propperties can be looked up here: https://github.com/integrii/flaggy/blob/master/helpValues.go const helpTemplate = `{{.CommandName}}{{if .Description}} - {{.Description}}{{end}}{{if .PrependMessage}} {{.PrependMessage}}{{end}} {{if .UsageString}} Usage: {{.UsageString}}{{end}}{{if .Positionals}} Positional Variables: {{range .Positionals}} {{.Name}} {{.Spacer}}{{if .Description}} {{.Description}}{{end}}{{if .DefaultValue}} (default: {{.DefaultValue}}){{else}}{{if .Required}} (Required){{end}}{{end}}{{end}}{{end}}{{if .Subcommands}} Subcommands: {{range .Subcommands}} {{.LongName}}{{if .ShortName}} ({{.ShortName}}){{end}}{{if .Position}}{{if gt .Position 1}} (position {{.Position}}){{end}}{{end}}{{if .Description}} {{.Spacer}}{{.Description}}{{end}}{{end}} {{end}}{{if (gt (len .Flags) 0)}} Flags: {{if .Flags}}{{range .Flags}} {{if .ShortName}}-{{.ShortName}} {{else}} {{end}}{{if .LongName}}--{{.LongName}}{{end}}{{if .Description}} {{.Spacer}}{{.Description}}{{if .DefaultValue}} (default: {{.DefaultValue}}){{end}}{{end}}{{end}}{{end}} {{end}}{{if .AppendMessage}}{{.AppendMessage}} {{end}}{{if .Message}} {{.Message}}{{end}} ` func main() { // Declare variables and their defaults var stringFlag = "defaultValue" // Add a flag flaggy.String(&stringFlag, "f", "flag", "A test string flag") // Set the help template flaggy.DefaultParser.SetHelpTemplate(helpTemplate) // Parse the flag flaggy.Parse() } golang-github-integrii-flaggy-1.4.0/examples/positionalValue/000077500000000000000000000000001357570074000243265ustar00rootroot00000000000000golang-github-integrii-flaggy-1.4.0/examples/positionalValue/main.go000066400000000000000000000006001357570074000255750ustar00rootroot00000000000000package main import "github.com/integrii/flaggy" func main() { // Declare variables and their defaults var positionalValue = "defaultValue" // Add the positional value to the parser at position 1 flaggy.AddPositionalValue(&positionalValue, "test", 1, true, "a test positional value") // Parse the positional value flaggy.Parse() // Use the value print(positionalValue) } golang-github-integrii-flaggy-1.4.0/examples/simple/000077500000000000000000000000001357570074000224415ustar00rootroot00000000000000golang-github-integrii-flaggy-1.4.0/examples/simple/main.go000066400000000000000000000004451357570074000237170ustar00rootroot00000000000000package main import "github.com/integrii/flaggy" func main() { // Declare variables and their defaults var stringFlag = "defaultValue" // Add a flag flaggy.String(&stringFlag, "f", "flag", "A test string flag") // Parse the flag flaggy.Parse() // Use the flag print(stringFlag) } golang-github-integrii-flaggy-1.4.0/examples/sliceFlag/000077500000000000000000000000001357570074000230415ustar00rootroot00000000000000golang-github-integrii-flaggy-1.4.0/examples/sliceFlag/.gitignore000066400000000000000000000000121357570074000250220ustar00rootroot00000000000000sliceFlag golang-github-integrii-flaggy-1.4.0/examples/sliceFlag/main.go000066400000000000000000000013401357570074000243120ustar00rootroot00000000000000package main import "github.com/integrii/flaggy" func main() { // Declare variables and their defaults var stringSliceFlag []string var boolSliceFlag []bool // Add a slice flag flaggy.DefaultParser.AdditionalHelpAppend = "Example: ./sliceFlag -b -b -s one -s two -b=false" flaggy.StringSlice(&stringSliceFlag, "s", "string", "A test string slice flag") flaggy.BoolSlice(&boolSliceFlag, "b", "bool", "A test bool slice flag") // Parse the flag flaggy.Parse() // output the flag contents for i := range stringSliceFlag { println(stringSliceFlag[i]) } for i := range boolSliceFlag { println(boolSliceFlag[i]) } // ./sliceFlag -b -b -s one -s two -b=false // output: // one // two // true // true // false } golang-github-integrii-flaggy-1.4.0/examples/subcommand/000077500000000000000000000000001357570074000233005ustar00rootroot00000000000000golang-github-integrii-flaggy-1.4.0/examples/subcommand/main.go000066400000000000000000000012751357570074000245600ustar00rootroot00000000000000package main import "github.com/integrii/flaggy" // Declare variables and their defaults var stringFlagA = "defaultValueA" var stringFlagB = "defaultValueB" func main() { // Add a flag to the root of flaggy flaggy.String(&stringFlagA, "a", "flagA", "A test string flag (A)") // Create the subcommand subcommand := flaggy.NewSubcommand("subcommandExample") // Add a flag to the subcommand subcommand.String(&stringFlagB, "b", "flagB", "A test string flag (B)") // Add the subcommand to the parser at position 1 flaggy.AttachSubcommand(subcommand, 1) // Parse the subcommand and all flags flaggy.Parse() // Use the flags println("A: " + stringFlagA) println("B: " + stringFlagB) } golang-github-integrii-flaggy-1.4.0/examples_test.go000066400000000000000000000144211357570074000225400ustar00rootroot00000000000000package flaggy_test import ( "fmt" "os" "github.com/integrii/flaggy" ) // ExampleSubcommand_AddPositionalValue adds two levels of subcommands with a // positional value on the second level one func ExampleSubcommand_AddPositionalValue() { // Simulate some input from the CLI. Don't do this in your program. flaggy.ResetParser() os.Args = []string{"binaryName", "subcommandA", "subcommandB", "subcommandBPositionalValue"} // Imagine the following program usage: // // ./binaryName subcommandA subcommandB subcommandBPositional // var subcommandBPositional string // create a subcommand subcommandA := flaggy.NewSubcommand("subcommandA") // add the subcommand at relative position 1 within the default root parser flaggy.AttachSubcommand(subcommandA, 1) // create a second subcommand subcommandB := flaggy.NewSubcommand("subcommandB") // add the second subcommand to the first subcommand as a child at relative // position 1 subcommandA.AttachSubcommand(subcommandB, 1) // add a positional to the second subcommand with a relative position of 1 subcommandB.AddPositionalValue(&subcommandBPositional, "subcommandTestPositonalValue", 1, false, "A test positional input variable") // Parse the input arguments from the OS (os.Args) using the default parser flaggy.Parse() // see if our flag was set properly fmt.Println("Positional flag set to", subcommandBPositional) // Output: Positional flag set to subcommandBPositionalValue } // ExamplePositionalValue shows how to add positional variables at the // global level. func ExamplePositionalValue() { // Simulate some input from the CLI. Don't do this in your program. flaggy.ResetParser() os.Args = []string{"binaryName", "positionalValue"} // Imagine the following program usage: // // ./binaryName positionalValue // add a bool flag at the global level var stringVar string flaggy.AddPositionalValue(&stringVar, "positionalVar", 1, false, "A test positional flag") // Parse the input arguments from the OS (os.Args) flaggy.Parse() // see if our flag was set properly if stringVar == "positionalValue" { fmt.Println("Flag set to", stringVar) } // Output: Flag set to positionalValue } // ExampleBoolFlag shows how to global bool flags in your program. func ExampleBool() { // Simulate some input from the CLI. Don't do these two lines in your program. flaggy.ResetParser() os.Args = []string{"binaryName", "-f"} // Imagine the following program usage: // // ./binaryName -f // or // ./binaryName --flag=true // add a bool flag at the global level var boolFlag bool flaggy.Bool(&boolFlag, "f", "flag", "A test bool flag") // Parse the input arguments from the OS (os.Args) flaggy.Parse() // see if our flag was set properly if boolFlag == true { fmt.Println("Flag set") } // Output: Flag set } // ExampleIntFlag shows how to global int flags in your program. func ExampleInt() { // Simulate some input from the CLI. Don't do these two lines in your program. flaggy.ResetParser() os.Args = []string{"binaryName", "-f", "5"} // Imagine the following program usage: // // ./binaryName -f 5 // or // ./binaryName --flag=5 // add a int flag at the global level var intFlag int flaggy.Int(&intFlag, "f", "flag", "A test int flag") // Parse the input arguments from the OS (os.Args) flaggy.Parse() // see if our flag was set properly if intFlag == 5 { fmt.Println("Flag set to:", intFlag) } // Output: Flag set to: 5 } // Example shows how to add string flags in your program. func Example() { // Simulate some input from the CLI. Don't do this in your program. flaggy.ResetParser() os.Args = []string{"binaryName", "-f", "flagName"} // Imagine the following program usage: // // ./binaryName -f flagName // or // ./binaryName --flag=flagName // add a string flag at the global level var stringFlag string flaggy.String(&stringFlag, "f", "flag", "A test string flag") // Parse the input arguments from the OS (os.Args) flaggy.Parse() // see if our flag was set properly if stringFlag == "flagName" { fmt.Println("Flag set to:", stringFlag) } // Output: Flag set to: flagName } // ExampleSubcommand shows usage of subcommands in flaggy. func ExampleSubcommand() { // Do not include the following two lines in your real program, it is for this // example only: flaggy.ResetParser() os.Args = []string{"programName", "-v", "VariableHere", "subcommandName", "subcommandPositional", "--", "trailingVar"} // Imagine the input to this program is as follows: // // ./programName subcommandName -v VariableHere subcommandPositional -- trailingVar // or // ./programName subcommandName subcommandPositional --variable VariableHere -- trailingVar // or // ./programName subcommandName --variable=VariableHere subcommandPositional -- trailingVar // or even // ./programName subcommandName subcommandPositional -v=VariableHere -- trailingVar // // Create a new subcommand to attach flags and other subcommands to. It must be attached // to something before being used. newSC := flaggy.NewSubcommand("subcommandName") // Attach a string variable to the subcommand var subcommandVariable string newSC.String(&subcommandVariable, "v", "variable", "A test variable.") var subcommandPositional string newSC.AddPositionalValue(&subcommandPositional, "testPositionalVar", 1, false, "A test positional variable to a subcommand.") // Attach the subcommand to the parser. This will panic if another // positional value or subcommand is already present at the depth supplied. // Later you can check if this command was used with a simple bool (newSC.Used). flaggy.AttachSubcommand(newSC, 1) // Parse the input arguments from the OS (os.Args) flaggy.Parse() // see if the subcommand was found during parsing: if newSC.Used { // Do subcommand operations here fmt.Println("Subcommand used") // check the input on your subcommand variable if subcommandVariable == "VariableHere" { fmt.Println("Subcommand variable set correctly") } // Print the subcommand positional value fmt.Println("Subcommand Positional:", subcommandPositional) // Print the first trailing argument fmt.Println("Trailing variable 1:", flaggy.TrailingArguments[0]) } // Output: // Subcommand used // Subcommand variable set correctly // Subcommand Positional: subcommandPositional // Trailing variable 1: trailingVar } golang-github-integrii-flaggy-1.4.0/flag.go000066400000000000000000000411451357570074000205770ustar00rootroot00000000000000package flaggy import ( "errors" "fmt" "net" "reflect" "strconv" "strings" "time" ) // Flag holds the base methods for all flag types type Flag struct { ShortName string LongName string Description string rawValue string // the value as a string before being parsed Hidden bool // indicates this flag should be hidden from help and suggestions AssignmentVar interface{} defaultValue string // the value (as a string), that was set by default before any parsing and assignment parsed bool // indicates that this flag has already been parsed } // HasName indicates that this flag's short or long name matches the // supplied name string func (f *Flag) HasName(name string) bool { name = strings.TrimSpace(name) if f.ShortName == name || f.LongName == name { return true } return false } // identifyAndAssignValue identifies the type of the incoming value // and assigns it to the AssignmentVar pointer's target value. If // the value is a type that needs parsing, that is performed as well. func (f *Flag) identifyAndAssignValue(value string) error { var err error // Only parse this flag default value once. This keeps us from // overwriting the default value in help output if !f.parsed { f.parsed = true // parse the default value as a string and remember it for help output f.defaultValue, err = f.returnAssignmentVarValueAsString() if err != nil { return err } } debugPrint("attempting to assign value", value, "to flag", f.LongName) f.rawValue = value // remember the raw value // depending on the type of the assignment variable, we convert the // incoming string and assign it. We only use pointers to variables // in flagy. No returning vars by value. switch f.AssignmentVar.(type) { case *string: v, _ := (f.AssignmentVar).(*string) *v = value case *[]string: v := f.AssignmentVar.(*[]string) splitString := strings.Split(value, ",") new := append(*v, splitString...) *v = new case *bool: v, err := strconv.ParseBool(value) if err != nil { return err } a, _ := (f.AssignmentVar).(*bool) *a = v case *[]bool: // parse the incoming bool b, err := strconv.ParseBool(value) if err != nil { return err } // cast the assignment var existing := f.AssignmentVar.(*[]bool) // deref the assignment var and append to it v := append(*existing, b) // pointer the new value and assign it a, _ := (f.AssignmentVar).(*[]bool) *a = v case *time.Duration: v, err := time.ParseDuration(value) if err != nil { return err } a, _ := (f.AssignmentVar).(*time.Duration) *a = v case *[]time.Duration: t, err := time.ParseDuration(value) if err != nil { return err } existing := f.AssignmentVar.(*[]time.Duration) // deref the assignment var and append to it v := append(*existing, t) // pointer the new value and assign it a, _ := (f.AssignmentVar).(*[]time.Duration) *a = v case *float32: v, err := strconv.ParseFloat(value, 32) if err != nil { return err } float := float32(v) a, _ := (f.AssignmentVar).(*float32) *a = float case *[]float32: v, err := strconv.ParseFloat(value, 32) if err != nil { return err } float := float32(v) existing := f.AssignmentVar.(*[]float32) new := append(*existing, float) *existing = new case *float64: v, err := strconv.ParseFloat(value, 64) if err != nil { return err } a, _ := (f.AssignmentVar).(*float64) *a = v case *[]float64: v, err := strconv.ParseFloat(value, 64) if err != nil { return err } existing := f.AssignmentVar.(*[]float64) new := append(*existing, v) *existing = new case *int: v, err := strconv.Atoi(value) if err != nil { return err } e := f.AssignmentVar.(*int) *e = v case *[]int: v, err := strconv.Atoi(value) if err != nil { return err } existing := f.AssignmentVar.(*[]int) new := append(*existing, v) *existing = new case *uint: v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } existing := f.AssignmentVar.(*uint) *existing = uint(v) case *[]uint: v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } existing := f.AssignmentVar.(*[]uint) new := append(*existing, uint(v)) *existing = new case *uint64: v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } existing := f.AssignmentVar.(*uint64) *existing = v case *[]uint64: v, err := strconv.ParseUint(value, 10, 64) if err != nil { return err } existing := f.AssignmentVar.(*[]uint64) new := append(*existing, v) *existing = new case *uint32: v, err := strconv.ParseUint(value, 10, 32) if err != nil { return err } existing := f.AssignmentVar.(*uint32) *existing = uint32(v) case *[]uint32: v, err := strconv.ParseUint(value, 10, 32) if err != nil { return err } existing := f.AssignmentVar.(*[]uint32) new := append(*existing, uint32(v)) *existing = new case *uint16: v, err := strconv.ParseUint(value, 10, 16) if err != nil { return err } val := uint16(v) existing := f.AssignmentVar.(*uint16) *existing = val case *[]uint16: v, err := strconv.ParseUint(value, 10, 16) if err != nil { return err } existing := f.AssignmentVar.(*[]uint16) new := append(*existing, uint16(v)) *existing = new case *uint8: v, err := strconv.ParseUint(value, 10, 8) if err != nil { return err } val := uint8(v) existing := f.AssignmentVar.(*uint8) *existing = val case *[]uint8: var newSlice []uint8 v, err := strconv.ParseUint(value, 10, 8) if err != nil { return err } newV := uint8(v) existing := f.AssignmentVar.(*[]uint8) newSlice = append(*existing, newV) *existing = newSlice case *int64: v, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } existing := f.AssignmentVar.(*int64) *existing = v case *[]int64: v, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } existingSlice := f.AssignmentVar.(*[]int64) newSlice := append(*existingSlice, v) *existingSlice = newSlice case *int32: v, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } converted := int32(v) existing := f.AssignmentVar.(*int32) *existing = converted case *[]int32: v, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } existingSlice := f.AssignmentVar.(*[]int32) newSlice := append(*existingSlice, int32(v)) *existingSlice = newSlice case *int16: v, err := strconv.ParseInt(value, 10, 16) if err != nil { return err } converted := int16(v) existing := f.AssignmentVar.(*int16) *existing = converted case *[]int16: v, err := strconv.ParseInt(value, 10, 16) if err != nil { return err } existingSlice := f.AssignmentVar.(*[]int16) newSlice := append(*existingSlice, int16(v)) *existingSlice = newSlice case *int8: v, err := strconv.ParseInt(value, 10, 8) if err != nil { return err } converted := int8(v) existing := f.AssignmentVar.(*int8) *existing = converted case *[]int8: v, err := strconv.ParseInt(value, 10, 8) if err != nil { return err } existingSlice := f.AssignmentVar.(*[]int8) newSlice := append(*existingSlice, int8(v)) *existingSlice = newSlice case *net.IP: v := net.ParseIP(value) existing := f.AssignmentVar.(*net.IP) *existing = v case *[]net.IP: v := net.ParseIP(value) existing := f.AssignmentVar.(*[]net.IP) new := append(*existing, v) *existing = new case *net.HardwareAddr: v, err := net.ParseMAC(value) if err != nil { return err } existing := f.AssignmentVar.(*net.HardwareAddr) *existing = v case *[]net.HardwareAddr: v, err := net.ParseMAC(value) if err != nil { return err } existing := f.AssignmentVar.(*[]net.HardwareAddr) new := append(*existing, v) *existing = new case *net.IPMask: v := net.IPMask(net.ParseIP(value).To4()) existing := f.AssignmentVar.(*net.IPMask) *existing = v case *[]net.IPMask: v := net.IPMask(net.ParseIP(value).To4()) existing := f.AssignmentVar.(*[]net.IPMask) new := append(*existing, v) *existing = new default: return errors.New("Unknown flag assignmentVar supplied in flag " + f.LongName + " " + f.ShortName) } return err } const argIsPositional = "positional" // subcommand or positional value const argIsFlagWithSpace = "flagWithSpace" // -f path or --file path const argIsFlagWithValue = "flagWithValue" // -f=path or --file=path const argIsFinal = "final" // the final argument only '--' // determineArgType determines if the specified arg is a flag with space // separated value, a flag with a connected value, or neither (positional) func determineArgType(arg string) string { // if the arg is --, then its the final arg if arg == "--" { return argIsFinal } // if it has the prefix --, then its a long flag if strings.HasPrefix(arg, "--") { // if it contains an equals, it is a joined value if strings.Contains(arg, "=") { return argIsFlagWithValue } return argIsFlagWithSpace } // if it has the prefix -, then its a short flag if strings.HasPrefix(arg, "-") { // if it contains an equals, it is a joined value if strings.Contains(arg, "=") { return argIsFlagWithValue } return argIsFlagWithSpace } return argIsPositional } // parseArgWithValue parses a key=value concatenated argument into a key and // value func parseArgWithValue(arg string) (key string, value string) { // remove up to two minuses from start of flag arg = strings.TrimPrefix(arg, "-") arg = strings.TrimPrefix(arg, "-") // debugPrint("parseArgWithValue parsing", arg) // break at the equals args := strings.SplitN(arg, "=", 2) // if its a bool arg, with no explicit value, we return a blank if len(args) == 1 { return args[0], "" } // if its a key and value pair, we return those if len(args) == 2 { // debugPrint("parseArgWithValue parsed", args[0], args[1]) return args[0], args[1] } fmt.Println("Warning: attempted to parseArgWithValue but did not have correct parameter count.", arg, "->", args) return "", "" } // parseFlagToName parses a flag with space value down to a key name: // --path -> path // -p -> p func parseFlagToName(arg string) string { // remove minus from start arg = strings.TrimLeft(arg, "-") arg = strings.TrimLeft(arg, "-") return arg } // flagIsBool determines if the flag is a bool within the specified parser // and subcommand's context func flagIsBool(sc *Subcommand, p *Parser, key string) bool { for _, f := range append(sc.Flags, p.Flags...) { if f.HasName(key) { _, isBool := f.AssignmentVar.(*bool) _, isBoolSlice := f.AssignmentVar.(*[]bool) if isBool || isBoolSlice { return true } } } // by default, the answer is false return false } // returnAssignmentVarValueAsString returns the value of the flag's // assignment variable as a string. This is used to display the // default value of flags before they are assigned (like when help is output). func (f *Flag) returnAssignmentVarValueAsString() (string, error) { debugPrint("returning current value of assignment var of flag", f.LongName) var err error // depending on the type of the assignment variable, we convert the // incoming string and assign it. We only use pointers to variables // in flagy. No returning vars by value. switch f.AssignmentVar.(type) { case *string: v, _ := (f.AssignmentVar).(*string) return *v, err case *[]string: v := f.AssignmentVar.(*[]string) return strings.Join(*v, ","), err case *bool: a, _ := (f.AssignmentVar).(*bool) return strconv.FormatBool(*a), err case *[]bool: value := f.AssignmentVar.(*[]bool) var ss []string for _, b := range *value { ss = append(ss, strconv.FormatBool(b)) } return strings.Join(ss, ","), err case *time.Duration: a := f.AssignmentVar.(*time.Duration) return (*a).String(), err case *[]time.Duration: tds := f.AssignmentVar.(*[]time.Duration) var asSlice []string for _, td := range *tds { asSlice = append(asSlice, td.String()) } return strings.Join(asSlice, ","), err case *float32: a := f.AssignmentVar.(*float32) return strconv.FormatFloat(float64(*a), 'f', 2, 32), err case *[]float32: v := f.AssignmentVar.(*[]float32) var strSlice []string for _, f := range *v { formatted := strconv.FormatFloat(float64(f), 'f', 2, 32) strSlice = append(strSlice, formatted) } return strings.Join(strSlice, ","), err case *float64: a := f.AssignmentVar.(*float64) return strconv.FormatFloat(float64(*a), 'f', 2, 64), err case *[]float64: v := f.AssignmentVar.(*[]float64) var strSlice []string for _, f := range *v { formatted := strconv.FormatFloat(float64(f), 'f', 2, 64) strSlice = append(strSlice, formatted) } return strings.Join(strSlice, ","), err case *int: a := f.AssignmentVar.(*int) return strconv.Itoa(*a), err case *[]int: val := f.AssignmentVar.(*[]int) var strSlice []string for _, i := range *val { str := strconv.Itoa(i) strSlice = append(strSlice, str) } return strings.Join(strSlice, ","), err case *uint: v := f.AssignmentVar.(*uint) return strconv.FormatUint(uint64(*v), 10), err case *[]uint: values := f.AssignmentVar.(*[]uint) var strVars []string for _, i := range *values { strVars = append(strVars, strconv.FormatUint(uint64(i), 10)) } return strings.Join(strVars, ","), err case *uint64: v := f.AssignmentVar.(*uint64) return strconv.FormatUint(*v, 10), err case *[]uint64: values := f.AssignmentVar.(*[]uint64) var strVars []string for _, i := range *values { strVars = append(strVars, strconv.FormatUint(i, 10)) } return strings.Join(strVars, ","), err case *uint32: v := f.AssignmentVar.(*uint32) return strconv.FormatUint(uint64(*v), 10), err case *[]uint32: values := f.AssignmentVar.(*[]uint32) var strVars []string for _, i := range *values { strVars = append(strVars, strconv.FormatUint(uint64(i), 10)) } return strings.Join(strVars, ","), err case *uint16: v := f.AssignmentVar.(*uint16) return strconv.FormatUint(uint64(*v), 10), err case *[]uint16: values := f.AssignmentVar.(*[]uint16) var strVars []string for _, i := range *values { strVars = append(strVars, strconv.FormatUint(uint64(i), 10)) } return strings.Join(strVars, ","), err case *uint8: v := f.AssignmentVar.(*uint8) return strconv.FormatUint(uint64(*v), 10), err case *[]uint8: values := f.AssignmentVar.(*[]uint8) var strVars []string for _, i := range *values { strVars = append(strVars, strconv.FormatUint(uint64(i), 10)) } return strings.Join(strVars, ","), err case *int64: v := f.AssignmentVar.(*int64) return strconv.FormatInt(int64(*v), 10), err case *[]int64: values := f.AssignmentVar.(*[]int64) var strVars []string for _, i := range *values { strVars = append(strVars, strconv.FormatInt(i, 10)) } return strings.Join(strVars, ","), err case *int32: v := f.AssignmentVar.(*int32) return strconv.FormatInt(int64(*v), 10), err case *[]int32: values := f.AssignmentVar.(*[]int32) var strVars []string for _, i := range *values { strVars = append(strVars, strconv.FormatInt(int64(i), 10)) } return strings.Join(strVars, ","), err case *int16: v := f.AssignmentVar.(*int16) return strconv.FormatInt(int64(*v), 10), err case *[]int16: values := f.AssignmentVar.(*[]int16) var strVars []string for _, i := range *values { strVars = append(strVars, strconv.FormatInt(int64(i), 10)) } return strings.Join(strVars, ","), err case *int8: v := f.AssignmentVar.(*int8) return strconv.FormatInt(int64(*v), 10), err case *[]int8: values := f.AssignmentVar.(*[]int8) var strVars []string for _, i := range *values { strVars = append(strVars, strconv.FormatInt(int64(i), 10)) } return strings.Join(strVars, ","), err case *net.IP: val := f.AssignmentVar.(*net.IP) return val.String(), err case *[]net.IP: val := f.AssignmentVar.(*[]net.IP) var strSlice []string for _, ip := range *val { strSlice = append(strSlice, ip.String()) } return strings.Join(strSlice, ","), err case *net.HardwareAddr: val := f.AssignmentVar.(*net.HardwareAddr) return val.String(), err case *[]net.HardwareAddr: val := f.AssignmentVar.(*[]net.HardwareAddr) var strSlice []string for _, mac := range *val { strSlice = append(strSlice, mac.String()) } return strings.Join(strSlice, ","), err case *net.IPMask: val := f.AssignmentVar.(*net.IPMask) return val.String(), err case *[]net.IPMask: val := f.AssignmentVar.(*[]net.IPMask) var strSlice []string for _, m := range *val { strSlice = append(strSlice, m.String()) } return strings.Join(strSlice, ","), err default: return "", errors.New("Unknown flag assignmentVar found in flag " + f.LongName + " " + f.ShortName + ". Type not supported: " + reflect.TypeOf(f.AssignmentVar).String()) } } golang-github-integrii-flaggy-1.4.0/flag_test.go000066400000000000000000000345501357570074000216400ustar00rootroot00000000000000package flaggy import ( "fmt" "net" "os" "testing" "time" ) // debugOff makes defers easier and turns off debug mode func debugOff() { DebugMode = false } // debugOn turns on debug mode func debugOn() { DebugMode = true } func TestGlobs(t *testing.T) { for _, a := range os.Args { fmt.Println(a) } } func TestParseArgWithValue(t *testing.T) { testCases := make(map[string][]string) testCases["-f=test"] = []string{"f", "test"} testCases["--f=test"] = []string{"f", "test"} testCases["--flag=test"] = []string{"flag", "test"} testCases["-flag=test"] = []string{"flag", "test"} testCases["----flag=--test"] = []string{"--flag", "--test"} testCases["-b"] = []string{"b", ""} testCases["--bool"] = []string{"bool", ""} for arg, correctValues := range testCases { key, value := parseArgWithValue(arg) if key != correctValues[0] { t.Fatalf("Flag %s parsed key as %s but expected key %s", arg, key, correctValues[0]) } if value != correctValues[1] { t.Fatalf("Flag %s parsed value as %s but expected value %s", arg, value, correctValues[1]) } t.Logf("Flag %s parsed key as %s and value as %s correctly", arg, key, value) } } func TestDetermineArgType(t *testing.T) { testCases := make(map[string]string) testCases["-f"] = argIsFlagWithSpace testCases["--f"] = argIsFlagWithSpace testCases["-flag"] = argIsFlagWithSpace testCases["--flag"] = argIsFlagWithSpace testCases["positionalArg"] = argIsPositional testCases["subcommand"] = argIsPositional testCases["sub--+/\\324command"] = argIsPositional testCases["--flag=CONTENT"] = argIsFlagWithValue testCases["-flag=CONTENT"] = argIsFlagWithValue testCases["-anotherfl-ag=CONTENT"] = argIsFlagWithValue testCases["--anotherfl-ag=CONTENT"] = argIsFlagWithValue testCases["1--anotherfl-ag=CONTENT"] = argIsPositional for arg, correctArgType := range testCases { argType := determineArgType(arg) if argType != correctArgType { t.Fatalf("Flag %s determined to be type %s but expected type %s", arg, argType, correctArgType) } else { t.Logf("Flag %s correctly determined to be type %s", arg, argType) } } } // TestInputParsing tests all flag types. func TestInputParsing(t *testing.T) { defer debugOff() DebugMode = true ResetParser() var err error inputArgs := []string{} // Setup input arguments for every input type var stringFlag = "defaultVar" String(&stringFlag, "s", "string", "string flag") inputArgs = append(inputArgs, "-s", "flaggy") var stringFlagExpected = "flaggy" var stringSliceFlag []string StringSlice(&stringSliceFlag, "ssf", "stringSlice", "string slice flag") inputArgs = append(inputArgs, "-ssf", "one", "-ssf", "two") var stringSliceFlagExpected = []string{"one", "two"} var boolFlag bool Bool(&boolFlag, "bf", "bool", "bool flag") inputArgs = append(inputArgs, "-bf") var boolFlagExpected = true var boolSliceFlag []bool BoolSlice(&boolSliceFlag, "bsf", "boolSlice", "bool slice flag") inputArgs = append(inputArgs, "-bsf", "-bsf") var boolSliceFlagExpected = []bool{true, true} var byteSliceFlag []byte ByteSlice(&byteSliceFlag, "bysf", "byteSlice", "byte slice flag") inputArgs = append(inputArgs, "-bysf", "17", "-bysf", "18") var byteSliceFlagExpected = []uint8{17, 18} var durationFlag time.Duration Duration(&durationFlag, "df", "duration", "duration flag") inputArgs = append(inputArgs, "-df", "33s") var durationFlagExpected = time.Second * 33 var durationSliceFlag []time.Duration DurationSlice(&durationSliceFlag, "dsf", "durationSlice", "duration slice flag") inputArgs = append(inputArgs, "-dsf", "33s", "-dsf", "1h") var durationSliceFlagExpected = []time.Duration{time.Second * 33, time.Hour} var float32Flag float32 Float32(&float32Flag, "f32", "float32", "float32 flag") inputArgs = append(inputArgs, "-f32", "33.343") var float32FlagExpected float32 = 33.343 var float32SliceFlag []float32 Float32Slice(&float32SliceFlag, "f32s", "float32Slice", "float32 slice flag") inputArgs = append(inputArgs, "-f32s", "33.343", "-f32s", "33.222") var float32SliceFlagExpected = []float32{33.343, 33.222} var float64Flag float64 Float64(&float64Flag, "f64", "float64", "float64 flag") inputArgs = append(inputArgs, "-f64", "33.222343") var float64FlagExpected = 33.222343 var float64SliceFlag []float64 Float64Slice(&float64SliceFlag, "f64s", "float64Slice", "float64 slice flag") inputArgs = append(inputArgs, "-f64s", "64.343", "-f64s", "64.222") var float64SliceFlagExpected = []float64{64.343, 64.222} var intFlag int Int(&intFlag, "i", "int", "int flag") inputArgs = append(inputArgs, "-i", "3553") var intFlagExpected = 3553 var intSliceFlag []int IntSlice(&intSliceFlag, "is", "intSlice", "int slice flag") inputArgs = append(inputArgs, "-is", "6446", "-is", "64") var intSliceFlagExpected = []int{6446, 64} var uintFlag uint UInt(&uintFlag, "ui", "uint", "uint flag") inputArgs = append(inputArgs, "-ui", "3553") var uintFlagExpected uint = 3553 var uintSliceFlag []uint UIntSlice(&uintSliceFlag, "uis", "uintSlice", "uint slice flag") inputArgs = append(inputArgs, "-uis", "6446", "-uis", "64") var uintSliceFlagExpected = []uint{6446, 64} var uint64Flag uint64 UInt64(&uint64Flag, "ui64", "uint64", "uint64 flag") inputArgs = append(inputArgs, "-ui64", "3553") var uint64FlagExpected uint64 = 3553 var uint64SliceFlag []uint64 UInt64Slice(&uint64SliceFlag, "ui64s", "uint64Slice", "uint64 slice flag") inputArgs = append(inputArgs, "-ui64s", "6446", "-ui64s", "64") var uint64SliceFlagExpected = []uint64{6446, 64} var uint32Flag uint32 UInt32(&uint32Flag, "ui32", "uint32", "uint32 flag") inputArgs = append(inputArgs, "-ui32", "6446") var uint32FlagExpected uint32 = 6446 var uint32SliceFlag []uint32 UInt32Slice(&uint32SliceFlag, "ui32s", "uint32Slice", "uint32 slice flag") inputArgs = append(inputArgs, "-ui32s", "6446", "-ui32s", "64") var uint32SliceFlagExpected = []uint32{6446, 64} var uint16Flag uint16 UInt16(&uint16Flag, "ui16", "uint16", "uint16 flag") inputArgs = append(inputArgs, "-ui16", "6446") var uint16FlagExpected uint16 = 6446 var uint16SliceFlag []uint16 UInt16Slice(&uint16SliceFlag, "ui16s", "uint16Slice", "uint16 slice flag") inputArgs = append(inputArgs, "-ui16s", "6446", "-ui16s", "64") var uint16SliceFlagExpected = []uint16{6446, 64} var uint8Flag uint8 UInt8(&uint8Flag, "ui8", "uint8", "uint8 flag") inputArgs = append(inputArgs, "-ui8", "50") var uint8FlagExpected uint8 = 50 var uint8SliceFlag []uint8 UInt8Slice(&uint8SliceFlag, "ui8s", "uint8Slice", "uint8 slice flag") inputArgs = append(inputArgs, "-ui8s", "3", "-ui8s", "2") var uint8SliceFlagExpected = []uint8{uint8(3), uint8(2)} var int64Flag int64 Int64(&int64Flag, "i64", "i64", "int64 flag") inputArgs = append(inputArgs, "-i64", "33445566") var int64FlagExpected int64 = 33445566 var int64SliceFlag []int64 Int64Slice(&int64SliceFlag, "i64s", "int64Slice", "int64 slice flag") if err != nil { t.Fatal(err) } inputArgs = append(inputArgs, "-i64s", "40", "-i64s", "50") var int64SliceFlagExpected = []int64{40, 50} var int32Flag int32 Int32(&int32Flag, "i32", "int32", "int32 flag") inputArgs = append(inputArgs, "-i32", "445566") var int32FlagExpected int32 = 445566 var int32SliceFlag []int32 Int32Slice(&int32SliceFlag, "i32s", "int32Slice", "uint32 slice flag") inputArgs = append(inputArgs, "-i32s", "40", "-i32s", "50") var int32SliceFlagExpected = []int32{40, 50} var int16Flag int16 Int16(&int16Flag, "i16", "int16", "int16 flag") if err != nil { t.Fatal(err) } inputArgs = append(inputArgs, "-i16", "5566") var int16FlagExpected int16 = 5566 var int16SliceFlag []int16 Int16Slice(&int16SliceFlag, "i16s", "int16Slice", "int16 slice flag") inputArgs = append(inputArgs, "-i16s", "40", "-i16s", "50") var int16SliceFlagExpected = []int16{40, 50} var int8Flag int8 Int8(&int8Flag, "i8", "int8", "int8 flag") inputArgs = append(inputArgs, "-i8", "32") var int8FlagExpected int8 = 32 var int8SliceFlag []int8 Int8Slice(&int8SliceFlag, "i8s", "int8Slice", "uint8 slice flag") inputArgs = append(inputArgs, "-i8s", "4", "-i8s", "2") var int8SliceFlagExpected = []int8{4, 2} var ipFlag net.IP IP(&ipFlag, "ip", "ipFlag", "ip flag") inputArgs = append(inputArgs, "-ip", "1.1.1.1") var ipFlagExpected = net.IPv4(1, 1, 1, 1) var ipSliceFlag []net.IP IPSlice(&ipSliceFlag, "ips", "ipFlagSlice", "ip slice flag") inputArgs = append(inputArgs, "-ips", "1.1.1.1", "-ips", "4.4.4.4") var ipSliceFlagExpected = []net.IP{net.IPv4(1, 1, 1, 1), net.IPv4(4, 4, 4, 4)} var hwFlag net.HardwareAddr HardwareAddr(&hwFlag, "hw", "hwFlag", "hw flag") inputArgs = append(inputArgs, "-hw", "32:00:16:46:20:00") hwFlagExpected, err := net.ParseMAC("32:00:16:46:20:00") if err != nil { t.Fatal(err) } var hwFlagSlice []net.HardwareAddr HardwareAddrSlice(&hwFlagSlice, "hws", "hwFlagSlice", "hw slice flag") inputArgs = append(inputArgs, "-hws", "32:00:16:46:20:00", "-hws", "32:00:16:46:20:01") macA, err := net.ParseMAC("32:00:16:46:20:00") if err != nil { t.Fatal(err) } macB, err := net.ParseMAC("32:00:16:46:20:01") if err != nil { t.Fatal(err) } var hwFlagSliceExpected = []net.HardwareAddr{macA, macB} var maskFlag net.IPMask IPMask(&maskFlag, "m", "mFlag", "mask flag") inputArgs = append(inputArgs, "-m", "255.255.255.255") var maskFlagExpected = net.IPMask([]byte{255, 255, 255, 255}) var maskSliceFlag []net.IPMask IPMaskSlice(&maskSliceFlag, "ms", "mFlagSlice", "mask slice flag") if err != nil { t.Fatal(err) } inputArgs = append(inputArgs, "-ms", "255.255.255.255", "-ms", "255.255.255.0") var maskSliceFlagExpected = []net.IPMask{net.IPMask([]byte{255, 255, 255, 255}), net.IPMask([]byte{255, 255, 255, 0})} // display help with all flags used ShowHelp("Showing help for test: " + t.Name()) // Parse arguments ParseArgs(inputArgs) // validate parsed values if stringFlag != stringFlagExpected { t.Fatal("string flag incorrect", stringFlag, stringFlagExpected) } for i, f := range stringSliceFlagExpected { if stringSliceFlag[i] != f { t.Fatal("stringSlice value incorrect", stringSliceFlag[i], f) } } if boolFlag != boolFlagExpected { t.Fatal("bool flag incorrect", boolFlag, boolFlagExpected) } for i, f := range boolSliceFlagExpected { if boolSliceFlag[i] != f { t.Fatal("boolSlice value incorrect", boolSliceFlag[i], f) } } for i, f := range byteSliceFlagExpected { if byteSliceFlag[i] != f { t.Fatal("byteSlice value incorrect", boolSliceFlag[i], f) } } if durationFlag != durationFlagExpected { t.Fatal("duration flag incorrect", durationFlag, durationFlagExpected) } for i, f := range durationSliceFlagExpected { if durationSliceFlag[i] != f { t.Fatal("durationSlice value incorrect", durationSliceFlag[i], f) } } if float32Flag != float32FlagExpected { t.Fatal("float32 flag incorrect", float32Flag, float32FlagExpected) } for i, f := range float32SliceFlagExpected { if float32SliceFlag[i] != f { t.Fatal("float32Slice value incorrect", float32SliceFlag[i], f) } } if float64Flag != float64FlagExpected { t.Fatal("float64 flag incorrect", float64Flag, float64FlagExpected) } for i, f := range float64SliceFlagExpected { if float64SliceFlag[i] != f { t.Fatal("float64Slice value incorrect", float64SliceFlag[i], f) } } if intFlag != intFlagExpected { t.Fatal("int flag incorrect", intFlag, intFlagExpected) } for i, f := range intSliceFlagExpected { if intSliceFlag[i] != f { t.Fatal("intSlice value incorrect", intSliceFlag[i], f) } } if uintFlag != uintFlagExpected { t.Fatal("uint flag incorrect", uintFlag, uintFlagExpected) } for i, f := range uintSliceFlagExpected { if uintSliceFlag[i] != f { t.Fatal("uintSlice value incorrect", uintSliceFlag[i], f) } } if uint64Flag != uint64FlagExpected { t.Fatal("uint64 flag incorrect", uint64Flag, uint64FlagExpected) } for i, f := range uint64SliceFlagExpected { if uint64SliceFlag[i] != f { t.Fatal("uint64Slice value incorrect", uint64SliceFlag[i], f) } } if uint32Flag != uint32FlagExpected { t.Fatal("uint32 flag incorrect", uint32Flag, uint32FlagExpected) } for i, f := range uint32SliceFlagExpected { if uint32SliceFlag[i] != f { t.Fatal("uint32Slice value incorrect", uint32SliceFlag[i], f) } } if uint16Flag != uint16FlagExpected { t.Fatal("uint16 flag incorrect", uint16Flag, uint16FlagExpected) } for i, f := range uint16SliceFlagExpected { if uint16SliceFlag[i] != f { t.Fatal("uint16Slice value incorrect", uint16SliceFlag[i], f) } } if uint8Flag != uint8FlagExpected { t.Fatal("uint8 flag incorrect", uint8Flag, uint8FlagExpected) } for i, f := range uint8SliceFlagExpected { if uint8SliceFlag[i] != f { t.Fatal("uint8Slice value", i, "incorrect", uint8SliceFlag[i], f) } } if int64Flag != int64FlagExpected { t.Fatal("int64 flag incorrect", int64Flag, int64FlagExpected) } for i, f := range int64SliceFlagExpected { if int64SliceFlag[i] != f { t.Fatal("int64Slice value incorrect", int64SliceFlag[i], f) } } if int32Flag != int32FlagExpected { t.Fatal("int32 flag incorrect", int32Flag, int32FlagExpected) } for i, f := range int32SliceFlagExpected { if int32SliceFlag[i] != f { t.Fatal("int32Slice value incorrect", int32SliceFlag[i], f) } } if int16Flag != int16FlagExpected { t.Fatal("int16 flag incorrect", int16Flag, int16FlagExpected) } for i, f := range int16SliceFlagExpected { if int16SliceFlag[i] != f { t.Fatal("int16Slice value incorrect", int16SliceFlag[i], f) } } if int8Flag != int8FlagExpected { t.Fatal("int8 flag incorrect", int8Flag, int8FlagExpected) } for i, f := range int8SliceFlagExpected { if int8SliceFlag[i] != f { t.Fatal("int8Slice value incorrect", int8SliceFlag[i], f) } } if !ipFlag.Equal(ipFlagExpected) { t.Fatal("ip flag incorrect", ipFlag, ipFlagExpected) } for i, f := range ipSliceFlagExpected { if !f.Equal(ipSliceFlag[i]) { t.Fatal("ipSlice value incorrect", ipSliceFlag[i], f) } } if hwFlag.String() != hwFlagExpected.String() { t.Fatal("hw flag incorrect", hwFlag, hwFlagExpected) } for i, f := range hwFlagSliceExpected { if f.String() != hwFlagSlice[i].String() { t.Fatal("hw flag slice value incorrect", hwFlagSlice[i].String(), f.String()) } } if maskFlag.String() != maskFlagExpected.String() { t.Fatal("mask flag incorrect", maskFlag, maskFlagExpected) } for i, f := range maskSliceFlagExpected { if f.String() != maskSliceFlag[i].String() { t.Fatal("mask flag slice value incorrect", maskSliceFlag[i].String(), f.String()) } } } golang-github-integrii-flaggy-1.4.0/flaggy_test.go000066400000000000000000000106161357570074000221750ustar00rootroot00000000000000package flaggy_test import ( "os" "testing" "github.com/integrii/flaggy" ) // TestTrailingArguments tests trailing argument parsing func TestTrailingArguments(t *testing.T) { flaggy.ResetParser() args := []string{"./flaggy.text", "--", "one", "two"} os.Args = args flaggy.Parse() if len(flaggy.TrailingArguments) != 2 { t.Fatal("incorrect argument count parsed. Got", len(flaggy.TrailingArguments), "but expected", 2) } if flaggy.TrailingArguments[0] != "one" { t.Fatal("incorrect argument parsed. Got", flaggy.TrailingArguments[0], "but expected one") } if flaggy.TrailingArguments[1] != "two" { t.Fatal("incorrect argument parsed. Got", flaggy.TrailingArguments[1], "but expected two") } } // TestComplexNesting tests various levels of nested subcommands and // positional values intermixed with eachother. func TestComplexNesting(t *testing.T) { flaggy.DebugMode = true defer debugOff() flaggy.ResetParser() var testA string var testB string var testC string var testD string var testE string var testF bool scA := flaggy.NewSubcommand("scA") scB := flaggy.NewSubcommand("scB") scC := flaggy.NewSubcommand("scC") scD := flaggy.NewSubcommand("scD") flaggy.Bool(&testF, "f", "testF", "") flaggy.AttachSubcommand(scA, 1) scA.AddPositionalValue(&testA, "testA", 1, false, "") scA.AddPositionalValue(&testB, "testB", 2, false, "") scA.AddPositionalValue(&testC, "testC", 3, false, "") scA.AttachSubcommand(scB, 4) scB.AddPositionalValue(&testD, "testD", 1, false, "") scB.AttachSubcommand(scC, 2) scC.AttachSubcommand(scD, 1) scD.AddPositionalValue(&testE, "testE", 1, true, "") args := []string{"scA", "-f", "A", "B", "C", "scB", "D", "scC", "scD", "E"} t.Log(args) flaggy.ParseArgs(args) if !testF { t.Log("testF", testF) t.FailNow() } if !scA.Used { t.Log("sca", scA.Name) t.FailNow() } if !scB.Used { t.Log("scb", scB.Name) t.FailNow() } if !scC.Used { t.Log("scc", scC.Name) t.FailNow() } if !scD.Used { t.Log("scd", scD.Name) t.FailNow() } if testA != "A" { t.Log("testA", testA) t.FailNow() } if testB != "B" { t.Log("testB", testB) t.FailNow() } if testC != "C" { t.Log("testC", testC) t.FailNow() } if testD != "D" { t.Log("testD", testD) t.FailNow() } if testE != "E" { t.Log("testE", testE) t.FailNow() } } func TestParsePositionalsA(t *testing.T) { inputLine := []string{"-t", "-i=3", "subcommand", "-n", "testN", "-j=testJ", "positionalA", "positionalB", "--testK=testK", "--", "trailingA", "trailingB"} flaggy.DebugMode = true var boolT bool var intT int var testN string var testJ string var testK string var positionalA string var positionalB string var err error // make a new parser parser := flaggy.NewParser("testParser") // add a bool flag to the parser parser.Bool(&boolT, "t", "", "test flag for bool arg") // add an int flag to the parser parser.Int(&intT, "i", "", "test flag for int arg") // create a subcommand subCommand := flaggy.NewSubcommand("subcommand") parser.AttachSubcommand(subCommand, 1) // add flags to subcommand subCommand.String(&testN, "n", "testN", "test flag for value with space arg") subCommand.String(&testJ, "j", "testJ", "test flag for value with equals arg") subCommand.String(&testK, "k", "testK", "test full length flag with attached arg") // add positionals to subcommand subCommand.AddPositionalValue(&positionalA, "PositionalA", 1, false, "PositionalA test value") subCommand.AddPositionalValue(&positionalB, "PositionalB", 2, false, "PositionalB test value") // parse input err = parser.ParseArgs(inputLine) if err != nil { t.Fatal(err) } // check the results if intT != 3 { t.Fatal("Global int flag -i was incorrect:", intT) } if boolT != true { t.Fatal("Global bool flag -t was incorrect:", boolT) } if testN != "testN" { t.Fatal("Subcommand flag testN was incorrect:", testN) } if positionalA != "positionalA" { t.Fatal("Positional A was incorrect:", positionalA) } if positionalB != "positionalB" { t.Fatal("Positional B was incorrect:", positionalB) } if len(parser.TrailingArguments) < 2 { t.Fatal("Incorrect number of trailing arguments. Got", len(parser.TrailingArguments)) } if parser.TrailingArguments[0] != "trailingA" { t.Fatal("Trailing argumentA was incorrect:", parser.TrailingArguments[0]) } if parser.TrailingArguments[1] != "trailingB" { t.Fatal("Trailing argumentB was incorrect:", parser.TrailingArguments[1]) } } golang-github-integrii-flaggy-1.4.0/go.mod000066400000000000000000000000531357570074000204360ustar00rootroot00000000000000module github.com/integrii/flaggy go 1.12 golang-github-integrii-flaggy-1.4.0/help.go000066400000000000000000000024521357570074000206140ustar00rootroot00000000000000package flaggy // defaultHelpTemplate is the help template used by default // {{if (or (or (gt (len .StringFlags) 0) (gt (len .IntFlags) 0)) (gt (len .BoolFlags) 0))}} // {{if (or (gt (len .StringFlags) 0) (gt (len .BoolFlags) 0))}} const defaultHelpTemplate = `{{.CommandName}}{{if .Description}} - {{.Description}}{{end}}{{if .PrependMessage}} {{.PrependMessage}}{{end}} {{if .UsageString}} Usage: {{.UsageString}}{{end}}{{if .Positionals}} Positional Variables: {{range .Positionals}} {{.Name}} {{.Spacer}}{{if .Description}} {{.Description}}{{end}}{{if .DefaultValue}} (default: {{.DefaultValue}}){{else}}{{if .Required}} (Required){{end}}{{end}}{{end}}{{end}}{{if .Subcommands}} Subcommands: {{range .Subcommands}} {{.LongName}}{{if .ShortName}} ({{.ShortName}}){{end}}{{if .Position}}{{if gt .Position 1}} (position {{.Position}}){{end}}{{end}}{{if .Description}} {{.Spacer}}{{.Description}}{{end}}{{end}} {{end}}{{if (gt (len .Flags) 0)}} Flags: {{if .Flags}}{{range .Flags}} {{if .ShortName}}-{{.ShortName}} {{else}} {{end}}{{if .LongName}}--{{.LongName}}{{end}}{{if .Description}} {{.Spacer}}{{.Description}}{{if .DefaultValue}} (default: {{.DefaultValue}}){{end}}{{end}}{{end}}{{end}} {{end}}{{if .AppendMessage}}{{.AppendMessage}} {{end}}{{if .Message}} {{.Message}}{{end}} ` golang-github-integrii-flaggy-1.4.0/helpValues.go000066400000000000000000000175221357570074000220000ustar00rootroot00000000000000package flaggy import ( "log" "reflect" "strings" "unicode/utf8" ) // Help represents the values needed to render a Help page type Help struct { Subcommands []HelpSubcommand Positionals []HelpPositional Flags []HelpFlag UsageString string CommandName string PrependMessage string AppendMessage string Message string Description string } // HelpSubcommand is used to template subcommand Help output type HelpSubcommand struct { ShortName string LongName string Description string Position int Spacer string } // HelpPositional is used to template positional Help output type HelpPositional struct { Name string Description string Required bool Position int DefaultValue string Spacer string } // HelpFlag is used to template string flag Help output type HelpFlag struct { ShortName string LongName string Description string DefaultValue string Spacer string } // ExtractValues extracts Help template values from a subcommand and its parent // parser. The parser is required in order to detect default flag settings // for help and version output. func (h *Help) ExtractValues(p *Parser, message string) { // accept message string for output h.Message = message // extract Help values from the current subcommand in context // prependMessage string h.PrependMessage = p.subcommandContext.AdditionalHelpPrepend // appendMessage string h.AppendMessage = p.subcommandContext.AdditionalHelpAppend // command name h.CommandName = p.subcommandContext.Name // description h.Description = p.subcommandContext.Description maxLength := getLongestNameLength(p.subcommandContext.Subcommands, 0) // subcommands []HelpSubcommand for _, cmd := range p.subcommandContext.Subcommands { if cmd.Hidden { continue } newHelpSubcommand := HelpSubcommand{ ShortName: cmd.ShortName, LongName: cmd.Name, Description: cmd.Description, Position: cmd.Position, Spacer: makeSpacer(cmd.Name, maxLength), } h.Subcommands = append(h.Subcommands, newHelpSubcommand) } maxLength = getLongestNameLength(p.subcommandContext.PositionalFlags, 0) // parse positional flags into help output structs for _, pos := range p.subcommandContext.PositionalFlags { if pos.Hidden { continue } newHelpPositional := HelpPositional{ Name: pos.Name, Position: pos.Position, Description: pos.Description, Required: pos.Required, DefaultValue: pos.defaultValue, Spacer: makeSpacer(pos.Name, maxLength), } h.Positionals = append(h.Positionals, newHelpPositional) } maxLength = len(versionFlagLongName) if len(helpFlagLongName) > maxLength { maxLength = len(helpFlagLongName) } maxLength = getLongestNameLength(p.subcommandContext.Flags, maxLength) maxLength = getLongestNameLength(p.Flags, maxLength) // if the built-in version flag is enabled, then add it as a help flag if p.ShowVersionWithVersionFlag { defaultVersionFlag := HelpFlag{ ShortName: "", LongName: versionFlagLongName, Description: "Displays the program version string.", DefaultValue: "", Spacer: makeSpacer(versionFlagLongName, maxLength), } h.Flags = append(h.Flags, defaultVersionFlag) } // if the built-in help flag exists, then add it as a help flag if p.ShowHelpWithHFlag { defaultHelpFlag := HelpFlag{ ShortName: helpFlagShortName, LongName: helpFlagLongName, Description: "Displays help with available flag, subcommand, and positional value parameters.", DefaultValue: "", Spacer: makeSpacer(helpFlagLongName, maxLength), } h.Flags = append(h.Flags, defaultHelpFlag) } // go through every flag in the subcommand and add it to help output h.parseFlagsToHelpFlags(p.subcommandContext.Flags, maxLength) // go through every flag in the parent parser and add it to help output h.parseFlagsToHelpFlags(p.Flags, maxLength) // formulate the usage string // first, we capture all the command and positional names by position commandsByPosition := make(map[int]string) for _, pos := range p.subcommandContext.PositionalFlags { if pos.Hidden { continue } if len(commandsByPosition[pos.Position]) > 0 { commandsByPosition[pos.Position] = commandsByPosition[pos.Position] + "|" + pos.Name } else { commandsByPosition[pos.Position] = pos.Name } } for _, cmd := range p.subcommandContext.Subcommands { if cmd.Hidden { continue } if len(commandsByPosition[cmd.Position]) > 0 { commandsByPosition[cmd.Position] = commandsByPosition[cmd.Position] + "|" + cmd.Name } else { commandsByPosition[cmd.Position] = cmd.Name } } // find the highest position count in the map var highestPosition int for i := range commandsByPosition { if i > highestPosition { highestPosition = i } } // only have a usage string if there are positional items var usageString string if highestPosition > 0 { // find each positional value and make our final string usageString = p.subcommandContext.Name for i := 1; i <= highestPosition; i++ { if len(commandsByPosition[i]) > 0 { usageString = usageString + " [" + commandsByPosition[i] + "]" } else { // dont keep listing after the first position without any properties // it will be impossible to reach anything beyond here anyway break } } } h.UsageString = usageString } // parseFlagsToHelpFlags parses the specified slice of flags into // help flags on the the calling help command func (h *Help) parseFlagsToHelpFlags(flags []*Flag, maxLength int) { for _, f := range flags { if f.Hidden { continue } // parse help values out if the flag hasn't been parsed yet if !f.parsed { f.parsed = true // parse the default value as a string and remember it for help output f.defaultValue, _ = f.returnAssignmentVarValueAsString() } // determine the default value based on the assignment variable defaultValue := f.defaultValue // dont show nils if defaultValue == "" { defaultValue = "" } // for bools, dont show a default of false _, isBool := f.AssignmentVar.(*bool) if isBool { b := f.AssignmentVar.(*bool) if *b == false { defaultValue = "" } } newHelpFlag := HelpFlag{ ShortName: f.ShortName, LongName: f.LongName, Description: f.Description, DefaultValue: defaultValue, Spacer: makeSpacer(f.LongName, maxLength), } h.AddFlagToHelp(newHelpFlag) } } // AddFlagToHelp adds a flag to help output if it does not exist func (h *Help) AddFlagToHelp(f HelpFlag) { for _, existingFlag := range h.Flags { if len(existingFlag.ShortName) > 0 && existingFlag.ShortName == f.ShortName { return } if len(existingFlag.LongName) > 0 && existingFlag.LongName == f.LongName { return } } h.Flags = append(h.Flags, f) } // getLongestNameLength takes a slice of any supported flag and returns the length of the longest of their names func getLongestNameLength(slice interface{}, min int) int { var maxLength = min s := reflect.ValueOf(slice) if s.Kind() != reflect.Slice { log.Panicf("Paremeter given to getLongestNameLength() is of type %s. Expected slice", s.Kind()) } for i := 0; i < s.Len(); i++ { option := s.Index(i).Interface() var name string switch t := option.(type) { case *Subcommand: name = t.Name case *Flag: name = t.LongName case *PositionalValue: name = t.Name default: log.Panicf("Unexpected type %T found in slice passed to getLongestNameLength(). Possible types: *Subcommand, *Flag, *PositionalValue", t) } length := len(name) if length > maxLength { maxLength = length } } return maxLength } // makeSpacer creates a string of whitespaces, with a length of the given // maxLength minus the length of the given name func makeSpacer(name string, maxLength int) string { length := maxLength - utf8.RuneCountInString(name) if length < 0 { length = 0 } return strings.Repeat(" ", length) } golang-github-integrii-flaggy-1.4.0/helpValues_blackbox_test.go000066400000000000000000000045061357570074000247020ustar00rootroot00000000000000package flaggy_test import ( "testing" "time" "github.com/integrii/flaggy" ) func TestMinimalHelpOutput(t *testing.T) { p := flaggy.NewParser("TestMinimalHelpOutput") p.ShowHelp() } func TestHelpWithMissingSCName(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected panic with subcommand avilability at position, but did not get one") } }() flaggy.ResetParser() sc := flaggy.NewSubcommand("") sc.ShortName = "sn" flaggy.AttachSubcommand(sc, 1) flaggy.ParseArgs([]string{"x"}) } // TestHelpOutput tests the dislay of help with -h func TestHelpOutput(t *testing.T) { flaggy.ResetParser() // flaggy.DebugMode = true // defer debugOff() p := flaggy.NewParser("testCommand") p.Description = "Description goes here. Get more information at https://github.com/integrii/flaggy." scA := flaggy.NewSubcommand("subcommandA") scA.ShortName = "a" scA.Description = "Subcommand A is a command that does stuff" scB := flaggy.NewSubcommand("subcommandB") scB.ShortName = "b" scB.Description = "Subcommand B is a command that does other stuff" scX := flaggy.NewSubcommand("subcommandX") scX.Description = "This should be hidden." scX.Hidden = true var posA = "defaultPosA" var posB string p.AttachSubcommand(scA, 1) scA.AttachSubcommand(scB, 1) scA.AddPositionalValue(&posA, "testPositionalA", 2, false, "Test positional A does some things with a positional value.") scB.AddPositionalValue(&posB, "hiddenPositional", 1, false, "Hidden test positional B does some less than serious things with a positional value.") scB.PositionalFlags[0].Hidden = true var stringFlag = "defaultStringHere" var intFlag int var boolFlag bool var durationFlag time.Duration p.String(&stringFlag, "s", "stringFlag", "This is a test string flag that does some stringy string stuff.") p.Int(&intFlag, "i", "intFlg", "This is a test int flag that does some interesting int stuff.") p.Bool(&boolFlag, "b", "boolFlag", "This is a test bool flag that does some booly bool stuff.") p.Duration(&durationFlag, "d", "durationFlag", "This is a test duration flag that does some untimely stuff.") p.AdditionalHelpPrepend = "This is a prepend for help" p.AdditionalHelpAppend = "This is an append for help" p.ParseArgs([]string{"subcommandA", "subcommandB", "hiddenPositional1"}) p.ShowHelpWithMessage("This is a help message on exit") } golang-github-integrii-flaggy-1.4.0/helpValues_whitebox_test.go000066400000000000000000000030041357570074000247360ustar00rootroot00000000000000package flaggy import ( "testing" ) func TestMakeSpacer(t *testing.T) { if spacer := makeSpacer("short", 20); len(spacer) != 15 { t.Errorf("spacer length expected to be 15, got %d.", len(spacer)) } if spacer := makeSpacer("very long", 20); len(spacer) != 11 { t.Errorf("spacer length expected to be 11, got %d.", len(spacer)) } if spacer := makeSpacer("very long", 0); len(spacer) != 0 { t.Errorf("spacer length expected to be 0, got %d.", len(spacer)) } } func TestGetLongestNameLength(t *testing.T) { input := []string{"short", "longer", "very-long"} var subcommands []*Subcommand var flags []*Flag var positionalValues []*PositionalValue for _, name := range input { subcommands = append(subcommands, NewSubcommand(name)) flags = append(flags, &Flag{LongName: name}) positionalValues = append(positionalValues, &PositionalValue{Name: name}) } if l := getLongestNameLength(subcommands, 0); l != 9 { t.Errorf("should have returned 9, got %d.", l) } if l := getLongestNameLength(subcommands, 15); l != 15 { t.Errorf("should have returned 15, got %d.", l) } if l := getLongestNameLength(flags, 0); l != 9 { t.Errorf("should have returned 9, got %d.", l) } if l := getLongestNameLength(flags, 15); l != 15 { t.Errorf("should have returned 15, got %d.", l) } if l := getLongestNameLength(positionalValues, 0); l != 9 { t.Errorf("should have returned 15, got %d.", l) } if l := getLongestNameLength(positionalValues, 15); l != 15 { t.Errorf("should have returned 9, got %d.", l) } } golang-github-integrii-flaggy-1.4.0/logo.png000066400000000000000000000264441357570074000210120ustar00rootroot00000000000000‰PNG  IHDR/zc°¶ iCCPICC Profile(‘c``’H,(Èa``ÈÍ+) rwRˆˆŒR`ÄÀÌ Æ ÄÀÉ ž˜\\ààÃ0|»ÆÀ¢/ë‚ÌÂ”Ç ¸RR‹“ô NI.(*a``L²•ËK @ì [$)Ìžbd¯±Ó!ì`5ö°š g ûÍ—aÿ±ÓÁl&j/Ø Ž@w§*}O¢ã ’ÔŠíœ_PY”™žQ¢±É3/YOGÁÈÀЂÞÕŸÁáÈ(v!†±Ê½À˜b``Ù‰ úo>ƒ,3BLM‰A¨žacAriQÔFƳ „øT«K”μçiTXtXML:com.adobe.xmp 303 276 ˜8 4*)IDATxí]˜Å•.åœVÒJ«UF @"X ‘…1Øà6ÇÙìóÙswŸ¹³}`î|ÇwNœÓŒ ¶ÉÁ‰$‚‘„P^I«´ »Ò*Ýû[jÑSS=ÓÝÓ=ÓÕý?XÍtOuõ«ÿõüSõêÕ«{E…"`-Ó—ê"@H^|ˆ°’—•f£ÒD€¼ø "`%$/+ÍF¥‰ yñ DÀJH^VšJ"@òâ3@ˆ€•¼¬4•&D€äÅg€+ yYi6*MˆÉ‹Ï V"@ò²ÒlTš’Ÿ"@¬D€äe¥Ù¨4 $/>D€X‰ÉËJ³Qi"@H^|ˆ°’—•f£ÒD€¼ø "`%$/+ÍF¥‰ yñ DÀJH^VšJ"@òâ3@ˆ€•¼¬4•&D€äÅg€+ yYi6*MˆÉ‹Ï V"@ò²ÒlTš’Ÿ"@¬D€äe¥Ù¨4 $/>D€X‰ÉËJ³Qi"@H^|ˆ°ÎVjM¥«ŽÀ‚•mj¡ü­ÚЮ¬lU[Úv«&yc“ôéÑIÒØÃùè˜q}Ô°º®ê`9¿ÿœéž#aè°W$Ì,›æ½»Uáoî»[œ×8[=y\oB›6±É,N`sVÉ+g÷k.zR³ç·¨'çorÈ ÇÕôȦOì¯><¥ŽDV À3t’W†Œ¶)òy +ìõq—ÇòÜ)"ð“BJ!@ò*…N?óö°fIO+âš.CÊOM«go,J‰N$¯”¢Zj\þã…±û°’Ôþ±óö÷Æ’¼ë¶’—}6«Hãé×¼æÌVTI .†oÌRâ}-Ãí›nR×ÎUK5ro’WÎÉ_}ÅúcHyŠ8ù1[Y-ßëe™}Å„†;Üž÷ÃIÖcisçe³õrª;ÈÃ%se–Ò »ˆ›ÈÜp‘YBXˆs£¤ ’WºìAmB"pß‹þ ˜­œ,ñcx ‹IŒw„œt ’š'±m$«†¨Aq’W @¯Õ-«»U«öpL¤§¿Ÿ wE±’—v‹¤5zyT6­Î…ÙÙ´+[UL½¼*Ü–·ØÉ+G‚ß"êAkS·Vi U¬Jg¨2’W†ŒY®)«6ì(W„Ÿk yYcªÊͺþr„XƒM¼l²V…ºæÕa_!l¼<¥¼RjªEˆ@iH^¥ñÉÔ§ È”9sß’Wî@ìD€äe§ÝBk͘¤Ðñ‚”#@òJ¹âR1Iq!ÉzÒ‚É+-–HXìøC‰ýÆ‹gØÚH^a³´¢Ùçè×õ/Gb.Qé×ñØ.H^vÙ«¬¶ šTŽOª,P,`= RµÞ„… ;uß…؈ÉËF«•й–3r%ÔâGD vH^±CZÛ Ã’¦þ)DÀFH^6Z­„Îa‡%ªâGD Õ¼RmžpÊE!.ú¼ÂaÌÒéA€ä•[T¬IØ!cÅ7dD †¼j~Ü·Ûób¯+n °¾j"@òª&Ú ßkÕ† ßÕô @òJ-*Ö„=¯Š!d!@ò²ÈXåT¥Ï«BüD€X‰ÉËJ³Qi"@H^|ˆ°’—•f£ÒD€¼ø "`%$/+ÍF¥‰ yñ DÀJH^VšJ"@òâ3@ˆ€•¼¬4•&D€äÅg€+ yYi6*MˆÉ‹Ï V"@ò²ÒlTš’Ÿ"@¬D€äe¥Ù¨4 $/>D€X‰ÉËJ³Qi"@¬ÝúìÍe­jÝ–5µ`ÏnÔ¡Ã{(ì‡XJ°?ÓÓo¶(2 wg5jpwÕ·géë\À7D€!`%y͚ߢ¾ÿ—•ªeÛ.Õ¹SÕ±ýÇÝ{”jß¹GÔÐ]ýêÊCT§ŽŠÀuOÜxßJõû§š’kkßãÖY“¨¯~¤Ñ-ÂW"@B"`%yÍy{³Z¹~GȦ&Süõ¥­jñêíêàa=Œ7ؽg¯šõZ‹Ú¹k¯Ú¸u—Sf»6ŠÝµ{¯C¾Æ y’’Ô ÏRRŸ@º$¨pÂ…ºuéèßmÐÔ·KÑÇÂijÇ.é¾Qˆˆ„€•䕦-¾Û…€ÊîBí7¢LSC"=>¼ˆÔ+‡¥àêß«³ªëÓÙñAùqF©ëƒ|ÎÁppÛöÝjÂÈ^ªŸÜ“Bˆ@uÈÔ·î£ÇTžÌÅV"à"@òr‘à+ V!@ò²Ê\T–’—‹_‰° ’—U梲D€¸¼\$øJˆ€U¼¬2•%DÀE€Q.|ED?IcLœIß4êéÅÔ¤3>O»ÞÞ6Øúžäe«å¾F $¨Ü)Ù"/8qPlËÅ{ ¸.’Üm‹›ÚÔ»MÛUKë.'’žˆ‹ö‘-w¸è9´Wu؈žê ѽG·B¡ÿK ·¨G_Þä|¶C’[-KÜÎ>¦ÎÛ´¢÷O¾¶I2ónV}$ƒïNÑkÒA½ÕÉú:6-*œÐ‰çÞÚ,¶kU%!(òÓ5ì¦.sj½ê—à2«ùK¶©?ÍY¯ž‘´Ø š°Â=ö>ê“S+¬e…€ãþ³¯Ü¼¨`Hãý<èûß<±FýäU±—÷Þ¿|tµºöŽ¥.Þóî{l”V0ܼú—ñ—Wì_ð ѽ]üX˜d—Ÿ|@|\ð¡éòÀKÔùKRæÊÚ»âóÓeÆä:Õ«Ì.Zú5•³çU)‚U¼~耮ª“ãŒ6?Øèº×õé¢Ĺ-ÙU¶kgóïÓ{kÄa¾²M¤¶JFX“,‘27ܽBÝpéèHIáÔý‰ 1lòèGüéM`ÁOàÔ‡è=q˜ëä]ª~¿úüÎcCWëÖÃpzܰîŽ#~X9ñ%úL[Úv;ÃäùK·‘Ô}Ò{™ “Q½áiû9“Þë1i³Tpš8jŸ/ÐûYï1Ñr¯ OuÁ„ÅÙ“è§?&y%q|7yph_l:P)¾ügLê¯N”óð“ôèÚÑ!8wl¯ü´ë¿îî äù~bFúù#«ÕB™ýÓ½L hý£’Çðýç–û׉‡õU3§ v ONÎÌ(tÑuÅM /Ȉžü;·ˆ³q fûàãò#®C‡÷TŸÛTÑÛí¹:™t†+ ³§Wìê 1 ¿çùõ NW0ÉUΖž|”ú½ï|z]bä…¡4&\tù àѯ{’¼tK¤üøòCg/Èáøñ}Ô±2{…_bÌhÅe*ÇœŽNg–½Ÿë~·¬èáDžþ»ž[š¼îya½¶¡ß¤z‘¤êþÂÙ 2­^ØÓêTBW·èü1 8LˆÃÑçlq?ªøÃ.8ËMò! _øâ‡BûuЦ!.ñácëÔ©GöW_¿å=õ¬ôH+•QõÝÄþ}¶ôÊ‹‚H1j¯Î[—þþ±W7*ø]u9ÿ„Ez™$Ž Ÿž$îÀ:cEi®ÿõ#$`Œúø)<ľÄâÎpzç’ÑqVîåøBl•!PPAîÏ4‹_}~£úÚG‹ˆËX¸ÄIÄ{Ýpéu¸ÄPÅ!dOMø¡ôž êé|ûâQ¡‰K× dýƒËƪ“&ôÓ? }Œ™ÓÍH®—]ä}ecèúÊ]€^ésoÿP ×|œüˆÖBH^µ@=¥÷ÄlzEº´¶ïõ…¸_z0Ðå!n\Ò[¾8WÊ®ã}c²`(¾Æ 3z0ÿváˆ"’ˆÚη>5R5ˆ  R™,Ž{ø7uyPð[0Ü]µ¡84a5¥|•qëá­/3ä…É™¶NVo£ùÞÓŽà K½%Й+3d%&À¼ÅÕ³âCÓ> /ÓÉñ¯×å=>f\ouÞq½§"½G´¸I.ž^ïL|˜>‹z®N¢ïŸV©€4àëÔQïoKs\‚ðÛŸ\[Ô+EÀ𙓪ï¨wÛ•)Ÿ×Có68Ë2à¿ÓUÜ¿n;#¿bH6PfðàσŒkè®01 G¿/—i} ÊမK“#u&@‰•ørE´uźbç?–%'³ IÈ©¢3fri%rÆÑý^±×…›?>³N}SbÉâØó™¸ÑþV ßk%™"/Ìšá/nÝüXŽwýiª¯·Äêô45H Så=ŠG)ê#Òݺ€/kR‚ž H5j@*¾œk[ŠÉëÔ#û) M“øŠðCQÉŒ#ô:jlou„„F ,Å+¼¼Q]y^1j°\ÈùõT€žòoe!xTÁÂñ§$ƒ„.ãeéYœºëõ=ŽÞ×z‡*–CÄ9ˆ"⿦¦€Ã°êôê¾ÏŸG\NØ{ת¼þ%¨•Y¿o¹™Û0í!>¿ÃÅG§ñìßL„•»ž]W”­?à>v`¢™+‚ê™)òÂ8Üä¼ †ÍåàÃpþ¹¨ßQ›B¢ÖÉëü¨ÀTE•Â77ãœa¢×7»Y²½bÙ“)ø¸¨Ï ¤åÁuº “D‰Xê”É yÁxIû(Ò`0¯HÌ÷ƲmNî-YVB\¨W’F;ËvæÄ¸^Ы/ß'‹èßöäµrýûa È{eiXòzMa›~Ä="Ž q ‘ò4’œŠì8êÀÈ+X‚óøß69±M˜åC˜%ß``úÄþê·³ ý\óY,©„¼Dœ^—–©×áû•TÐn½ô2tØëˆ¤ø«n¸{¹úÊM‹ÔÝâ@VK8eI\)6Z•U;E÷ú¿kX}t˜ŠÕ¦ÅÝH}sˆ!°ÊMv¡©Õ¦¥àê(3\~;%9ì¨4a Iç}¿ÅC¨RíûÙbƒ/3l¥Ê#Aã±Ç|=ñZ‹j–Œ^ÁVu¦ÖžsÌ€D2zuûžä±*–ß!Ë~ôénÜ>iâBÖÔ(‚ð „i˜Èd»q’âçÇ rÏ!24Ò==¿^¦Òã¤ë‡~&÷ž©<ëW±kúã2˨Ëx xÐîÛú½¢“¼¢ V¥k¶K*_“T€jªÏ{®Y2¡FM`‡Œ_j`ä†JJφì Qe|cOgÿKïõø‚¿é¤´öž÷}Ò>/h‹xx:õ.¦†ÇÌýˆ¯µF g·NÆp÷3nýßõÙBÌ4T z/ÌN ê[< à WR= ô*ѹê$nN—¿IPç>û9êeÃl[da}5YVõ46˜]Dúœçd·­Už:®>XYɾn=I¾’¼’D·Âº{‰cûê²LzF¦YH½\˜ãvñ¯ýûËT“lV‰`jþ¨1ÅÈ1å—½’û!°ò/ϯ¯¤ çÚÃ$µ±I÷9ïMŸF;‡úî©rƒÞk]±Ç¢.Øxäú;—é§Ÿê™’,íBòJ±…úËŽ8G-&‚EMÛñ_Q›‚^Ë×oY¢0dŒCÎ]”;þÿèþUÎîBqÜí[Û/Z]ù¶tȃešyF7ã^*…(öJ6ÉuÛæõ‚9ëVõkàãÔå ÙOòDI“v!y¥ØBðwa‘­.è%ýò¯kŒazÙrÇ*~YòƒÍ6l´PîZ¿Ï±—€éáGB¼ÿøý²ÀÙ1üêwÏÏ“¸¤›i*ò׸Ÿ‡ym¡£)…8bí®“©éK¦~·,GƒÄãî͹õû½Â& ¥ r¦üø`~Ú…ä•r Á÷`Ú<áÝUmò¥Zê$$ŒÒ|)±T‰ ½ÓæpÔV*èÁà—Þ?õ¬,wºF²V`jÞ´%Ƚ±Fï6ñÍtõEÄA®7•AGñÒÓ†­DÙ%k·«+~¼PúmTØ9<Š õw³›…¼—W<4r\sœX9A¨ËÔ”;êÝ6Tþ¤º5ñ5zÉ©õê'4Õÿ°ìdüÜÛ›m×±ùv1öK‡8(ôÖljwò:=4w£Z!9žt¹ì¬¡êÏsÖ) M+ÌT}LìΧ›‹ªY(Äû/¿~O¶»ïáè>y\oÇ¡lrôãb k…|fÈIõ ¤(6m[t£'°ëԗϦ¾÷ç•EW.?ã7n[âìÌu¥§È0Ù’È0Bv®ÖðõaÿDDµ{sËÝ  'N?j€úÃÓû2ðúÝvâ„äe•.:¹^½ _Ú¹ ‹—o õ+BâWÄÕ¯W'§Ëï ±À,¾ìØCN¿e)gHΦON¤ÖKLV¥ä…/ô×>Ú(„´úúŒÞmjsÒZ÷’YUL锘½³bZí’UâHŽÝÑËÂk’‚ئ­m{ÔMÿXà¾èüÙƒMŽÏ ?,=ºvrtÞÞUè‚p‘¦Û¤3®…øÙÂù0æ°&ö¤ }ÉÝ[!’-[ßÙI^Ék»vLÍ+ô/ÌhP׬]âë{q¾ÜòÅŽF¡ÂurâÊ:‡`ØøÝÏŽQ߸}‰‘xqĹC±(\O>¢Ÿš8ª§³ƒzeqÈçÎâè¤odá­yÖö¥ã.vx{Ë™Þc#ŒkgŽRóe7ëÿùÓ S‘DÎáâ#’çË”)7Äs† V[ÄJŸ—©›žf>3Å7áWºƒ÷§ºÌ3I|_ üzÆ)Páâéõê{Ÿ{`¶ÍÔSˆzO ­~úÅq ùÏãlñ­‹F: ÇY7zQè5^y^£ÂzÍ8eŒ,”¾þÓ£$?VOcLœ÷2ÕuÔ˜ÞŽ{Áô¶G‹s#\Ó=â ñ&Ë:»ç%"C—æ–vÕ">»âBÄ·Ÿà‹hïFÑûTÑiZFÕ¿ß­$ºÞïžîy,Nþ¶ §__Úêì#€×Õâˆo} ë'À{°¬4"Ïå”Cú83x‡ iaèæÊ.8õª,Ëš·«u½‘f ½B›¤øÉµ@ûÑò7.éì?P¾€xÈLA†ihfÝuÃCtP“þè%!x˜E|Oâ§0‡­­y¿IÇÝ==¿®]¤¼ Ë4\fÔ€_)Á¬å —Ž–Ím[”Ðø"víÜÑq¨—º.ÈgpÎ#Iþ0øRÃ÷ƒ?¯@÷¨z{ëÁ{ôÊã¤WÖS,GÑÙÔóvϹ¯qê¾A²¨>'qvºÀ}$L¿®ÖÇÖ’W­Kûýãúò×¢qê×Ìi9¢èìɽuw‘.\ ¤w·`Åé5ê2íˆþ±ôªõz“>Nàw)i•Y?(DÀ80õر´ ‚XuK Q2 ÆO]J=á³v±_6 ÉËF«QçL~0À¬´×A^pQ š6LjuÿdÀø ÷í­j¡¡×u²ì dš½O+\çYI¸3uÈ&‘„ «ÇW%oXk ÑÓÓ3µ‚X‘( ­³°a0&y…A‹e+F`_öÑŠ«)ªY;ã+nâÂ6&´9,&®›9R²ƒÄŸ½©¼Ÿy£xˆ '}Ü -‹ŒQ¥$¯*ÍÛìC‹Ž±n¯p  ¿tNC¢Á–q±°XüŒ£¨ËÏêäW‹Þzÿ+ÿ4g½jm/Þéè,Éã–!yeÅ’–´ãXIÌwŠÌªaƒ[{+ö]dÑ2"©â8ÙƒpªYb?BÓ®â* Xõ¹?›ß$_UžaVÛ× ’4NH‰zFחΩæW_ÐóÈ¡ã‹môÄ2+ÒA¿Åõ#˜LØŽ„@ÂÁçlQo-ouvÐ!À©½a³dƒ•Ý0«‡ JôxÜ”6pê#{*N×ÇÙ,"a5 ª‡ÎØíè•EÛÔ›¢7¾6XXY;d&LÈu¸dÊEÖYä+/ÙS¡3rf%•®º@ÑýØØö.™‘E’G,N+=ÔþØp«6Ø0µË{ŽäåEƒï«Ž~:÷â?yÝ#ÙH1ÛˆÉ;A' ¡v3Š¢ƒáfDÔsôa!µ3‹('‘øzƒ|áßÿRS•w‚XEôZ³&$¯¬Y”í!9A€Ëƒrbh6“d ’WÖ,Êöœ @òʉ¡ÙL"5H^Y³(ÛCr‚É+'†f3‰@Ö yeÍ¢lÈ $¯œšÍ$YC€ä•5‹²=D '¼rbh6“d ’WÖ,Êöœ @òʉ¡ÙL"5H^Y³(ÛCr‚É+'†f3‰@Ö yeÍ¢lÈ $¯œšÍ$YC€ä•5‹²=D '¼rbh6“d ’WÖ,Êöœ @òʉ¡ÙL"5H^Y³(ÛCr‚É+'†f3‰@Ö yeÍ¢lÈ $¯œšÍ$YC€ä•5‹²=D '¼rbh6“d ’WÖ,Êöœ @òʉ¡ÙL"5H^Y³(ÛCr‚Àÿ¡|Þº8"˜#IEND®B`‚golang-github-integrii-flaggy-1.4.0/main.go000066400000000000000000000314671357570074000206200ustar00rootroot00000000000000// Package flaggy is a input flag parsing package that supports recursive // subcommands, positional values, and any-position flags without // unnecessary complexeties. // // For a getting started tutorial and full feature list, check out the // readme at https://github.com/integrii/flaggy. package flaggy // import "github.com/integrii/flaggy" import ( "fmt" "log" "net" "os" "strconv" "strings" "time" ) // strings used for builtin help and version flags both short and long const versionFlagLongName = "version" const helpFlagLongName = "help" const helpFlagShortName = "h" // defaultVersion is applied to parsers when they are created const defaultVersion = "0.0.0" // DebugMode indicates that debug output should be enabled var DebugMode bool // DefaultHelpTemplate is the help template that will be used // for newly created subcommands and commands var DefaultHelpTemplate = defaultHelpTemplate // DefaultParser is the default parser that is used with the package-level public // functions var DefaultParser *Parser // TrailingArguments holds trailing arguments in the main parser after parsing // has been run. var TrailingArguments []string func init() { // set the default help template // allow usage like flaggy.StringVar by enabling a default Parser ResetParser() } // ResetParser resets the default parser to a fresh instance. Uses the // name of the binary executing as the program name by default. func ResetParser() { if len(os.Args) > 0 { chunks := strings.Split(os.Args[0], "/") DefaultParser = NewParser(chunks[len(chunks)-1]) } else { DefaultParser = NewParser("default") } } // Parse parses flags as requested in the default package parser func Parse() { err := DefaultParser.Parse() TrailingArguments = DefaultParser.TrailingArguments if err != nil { log.Panicln("Error from argument parser:", err) } } // ParseArgs parses the passed args as if they were the arguments to the // running binary. Targets the default main parser for the package. func ParseArgs(args []string) { err := DefaultParser.ParseArgs(args) TrailingArguments = DefaultParser.TrailingArguments if err != nil { log.Panicln("Error from argument parser:", err) } } // String adds a new string flag func String(assignmentVar *string, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // StringSlice adds a new slice of strings flag // Specify the flag multiple times to fill the slice func StringSlice(assignmentVar *[]string, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Bool adds a new bool flag func Bool(assignmentVar *bool, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // BoolSlice adds a new slice of bools flag // Specify the flag multiple times to fill the slice func BoolSlice(assignmentVar *[]bool, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // ByteSlice adds a new slice of bytes flag // Specify the flag multiple times to fill the slice. Takes hex as input. func ByteSlice(assignmentVar *[]byte, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Duration adds a new time.Duration flag. // Input format is described in time.ParseDuration(). // Example values: 1h, 1h50m, 32s func Duration(assignmentVar *time.Duration, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // DurationSlice adds a new time.Duration flag. // Input format is described in time.ParseDuration(). // Example values: 1h, 1h50m, 32s // Specify the flag multiple times to fill the slice. func DurationSlice(assignmentVar *[]time.Duration, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Float32 adds a new float32 flag. func Float32(assignmentVar *float32, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Float32Slice adds a new float32 flag. // Specify the flag multiple times to fill the slice. func Float32Slice(assignmentVar *[]float32, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Float64 adds a new float64 flag. func Float64(assignmentVar *float64, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Float64Slice adds a new float64 flag. // Specify the flag multiple times to fill the slice. func Float64Slice(assignmentVar *[]float64, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Int adds a new int flag func Int(assignmentVar *int, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // IntSlice adds a new int slice flag. // Specify the flag multiple times to fill the slice. func IntSlice(assignmentVar *[]int, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UInt adds a new uint flag func UInt(assignmentVar *uint, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UIntSlice adds a new uint slice flag. // Specify the flag multiple times to fill the slice. func UIntSlice(assignmentVar *[]uint, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UInt64 adds a new uint64 flag func UInt64(assignmentVar *uint64, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UInt64Slice adds a new uint64 slice flag. // Specify the flag multiple times to fill the slice. func UInt64Slice(assignmentVar *[]uint64, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UInt32 adds a new uint32 flag func UInt32(assignmentVar *uint32, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UInt32Slice adds a new uint32 slice flag. // Specify the flag multiple times to fill the slice. func UInt32Slice(assignmentVar *[]uint32, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UInt16 adds a new uint16 flag func UInt16(assignmentVar *uint16, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UInt16Slice adds a new uint16 slice flag. // Specify the flag multiple times to fill the slice. func UInt16Slice(assignmentVar *[]uint16, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UInt8 adds a new uint8 flag func UInt8(assignmentVar *uint8, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // UInt8Slice adds a new uint8 slice flag. // Specify the flag multiple times to fill the slice. func UInt8Slice(assignmentVar *[]uint8, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Int64 adds a new int64 flag func Int64(assignmentVar *int64, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Int64Slice adds a new int64 slice flag. // Specify the flag multiple times to fill the slice. func Int64Slice(assignmentVar *[]int64, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Int32 adds a new int32 flag func Int32(assignmentVar *int32, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Int32Slice adds a new int32 slice flag. // Specify the flag multiple times to fill the slice. func Int32Slice(assignmentVar *[]int32, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Int16 adds a new int16 flag func Int16(assignmentVar *int16, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Int16Slice adds a new int16 slice flag. // Specify the flag multiple times to fill the slice. func Int16Slice(assignmentVar *[]int16, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Int8 adds a new int8 flag func Int8(assignmentVar *int8, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // Int8Slice adds a new int8 slice flag. // Specify the flag multiple times to fill the slice. func Int8Slice(assignmentVar *[]int8, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // IP adds a new net.IP flag. func IP(assignmentVar *net.IP, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // IPSlice adds a new int8 slice flag. // Specify the flag multiple times to fill the slice. func IPSlice(assignmentVar *[]net.IP, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // HardwareAddr adds a new net.HardwareAddr flag. func HardwareAddr(assignmentVar *net.HardwareAddr, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // HardwareAddrSlice adds a new net.HardwareAddr slice flag. // Specify the flag multiple times to fill the slice. func HardwareAddrSlice(assignmentVar *[]net.HardwareAddr, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // IPMask adds a new net.IPMask flag. IPv4 Only. func IPMask(assignmentVar *net.IPMask, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // IPMaskSlice adds a new net.HardwareAddr slice flag. IPv4 only. // Specify the flag multiple times to fill the slice. func IPMaskSlice(assignmentVar *[]net.IPMask, shortName string, longName string, description string) { DefaultParser.add(assignmentVar, shortName, longName, description) } // AttachSubcommand adds a subcommand for parsing func AttachSubcommand(subcommand *Subcommand, relativePosition int) { DefaultParser.AttachSubcommand(subcommand, relativePosition) } // ShowHelp shows parser help func ShowHelp(message string) { DefaultParser.ShowHelpWithMessage(message) } // SetDescription sets the description of the default package command parser func SetDescription(description string) { DefaultParser.Description = description } // SetVersion sets the version of the default package command parser func SetVersion(version string) { DefaultParser.Version = version } // SetName sets the name of the default package command parser func SetName(name string) { DefaultParser.Name = name } // ShowHelpAndExit shows parser help and exits with status code 2 func ShowHelpAndExit(message string) { ShowHelp(message) exitOrPanic(2) } // PanicInsteadOfExit is used when running tests var PanicInsteadOfExit bool // exitOrPanic panics instead of calling os.Exit so that tests can catch // more failures func exitOrPanic(code int) { if PanicInsteadOfExit { panic("Panic instead of exit with code: " + strconv.Itoa(code)) } os.Exit(code) } // ShowHelpOnUnexpectedEnable enables the ShowHelpOnUnexpected behavior on the // default parser. This causes unknown inputs to error out. func ShowHelpOnUnexpectedEnable() { DefaultParser.ShowHelpOnUnexpected = true } // ShowHelpOnUnexpectedDisable disables the ShowHelpOnUnexpected behavior on the // default parser. This causes unknown inputs to error out. func ShowHelpOnUnexpectedDisable() { DefaultParser.ShowHelpOnUnexpected = false } // AddPositionalValue adds a positional value to the main parser at the global // context func AddPositionalValue(assignmentVar *string, name string, relativePosition int, required bool, description string) { DefaultParser.AddPositionalValue(assignmentVar, name, relativePosition, required, description) } // debugPrint prints if debugging is enabled func debugPrint(i ...interface{}) { if DebugMode { fmt.Println(i...) } } golang-github-integrii-flaggy-1.4.0/main_test.go000066400000000000000000000017751357570074000216560ustar00rootroot00000000000000package flaggy_test import ( "os" "testing" "github.com/integrii/flaggy" ) func TestMain(m *testing.M) { flaggy.PanicInsteadOfExit = true os.Exit(m.Run()) } func TestSetDescription(t *testing.T) { desc := "Test Description" flaggy.SetDescription(desc) if flaggy.DefaultParser.Description != desc { t.Fatal("set description does not match") } } func TestSetVersion(t *testing.T) { ver := "Test Version" flaggy.SetVersion(ver) if flaggy.DefaultParser.Version != ver { t.Fatal("set version does not match") } } func TestParserWithNoArgs(t *testing.T) { os.Args = []string{} flaggy.ResetParser() } func TestSetName(t *testing.T) { name := "Test Name" flaggy.SetName(name) if flaggy.DefaultParser.Name != name { t.Fatal("set name does not match") } } func TestShowHelpAndExit(t *testing.T) { flaggy.PanicInsteadOfExit = true defer func() { r := recover() if r == nil { t.Fatal("Expected panic on show help and exit call") } }() flaggy.ShowHelpAndExit("test show help and exit") } golang-github-integrii-flaggy-1.4.0/parsedValue.go000066400000000000000000000014031357570074000221320ustar00rootroot00000000000000package flaggy // parsedValue represents a flag or subcommand that was parsed. Primairily used // to account for all parsed values in order to determine if unknown values were // passed to the root parser after all subcommands have been parsed. type parsedValue struct { Key string Value string IsPositional bool // indicates that this value was positional and not a key/value } // newParsedValue creates and returns a new parsedValue struct with the // supplied values set func newParsedValue(key string, value string, isPositional bool) parsedValue { if len(key) == 0 && len(value) == 0 { panic("cant add parsed value with no key or value") } return parsedValue{ Key: key, Value: value, IsPositional: isPositional, } } golang-github-integrii-flaggy-1.4.0/parser.go000066400000000000000000000144221357570074000211600ustar00rootroot00000000000000package flaggy import ( "errors" "fmt" "os" "strconv" "text/template" ) // Parser represents the set of flags and subcommands we are expecting // from our input arguments. Parser is the top level struct responsible for // parsing an entire set of subcommands and flags. type Parser struct { Subcommand Version string // the optional version of the parser. ShowHelpWithHFlag bool // display help when -h or --help passed ShowVersionWithVersionFlag bool // display the version when --version passed ShowHelpOnUnexpected bool // display help when an unexpected flag or subcommand is passed TrailingArguments []string // everything after a -- is placed here HelpTemplate *template.Template // template for Help output trailingArgumentsExtracted bool // indicates that trailing args have been parsed and should not be appended again parsed bool // indicates this parser has parsed subcommandContext *Subcommand // points to the most specific subcommand being used } // NewParser creates a new ArgumentParser ready to parse inputs func NewParser(name string) *Parser { // this can not be done inline because of struct embedding p := &Parser{} p.Name = name p.Version = defaultVersion p.ShowHelpOnUnexpected = true p.ShowHelpWithHFlag = true p.ShowVersionWithVersionFlag = true p.SetHelpTemplate(DefaultHelpTemplate) p.subcommandContext = &Subcommand{} return p } // ParseArgs parses as if the passed args were the os.Args, but without the // binary at the 0 position in the array. An error is returned if there // is a low level issue converting flags to their proper type. No error // is returned for invalid arguments or missing require subcommands. func (p *Parser) ParseArgs(args []string) error { if p.parsed { return errors.New("Parser.Parse() called twice on parser with name: " + " " + p.Name + " " + p.ShortName) } p.parsed = true debugPrint("Kicking off parsing with args:", args) err := p.parse(p, args, 0) if err != nil { return err } // if we are set to crash on unexpected args, look for those here TODO if p.ShowHelpOnUnexpected { parsedValues := p.findAllParsedValues() debugPrint("parsedValues:", parsedValues) argsNotParsed := findArgsNotInParsedValues(args, parsedValues) if len(argsNotParsed) > 0 { // flatten out unused args for our error message var argsNotParsedFlat string for _, a := range argsNotParsed { argsNotParsedFlat = argsNotParsedFlat + " " + a } p.ShowHelpAndExit("Unknown arguments supplied: " + argsNotParsedFlat) } } return nil } // findArgsNotInParsedValues finds arguments not used in parsed values. The // incoming args should be in the order supplied by the user and should not // include the invoked binary, which is normally the first thing in os.Args. func findArgsNotInParsedValues(args []string, parsedValues []parsedValue) []string { var argsNotUsed []string var skipNext bool for _, a := range args { // if the final argument (--) is seen, then we stop checking because all // further values are trailing arguments. if determineArgType(a) == argIsFinal { return argsNotUsed } // allow for skipping the next arg when needed if skipNext { skipNext = false continue } // strip flag slashes from incoming arguments so they match up with the // keys from parsedValues. arg := parseFlagToName(a) // indicates that we found this arg used in one of the parsed values. Used // to indicate which values should be added to argsNotUsed. var foundArgUsed bool // search all args for a corresponding parsed value for _, pv := range parsedValues { // this argumenet was a key // debugPrint(pv.Key, "==", arg) debugPrint(pv.Key + "==" + arg + " || (" + strconv.FormatBool(pv.IsPositional) + " && " + pv.Value + " == " + arg + ")") if pv.Key == arg || (pv.IsPositional && pv.Value == arg) { debugPrint("Found matching parsed arg for " + pv.Key) foundArgUsed = true // the arg was used in this parsedValues set // if the value is not a positional value and the parsed value had a // value that was not blank, we skip the next value in the argument list if !pv.IsPositional && len(pv.Value) > 0 { skipNext = true break } } // this prevents excessive parsed values from being checked after we find // the arg used for the first time if foundArgUsed { break } } // if the arg was not used in any parsed values, then we add it to the slice // of arguments not used if !foundArgUsed { argsNotUsed = append(argsNotUsed, arg) } } return argsNotUsed } // ShowVersionAndExit shows the version of this parser func (p *Parser) ShowVersionAndExit() { fmt.Println("Version:", p.Version) exitOrPanic(0) } // SetHelpTemplate sets the go template this parser will use when rendering // Help. func (p *Parser) SetHelpTemplate(tmpl string) error { var err error p.HelpTemplate = template.New(helpFlagLongName) p.HelpTemplate, err = p.HelpTemplate.Parse(tmpl) if err != nil { return err } return nil } // Parse calculates all flags and subcommands func (p *Parser) Parse() error { err := p.ParseArgs(os.Args[1:]) if err != nil { return err } return nil } // ShowHelp shows Help without an error message func (p *Parser) ShowHelp() { debugPrint("showing help for", p.subcommandContext.Name) p.ShowHelpWithMessage("") } // ShowHelpAndExit shows parser help and exits with status code 2 func (p *Parser) ShowHelpAndExit(message string) { p.ShowHelpWithMessage(message) exitOrPanic(2) } // ShowHelpWithMessage shows the Help for this parser with an optional string error // message as a header. The supplied subcommand will be the context of Help // displayed to the user. func (p *Parser) ShowHelpWithMessage(message string) { // create a new Help values template and extract values into it help := Help{} help.ExtractValues(p, message) err := p.HelpTemplate.Execute(os.Stderr, help) if err != nil { fmt.Fprintln(os.Stderr, "Error rendering Help template:", err) } } // DisableShowVersionWithVersion disables the showing of version information // with --version. It is enabled by default. func (p *Parser) DisableShowVersionWithVersion() { p.ShowVersionWithVersionFlag = false } golang-github-integrii-flaggy-1.4.0/parser_test.go000066400000000000000000000013231357570074000222130ustar00rootroot00000000000000package flaggy import "testing" func TestDoubleParse(t *testing.T) { ResetParser() DefaultParser.ShowHelpOnUnexpected = false err := DefaultParser.Parse() if err != nil { t.Fatal(err) } err = DefaultParser.Parse() if err == nil { t.Fatal(err) } } func TestDisableShowVersionFlag(t *testing.T) { ResetParser() // if this fails the function tested might be useless. // Review if it's still useful and adjust. if DefaultParser.ShowVersionWithVersionFlag != true { t.Fatal("The tested function might not make sense any more.") } DefaultParser.DisableShowVersionWithVersion() if DefaultParser.ShowVersionWithVersionFlag != false { t.Fatal("ShowVersionWithVersionFlag should have been false.") } } golang-github-integrii-flaggy-1.4.0/positionalValue.go000066400000000000000000000012161357570074000230370ustar00rootroot00000000000000package flaggy // PositionalValue represents a value which is determined by its position // relative to where a subcommand was detected. type PositionalValue struct { Name string // used in documentation only Description string AssignmentVar *string // the var that will get this variable Position int // the position, not including switches, of this variable Required bool // this subcommand must always be specified Found bool // was this positional found during parsing? Hidden bool // indicates this positional value should be hidden from help defaultValue string // used for help output } golang-github-integrii-flaggy-1.4.0/subCommand.go000066400000000000000000000676031357570074000217650ustar00rootroot00000000000000package flaggy import ( "fmt" "log" "net" "os" "strconv" "strings" "time" ) // Subcommand represents a subcommand which contains a set of child // subcommands along with a set of flags relevant to it. Parsing // runs until a subcommand is detected by matching its name and // position. Once a matching subcommand is found, the next set // of parsing occurs within that matched subcommand. type Subcommand struct { Name string ShortName string Description string Position int // the position of this subcommand, not including flags Subcommands []*Subcommand Flags []*Flag PositionalFlags []*PositionalValue ParsedValues []parsedValue // a list of values and positionals parsed AdditionalHelpPrepend string // additional prepended message when Help is displayed AdditionalHelpAppend string // additional appended message when Help is displayed Used bool // indicates this subcommand was found and parsed Hidden bool // indicates this subcommand should be hidden from help } // NewSubcommand creates a new subcommand that can have flags or PositionalFlags // added to it. The position starts with 1, not 0 func NewSubcommand(name string) *Subcommand { if len(name) == 0 { fmt.Fprintln(os.Stderr, "Error creating subcommand (NewSubcommand()). No subcommand name was specified.") exitOrPanic(2) } newSC := &Subcommand{ Name: name, } return newSC } // parseAllFlagsFromArgs parses the non-positional flags such as -f or -v=value // out of the supplied args and returns the resulting positional items in order, // all the flag names found (without values), a bool to indicate if help was // requested, and any errors found during parsing func (sc *Subcommand) parseAllFlagsFromArgs(p *Parser, args []string) ([]string, bool, error) { var positionalOnlyArguments []string var helpRequested bool // indicates the user has supplied -h and we // should render help if we are the last subcommand // indicates we should skip the next argument, like when parsing a flag // that separates key and value by space var skipNext bool // endArgfound indicates that a -- was found and everything // remaining should be added to the trailing arguments slices var endArgFound bool // find all the normal flags (not positional) and parse them out for i, a := range args { debugPrint("parsing arg:", a) // evaluate if there is a following arg to avoid panics var nextArgExists bool var nextArg string if len(args)-1 >= i+1 { nextArgExists = true nextArg = args[i+1] } // if end arg -- has been found, just add everything to TrailingArguments if endArgFound { if !p.trailingArgumentsExtracted { p.TrailingArguments = append(p.TrailingArguments, a) } continue } // skip this run if specified if skipNext { skipNext = false debugPrint("skipping flag because it is an arg:", a) continue } // parse the flag into its name for consideration without dashes flagName := parseFlagToName(a) // if the flag being passed is version or v and the option to display // version with version flags, then display version if p.ShowVersionWithVersionFlag { if flagName == versionFlagLongName { p.ShowVersionAndExit() } } // if the show Help on h flag option is set, then show Help when h or Help // is passed as an option if p.ShowHelpWithHFlag { if flagName == helpFlagShortName || flagName == helpFlagLongName { // Ensure this is the last subcommand passed so we give the correct // help output helpRequested = true continue } } // determine what kind of flag this is argType := determineArgType(a) // strip flags from arg // debugPrint("Parsing flag named", a, "of type", argType) // depending on the flag type, parse the key and value out, then apply it switch argType { case argIsFinal: // debugPrint("Arg", i, "is final:", a) endArgFound = true case argIsPositional: // debugPrint("Arg is positional or subcommand:", a) // this positional argument into a slice of their own, so that // we can determine if its a subcommand or positional value later positionalOnlyArguments = append(positionalOnlyArguments, a) // track this as a parsed value with the subcommand sc.addParsedPositionalValue(a) case argIsFlagWithSpace: // a flag with a space. ex) -k v or --key value a = parseFlagToName(a) // debugPrint("Arg", i, "is flag with space:", a) // parse next arg as value to this flag and apply to subcommand flags // if the flag is a bool flag, then we check for a following positional // and skip it if necessary if flagIsBool(sc, p, a) { debugPrint(sc.Name, "bool flag", a, "next var is:", nextArg) // set the value in this subcommand and its root parser valueSet, err := setValueForParsers(a, "true", p, sc) // if an error occurs, just return it and quit parsing if err != nil { return []string{}, false, err } // log all values parsed by this subcommand. We leave the value blank // because the bool value had no explicit true or false supplied if valueSet { sc.addParsedFlag(a, "") } // we've found and set a standalone bool flag, so we move on to the next // argument in the list of arguments continue } skipNext = true // debugPrint(sc.Name, "NOT bool flag", a) // if the next arg was not found, then show a Help message if !nextArgExists { p.ShowHelpWithMessage("Expected a following arg for flag " + a + ", but it did not exist.") exitOrPanic(2) } valueSet, err := setValueForParsers(a, nextArg, p, sc) if err != nil { return []string{}, false, err } // log all parsed values in the subcommand if valueSet { sc.addParsedFlag(a, nextArg) } case argIsFlagWithValue: // a flag with an equals sign. ex) -k=v or --key=value // debugPrint("Arg", i, "is flag with value:", a) a = parseFlagToName(a) // parse flag into key and value and apply to subcommand flags key, val := parseArgWithValue(a) // set the value in this subcommand and its root parser valueSet, err := setValueForParsers(key, val, p, sc) if err != nil { return []string{}, false, err } // log all values parsed by the subcommand if valueSet { sc.addParsedFlag(a, val) } } } return positionalOnlyArguments, helpRequested, nil } // findAllParsedValues finds all values parsed by all subcommands and this // subcommand and its child subcommands func (sc *Subcommand) findAllParsedValues() []parsedValue { parsedValues := sc.ParsedValues for _, sc := range sc.Subcommands { // skip unused subcommands if !sc.Used { continue } parsedValues = append(parsedValues, sc.findAllParsedValues()...) } return parsedValues } // parse causes the argument parser to parse based on the supplied []string. // depth specifies the non-flag subcommand positional depth. A slice of flags // and subcommands parsed is returned so that the parser can ultimately decide // if there were any unexpected values supplied by the user func (sc *Subcommand) parse(p *Parser, args []string, depth int) error { debugPrint("- Parsing subcommand", sc.Name, "with depth of", depth, "and args", args) // if a command is parsed, its used sc.Used = true debugPrint("used subcommand", sc.Name, sc.ShortName) if len(sc.Name) > 0 { sc.addParsedPositionalValue(sc.Name) } if len(sc.ShortName) > 0 { sc.addParsedPositionalValue(sc.ShortName) } // as subcommands are used, they become the context of the parser. This helps // us understand how to display help based on which subcommand is being used p.subcommandContext = sc // ensure that help and version flags are not used if the parser has the // built-in help and version flags enabled if p.ShowHelpWithHFlag { sc.ensureNoConflictWithBuiltinHelp() } if p.ShowVersionWithVersionFlag { sc.ensureNoConflictWithBuiltinVersion() } // Parse the normal flags out of the argument list and return the positionals // (subcommands and positional values), along with the flags used. // Then the flag values are applied to the parent parser and the current // subcommand being parsed. positionalOnlyArguments, helpRequested, err := sc.parseAllFlagsFromArgs(p, args) if err != nil { return err } // indicate that trailing arguments have been extracted, so that they aren't // appended a second time p.trailingArgumentsExtracted = true // loop over positional values and look for their matching positional // parameter, or their positional command. If neither are found, then // we throw an error var parsedArgCount int for pos, v := range positionalOnlyArguments { // the first relative positional argument will be human natural at position 1 // but offset for the depth of relative commands being parsed for currently. relativeDepth := pos - depth + 1 // debugPrint("Parsing positional only position", relativeDepth, "with value", v) if relativeDepth < 1 { // debugPrint(sc.Name, "skipped value:", v) continue } parsedArgCount++ // determine subcommands and parse them by positional value and name for _, cmd := range sc.Subcommands { // debugPrint("Subcommand being compared", relativeDepth, "==", cmd.Position, "and", v, "==", cmd.Name, "==", cmd.ShortName) if relativeDepth == cmd.Position && (v == cmd.Name || v == cmd.ShortName) { debugPrint("Decending into positional subcommand", cmd.Name, "at relativeDepth", relativeDepth, "and absolute depth", depth+1) return cmd.parse(p, args, depth+parsedArgCount) // continue recursive positional parsing } } // determine positional args and parse them by positional value and name var foundPositional bool for _, val := range sc.PositionalFlags { if relativeDepth == val.Position { debugPrint("Found a positional value at relativePos:", relativeDepth, "value:", v) // set original value for help output val.defaultValue = *val.AssignmentVar // defrerence the struct pointer, then set the pointer property within it *val.AssignmentVar = v // debugPrint("set positional to value", *val.AssignmentVar) foundPositional = true val.Found = true break } } // if there aren't any positional flags but there are subcommands that // were not used, display a useful message with subcommand options. if !foundPositional && p.ShowHelpOnUnexpected { debugPrint("No positional at position", relativeDepth) var foundSubcommandAtDepth bool for _, cmd := range sc.Subcommands { if cmd.Position == relativeDepth { foundSubcommandAtDepth = true } } // if there is a subcommand here but it was not specified, display them all // as a suggestion to the user before exiting. if foundSubcommandAtDepth { // determine which name to use in upcoming help output fmt.Fprintln(os.Stderr, sc.Name+":", "No subcommand or positional value found at position", strconv.Itoa(relativeDepth)+".") var output string for _, cmd := range sc.Subcommands { if cmd.Hidden { continue } output = output + " " + cmd.Name } // if there are available subcommands, let the user know if len(output) > 0 { output = strings.TrimLeft(output, " ") fmt.Println("Available subcommands:", output) } exitOrPanic(2) } // if there were not any flags or subcommands at this position at all, then // throw an error (display Help if necessary) p.ShowHelpWithMessage("Unexpected argument: " + v) exitOrPanic(2) } } // if help was requested and we should show help when h is passed, if helpRequested && p.ShowHelpWithHFlag { p.ShowHelp() exitOrPanic(0) } // find any positionals that were not used on subcommands that were // found and throw help (unknown argument) in the global parse or subcommand for _, pv := range p.PositionalFlags { if pv.Required && !pv.Found { p.ShowHelpWithMessage("Required global positional variable " + pv.Name + " not found at position " + strconv.Itoa(pv.Position)) exitOrPanic(2) } } for _, pv := range sc.PositionalFlags { if pv.Required && !pv.Found { p.ShowHelpWithMessage("Required positional of subcommand " + sc.Name + " named " + pv.Name + " not found at position " + strconv.Itoa(pv.Position)) exitOrPanic(2) } } return nil } // addParsedFlag makes it easy to append flag values parsed by the subcommand func (sc *Subcommand) addParsedFlag(key string, value string) { sc.ParsedValues = append(sc.ParsedValues, newParsedValue(key, value, false)) } // addParsedPositionalValue makes it easy to append positionals parsed by the // subcommand func (sc *Subcommand) addParsedPositionalValue(value string) { sc.ParsedValues = append(sc.ParsedValues, newParsedValue("", value, true)) } // FlagExists lets you know if the flag name exists as either a short or long // name in the (sub)command func (sc *Subcommand) FlagExists(name string) bool { for _, f := range sc.Flags { if f.HasName(name) { return true } } return false } // AttachSubcommand adds a possible subcommand to the Parser. func (sc *Subcommand) AttachSubcommand(newSC *Subcommand, relativePosition int) { // assign the depth of the subcommand when its attached newSC.Position = relativePosition // ensure no subcommands at this depth with this name for _, other := range sc.Subcommands { if newSC.Position == other.Position { if newSC.Name != "" { if newSC.Name == other.Name { log.Panicln("Unable to add subcommand because one already exists at position" + strconv.Itoa(newSC.Position) + " with name " + other.Name) } } if newSC.ShortName != "" { if newSC.ShortName == other.ShortName { log.Panicln("Unable to add subcommand because one already exists at position" + strconv.Itoa(newSC.Position) + " with name " + other.ShortName) } } } } // ensure no positionals at this depth for _, other := range sc.PositionalFlags { if newSC.Position == other.Position { log.Panicln("Unable to add subcommand because a positional value already exists at position " + strconv.Itoa(newSC.Position) + ": " + other.Name) } } sc.Subcommands = append(sc.Subcommands, newSC) } // add is a "generic" to add flags of any type. Checks the supplied parent // parser to ensure that the user isn't setting version or help flags that // conflict with the built-in help and version flag behavior. func (sc *Subcommand) add(assignmentVar interface{}, shortName string, longName string, description string) { // if the flag is already used, throw an error for _, existingFlag := range sc.Flags { if longName != "" && existingFlag.LongName == longName { log.Panicln("Flag " + longName + " added to subcommand " + sc.Name + " but the name is already assigned.") } if shortName != "" && existingFlag.ShortName == shortName { log.Panicln("Flag " + shortName + " added to subcommand " + sc.Name + " but the short name is already assigned.") } } newFlag := Flag{ AssignmentVar: assignmentVar, ShortName: shortName, LongName: longName, Description: description, } sc.Flags = append(sc.Flags, &newFlag) } // String adds a new string flag func (sc *Subcommand) String(assignmentVar *string, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // StringSlice adds a new slice of strings flag // Specify the flag multiple times to fill the slice func (sc *Subcommand) StringSlice(assignmentVar *[]string, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Bool adds a new bool flag func (sc *Subcommand) Bool(assignmentVar *bool, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // BoolSlice adds a new slice of bools flag // Specify the flag multiple times to fill the slice func (sc *Subcommand) BoolSlice(assignmentVar *[]bool, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // ByteSlice adds a new slice of bytes flag // Specify the flag multiple times to fill the slice. Takes hex as input. func (sc *Subcommand) ByteSlice(assignmentVar *[]byte, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Duration adds a new time.Duration flag. // Input format is described in time.ParseDuration(). // Example values: 1h, 1h50m, 32s func (sc *Subcommand) Duration(assignmentVar *time.Duration, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // DurationSlice adds a new time.Duration flag. // Input format is described in time.ParseDuration(). // Example values: 1h, 1h50m, 32s // Specify the flag multiple times to fill the slice. func (sc *Subcommand) DurationSlice(assignmentVar *[]time.Duration, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Float32 adds a new float32 flag. func (sc *Subcommand) Float32(assignmentVar *float32, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Float32Slice adds a new float32 flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) Float32Slice(assignmentVar *[]float32, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Float64 adds a new float64 flag. func (sc *Subcommand) Float64(assignmentVar *float64, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Float64Slice adds a new float64 flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) Float64Slice(assignmentVar *[]float64, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Int adds a new int flag func (sc *Subcommand) Int(assignmentVar *int, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // IntSlice adds a new int slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) IntSlice(assignmentVar *[]int, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UInt adds a new uint flag func (sc *Subcommand) UInt(assignmentVar *uint, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UIntSlice adds a new uint slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) UIntSlice(assignmentVar *[]uint, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UInt64 adds a new uint64 flag func (sc *Subcommand) UInt64(assignmentVar *uint64, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UInt64Slice adds a new uint64 slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) UInt64Slice(assignmentVar *[]uint64, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UInt32 adds a new uint32 flag func (sc *Subcommand) UInt32(assignmentVar *uint32, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UInt32Slice adds a new uint32 slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) UInt32Slice(assignmentVar *[]uint32, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UInt16 adds a new uint16 flag func (sc *Subcommand) UInt16(assignmentVar *uint16, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UInt16Slice adds a new uint16 slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) UInt16Slice(assignmentVar *[]uint16, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UInt8 adds a new uint8 flag func (sc *Subcommand) UInt8(assignmentVar *uint8, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // UInt8Slice adds a new uint8 slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) UInt8Slice(assignmentVar *[]uint8, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Int64 adds a new int64 flag. func (sc *Subcommand) Int64(assignmentVar *int64, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Int64Slice adds a new int64 slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) Int64Slice(assignmentVar *[]int64, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Int32 adds a new int32 flag func (sc *Subcommand) Int32(assignmentVar *int32, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Int32Slice adds a new int32 slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) Int32Slice(assignmentVar *[]int32, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Int16 adds a new int16 flag func (sc *Subcommand) Int16(assignmentVar *int16, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Int16Slice adds a new int16 slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) Int16Slice(assignmentVar *[]int16, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Int8 adds a new int8 flag func (sc *Subcommand) Int8(assignmentVar *int8, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // Int8Slice adds a new int8 slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) Int8Slice(assignmentVar *[]int8, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // IP adds a new net.IP flag. func (sc *Subcommand) IP(assignmentVar *net.IP, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // IPSlice adds a new int8 slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) IPSlice(assignmentVar *[]net.IP, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // HardwareAddr adds a new net.HardwareAddr flag. func (sc *Subcommand) HardwareAddr(assignmentVar *net.HardwareAddr, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // HardwareAddrSlice adds a new net.HardwareAddr slice flag. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) HardwareAddrSlice(assignmentVar *[]net.HardwareAddr, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // IPMask adds a new net.IPMask flag. IPv4 Only. func (sc *Subcommand) IPMask(assignmentVar *net.IPMask, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // IPMaskSlice adds a new net.HardwareAddr slice flag. IPv4 only. // Specify the flag multiple times to fill the slice. func (sc *Subcommand) IPMaskSlice(assignmentVar *[]net.IPMask, shortName string, longName string, description string) { sc.add(assignmentVar, shortName, longName, description) } // AddPositionalValue adds a positional value to the subcommand. the // relativePosition starts at 1 and is relative to the subcommand it belongs to func (sc *Subcommand) AddPositionalValue(assignmentVar *string, name string, relativePosition int, required bool, description string) { // ensure no other positionals are at this depth for _, other := range sc.PositionalFlags { if relativePosition == other.Position { log.Panicln("Unable to add positional value because one already exists at position: " + strconv.Itoa(relativePosition)) } } // ensure no subcommands at this depth for _, other := range sc.Subcommands { if relativePosition == other.Position { log.Panicln("Unable to add positional value a subcommand already exists at position: " + strconv.Itoa(relativePosition)) } } newPositionalValue := PositionalValue{ Name: name, Position: relativePosition, AssignmentVar: assignmentVar, Required: required, Description: description, defaultValue: *assignmentVar, } sc.PositionalFlags = append(sc.PositionalFlags, &newPositionalValue) } // SetValueForKey sets the value for the specified key. If setting a bool // value, then send "true" or "false" as strings. The returned bool indicates // that a value was set. func (sc *Subcommand) SetValueForKey(key string, value string) (bool, error) { // debugPrint("Looking to set key", key, "to value", value) // check for and assign flags that match the key for _, f := range sc.Flags { // debugPrint("Evaluating string flag", f.ShortName, "==", key, "||", f.LongName, "==", key) if f.ShortName == key || f.LongName == key { // debugPrint("Setting string value for", key, "to", value) f.identifyAndAssignValue(value) return true, nil } } // debugPrint(sc.Name, "was unable to find a key named", key, "to set to value", value) return false, nil } // ensureNoConflictWithBuiltinHelp ensures that the flags on this subcommand do // not conflict with the builtin help flags (-h or --help). Exits the program // if a conflict is found. func (sc *Subcommand) ensureNoConflictWithBuiltinHelp() { for _, f := range sc.Flags { if f.LongName == helpFlagLongName { sc.exitBecauseOfHelpFlagConflict(f.LongName) } if f.LongName == helpFlagShortName { sc.exitBecauseOfHelpFlagConflict(f.LongName) } if f.ShortName == helpFlagLongName { sc.exitBecauseOfHelpFlagConflict(f.ShortName) } if f.ShortName == helpFlagShortName { sc.exitBecauseOfHelpFlagConflict(f.ShortName) } } } // ensureNoConflictWithBuiltinVersion ensures that the flags on this subcommand do // not conflict with the builtin version flag (--version). Exits the program // if a conflict is found. func (sc *Subcommand) ensureNoConflictWithBuiltinVersion() { for _, f := range sc.Flags { if f.LongName == versionFlagLongName { sc.exitBecauseOfVersionFlagConflict(f.LongName) } if f.ShortName == versionFlagLongName { sc.exitBecauseOfVersionFlagConflict(f.ShortName) } } } // exitBecauseOfVersionFlagConflict exits the program with a message about how to prevent // flags being defined from conflicting with the builtin flags. func (sc *Subcommand) exitBecauseOfVersionFlagConflict(flagName string) { fmt.Println(`Flag with name '` + flagName + `' conflicts with the internal --version flag in flaggy. You must either change the flag's name, or disable flaggy's internal version flag with 'flaggy.DefaultParser.ShowVersionWithVersionFlag = false'. If you are using a custom parser, you must instead set '.ShowVersionWithVersionFlag = false' on it.`) exitOrPanic(1) } // exitBecauseOfHelpFlagConflict exits the program with a message about how to prevent // flags being defined from conflicting with the builtin flags. func (sc *Subcommand) exitBecauseOfHelpFlagConflict(flagName string) { fmt.Println(`Flag with name '` + flagName + `' conflicts with the internal --help or -h flag in flaggy. You must either change the flag's name, or disable flaggy's internal help flag with 'flaggy.DefaultParser.ShowHelpWithHFlag = false'. If you are using a custom parser, you must instead set '.ShowHelpWithHFlag = false' on it.`) exitOrPanic(1) } golang-github-integrii-flaggy-1.4.0/subcommand_test.go000066400000000000000000000536111357570074000230560ustar00rootroot00000000000000package flaggy_test import ( "net" "os" "testing" "time" "github.com/integrii/flaggy" ) func TestSCNameExists(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash from double subcommand name use at same position") } }() flaggy.ResetParser() scA := flaggy.NewSubcommand("test") scB := flaggy.NewSubcommand("test") flaggy.AttachSubcommand(scA, 1) flaggy.AttachSubcommand(scB, 1) } func TestFlagExists(t *testing.T) { sc := flaggy.NewSubcommand("testFlagExists") e := sc.FlagExists("test") if e == true { t.Fatal("Flag exists on subcommand that should not") } var testA string sc.String(&testA, "", "test", "a test flag") e = sc.FlagExists("test") if e == false { t.Fatal("Flag does not exist on a subcommand that should") } } // TestExitOnUnknownFlag tests that when an unknown flag is supplied and the // ShowHelpOnUnexpected value is set, an error is thrown on unknown flags. func TestExitOnUnknownFlag(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash on unknown flag") } }() flaggy.DebugMode = true defer debugOff() var expectedFlag string var expectedPositional string flaggy.ResetParser() flaggy.String(&expectedFlag, "f", "flag", "an expected positonal flag") flaggy.AddPositionalValue(&expectedPositional, "positionalTest", 1, true, "A test positional value") flaggy.ParseArgs([]string{"positionalHere", "-f", "flagHere", "unexpectedValue"}) } // TestExitOnUnknownFlagWithValue tests that when an unknown flag with a value // is supplied and the ShowHelpOnUnexpected value is set, an error is thrown on // the unknown flags. func TestExitOnUnknownFlagWithValue(t *testing.T) { flaggy.ResetParser() flaggy.ShowHelpOnUnexpectedEnable() defer func() { r := recover() if r == nil { t.Fatal("Expected crash on unknown flag with value") } }() flaggy.DebugMode = true defer debugOff() var expectedFlag string var expectedPositional string flaggy.ResetParser() flaggy.String(&expectedFlag, "f", "flag", "an expected positonal flag") flaggy.AddPositionalValue(&expectedPositional, "positionalTest", 1, true, "A test positional value") flaggy.ParseArgs([]string{"positionalHere", "-f", "flagHere", "--unexpectedValue=true"}) } // TestDoublePositional tests errors when two positionals are // specified at the same time func TestDoublePositional(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash on double assignment") } }() // flaggy.DebugMode = true defer debugOff() var posTest string flaggy.ResetParser() flaggy.AddPositionalValue(&posTest, "posTest", 1, true, "First test positional") flaggy.AddPositionalValue(&posTest, "posTest2", 1, true, "Second test positional") } func TestNextArgDoesNotExist(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash when next arg not specifid") } }() flaggy.ResetParser() flaggy.PanicInsteadOfExit = true var test string flaggy.String(&test, "t", "test", "Description goes here") flaggy.ParseArgs([]string{"-t"}) } func TestSubcommandHidden(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash instead of exit. Subcommand id was set to a blank") } }() flaggy.ResetParser() sc := flaggy.NewSubcommand("") sc.Hidden = true sc.ShortName = "sc" flaggy.AttachSubcommand(sc, 1) flaggy.ParseArgs([]string{"x"}) } // TestRequiredPositional tests required positionals func TestRequiredPositional(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash on unused required positional") } }() // flaggy.DebugMode = true defer debugOff() var posTest string flaggy.AddPositionalValue(&posTest, "posTest", 1, true, "First test positional") flaggy.Parse() } // TestTypoSubcommand tests what happens when an invalid subcommand is passed func TestTypoSubcommand(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash on subcommand typo") } }() p := flaggy.NewParser("TestTypoSubcommand") p.ShowHelpOnUnexpected = true args := []string{"unexpectedArg"} newSCA := flaggy.NewSubcommand("TestTypoSubcommandA") newSCB := flaggy.NewSubcommand("TestTypoSubcommandB") p.AttachSubcommand(newSCA, 1) p.AttachSubcommand(newSCB, 1) p.ParseArgs(args) } // TestIgnoreUnexpected tests what happens when an invalid subcommand is passed but should be ignored func TestIgnoreUnexpected(t *testing.T) { p := flaggy.NewParser("TestTypoSubcommand") p.ShowHelpOnUnexpected = false args := []string{"unexpectedArg"} newSCA := flaggy.NewSubcommand("TestTypoSubcommandA") p.AttachSubcommand(newSCA, 1) p.ParseArgs(args) } // TestSubcommandHelp tests displaying of help on unspecified commands func TestSubcommandHelp(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash on subcommand help display") } }() p := flaggy.NewParser("TestSubcommandHelp") p.ShowHelpOnUnexpected = true args := []string{"unexpectedArg"} p.ParseArgs(args) } func TestHelpWithHFlagA(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash on help flag use") } }() p := flaggy.NewParser("TestHelpWithHFlag") p.ShowHelpWithHFlag = true args := []string{"-h"} p.ParseArgs(args) } func TestHelpWithHFlagB(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash on help flag use") } }() p := flaggy.NewParser("TestHelpWithHFlag") p.ShowHelpWithHFlag = true args := []string{"--help"} p.ParseArgs(args) } func TestVersionWithVFlagB(t *testing.T) { defer func() { r := recover() if r == nil { t.Fatal("Expected crash on version flag use") } }() p := flaggy.NewParser("TestSubcommandVersion") p.ShowVersionWithVersionFlag = true p.Version = "TestVersionWithVFlagB 0.0.0a" args := []string{"--version"} p.ParseArgs(args) } // TestSubcommandParse tests paring of a single subcommand func TestSubcommandParse(t *testing.T) { var positionA string // create the argument parser p := flaggy.NewParser("TestSubcommandParse") // create a subcommand newSC := flaggy.NewSubcommand("testSubcommand") // add the subcommand into the parser p.AttachSubcommand(newSC, 1) // add a positional arg onto the subcommand at relative position 1 newSC.AddPositionalValue(&positionA, "positionalA", 1, false, "This is a test positional value") // override os args and parse them os.Args = []string{"binaryName", "testSubcommand", "testPositional"} p.Parse() // ensure subcommand and positional used if !newSC.Used { t.Fatal("Subcommand was not used, but it was expected to be") } if positionA != "testPositional" { t.Fatal("Positional argument was not set to testPositional, was:", positionA) } } func TestBadSubcommand(t *testing.T) { // create the argument parser p := flaggy.NewParser("TestBadSubcommand") // create a subcommand newSC := flaggy.NewSubcommand("testSubcommand") p.AttachSubcommand(newSC, 1) // test what happens if you add a bad subcommand os.Args = []string{"test"} p.Parse() } func TestBadPositional(t *testing.T) { // create the argument parser p := flaggy.NewParser("TestBadPositional") // create a subcommand // add a positional arg into the subcommand var positionA string var err error p.AddPositionalValue(&positionA, "positionalA", 1, false, "This is a test positional value") // test what happens if you add a bad subcommand os.Args = []string{"test", "badPositional"} err = p.Parse() if err != nil { t.Fatal("Threw an error when bad positional was passed, but shouldn't have") } } // TestNakedBoolFlag tests a naked boolean flag, which mean it has no // specified value beyond the flag being present. func TestNakedBool(t *testing.T) { flaggy.ResetParser() os.Args = []string{"testBinary", "-t"} // add a bool var var boolVar bool flaggy.Bool(&boolVar, "t", "boolVar", "A boolean flag for testing") flaggy.Parse() if !boolVar { t.Fatal("Boolean naked val not set to true") } } // debugOff makes defers easier func debugOff() { flaggy.DebugMode = false } // BenchmarkSubcommandParse benchmarks the creation and parsing of // a basic subcommand func BenchmarkSubcommandParse(b *testing.B) { // catch errors that may occur defer func(b *testing.B) { err := recover() if err != nil { b.Fatal("Benchmark had error:", err) } }(b) for i := 0; i < b.N; i++ { var positionA string // create the argument parser p := flaggy.NewParser("TestSubcommandParse") // create a subcommand newSC := flaggy.NewSubcommand("testSubcommand") // add the subcommand into the parser p.AttachSubcommand(newSC, 1) // add a positional arg onto the subcommand at relative position 1 newSC.AddPositionalValue(&positionA, "positionalA", 1, false, "This is a test positional value") // override os args and parse them os.Args = []string{"binaryName", "testSubcommand", "testPositional"} err := p.Parse() if err != nil { b.Fatal("Error parsing args: " + err.Error()) } } } // TestSCInputParsing tests all flag types on subcommands func TestSCInputParsing(t *testing.T) { defer debugOff() flaggy.DebugMode = true flaggy.ResetParser() var err error inputArgs := []string{} // setup a subcommand for all our flags to hang on sc := flaggy.NewSubcommand("subCommand") flaggy.AttachSubcommand(sc, 1) inputArgs = append(inputArgs, "subCommand") // Setup input arguments for every input type on our subcommand var stringFlag = "defaultVar" sc.String(&stringFlag, "s", "string", "string flag") inputArgs = append(inputArgs, "-s", "flaggy") var stringFlagExpected = "flaggy" var stringSliceFlag []string sc.StringSlice(&stringSliceFlag, "ssf", "stringSlice", "string slice flag") inputArgs = append(inputArgs, "-ssf", "one", "-ssf", "two") var stringSliceFlagExpected = []string{"one", "two"} var stringSliceCommaFlag []string sc.StringSlice(&stringSliceCommaFlag, "sscf", "stringSliceComma", "string slice flag") inputArgs = append(inputArgs, "-sscf", "one,two") var stringSliceCommaFlagExpected = []string{"one", "two"} var boolFlag bool sc.Bool(&boolFlag, "bf", "bool", "bool flag") inputArgs = append(inputArgs, "-bf") var boolFlagExpected = true var boolSliceFlag []bool sc.BoolSlice(&boolSliceFlag, "bsf", "boolSlice", "bool slice flag") inputArgs = append(inputArgs, "-bsf", "-bsf") var boolSliceFlagExpected = []bool{true, true} var byteSliceFlag []byte sc.ByteSlice(&byteSliceFlag, "bysf", "byteSlice", "byte slice flag") inputArgs = append(inputArgs, "-bysf", "17", "-bysf", "18") var byteSliceFlagExpected = []uint8{17, 18} var durationFlag time.Duration sc.Duration(&durationFlag, "df", "duration", "duration flag") inputArgs = append(inputArgs, "-df", "33s") var durationFlagExpected = time.Second * 33 var durationSliceFlag []time.Duration sc.DurationSlice(&durationSliceFlag, "dsf", "durationSlice", "duration slice flag") inputArgs = append(inputArgs, "-dsf", "33s", "-dsf", "1h") var durationSliceFlagExpected = []time.Duration{time.Second * 33, time.Hour} var float32Flag float32 sc.Float32(&float32Flag, "f32", "float32", "float32 flag") inputArgs = append(inputArgs, "-f32", "33.343") var float32FlagExpected float32 = 33.343 var float32SliceFlag []float32 sc.Float32Slice(&float32SliceFlag, "f32s", "float32Slice", "float32 slice flag") inputArgs = append(inputArgs, "-f32s", "33.343", "-f32s", "33.222") var float32SliceFlagExpected = []float32{33.343, 33.222} var float64Flag float64 sc.Float64(&float64Flag, "f64", "float64", "float64 flag") inputArgs = append(inputArgs, "-f64", "33.222343") var float64FlagExpected = 33.222343 var float64SliceFlag []float64 sc.Float64Slice(&float64SliceFlag, "f64s", "float64Slice", "float64 slice flag") inputArgs = append(inputArgs, "-f64s", "64.343", "-f64s", "64.222") var float64SliceFlagExpected = []float64{64.343, 64.222} var intFlag int sc.Int(&intFlag, "i", "int", "int flag") inputArgs = append(inputArgs, "-i", "3553") var intFlagExpected = 3553 var intSliceFlag []int sc.IntSlice(&intSliceFlag, "is", "intSlice", "int slice flag") inputArgs = append(inputArgs, "-is", "6446", "-is", "64") var intSliceFlagExpected = []int{6446, 64} var uintFlag uint sc.UInt(&uintFlag, "ui", "uint", "uint flag") inputArgs = append(inputArgs, "-ui", "3553") var uintFlagExpected uint = 3553 var uintSliceFlag []uint sc.UIntSlice(&uintSliceFlag, "uis", "uintSlice", "uint slice flag") inputArgs = append(inputArgs, "-uis", "6446", "-uis", "64") var uintSliceFlagExpected = []uint{6446, 64} var uint64Flag uint64 sc.UInt64(&uint64Flag, "ui64", "uint64", "uint64 flag") inputArgs = append(inputArgs, "-ui64", "3553") var uint64FlagExpected uint64 = 3553 var uint64SliceFlag []uint64 sc.UInt64Slice(&uint64SliceFlag, "ui64s", "uint64Slice", "uint64 slice flag") inputArgs = append(inputArgs, "-ui64s", "6446", "-ui64s", "64") var uint64SliceFlagExpected = []uint64{6446, 64} var uint32Flag uint32 sc.UInt32(&uint32Flag, "ui32", "uint32", "uint32 flag") inputArgs = append(inputArgs, "-ui32", "6446") var uint32FlagExpected uint32 = 6446 var uint32SliceFlag []uint32 sc.UInt32Slice(&uint32SliceFlag, "ui32s", "uint32Slice", "uint32 slice flag") inputArgs = append(inputArgs, "-ui32s", "6446", "-ui32s", "64") var uint32SliceFlagExpected = []uint32{6446, 64} var uint16Flag uint16 sc.UInt16(&uint16Flag, "ui16", "uint16", "uint16 flag") inputArgs = append(inputArgs, "-ui16", "6446") var uint16FlagExpected uint16 = 6446 var uint16SliceFlag []uint16 sc.UInt16Slice(&uint16SliceFlag, "ui16s", "uint16Slice", "uint16 slice flag") inputArgs = append(inputArgs, "-ui16s", "6446", "-ui16s", "64") var uint16SliceFlagExpected = []uint16{6446, 64} var uint8Flag uint8 sc.UInt8(&uint8Flag, "ui8", "uint8", "uint8 flag") inputArgs = append(inputArgs, "-ui8", "50") var uint8FlagExpected uint8 = 50 var uint8SliceFlag []uint8 sc.UInt8Slice(&uint8SliceFlag, "ui8s", "uint8Slice", "uint8 slice flag") inputArgs = append(inputArgs, "-ui8s", "3", "-ui8s", "2") var uint8SliceFlagExpected = []uint8{uint8(3), uint8(2)} var int64Flag int64 sc.Int64(&int64Flag, "i64", "i64", "int64 flag") inputArgs = append(inputArgs, "-i64", "33445566") var int64FlagExpected int64 = 33445566 var int64SliceFlag []int64 sc.Int64Slice(&int64SliceFlag, "i64s", "int64Slice", "int64 slice flag") if err != nil { t.Fatal(err) } inputArgs = append(inputArgs, "-i64s", "40", "-i64s", "50") var int64SliceFlagExpected = []int64{40, 50} var int32Flag int32 sc.Int32(&int32Flag, "i32", "int32", "int32 flag") inputArgs = append(inputArgs, "-i32", "445566") var int32FlagExpected int32 = 445566 var int32SliceFlag []int32 sc.Int32Slice(&int32SliceFlag, "i32s", "int32Slice", "uint32 slice flag") inputArgs = append(inputArgs, "-i32s", "40", "-i32s", "50") var int32SliceFlagExpected = []int32{40, 50} var int16Flag int16 sc.Int16(&int16Flag, "i16", "int16", "int16 flag") if err != nil { t.Fatal(err) } inputArgs = append(inputArgs, "-i16", "5566") var int16FlagExpected int16 = 5566 var int16SliceFlag []int16 sc.Int16Slice(&int16SliceFlag, "i16s", "int16Slice", "int16 slice flag") inputArgs = append(inputArgs, "-i16s", "40", "-i16s", "50") var int16SliceFlagExpected = []int16{40, 50} var int8Flag int8 sc.Int8(&int8Flag, "i8", "int8", "int8 flag") inputArgs = append(inputArgs, "-i8", "32") var int8FlagExpected int8 = 32 var int8SliceFlag []int8 sc.Int8Slice(&int8SliceFlag, "i8s", "int8Slice", "uint8 slice flag") inputArgs = append(inputArgs, "-i8s", "4", "-i8s", "2") var int8SliceFlagExpected = []int8{4, 2} var ipFlag net.IP sc.IP(&ipFlag, "ip", "ipFlag", "ip flag") inputArgs = append(inputArgs, "-ip", "1.1.1.1") var ipFlagExpected = net.IPv4(1, 1, 1, 1) var ipSliceFlag []net.IP sc.IPSlice(&ipSliceFlag, "ips", "ipFlagSlice", "ip slice flag") inputArgs = append(inputArgs, "-ips", "1.1.1.1", "-ips", "4.4.4.4") var ipSliceFlagExpected = []net.IP{net.IPv4(1, 1, 1, 1), net.IPv4(4, 4, 4, 4)} var hwFlag net.HardwareAddr sc.HardwareAddr(&hwFlag, "hw", "hwFlag", "hw flag") inputArgs = append(inputArgs, "-hw", "32:00:16:46:20:00") hwFlagExpected, err := net.ParseMAC("32:00:16:46:20:00") if err != nil { t.Fatal(err) } var hwFlagSlice []net.HardwareAddr sc.HardwareAddrSlice(&hwFlagSlice, "hws", "hwFlagSlice", "hw slice flag") inputArgs = append(inputArgs, "-hws", "32:00:16:46:20:00", "-hws", "32:00:16:46:20:01") macA, err := net.ParseMAC("32:00:16:46:20:00") if err != nil { t.Fatal(err) } macB, err := net.ParseMAC("32:00:16:46:20:01") if err != nil { t.Fatal(err) } var hwFlagSliceExpected = []net.HardwareAddr{macA, macB} var maskFlag net.IPMask sc.IPMask(&maskFlag, "m", "mFlag", "mask flag") inputArgs = append(inputArgs, "-m", "255.255.255.255") var maskFlagExpected = net.IPMask([]byte{255, 255, 255, 255}) var maskSliceFlag []net.IPMask sc.IPMaskSlice(&maskSliceFlag, "ms", "mFlagSlice", "mask slice flag") if err != nil { t.Fatal(err) } inputArgs = append(inputArgs, "-ms", "255.255.255.255", "-ms", "255.255.255.0") var maskSliceFlagExpected = []net.IPMask{net.IPMask([]byte{255, 255, 255, 255}), net.IPMask([]byte{255, 255, 255, 0})} // display help with all flags used flaggy.ShowHelp("Showing help from TestSCInputParsing test.") // Parse arguments flaggy.ParseArgs(inputArgs) // validate parsed values if stringFlag != stringFlagExpected { t.Fatal("string flag incorrect", stringFlag, stringFlagExpected) } for i, f := range stringSliceFlagExpected { if stringSliceFlag[i] != f { t.Fatal("stringSlice value incorrect", stringSliceFlag[i], f) } } for i, f := range stringSliceCommaFlagExpected { if stringSliceCommaFlag[i] != f { t.Fatal("stringSlice value incorrect", stringSliceCommaFlag[i], f) } } if boolFlag != boolFlagExpected { t.Fatal("bool flag incorrect", boolFlag, boolFlagExpected) } for i, f := range boolSliceFlagExpected { if boolSliceFlag[i] != f { t.Fatal("boolSlice value incorrect", boolSliceFlag[i], f) } } for i, f := range byteSliceFlagExpected { if byteSliceFlag[i] != f { t.Fatal("byteSlice value incorrect", boolSliceFlag[i], f) } } if durationFlag != durationFlagExpected { t.Fatal("duration flag incorrect", durationFlag, durationFlagExpected) } for i, f := range durationSliceFlagExpected { if durationSliceFlag[i] != f { t.Fatal("durationSlice value incorrect", durationSliceFlag[i], f) } } if float32Flag != float32FlagExpected { t.Fatal("float32 flag incorrect", float32Flag, float32FlagExpected) } for i, f := range float32SliceFlagExpected { if float32SliceFlag[i] != f { t.Fatal("float32Slice value incorrect", float32SliceFlag[i], f) } } if float64Flag != float64FlagExpected { t.Fatal("float64 flag incorrect", float64Flag, float64FlagExpected) } for i, f := range float64SliceFlagExpected { if float64SliceFlag[i] != f { t.Fatal("float64Slice value incorrect", float64SliceFlag[i], f) } } if intFlag != intFlagExpected { t.Fatal("int flag incorrect", intFlag, intFlagExpected) } for i, f := range intSliceFlagExpected { if intSliceFlag[i] != f { t.Fatal("intSlice value incorrect", intSliceFlag[i], f) } } if uintFlag != uintFlagExpected { t.Fatal("uint flag incorrect", uintFlag, uintFlagExpected) } for i, f := range uintSliceFlagExpected { if uintSliceFlag[i] != f { t.Fatal("uintSlice value incorrect", uintSliceFlag[i], f) } } if uint64Flag != uint64FlagExpected { t.Fatal("uint64 flag incorrect", uint64Flag, uint64FlagExpected) } for i, f := range uint64SliceFlagExpected { if uint64SliceFlag[i] != f { t.Fatal("uint64Slice value incorrect", uint64SliceFlag[i], f) } } if uint32Flag != uint32FlagExpected { t.Fatal("uint32 flag incorrect", uint32Flag, uint32FlagExpected) } for i, f := range uint32SliceFlagExpected { if uint32SliceFlag[i] != f { t.Fatal("uint32Slice value incorrect", uint32SliceFlag[i], f) } } if uint16Flag != uint16FlagExpected { t.Fatal("uint16 flag incorrect", uint16Flag, uint16FlagExpected) } for i, f := range uint16SliceFlagExpected { if uint16SliceFlag[i] != f { t.Fatal("uint16Slice value incorrect", uint16SliceFlag[i], f) } } if uint8Flag != uint8FlagExpected { t.Fatal("uint8 flag incorrect", uint8Flag, uint8FlagExpected) } for i, f := range uint8SliceFlagExpected { if uint8SliceFlag[i] != f { t.Fatal("uint8Slice value", i, "incorrect", uint8SliceFlag[i], f) } } if int64Flag != int64FlagExpected { t.Fatal("int64 flag incorrect", int64Flag, int64FlagExpected) } for i, f := range int64SliceFlagExpected { if int64SliceFlag[i] != f { t.Fatal("int64Slice value incorrect", int64SliceFlag[i], f) } } if int32Flag != int32FlagExpected { t.Fatal("int32 flag incorrect", int32Flag, int32FlagExpected) } for i, f := range int32SliceFlagExpected { if int32SliceFlag[i] != f { t.Fatal("int32Slice value incorrect", int32SliceFlag[i], f) } } if int16Flag != int16FlagExpected { t.Fatal("int16 flag incorrect", int16Flag, int16FlagExpected) } for i, f := range int16SliceFlagExpected { if int16SliceFlag[i] != f { t.Fatal("int16Slice value incorrect", int16SliceFlag[i], f) } } if int8Flag != int8FlagExpected { t.Fatal("int8 flag incorrect", int8Flag, int8FlagExpected) } for i, f := range int8SliceFlagExpected { if int8SliceFlag[i] != f { t.Fatal("int8Slice value incorrect", int8SliceFlag[i], f) } } if !ipFlag.Equal(ipFlagExpected) { t.Fatal("ip flag incorrect", ipFlag, ipFlagExpected) } for i, f := range ipSliceFlagExpected { if !f.Equal(ipSliceFlag[i]) { t.Fatal("ipSlice value incorrect", ipSliceFlag[i], f) } } if hwFlag.String() != hwFlagExpected.String() { t.Fatal("hw flag incorrect", hwFlag, hwFlagExpected) } for i, f := range hwFlagSliceExpected { if f.String() != hwFlagSlice[i].String() { t.Fatal("hw flag slice value incorrect", hwFlagSlice[i].String(), f.String()) } } if maskFlag.String() != maskFlagExpected.String() { t.Fatal("mask flag incorrect", maskFlag, maskFlagExpected) } for i, f := range maskSliceFlagExpected { if f.String() != maskSliceFlag[i].String() { t.Fatal("mask flag slice value incorrect", maskSliceFlag[i].String(), f.String()) } } }