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