i am building a little cli tool that boots my app in development or production.
the way i want it to work is like this:
app run --dev or app run --prod
Atm it doest parses the flags after my command but only before my command. So this works
app --dev run or app --prod run
Any idee how to fix it this so i can use it after my command? here is my code
func main() {
//flag.Usage := usage
flag.Parse()
args := flag.Args()
if len(args) == 0 {
Usage()
os.Exit(0)
}
if *dev {
os.Setenv("ENV", "development")
}
if *prod {
os.Setenv("ENV", "production")
}
switch {
// Run
case args[0] == "run" && len(args) == 1:
os.Setenv("port", *port)
log.Printf("Booting in %s", os.Getenv("ENV"))
Run()
// Help
case args[0] == "help" && len(args) == 1:
Usage()
}
}
Traditionally, the UNIX option parser getopt()
stops parsing after the first non-option. The glibc altered this behavior to support options in arbitrary positions, a controversial decision. The flag
package implements the traditional behavior.
One thing you could do is permuting the arguments array before parsing the flags. That's what the glibc does:
func permutateArgs(args []string) int {
args = args[1:]
optind := 0
for i := range args {
if args[i][0] == '-' {
tmp := args[i]
args[i] = args[optind]
args[optind] = tmp
optind++
}
}
return optind + 1
}
This code permutates args
such that options are in front, leaving the program name untouched. permutateArgs
returns the index of the first non-option after permutation. Use this code like this:
optind := permutateArgs(os.Args)
flags.Parse()
// process non-options
for i := range os.Args[optind:] {
// ...
}
This is simply the way the flag package deals with arguments. Argument handling is always somewhat convention driven, and the flag package definitely does not follow the gnu-style that many are accustomed to.
One way is to sort the options before the commands:
// example argument sorting using sort.Stable
type ArgSlice []string
func (p ArgSlice) Len() int { return len(p) }
func (p ArgSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p ArgSlice) Less(i, j int) bool {
if len(p[i]) == 0 {
return false
}
if len(p[j]) == 0 {
return true
}
return p[i][0] == '-' && p[j][0] != '-'
}
func main() {
args := []string{"cmd", "-a", "arg", "-b", "-c"}
sort.Stable(ArgSlice(args))
// if sorting os.Args, make sure to omit the first argument
// sort.Stable(ArgSlice(os.Args[1:]))
fmt.Println(args)
}
Many packages use separate FlagSets for subcommands, which provides nice separation, but this would not work when there are possibly different flags between subcommands. The only solution there is to duplicate the flags across each level.
However, the best answer is still to follow the conventions used by the flag package, and not try to fight it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With