Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

golang flag stops parsing after the first non-option

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()
    }
}
like image 310
Anthony De Meulemeester Avatar asked Aug 04 '14 07:08

Anthony De Meulemeester


2 Answers

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:] {
    // ...
}
like image 77
fuz Avatar answered Oct 22 '22 12:10

fuz


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.

like image 34
JimB Avatar answered Oct 22 '22 12:10

JimB