github.com/laher/uggo@v0.0.0-20140418102112-0ad25fe11c5b/flagsetaliasedvars.go (about) 1 //extend FlagSet with support for flag aliasMap 2 package uggo 3 4 import ( 5 "flag" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "time" 11 ) 12 13 14 // This FlagSet embeds Golang's FlagSet but adds some extra flavour 15 // - support for 'aliased' arguments 16 // - support for 'gnuification' of short-form arguments 17 // - handling of --help and --version 18 // - some flexibility in 'usage' message 19 type FlagSetWithAliases struct { 20 *flag.FlagSet 21 AutoGnuify bool //sets whether it interprets options gnuishly (e.g. -lah being equivalent to -l -a -h) 22 name string 23 argUsage string 24 out io.Writer 25 aliasMap map[string][]string 26 isPrintUsage *bool //optional usage (you can use your own, or none, instead) 27 isPrintVersion *bool //optional (you can use your own, or none, instead) 28 version string 29 } 30 31 32 // factory which sets defaults and 33 // NOTE: discards thet output of the embedded flag.FlagSet. This was necessary in order to override the 'usage' message 34 func NewFlagSet(desc string, errorHandling flag.ErrorHandling) FlagSetWithAliases { 35 fs := flag.NewFlagSet(desc, errorHandling) 36 fs.SetOutput(ioutil.Discard) 37 return FlagSetWithAliases{fs, false, desc, "", os.Stderr, map[string][]string{}, nil, nil, "unknown"} 38 } 39 40 // Factory setting useful defaults 41 // Sets up --help and --version flags 42 func NewFlagSetDefault(name, argUsage, version string) FlagSetWithAliases { 43 fs := flag.NewFlagSet(name+" "+argUsage, flag.ContinueOnError) 44 fs.SetOutput(ioutil.Discard) 45 // temp variables for storing defaults 46 tmpPrintUsage := false 47 tmpPrintVersion := false 48 flagSet := FlagSetWithAliases{fs, true, name, argUsage, os.Stderr, map[string][]string{}, &tmpPrintUsage, &tmpPrintVersion, version} 49 flagSet.BoolVar(flagSet.isPrintUsage, "help", false, "Show this help") 50 flagSet.BoolVar(flagSet.isPrintVersion, "version", false, "Show version") 51 flagSet.version = version 52 return flagSet 53 } 54 55 // process built-in help and version flags. 56 // Returns 'true' when one of these was set. (i.e. stop processing) 57 func (flagSet FlagSetWithAliases) ProcessHelpOrVersion() bool { 58 if flagSet.IsHelp() { 59 flagSet.Usage() 60 return true 61 } else if flagSet.IsVersion() { 62 flagSet.PrintVersion() 63 return true 64 } 65 return false 66 } 67 68 //convenience method for storing 'usage' behaviour 69 func (flagSet FlagSetWithAliases) IsHelp() bool { 70 return *flagSet.isPrintUsage 71 } 72 73 //convenience method for storing 'get version' behaviour 74 func (flagSet FlagSetWithAliases) IsVersion() bool { 75 return *flagSet.isPrintVersion 76 } 77 78 // PrintVersion 79 func (flagSet FlagSetWithAliases) PrintVersion() { 80 fmt.Fprintf(flagSet.out, "`%s` version: '%s'\n", flagSet.name, flagSet.version) 81 } 82 83 // Print Usage message 84 func (flagSet FlagSetWithAliases) Usage() { 85 fmt.Fprintf(flagSet.out, "Usage: `%s %s`\n", flagSet.name, flagSet.argUsage) 86 flagSet.PrintDefaults() 87 } 88 89 // Set writer for displaying help and usage messages. 90 func (flagSet FlagSetWithAliases) SetOutput(out io.Writer) { 91 flagSet.out = out 92 } 93 94 // Set up multiple names for a bool flag 95 func (flagSet FlagSetWithAliases) AliasedBoolVar(p *bool, items []string, def bool, description string) { 96 flagSet.RecordAliases(items, "bool") 97 for _, item := range items { 98 flagSet.BoolVar(p, item, def, description) 99 } 100 } 101 102 // Set up multiple names for a time.Duration flag 103 func (flagSet FlagSetWithAliases) AliasedDurationVar(p *time.Duration, items []string, def time.Duration, description string) { 104 flagSet.RecordAliases(items, "duration") 105 for _, item := range items { 106 flagSet.DurationVar(p, item, def, description) 107 } 108 } 109 110 // Set up multiple names for a float64 flag 111 func (flagSet FlagSetWithAliases) AliasedFloat64Var(p *float64, items []string, def float64, description string) { 112 flagSet.RecordAliases(items, "float64") 113 for _, item := range items { 114 flagSet.Float64Var(p, item, def, description) 115 } 116 } 117 118 // Set up multiple names for an int flag 119 func (flagSet FlagSetWithAliases) AliasedIntVar(p *int, items []string, def int, description string) { 120 flagSet.RecordAliases(items, "int") 121 for _, item := range items { 122 flagSet.IntVar(p, item, def, description) 123 } 124 } 125 126 // Set up multiple names for an int64 flag 127 func (flagSet FlagSetWithAliases) AliasedInt64Var(p *int64, items []string, def int64, description string) { 128 flagSet.RecordAliases(items, "int64") 129 for _, item := range items { 130 flagSet.Int64Var(p, item, def, description) 131 } 132 } 133 134 // Set up multiple names for a string flag 135 func (flagSet FlagSetWithAliases) AliasedStringVar(p *string, items []string, def string, description string) { 136 flagSet.RecordAliases(items, "string") 137 for _, item := range items { 138 flagSet.StringVar(p, item, def, description) 139 } 140 } 141 142 // returns true if the given flag name is the 'main' name or a subsequent name 143 func (flagSet FlagSetWithAliases) isAlternative(name string) bool { 144 for _, altSlice := range flagSet.aliasMap { 145 for _, alt := range altSlice { 146 if alt == name { 147 return true 148 } 149 } 150 } 151 return false 152 } 153 154 // keep track of aliases to a given flag 155 func (flagSet FlagSetWithAliases) RecordAliases(items []string, typ string) { 156 var key string 157 for i, item := range items { 158 if i == 0 { 159 key = item 160 if _, ok := flagSet.aliasMap[key]; !ok { 161 flagSet.aliasMap[key] = []string{} 162 } 163 } else { 164 //key is same as before 165 flagSet.aliasMap[key] = append(flagSet.aliasMap[key], item) 166 } 167 } 168 } 169 170 // parse flags from a given set of argv type flags 171 func (flagSet FlagSetWithAliases) Parse(call []string) error { 172 if flagSet.AutoGnuify { 173 call = Gnuify(call) 174 } 175 return flagSet.FlagSet.Parse(call) 176 } 177 178 179 func (flagSet FlagSetWithAliases) ParsePlus(call []string) (error, int) { 180 err := flagSet.Parse(call) 181 if err != nil { 182 fmt.Fprintf(flagSet.out, "Flag error: %v\n\n", err.Error()) 183 flagSet.Usage() 184 return err, 0 185 } 186 if flagSet.ProcessHelpOrVersion() { 187 return EXIT_OK, 0 188 } 189 return nil, 0 190 } 191 192 // print defaults to the default output writer 193 func (flagSet FlagSetWithAliases) PrintDefaults() { 194 flagSet.PrintDefaultsTo(flagSet.out) 195 } 196 197 // print defaults to a given writer. 198 // Output distinguishes aliases 199 func (flagSet FlagSetWithAliases) PrintDefaultsTo(out io.Writer) { 200 flagSet.FlagSet.VisitAll(func(fl *flag.Flag) { 201 l := 0 202 alts, isAliased := flagSet.aliasMap[fl.Name] 203 if isAliased { 204 li, _ := fmt.Fprintf(out, " ") 205 l += li 206 if len(fl.Name) > 1 { 207 li, _ := fmt.Fprint(out, "-") 208 l += li 209 } 210 li, _ = fmt.Fprintf(out, "-%s", fl.Name) 211 l += li 212 for _, alt := range alts { 213 fmt.Fprint(out, " ") 214 l += 1 215 if len(alt) > 1 { 216 li, _ := fmt.Fprint(out, "-") 217 l += li 218 } 219 li, _ := fmt.Fprintf(out, "-%s", alt) 220 l += li 221 } 222 //defaults: 223 //no known straightforward way to test for boolean types 224 if fl.DefValue == "false" { 225 } else { 226 li, _ = fmt.Fprintf(out, "=%s", fl.DefValue) 227 l += li 228 } 229 fmt.Fprint(out, " ") 230 l += 1 231 } else if !flagSet.isAlternative(fl.Name) { 232 li, _ := fmt.Fprint(out, " ") 233 l += li 234 if len(fl.Name) > 1 { 235 li, _ := fmt.Fprint(out, "-") 236 l += li 237 } 238 if fl.DefValue == "false" { 239 li, _ = fmt.Fprintf(out, "-%s", fl.Name) 240 l += li 241 } else { 242 format := "-%s=%s" 243 li, _ = fmt.Fprintf(out, format, fl.Name, fl.DefValue) 244 l += li 245 } 246 } else { 247 //fmt.Fprintf(out, "alias %s\n", fl.Name) 248 } 249 if !flagSet.isAlternative(fl.Name) { 250 for l < 25 { 251 l += 1 252 fmt.Fprintf(out, " ") 253 } 254 fmt.Fprintf(out, ": %s\n", fl.Usage) 255 } 256 257 }) 258 fmt.Fprintln(out, "") 259 } 260 261 // function which can (open and) return a File at some later time 262 type FileOpener func() (*os.File, error) 263 264 // converts arguments to readable file references. 265 // An argument with filename "-" is treated as the 'standard input' 266 func (flagSet FlagSetWithAliases) ArgsAsReadables() []FileOpener { 267 args := flagSet.Args() 268 if len(args) > 0 { 269 readers := []FileOpener{} 270 for _, arg := range args { 271 if arg == "-" { 272 reader := func() (*os.File, error) { 273 return os.Stdin, nil 274 } 275 readers = append(readers, reader) 276 } else { 277 reader := func() (*os.File, error) { 278 return os.Open(arg) 279 } 280 readers = append(readers, reader) 281 } 282 } 283 return readers 284 } else { 285 reader := func() (*os.File, error) { 286 return os.Stdin, nil 287 } 288 return []FileOpener{reader} 289 } 290 } 291 292 // atomically open all files at once. 293 // Only use this when you actually want all open at once (rather than sequentially) 294 // e.g. writing the same data to all at once as-in a 'tee' operation. 295 func OpenAll(openers []FileOpener) ([]*os.File, error) { 296 files := []*os.File{} 297 for _, opener := range openers { 298 file, err := opener() 299 if err != nil { 300 //close all opened files 301 for _, openedfile := range files { 302 openedfile.Close() 303 } 304 return nil, err 305 } 306 files = append(files, file) 307 } 308 return files, nil 309 } 310 311 // Convert arguments to File openers. 312 // An argument with filename "-" is treated as the 'standard output' 313 func ToWriteableOpeners(args []string, flag int, perm os.FileMode) []FileOpener { 314 return ToPipeWriteableOpeners(args, flag, perm, os.Stdout) 315 } 316 317 // Convert arguments to File openers. 318 // An argument with filename "-" is treated as the 'standard output' 319 // Takes a writer 'outPipe' for handling this special case 320 func ToPipeWriteableOpeners(args []string, flag int, perm os.FileMode, outPipe *os.File) []FileOpener { 321 if len(args) > 0 { 322 writers := []FileOpener{} 323 for _, arg := range args { 324 if arg == "-" { 325 writer := func() (*os.File, error) { 326 return outPipe, nil 327 } 328 writers = append(writers, writer) 329 } else { 330 writer := func() (*os.File, error) { 331 return os.OpenFile(arg, os.O_WRONLY|flag, perm) 332 } 333 writers = append(writers, writer) 334 } 335 } 336 return writers 337 } else { 338 writer := func() (*os.File, error) { 339 return os.Stdout, nil 340 } 341 return []FileOpener{writer} 342 } 343 } 344 345 // Convert arguments to File openers. 346 // An argument with filename "-" is treated as the 'standard output' 347 func (flagSet FlagSetWithAliases) ArgsAsWriteables(flag int, perm os.FileMode) []FileOpener { 348 args := flagSet.Args() 349 return ToPipeWriteableOpeners(args, flag, perm, os.Stdout) 350 } 351 352 // Convert arguments to File openers. 353 // An argument with filename "-" is treated as the 'standard output' 354 // Takes a writer 'outPipe' for handling this special case 355 func (flagSet FlagSetWithAliases) ArgsAsPipeWriteables(flag int, perm os.FileMode, outPipe *os.File) []FileOpener { 356 args := flagSet.Args() 357 return ToPipeWriteableOpeners(args, flag, perm, outPipe) 358 } 359