github.com/gogf/gf/v2@v2.7.4/os/gcmd/gcmd_parser.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 // 7 8 package gcmd 9 10 import ( 11 "context" 12 "os" 13 "strings" 14 15 "github.com/gogf/gf/v2/container/gvar" 16 "github.com/gogf/gf/v2/errors/gcode" 17 "github.com/gogf/gf/v2/errors/gerror" 18 "github.com/gogf/gf/v2/internal/command" 19 "github.com/gogf/gf/v2/internal/json" 20 "github.com/gogf/gf/v2/text/gregex" 21 "github.com/gogf/gf/v2/text/gstr" 22 ) 23 24 // ParserOption manages the parsing options. 25 type ParserOption struct { 26 CaseSensitive bool // Marks options parsing in case-sensitive way. 27 Strict bool // Whether stops parsing and returns error if invalid option passed. 28 } 29 30 // Parser for arguments. 31 type Parser struct { 32 option ParserOption // Parse option. 33 parsedArgs []string // As name described. 34 parsedOptions map[string]string // As name described. 35 passedOptions map[string]bool // User passed supported options, like: map[string]bool{"name,n":true} 36 supportedOptions map[string]bool // Option [OptionName:WhetherNeedArgument], like: map[string]bool{"name":true, "n":true} 37 commandFuncMap map[string]func() // Command function map for function handler. 38 } 39 40 // ParserFromCtx retrieves and returns Parser from context. 41 func ParserFromCtx(ctx context.Context) *Parser { 42 if v := ctx.Value(CtxKeyParser); v != nil { 43 if p, ok := v.(*Parser); ok { 44 return p 45 } 46 } 47 return nil 48 } 49 50 // Parse creates and returns a new Parser with os.Args and supported options. 51 // 52 // Note that the parameter `supportedOptions` is as [option name: need argument], which means 53 // the value item of `supportedOptions` indicates whether corresponding option name needs argument or not. 54 // 55 // The optional parameter `strict` specifies whether stops parsing and returns error if invalid option passed. 56 func Parse(supportedOptions map[string]bool, option ...ParserOption) (*Parser, error) { 57 if supportedOptions == nil { 58 command.Init(os.Args...) 59 return &Parser{ 60 parsedArgs: GetArgAll(), 61 parsedOptions: GetOptAll(), 62 }, nil 63 } 64 return ParseArgs(os.Args, supportedOptions, option...) 65 } 66 67 // ParseArgs creates and returns a new Parser with given arguments and supported options. 68 // 69 // Note that the parameter `supportedOptions` is as [option name: need argument], which means 70 // the value item of `supportedOptions` indicates whether corresponding option name needs argument or not. 71 // 72 // The optional parameter `strict` specifies whether stops parsing and returns error if invalid option passed. 73 func ParseArgs(args []string, supportedOptions map[string]bool, option ...ParserOption) (*Parser, error) { 74 if supportedOptions == nil { 75 command.Init(args...) 76 return &Parser{ 77 parsedArgs: GetArgAll(), 78 parsedOptions: GetOptAll(), 79 }, nil 80 } 81 var parserOption ParserOption 82 if len(option) > 0 { 83 parserOption = option[0] 84 } 85 parser := &Parser{ 86 option: parserOption, 87 parsedArgs: make([]string, 0), 88 parsedOptions: make(map[string]string), 89 passedOptions: supportedOptions, 90 supportedOptions: make(map[string]bool), 91 commandFuncMap: make(map[string]func()), 92 } 93 for name, needArgument := range supportedOptions { 94 for _, v := range strings.Split(name, ",") { 95 parser.supportedOptions[strings.TrimSpace(v)] = needArgument 96 } 97 } 98 99 for i := 0; i < len(args); { 100 if option := parser.parseOption(args[i]); option != "" { 101 array, _ := gregex.MatchString(`^(.+?)=(.+)$`, option) 102 if len(array) == 3 { 103 if parser.isOptionValid(array[1]) { 104 parser.setOptionValue(array[1], array[2]) 105 } 106 } else { 107 if parser.isOptionValid(option) { 108 if parser.isOptionNeedArgument(option) { 109 if i < len(args)-1 { 110 parser.setOptionValue(option, args[i+1]) 111 i += 2 112 continue 113 } 114 } else { 115 parser.setOptionValue(option, "") 116 i++ 117 continue 118 } 119 } else { 120 // Multiple options? 121 if array = parser.parseMultiOption(option); len(array) > 0 { 122 for _, v := range array { 123 parser.setOptionValue(v, "") 124 } 125 i++ 126 continue 127 } else if parser.option.Strict { 128 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid option '%s'`, args[i]) 129 } 130 } 131 } 132 } else { 133 parser.parsedArgs = append(parser.parsedArgs, args[i]) 134 } 135 i++ 136 } 137 return parser, nil 138 } 139 140 // parseMultiOption parses option to multiple valid options like: --dav. 141 // It returns nil if given option is not multi-option. 142 func (p *Parser) parseMultiOption(option string) []string { 143 for i := 1; i <= len(option); i++ { 144 s := option[:i] 145 if p.isOptionValid(s) && !p.isOptionNeedArgument(s) { 146 if i == len(option) { 147 return []string{s} 148 } 149 array := p.parseMultiOption(option[i:]) 150 if len(array) == 0 { 151 return nil 152 } 153 return append(array, s) 154 } 155 } 156 return nil 157 } 158 159 func (p *Parser) parseOption(argument string) string { 160 array, _ := gregex.MatchString(`^\-{1,2}(.+)$`, argument) 161 if len(array) == 2 { 162 return array[1] 163 } 164 return "" 165 } 166 167 func (p *Parser) isOptionValid(name string) bool { 168 // Case-Sensitive. 169 if p.option.CaseSensitive { 170 _, ok := p.supportedOptions[name] 171 return ok 172 } 173 // Case-InSensitive. 174 for optionName := range p.supportedOptions { 175 if gstr.Equal(optionName, name) { 176 return true 177 } 178 } 179 return false 180 } 181 182 func (p *Parser) isOptionNeedArgument(name string) bool { 183 return p.supportedOptions[name] 184 } 185 186 // setOptionValue sets the option value for name and according alias. 187 func (p *Parser) setOptionValue(name, value string) { 188 // Accurate option name match. 189 for optionName := range p.passedOptions { 190 optionNameAndShort := gstr.SplitAndTrim(optionName, ",") 191 for _, optionNameItem := range optionNameAndShort { 192 if optionNameItem == name { 193 for _, v := range optionNameAndShort { 194 p.parsedOptions[v] = value 195 } 196 return 197 } 198 } 199 } 200 // Fuzzy option name match. 201 for optionName := range p.passedOptions { 202 optionNameAndShort := gstr.SplitAndTrim(optionName, ",") 203 for _, optionNameItem := range optionNameAndShort { 204 if strings.EqualFold(optionNameItem, name) { 205 for _, v := range optionNameAndShort { 206 p.parsedOptions[v] = value 207 } 208 return 209 } 210 } 211 } 212 } 213 214 // GetOpt returns the option value named `name` as gvar.Var. 215 func (p *Parser) GetOpt(name string, def ...interface{}) *gvar.Var { 216 if p == nil { 217 return nil 218 } 219 if v, ok := p.parsedOptions[name]; ok { 220 return gvar.New(v) 221 } 222 if len(def) > 0 { 223 return gvar.New(def[0]) 224 } 225 return nil 226 } 227 228 // GetOptAll returns all parsed options. 229 func (p *Parser) GetOptAll() map[string]string { 230 if p == nil { 231 return nil 232 } 233 return p.parsedOptions 234 } 235 236 // GetArg returns the argument at `index` as gvar.Var. 237 func (p *Parser) GetArg(index int, def ...string) *gvar.Var { 238 if p == nil { 239 return nil 240 } 241 if index >= 0 && index < len(p.parsedArgs) { 242 return gvar.New(p.parsedArgs[index]) 243 } 244 if len(def) > 0 { 245 return gvar.New(def[0]) 246 } 247 return nil 248 } 249 250 // GetArgAll returns all parsed arguments. 251 func (p *Parser) GetArgAll() []string { 252 if p == nil { 253 return nil 254 } 255 return p.parsedArgs 256 } 257 258 // MarshalJSON implements the interface MarshalJSON for json.Marshal. 259 func (p Parser) MarshalJSON() ([]byte, error) { 260 return json.Marshal(map[string]interface{}{ 261 "parsedArgs": p.parsedArgs, 262 "parsedOptions": p.parsedOptions, 263 "passedOptions": p.passedOptions, 264 "supportedOptions": p.supportedOptions, 265 }) 266 }