// mauflag - An extendable command-line argument parser for Golang // Copyright (C) 2016 Maunium // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . package mauflag import ( "fmt" "os" "strings" ) // Set is a set of flags with certain input arguments type Set struct { // The list of strings used as input InputArgs []string // Whether or not to ignore all flags after the user has entered two dashes with no flag key ("--") // If enabled, all arguments after two dashes with no flag key will go into the args array (@see Args()) DoubleLineEscape bool // Whether or not to exit the program when there's an error // If enabled, the error message will be printed to `stderr` after which `os.Exit(1)` will be called. ExitOnError bool wantHelp *bool basicUsage string helpFirstLine string args []string flags []*Flag } // New creates a new flagset func New(args []string) *Set { return &Set{InputArgs: args, DoubleLineEscape: true, ExitOnError: false} } // Args returns the arguments that weren't associated with any flag func (fs *Set) Args() []string { return fs.args } // Arg returns the string at the given index from the list Args() returns // If the index does not exist, Arg will return an empty string. func (fs *Set) Arg(i int) string { if len(fs.args) <= i { return "" } return fs.args[i] } // NArg returns the number of arguments not associated with any flags func (fs *Set) NArg() int { return len(fs.args) } // MakeHelpFlag creates the -h, --help flag func (fs *Set) MakeHelpFlag() (*bool, *Flag) { var flag = fs.Make().Key("h", "help").Usage("Show this help page.").UsageCategory("Help") fs.wantHelp = flag.Bool() return fs.wantHelp, flag } // CheckHelpFlag checks if the help flag is set and prints the help page if needed. // Return value tells whether or not the help page was printed func (fs *Set) CheckHelpFlag() bool { if fs.wantHelp != nil && *fs.wantHelp { fs.PrintHelp() return true } return false } // SetHelpTitles sets the first line (program name and basic explanation) and basic usage specification func (fs *Set) SetHelpTitles(firstLine, basicUsage string) { fs.helpFirstLine = firstLine fs.basicUsage = basicUsage } // PrintHelp prints the help page func (fs *Set) PrintHelp() { var helpSectNames = make(map[string]int) var helpSectIndexes = make(map[int]string) var helpSects = make([][]*Flag, 0) for _, flag := range fs.flags { index, ok := helpSectNames[flag.usageCat] if !ok { arr := []*Flag{flag} helpSects = append(helpSects, arr) helpSectNames[flag.usageCat] = len(helpSects) - 1 helpSectIndexes[len(helpSects)-1] = flag.usageCat } else { helpSects[index] = append(helpSects[index], flag) } } data, maxLen := fs.formatFlagHelp(helpSects) fmt.Printf(`%s Usage: %s `, fs.helpFirstLine, fs.basicUsage) for sect, sData := range data { fmt.Print(helpSectIndexes[sect], " options:\n") for i, fData := range sData { fmt.Print(fData, strings.Repeat(" ", maxLen-len(fData)+3), helpSects[sect][i].usage, "\n") } fmt.Print("\n") } } func (fs *Set) formatFlagHelp(helpSects [][]*Flag) (data [][]string, maxLen int) { maxLen = 0 data = make([][]string, len(helpSects)) for sect, flags := range helpSects { var sData = make([]string, len(flags)) for i, flag := range flags { var fData = []string{" "} for _, key := range flag.shortKeys { fData = append(fData, "-") fData = append(fData, key) fData = append(fData, ", ") } for _, key := range flag.longKeys { fData = append(fData, "--") fData = append(fData, key) if len(flag.usageValName) > 0 { fData = append(fData, "=") fData = append(fData, flag.usageValName) } fData = append(fData, ", ") } if fData[len(fData)-1] == ", " { fData = fData[:len(fData)-1] } sData[i] = strings.Join(fData, "") if len(sData[i]) > maxLen { maxLen = len(sData[i]) } } data[sect] = sData } return } func (fs *Set) err(format string, args ...interface{}) error { if fs.ExitOnError { fmt.Fprintf(os.Stderr, format, args...) fmt.Fprint(os.Stderr, "\n") os.Exit(1) return nil } return fmt.Errorf(format, args...) } // Parse the input arguments in this flagset into mauflag form // Before this function is called all the flags will have either the type default or the given default value func (fs *Set) Parse() error { var flag *Flag var key string var noMoreFlags = false for _, arg := range fs.InputArgs { if noMoreFlags { fs.args = append(fs.args, arg) } else if arg == "--" && fs.DoubleLineEscape { noMoreFlags = true } else if flag != nil { err := flag.setValue(arg) if err != nil { return fs.err("Flag %s was not a %s", key, flag.Value.Name()) } flag = nil } else if arg[0] == '-' && len(arg) > 1 { arg = strings.ToLower(arg) var err error flag, key, err = fs.flagFound(arg[1:]) if err != nil { return err } } else { fs.args = append(fs.args, arg) } } return nil } func (fs *Set) flagFound(arg string) (flag *Flag, key string, err error) { key = arg var val string if strings.ContainsRune(key, '=') { val = key[strings.Index(key, "=")+1:] key = key[:strings.Index(key, "=")] } var rem string flag, key, rem = fs.getFlag(key) flag, key, rem, err = fs.handleFlag(flag, key, rem, val) if len(rem) > 0 { return fs.flagFound(rem) } return } func (fs *Set) handleFlag(flag *Flag, key, rem, val string) (*Flag, string, string, error) { var err error var isBool bool if flag != nil { _, isBool = flag.Value.(*boolValue) } if flag == nil { err = fs.err("Unknown flag: %s", key) } else if len(val) > 0 && len(rem) == 0 { flag.setValue(val) } else if isBool { flag.setValue("true") } else if len(rem) > 0 { flag.setValue(rem) rem = "" } else { return flag, key, rem, err } return nil, "", rem, err } func (fs *Set) getFlag(key string) (*Flag, string, string) { if key[0] == '-' { key = key[1:] for _, lflag := range fs.flags { for _, lkey := range lflag.longKeys { if lkey == key { return lflag, lkey, "" } } } return nil, key, "" } rem := key[1:] key = key[0:1] for _, lflag := range fs.flags { for _, lkey := range lflag.shortKeys { if lkey == key { return lflag, lkey, rem } } } return nil, key, rem }