Proposal Details

u-root (u-root.org) is a set of almost 200 Unix-like command line tools that has found wide use, particularly in firmware, including Google, ByteDance, and TrenchBoot. In u-root, a tool called gobusybox does a source-to-source transformation of standalone programs, creating a single binary from many standalone programs. For firmware use, minimizing the size of this binary is critical, requiring minimal use of different packages that implement similar functions -- as in, e.g., using flag packages such as spf13 as well as the standard flag package.

The flag package argument parsing has been a pain point from our beginning in 2012, as it has a more Plan 9 style set of rules: the - character is followed by a single flag, of one or more characters, e.g., -aux means a single flag named aux, not three flags, a, u, and x.

Unix users expect that "long names" are introduced with double -, and multiple single-letter flags are introduced by a single -. The Go flag package behavior has led to frequent confusion.

U-root programmers have addressed this problem by using other flag packages, with the result that we have more than one flag package in use. This has led to unacceptable growth in our footprint in SPI Flash parts. A recent proposal to just use the standard flag package (https://github.com/u-root/u-root/pull/3004) seeks to resolve this problem, but points out: "...may need a solution for multiple combined shorthand flags", proposing that any individual tool that needs Unix-style flags just implement them, on a command-by-command basis.

That way lies madness. Instead, we propose a single addition to the flag package: func (f*FlagSet) UnixParse(...string) that uses Unix style rules. Here is a possible implementation, that would resolve many of the u-root needs:

func (f*FlagSet) UnixParse(args []string) {
    var out []string
    for i, f := range args {
        if strings.HasPrefix(f, "--") {
            out = append(out, f[1:])
            continue
        }
        if strings.HasPrefix(f, "-") {
            fs := strings.Split(f[1:], "")
            for _, ff := range fs {
                out = append(out, "-"+ff)
            }
            continue
        }
        out = append(out, args[i:]...)
        break
    }

    f.Parse(out)

}

simple test here: https://go.dev/play/p/wbwhFohoUNd

Note that we do not propose to add flag.UnixParse(), as we are discouraging and removing the use of globals, because they fail in a bare metal environment such as TinyGo or Tamago.

Comment From: seankhliao

I believe the Go project's stance is that the design of the flag package is intentional, and any alternative parsing rules should be layered on top, see https://github.com/golang/go/issues/35761#issuecomment-559170057