https://github.com/hellflame/argparse
argparse for golang
https://github.com/hellflame/argparse
argparse go levenshtein-matcher positional-arguments
Last synced: 8 months ago
JSON representation
argparse for golang
- Host: GitHub
- URL: https://github.com/hellflame/argparse
- Owner: hellflame
- License: mit
- Created: 2021-05-02T13:31:05.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2023-11-22T10:59:40.000Z (about 2 years ago)
- Last Synced: 2025-06-04T06:22:24.686Z (8 months ago)
- Topics: argparse, go, levenshtein-matcher, positional-arguments
- Language: Go
- Homepage:
- Size: 1.53 MB
- Stars: 24
- Watchers: 3
- Forks: 5
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# argparse
[](https://godoc.org/github.com/hellflame/argparse) [](https://goreportcard.com/report/github.com/hellflame/argparse) [](https://coveralls.io/github/hellflame/argparse?branch=master)
[中文文档](docs/cn.md)
Argparser is inspired by [python argparse](https://docs.python.org/3.9/library/argparse.html)
It's small but Powerful
Providing not only simple parsing args, but :
- [x] Sub Command
- [x] Argument Groups
- [x] Positional Arguments
- [x] Customizable Parse Formatter
- [x] Customizable Validate Checker
- [x] Argument Choice Support
- [x] Argument Action Support (infinite possible)
- [x] Shell Completion Support
- [x] Levenshtein Reminder
- [x] Output Color Schema Support
- [ ] ......
## Aim
The aim of the project is to help programers build better command line programs using `golang`.
## Installation
```bash
go get -u github.com/hellflame/argparse
```
> no third-party dependence required
## Usage
Just go:
```go
package main
import (
"fmt"
"github.com/hellflame/argparse"
)
func main() {
parser := argparse.NewParser("basic", "this is a basic program", nil)
name := parser.String("n", "name", nil)
if e := parser.Parse(nil); e != nil {
fmt.Println(e.Error())
return
}
fmt.Printf("hello %s\n", *name)
}
```
[example](examples/basic)
Check output:
```bash
=> go run main.go
usage: basic [-h] [-n NAME]
this is a basic program
options:
-h, --help show this help message
-n NAME, --name NAME
=> go run main.go -n hellflame
hello hellflame
```
A few points:
About the object __parser__ :
1. `NewParser`'s first argument is the name of your program, it's ok __to be empty__. When it's an empty string, the program's name will be `path.Base(os.Args[0])` . It can be handy when the release name is not decided yet.
2. `help` entry is usable by default, you can disable it with `&ParserConfig{DisableHelp: true}` when invoking `NewParser`, then you can define your own `help` .
3. When *help message* showed up, the program will default __exit with code 1 (version < v1.5)__ or __return error with type BreakAfterHelp (version >= 1.5)__ or the __error equals BreakAfterHelpError (version >= 1.10)__ , this is stoppable by setting `ParserConfig.ContinueOnHelp = true` .
About the action __parse__ :
1. The argument `name` is bond to user's input __after__ `parser.Parse`
2. When give `parser.Parse` a `nil` as the argument, `os.Args[1:]` is used as parse source
3. The short name of your argument can be __more than one character__
---
Based on those points above, the code can be altered like this:
```go
func main() {
parser := argparse.NewParser("", "this is a basic program", &argparse.ParserConfig{
DisableHelp:true,
DisableDefaultShowHelp: true})
name := parser.String("n", "name", nil)
help := parser.Flag("help", "help-me", nil)
if e := parser.Parse(os.Args[1:]); e != nil {
fmt.Println(e.Error())
return
}
if *help {
parser.PrintHelp()
return
}
if *name != "" {
fmt.Printf("hello %s\n", *name)
}
}
```
[example](examples/basic-bias)
Check output:
```bash
=> go run main.go # there will be no output
=> go run main.go -h
unrecognized arguments: -h
do you mean?: -n
# the real help entry is -help / --help-me
=> go run main.go -help
usage: main [-n NAME] [-help]
this is a basic program
options:
-n NAME, --name NAME
-help, --help-me
=> go run main.go --name hellflame
hello hellflame
```
A few more points:
1. `DisableHelp` only prevent `-h/--help` flag from registering to parser, but the `help` function is still available using `PrintHelp` and `FormatHelp`.
2. When `DisableDefaultShowHelp` is false, and there is no user input, the `help` message will still show up as Default Action.
3. Display help message by invoking `parser.PrintHelp()` , `return` will put an end to the `main` function.
4. Notice the order of usage array, it's mostly the order of creating arguments, I tried to keep them this way. [example](example/change-help)
### Features
Some show cases
#### 1. Levenshtein error correct [ >= v1.2.0 ]
the `Parser` will try to give most posibile __options__ as recommend when there is no match
```go
parser := NewParser("", "", nil)
parser.String("a", "aa", nil)
if e := parser.Parse([]string{"--ax"}); e != nil {
if e.Error() != "unrecognized arguments: --ax\ndo you mean?: --aa" {
t.Error("failed to guess input")
return
}
}
// when user input '--ax', Parser will try to find best matches with nearrest levenshtein-distance
// here for example is --aa
```
Notice that if there are multiple `Positional Argument` , the `unrecognized arguments` will be regard as `Positional Argument` , and there will be no recommend.
#### 2. Extra special positional arguments [ >= v1.12 ]
When the user want to input some special arguments starting with `-` or `--` , normally the parser will warn you with `unrecognized arguments`.
If you want to do so, simply input any arguments after a single `--`.
For example: `./run -- extra1 extra2`. In the example, `extra1` and `extra2` can be anything, `./run -- --1 -x`.
If the last positional argument receives multiple inputs, then the positional inputs before and after `--` will all be its value.
```go
names := parser.Strings("", "names", &argparse.Option{Positional: true})
parser.parse([]string{"a", "--", "b", "c"})
// names == ["a", "b", "c"]
```
### Supported Arguments
#### 1. Flag
```go
parser.Flag(short, full, *Option)
```
`Flag` create a flag argument, return a `*bool` pointer to the parse result
Python version is like `add_argument("-s", "--full", action="store_true")`
Flag Argument can only be used as an __OptionalArguments__, more restrictions see [restrictions](#restriction-of-flags).
#### 2. String
```go
parser.String(short, full, *Option)
```
`String` create a string argument, return a `*string` pointer to the parse result
String Argument can be used as Optional or Positional Argument, default to be Optional. It's like `add_argument("-s", "--full")` in python
Set `Option.Positional = true` to use as Positional Argument, it's like `add_argument("s", "full")` in python
#### 3. String List
```go
parser.Strings(short, full, *Option)
```
`Strings` create a string list argument, return a `*[]string` pointer to the parse result
Options are mostly like `*Parser.String()`
Python version is like `add_argument("-s", "--full", nargs="*")`
#### 4. Int
```go
parser.Int(short, full, *Option)
```
`Int` create an int argument, return a `*int` pointer to the parse result
Options are mostly like `*Parser.String()`, except the return type
Python version is like `add_argument("-s", "--full", type=int)`
#### 5. Int List
```go
parser.Ints(short, full, *Option)
```
`Ints` create an int list argument, return a `*[]int` pointer to the parse result
Options are mostly like `*Parser.Int()`
Python version is like `add_argument("-s", "--full", type=int, nargs="*")`
#### 6. Float
```go
parser.Float(short, full, *Option)
```
`Float` create a float argument, return a `*float64` pointer to the parse result
Options are mostly like `*Parser.String()`, except the return type
Python version is like `add_argument("-s", "--full", type=double)`
#### 7. Float List
```go
parser.Floats(short, full, *Option)
```
`Floats` create a float list argument, return a `*[]float64` pointer to the parse result
Options are mostly like `*Parser.Float()`
Python version is like `add_argument("-s", "--full", type=double, nargs="*")`
### Other Types
For complex types or even customized types, this library do __not directly support__ these feature , but it doesn't mean you can't do anything. Here are some cases:
#### 1. File type
You can check file's existence before reading it, and tells if it's a valid file, check modify time, etc. [example](examples/customzed-types/main.go)
Though the return type is still a `string` , but it's more garanteed to use them
```go
path := parser.String("f", "file", &argparse.Option{
Validate: func(arg string) error {
if _, e := os.Stat(arg); e != nil {
return fmt.Errorf("unable to access '%s'", arg)
}
return nil
},
})
if e := parser.Parse(nil); e != nil {
fmt.Println(e.Error())
return
}
if *path != "" {
if read, e := os.ReadFile(*path); e == nil {
fmt.Println(string(read))
}
}
```
The case above used `Validate` to do the trick, we'll talk about it later in more detail
Python code is like:
```python
def valid_type(arg):
if not os.path.exist(arg):
raise Exception("can't access {}".format(arg))
return arg
parser.add_argument("-s", "--full", type=valid_type)
```
The difference is that, python can return any type from the type function `valid_type` , and you can just return a `file` type in there
There could arise some problems if a `*File` is returned in go. the `*File` might be used somewhere before, which makes it non-idempotent, and you need to `Close` the file somewhere, or the memory may leak, the resource management can be a problem. Instead of using `*File` with danger, you can manage the resouce in much safer way:
```go
func dealFile(path) {
f, e := os.Open("")
if e != nil {
fmt.Println(e.Error())
return
}
defer f.Close() // close file
io.ReadAll(f)
}
```
#### 2. Any Type
Checkout `Action` for example, then you can handle any type when parsing arguments !
### Advanced
#### 1. Argument Group
Argument group is useful to arrange arguments help info in to groups, only affects how the help info displays, use `Group` config to do so. [example](examples/yt-download/main.go)
```go
parser.Flag("", "version", &argparse.Option{
Help: "Print program version and exit",
Group: "General Options",
})
```
#### 2. Display Meta
When the full name of the argument is too long to display, `Meta` can change how it displays in help info. More Control can be optimized by __MaxHeaderLength__. [example](examples/yt-download/main.go)
```go
parser.Int("", "playlist-start", &argparse.Option{
Help: "Playlist video to start at (default is 1)",
Meta: "NUMBER",
})
```
It will look like this in help message:
```bash
--playlist-start NUMBER Playlist video to start at (default is 1)
```
#### 3.Default Value
If the argument is not specified by user, default value will be applied. [example](examples/yt-download/main.go)
```go
parser.Int("", "playlist-start", &argparse.Option{
Help: "Playlist video to start at (default is 1)",
Default: "1",
})
```
Note that the Default value is a `String`, the value is used like an argument from `os.Args`, it has to get through `Validate` & `Formatter` & `parse` actions (if exist).
Also, the Default value can only be a `String` , and if you want an Array of arguments, you can only have one element Array as default value. You can apply your default array after *parse*.
#### 4. Required Argument
If the argument must be specified by the user, set `Required` to be `true`. [example](examples/yt-download/main.go)
```go
parser.Strings("", "url", &argparse.Option{
Help: "youtube links, like 'https://www.youtube.com/watch?v=xxxxxxxx'",
Required: true,
})
```
Flag argument can not be `Required` (flag argument has more [restrictions](#restriction-of-flags), you will be noticed while choosing it)
#### 5. Positional Argument
If you want users to input arguments by position, set `Positional` to be true. [example](examples/yt-download/main.go)
```go
parser.Strings("", "url", &argparse.Option{
Help: "youtube links, like 'https://www.youtube.com/watch?v=xxxxxxxx'",
Positional: true,
})
```
The position of the Positional Argument is quit flex, with not much restrictions, it's ok to be
1. in the middle of other arguments, `--play-list 2 xxxxxxxx --update`. If the argument before it is an Array argument, `url` will be treated as one of the Array argument: `--user-ids id1 id2 url --update`
2. after another single value Positional Argument, `--mode login username password` , the last `password` will be regard as another Positional Argument
So, use it carefully, it may __confuse__ (for the users), which is the same in argparse of Python Version.
#### 6. Argument Validation
Provide `Validate` function to check every one of its argument
```go
parser.Strings("", "url", &argparse.Option{
Help: "youtube links",
Validate: func(arg string) error {
if !strings.HasPrefix(arg, "https://") {
return fmt.Errorf("url should be start with 'https://'")
}
return nil
},
})
```
`Validate` function is executed just after when `Default` value is set, which means, the default value has to go through `Validate` check.
> If the argument is an array type (`Ints`, `Strings`, `Floats` ...), every element will be validated by this function.
#### 7. Argument Formatter
Re-format input argument, but remember that, the return type of `Formatter` should be the same as your argument type
```go
parser.String("", "b", &Option{
Formatter: func(arg string) (i interface{}, err error) {
if arg == "False" {
err = fmt.Errorf("no False")
return
}
i = fmt.Sprintf("=> %s", arg)
return
},
})
```
If `Validate` is set, `Formatter` is executed after `Validate`.
If error is raised in `Formatter`, it acts like `Validate`.
The return type of `interface{}` should be the same as your Argument Type, or Element Type of your Arguments, here, is a `string` in the Example.
#### 8. Argument Choices
Restrict inputs to be within the given choices, use `Choices`
```go
parser.Ints("", "hours", &Option{
Choices: []interface{}{1, 2, 3, 4},
})
```
The element type of the choice is the same as argument, or the element of the argument.
If `Formatter` is set, Choice check is right after `Formatter`
When it's single value, the value must be one of the `Choices`
When it's value array, each value must be one of of `Choices`
#### 9. Sub Commands
Create new parser scope, arguments won't interrupt other parsers(root parser and other sub parsers)
```go
func main() {
parser := argparse.NewParser("sub-command", "Go is a tool for managing Go source code.", nil)
t := parser.Flag("f", "flag", &argparse.Option{Help: "from main parser"})
testCommand := parser.AddCommand("test", "start a bug report", nil)
tFlag := testCommand.Flag("f", "flag", &argparse.Option{Help: "from test parser"})
otherFlag := testCommand.Flag("o", "other", nil)
defaultInt := testCommand.Int("i", "int", &argparse.Option{Default: "1"})
if e := parser.Parse(nil); e != nil {
fmt.Println(e.Error())
return
}
println(*tFlag, *otherFlag, *t, *defaultInt)
}
```
Output:
```bash
=> ./sub-command
usage: sub-command [-h] [-f]
Go is a tool for managing Go source code.
commands:
test start a bug report
options:
-h, --help show this help message
-f, --flag from main parser
# when using sub command, it's a total different context
=> ./sub-command test
usage: sub-command test [-h] [-f] [-o] [-i INT]
start a bug report
options:
-h, --help show this help message
-f, --flag from test parser
-o, --other
-i INT, --int INT
```
The two `--flag` will parse seperately, so you can use `tFlag` & `t` to reference flag in `test` parser and `main` parser.
1. sub command has different context, so you can have two `--flag`, and different help message output
2. sub command show help message seperately, it's for user to understand your program *step by step*. While `Group Argument` helps user to understand your program *group by group*
2. Theoretically, you can create sub parser infinitely. Create a sub parser of a sub parser of a subparser ... Somehow, which would make the program harder to use, and easy to run out of users' patience.
**[v1.7.3]** Fix:
* Invoked Action
if subparser is invoked, the root parser will **NOT** be invoked.
* When Not Invoked
if subparser is **NOT** invoked, no argument's default value will be applied, and required argument will not be checked ... The subparser remains dead. (It's a bug if the subparser gets alive !)
#### 10. Argument Action
Argument Action allows you to do anything with the argument if there is any match, this enables infinite possibility when parsing arguments. [example](examples/any-type-action/main.go)
```go
p := NewParser("action", "test action", nil)
sum := 0
p.Strings("", "number", &Option{Positional: true, Action: func(args []string) error {
// here tries to sum every input number
for _, a := range args {
if i, e := strconv.Atoi(a); e != nil {
return fmt.Errorf("I don't know this number: %s", a)
} else {
sum += i
}
}
return nil
}})
if e := p.Parse([]string{"1", "2", "3"}); e != nil {
fmt.Println(e.Error())
return
}
fmt.Println(sum) // 6
```
A few points:
1. `Action` is a function with `args []string` as input,the `args` has two kinds of input
* `nil` : which means it's a `Flag` argument
* `[]string{"a1", "a2"}` : which means you have bond other type of argument, other than `Flag` argument
2. Errors can be returned if necessary, it can be normally captured in *parse*.
3. The return type of the argument is not of much importance, using `p.Strings` is the same as `p.Ints` , because `arg.Action` will be executed __before binding value__, which means, `Action` has __top priority__
4. If `Action` is executed, `Validate`, `Formatter`, Choice check and value binding will be ignored.
#### 11. Default Parse Action [ >= v0.4 ]
Instead of showing help message as default, you can set your own default action when no user input is given, [example](examples/parse-action/main.go)
```go
parser := argparse.NewParser("basic", "this is a basic program", &argparse.ParserConfig{DefaultAction: func() {
fmt.Println("hi ~\ntell me what to do?")
}})
parser.AddCommand("test", "testing", &argparse.ParserConfig{DefaultAction: func() {
fmt.Println("ok, now you know we are testing")
}})
if e := parser.Parse(nil); e != nil {
fmt.Println(e.Error())
return
}
```
When `DefaultAction` is set, default action of showing help message will be ignored.
`DefaultAction` is effective on sub command, and if sub parser's `ParserConfig` is `nil`, `DefaultAction` from main parser will be inherited.
#### 12. Shell Completion Support [ >= v0.4 ]
Users can get terminal hint through tapping [tab]
Set `ParserConfig.AddShellCompletion` to `true` will register `--completion` to the parser. [example](examples/shell-completion/main.go)
```go
p := argparse.NewParser("start", "this is test", &argparse.ParserConfig{AddShellCompletion: true})
p.Strings("a", "aa", nil)
p.Int("", "bb", nil)
p.Float("c", "cc", &argparse.Option{Positional: true})
test := p.AddCommand("test", "", nil)
test.String("a", "aa", nil)
test.Int("", "bb", nil)
install := p.AddCommand("install", "", nil)
install.Strings("i", "in", nil)
if e := p.Parse(nil); e != nil {
fmt.Println(e.Error())
return
}
```
Though, if you didn't set `ParserConfig.AddShellCompletion` to `true` , shell complete script is still available via `parser.FormatCompletionScript` , which will generate the script.
__Note__:
1. the completion script only support `bash` & `zsh` for now
2. and it only generate simple complete code for basic use, it should be better than nothing.
3. sub command has __no__ completion entry
3. you will know if the user has triggered this input by checking the error returned from `Parse` function, it's a `BreakAfterShellScriptError`.
Save the output code (using `start --completion`) to `~/.bashrc` or `~/.zshrc` or `~/bash_profile` or some file at `/etc/bash_completion.d/` or `/usr/local/etc/bash_completion.d/` , then restart the shell or `source ~/.bashrc` will enable the completion. Or just save completion by appending this line in `~/.bashrc`:
```bash
eval `start --completion`
```
Completion the function is supported by shell, and the shell identify your program by its name, so you `MUST` give your program a fix name.
#### 13. Hide Entry [ >= 1.3 ]
Sometimes, you want to hide en entry from users, because they should not see or are not necessary to know the entry, but you can still use the entry. Situations like:
1. the entry is to help generate completion candidates (which has mess or not much meaningful output)
2. secret back door that users should not know (you can use `os.Getenv` instead, but `argparse` can do more)
You only need to set `Option{HideEntry: true}`
```go
func main() {
parser := argparse.NewParser("basic", "this is a basic program", nil)
name := parser.String("n", "name", nil)
greet := parser.String("g", "greet", &argparse.Option{HideEntry: true})
if e := parser.Parse(nil); e != nil {
fmt.Println(e.Error())
return
}
greetWord := "hello"
if *greet != "" {
greetWord = *greet
}
fmt.Printf("%s %s\n", greetWord, *name)
}
```
check ouput:
```bash
usage: basic [--help] [--name NAME]
this is a basic program
options:
--help, -h show this help message
--name NAME, -n NAME
```
Which will have effect in `Shell Completion Script`
[example](examples/hide-help-entry/main.go)
#### 14. Invoked & InvokeAction [ >= 1.4 ]
When there is valid match for main parser or sub parser, `Parser.Invoked` will be set true. If `Parser.InvokeAction` is set, it will be executed.
```go
p := NewParser("", "", nil)
a := p.String("a", "", nil)
sub := p.AddCommand("sub", "", nil)
b := sub.String("b", "", nil)
p.InvokeAction = func(invoked bool) {
// do things when main parser has any match
}
sub.InvokeAction = func(invoked bool) {
// do things when sub parser has any match
}
subNo2 := p.AddCommand("sub2", "", nil)
subNo2.Int("a", "", nil)
subNo2.InvokeAction = func(invoked bool) {
// do things when sub2 parser has any match
}
if e := p.Parse(nil); e != nil {
t.Error(e.Error())
return
}
// check parser Invoked
fmt.Println(p.Invoked, sub.Invoked, subNo2.Invoked)
```
#### 15. Help info hint [ >= v1.6 ]
Help message can be generated with some hint info, like default value, choice range, required mark, or even any hint message. Like:
```bash
usage: sub-command test [--help] [--flag] [--other] [--float FLOAT] [--int INT] [--string STRING]
start a bug report
options:
--help, -h show this help message
--flag, -f from test parser
--other, -o (optional => ∫)
--float FLOAT (options: [0.100000, 0.200000], required)
--int INT, -i INT this is int (default: 1)
--string STRING, -s STRING no hint message
```
Enable global hint by setting parser config `&argparse.ParserConfig{WithHint: true}` .
Disable one argument hint with `&argparse.Option{NoHint: true}`
Customize argument hint with `&argparse.Option{HintInfo: "customize info"}`
[example](examples/sub-command)
#### 16. Limit args header length [ >= 1.7 ]
When argument is too long, you can set `ParserConfig.MaxHeaderLength` to a reasonable length.
Before setting `MaxHeaderLength` , the help info may display like (which is default to adjust to the longest argument length):
```bash
usage: long-args [--help] [--short SHORT] [--medium-size MEDIUM-SIZE] [--this-is-a-very-long-args THIS-IS-A-VERY-LONG-ARGS]
options:
--help, -h show this help message
--short SHORT, -s SHORT this is a short args
--medium-size MEDIUM-SIZE, -m MEDIUM-SIZE this is a medium size args
--this-is-a-very-long-args THIS-IS-A-VERY-LONG-ARGS, -l THIS-IS-A-VERY-LONG-ARGS this is a very long args
```
After setting `ParserConfig.MaxHeaderLength = 20` (it's recommended to be around 20 ~ 30),argument's help info will display on new line with 20 space indent, if its header is too long.
```bash
usage: long-args [--help] [--short SHORT] [--medium-size MEDIUM-SIZE] [--this-is-a-very-long-args THIS-IS-A-VERY-LONG-ARGS]
options:
--help, -h show this help message
--short SHORT, -s SHORT
this is a short args
--medium-size MEDIUM-SIZE, -m MEDIUM-SIZE
this is a medium size args
--this-is-a-very-long-args THIS-IS-A-VERY-LONG-ARGS, -l THIS-IS-A-VERY-LONG-ARGS
this is a very long args
```
[example](examples/long-args/main.go)
#### 17. Inheriable argument [ >= 1.8 ]
When some argument represent the same thing among root parser and sub parsers, such as `debug`, `verbose` .... and you don't want to write duplicated arguments code for too many times, you have two recommended ways:
before *v1.8*, as all sub parsers are the same type as root parser, use a `for` loop with `Action` would do it.
```go
parser := argparse.NewParser("", "", nil)
sub := parser.AddCommand("sub", "", nil)
sub2 := parser.AddCommand("sub2", "", nil)
url := ""
for _, p := range []*argparse.Parser{parser, sub, sub2} {
p.String("", "url", &argparse.Option{Action: func(args []string) error {
url = args[0]
return nil
}})
}
if e := parser.Parse(nil); e != nil {
return
}
print(url)
```
The code above might be less clean, and there comes `Inheritable` , which is an `argument option` . When argument sets __`&argparse.Option{Inheritable: true}`__, sub parsers added __after__ the argument will be able to inherit this argument or override it.
```go
parser := argparse.NewParser("", "", nil)
verbose := parser.Flag("v", "", &argparse.Option{Inheritable: true, Help: "show verbose info"}) // inheritable argument
local := parser.AddCommand("local", "", nil)
service := parser.AddCommand("service", "", nil)
version := service.Int("v", "version", &argparse.Option{Help: "choose version"})
```
As a result, sub parser `local` will inhert `verbose` as a `Flag`, when pass user input `program local -v`, `*verbose` will be `true`, which means `*verbose` is shared among root parser and inherited parsers. However, as prefix `v` is also registered as `Int` by sub parser `service`, you can't use `-v` to `show verbose`, but `choose version` , it's `overrided`.
> Note `Inheritable` is valid for `Positional`, if their metaNames are the same, `argparse` will consider them the same.
[example](examples/inherit/main.go)
#### 18. Bind given parsers to an argument [ >= 1.9 ]
This is a very flexable way to create argument for multiple parsers. Create one argument using the main parser, and provide a series of parsers by setting the option when you create the argument:
__`Option{BindParsers: []*Parser{a, b, ...}}`__.
```go
parser := argparse.NewParser("", "", nil)
a := parser.AddCommand("a", "", nil)
b := parser.AddCommand("b", "", nil)
c := parser.AddCommand("c", "", nil)
ab := parser.String("", "ab", &argparse.Option{
BindParsers: []*argparse.Parser{a, b},
})
bc := parser.String("", "bc", &argparse.Option{
BindParsers: []*argparse.Parser{b, c},
})
```
As a result, subparser `a` & `b` will bind a `String` argument with entry `--ab` and referred to by var `ab` , subparser `b` & `c` will bind a `String` argument with entry `--bc` and referred to by var `bc`.
__Note__ that both arguments are detached from main `parser`, because you've set the `BindParsers`. If you still want to bind the argument to the main parser, append it to `BindParsers`, like `BindParsers: []*argparse.Parser{b, c, parser}`.
This is one way to share a single argument among different parsers. Be aware that creating argument like this still need to go through conflict check, if there's already a `--ab` exist in `a` or `b` parser, there will be a panic to notice the programer.
#### 19. Color Schema Support [ >= 1.11 ]
Set `ParserConfig.WithColor = true`, and the help message can be dyed with different colors, if the users' terminal support color.
Also Set `ParserConfig.EnsureColor = true`, and the help message will surely dye with colors. This is for some rare terminals without the environment variable `TERM`, the programer can check it for your own. Normally this is not necessary.
You can also set `ParserConfig.ColorSchema` to dye the help message with your own style. Take `DefaultColor` for reference, most part of the help message can be given a `Color` with *Code* and *Property*. A few knowledge of how color is presented in terminal is required. Normally you can try set Color *Code* within 30 and 49 to represent different text color and background color, and set Color *Property* within 10. Do some combinations, and you'll master them, just try!
[example](./examples/colorful/main.go)

##### Argument Process Flow Map
```
┌────► BindAction?
│ │
│ │ Consume it's Arguments
│ ▼
│ ┌──────┐
--date 20210102 --list │ arg1 │ arg2 arg3
└───┬──┘
│
│
▼
ApplyDefault?
│
│
┌─────▼──────┐
│ Validate │
└─────┬──────┘
│
┌─────▼──────┐
│ Formatter │
└─────┬──────┘
│
┌─────▼───────┐
│ ChoiceCheck │
└─────────────┘
```
The return value of last process will be the input of next process, if shows it in code, it's like
```python
with MatchFound:
if MatchFound.BindAction:
return MatchFound.BindAction(*args)
else:
for arg in args:
if Validate(arg):
yield ChoiceCheck(Formatter(arg))
```
##### Restrictions Of Flag Argument {#restriction-of-flags}
1. Can't be Positional
2. Can't has Choices
3. Can't be Required
4. Can't set Formatter
5. Can't set Validate function
## Config
### 1. Parser Config
Relative struct:
```go
type ParserConfig struct {
Usage string // manual usage display
EpiLog string // message after help
DisableHelp bool // disable help entry register [-h/--help]
ContinueOnHelp bool // set true to: continue program after default help is printed
DisableDefaultShowHelp bool // set false to: default show help when there is no args to parse (default action)
DefaultAction func() // set default action to replace default help action
AddShellCompletion bool // set true to register shell completion entry [--completion]
WithHint bool // argument help message with argument default value hint
MaxHeaderLength int // max argument header length in help menu, help info will start at new line if argument meta info is too long
WithColor bool // enable colorful help message if the terminal has support for color
EnsureColor bool // use color code for sure, skip terminal env check
ColorSchema *ColorSchema // use given color schema to draw help info
}
```
example:
```go
func main() {
parser := argparse.NewParser("basic", "this is a basic program",
&argparse.ParserConfig{
Usage: "basic xxx",
EpiLog: "more detail please visit https://github.com/hellflame/argparse",
DisableHelp: true,
ContinueOnHelp: true,
DisableDefaultShowHelp: true,
})
name := parser.String("n", "name", nil)
help := parser.Flag("help", "help-me", nil)
if e := parser.Parse(nil); e != nil {
fmt.Println(e.Error())
return
}
if *help {
parser.PrintHelp()
return
}
if *name != "" {
fmt.Printf("hello %s\n", *name)
}
}
```
[example](examples/parser-config)
Output:
```bash
=> go run main.go
# there will be no help message
# affected by DisableDefaultShowHelp
=> go run main.go --help-me
usage: basic xxx # <=== Usage
this is a basic program
options: # no [-h/--help] flag is registerd, which is affected by DisableHelp
-n NAME, --name NAME
-help, --help-me
more detail please visit https://github.com/hellflame/argparse # <=== EpiLog
```
Except the comment above, `ContinueOnHelp` is only affective on your program process, which gives you possibility to do something when `help` entry is invoked.
### 2. Argument Options
Related struct:
```go
type Option struct {
Meta string // meta value for help/usage generate
multi bool // take more than one argument
Default string // default argument value if not given
isFlag bool // use as flag
Required bool // require to be set
Positional bool // is positional argument
HideEntry bool // hide usage & help display
Help string // help message
Group string // argument group info, default to be no group
Inheritable bool // sub parsers after this argument can inherit it
Action func(args []string) error // bind actions when the match is found, 'args' can be nil to be a flag
Choices []interface{} // input argument must be one/some of the choice
Validate func(arg string) error // customize function to check argument validation
Formatter func(arg string) (interface{}, error) // format input arguments by the given method
}
```
## How it works
```
┌──────────────────────┐ ┌──────────────────────┐
│ │ │ │
│ OptionArgsMap │ │ PositionalArgsList │
│ │ │ │
│ -h ───► helpArg │ │ │
│ │ │[ posArg1 posArg2 ]│
│ -n ──┐ │ │ │
│ │► nameArg │ │ │
│ --name ──┘ │ │ │
│ │ │ │
└──────────────────────┘ └──────────────────────┘
▲ yes no ▲
│ │
│ match?──────────────────┘
│
│
┌─┴──┐ match helpArg:
args: │ -h │-n hellflame ▼
└────┘ ┌──isflag?───┐
▼ ▼
done ┌──MultiValue?───┐
▼ ▼
┌──parse consume untill
▼ NextOptionArgs
done
```
## Error & Panic
The principle of returning `error` or just panic is that, __no panic for production use__
Cases where `argparse` will panic:
1. failed to add subcommand
2. failed to add argument entry, `Strings`, `Flag`, etc.
Those failures is not allowed, and you will notice when you test your program. The rest errors will be returned in `Parse`, which you should be able to tell users what to do.
## [Examples](examples)
there are some useful use cases to help you build your own command line program
feel free to add different use cases
1. [more type action](examples/any-type-action)
2. [parse action(don't help yet)](examples/parse-action)
3. [shell completion(tabs-able)](examples/shell-completion)
4. [hide help entry](examples/hide-help-entry)
5. [customized types(it's all your call)](examples/customzed-types)
6. [how to add sub command](examples/sub-command)
7. [deal with long args](examples/long-args)
8. [multiple parser in one program](examples/multi-parser)
9. [decide help's position](examples/change-help)
9. [argument groups](examples/argument-groups)
9. [batch create arguments](examples/batch-create-arguments)
9. [argument inherit](examples/inherit)
9. [color support](examples/colorful)
9. [extra arguments](examples/extra-arguments)