github.com/haraldrudell/parl@v0.4.176/pflags/arg-parser.go (about) 1 /* 2 © 2020–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 // Package pflags provides declarative options and a string-slice option type. 7 package pflags 8 9 import ( 10 "flag" 11 "os" 12 13 "github.com/haraldrudell/parl" 14 "github.com/haraldrudell/parl/pstrings" 15 ) 16 17 const ( 18 // the os.Args value of debug option: “-debug” 19 DebugOption = "-" + DebugOptionName 20 // the name of debug option “debug” 21 DebugOptionName = "debug" 22 ) 23 24 const ( 25 offFlagFalse = false 26 offFlagTrue = true 27 ) 28 29 // ArgParser invokes [flag.Parse] with off-flags support. 30 // pflags package compared to flags package: 31 // - ability to use declarative options 32 // - ability to read default option values from yaml configuration files 33 // - support for multiple-strings option value 34 // - support for unary off-flags, “-no-flag” options 35 // - map or list of visited options 36 type ArgParser struct { 37 optionsList []OptionData 38 usage func() 39 } 40 41 // NewArgParser returns an options-parser with off-flags support 42 func NewArgParser(optionsList []OptionData, usage func()) (argParser *ArgParser) { 43 return &ArgParser{ 44 optionsList: optionsList, 45 usage: usage, 46 } 47 } 48 49 // Parse invokes [flag.Parse] after providing optionsList and usage to flag package 50 // - -no-flagname flags are inverted before and after 51 func (a *ArgParser) Parse() { 52 53 // options have not been parsed yet, so verbose state cannot be determined 54 // - if first option is “-debug”, it’s debug 55 if len(os.Args) > 1 && os.Args[1] == DebugOption { 56 var _, defaultsMap = OptionValues(a.optionsList) 57 parl.Log("option defaults: %v", defaultsMap) 58 parl.Log("os.args[1:]: %s", pstrings.QuoteList(os.Args[1:])) 59 defer func() { 60 var effectiveValueMap, _ = OptionValues(a.optionsList) 61 parl.Log("resulting option values: %v", effectiveValueMap) 62 }() 63 } 64 65 flag.Usage = a.usage 66 67 // booleanOffList is a list of pointers to the effective values of off-flags 68 // - on return from [flag.Parse], these values are inverted 69 var booleanOffList []*bool 70 defer a.parseEnd(&booleanOffList) 71 72 // provide optionData list to flag package 73 omLen := len(a.optionsList) 74 for i := 0; i < omLen; i++ { 75 option := &a.optionsList[i] 76 77 // flag package does not support -no-flagname off-flags. pflags does implement off-flags 78 // - an off-flag is a boolean flag with default value true 79 // - — any time the flag occurs on the command-line its value is set to false 80 // - — off-flags typically have names with leading “no” 81 // - — [OptionData.Name] is like “no-stdin” 82 // - — on command-line is provided like “-no-stdin” 83 // - the only option-type allowed by [flags.Parse] to not have an argument is a boolean flag 84 // - — therefore, off-flags must be plain boolean flags 85 // - but boolean flags have default value false, and are set to true on occurrence 86 // - — therefore, identify all boolean flags with default value true 87 // - — prior to invoking [flags.Parse], set their default value to false 88 // - — on occurrence, [flags.Parse] will set their value to true 89 // - — on return from [flags.Parse], invert the off-flags’ effective values 90 // - — if an off-flag did not occur, [flags.Parse] set its effective value to false, and result is true 91 // - — if an off-flag did occur, [flags.Parse] set its effective value to true, and result is false 92 93 // is this option’s effective value type bool? 94 if boolp, ok := option.P.(*bool); ok { 95 // is the default value for this boolean flag true? 96 if value, ok := option.Value.(bool); ok && value { 97 98 // this is a flag of type bool with default value true 99 // - this is used for off-flags -no-flagname 100 // - retain a pointer to the off-flag option’s effective value 101 booleanOffList = append(booleanOffList, boolp) 102 103 // in a copy of optionData, set off-option default value to false 104 var o = *option 105 o.Value = offFlagFalse // have default value false 106 option = &o // use the copy 107 } 108 } 109 option.AddOption() 110 } 111 112 // flag.Parse uses os.Args[1:] 113 flag.Parse() 114 } 115 116 // iterate over the -no-flagname off-flag options 117 // - if the value is false, the flag was not provided, result should be true 118 // - if the value is true, the flag was provided, the result should be false 119 func (a *ArgParser) parseEnd(booleanOffList *[]*bool) { 120 for _, boolp := range *booleanOffList { 121 // invert effective value 122 if *boolp { 123 *boolp = offFlagFalse 124 } else { 125 *boolp = offFlagTrue 126 } 127 } 128 }