// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. // package gcmd import ( "context" "os" "strings" "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/command" "github.com/gogf/gf/v2/internal/json" "github.com/gogf/gf/v2/text/gregex" "github.com/gogf/gf/v2/text/gstr" ) // ParserOption manages the parsing options. type ParserOption struct { CaseSensitive bool // Marks options parsing in case-sensitive way. Strict bool // Whether stops parsing and returns error if invalid option passed. } // Parser for arguments. type Parser struct { option ParserOption // Parse option. parsedArgs []string // As name described. parsedOptions map[string]string // As name described. passedOptions map[string]bool // User passed supported options, like: map[string]bool{"name,n":true} supportedOptions map[string]bool // Option [OptionName:WhetherNeedArgument], like: map[string]bool{"name":true, "n":true} commandFuncMap map[string]func() // Command function map for function handler. } // ParserFromCtx retrieves and returns Parser from context. func ParserFromCtx(ctx context.Context) *Parser { if v := ctx.Value(CtxKeyParser); v != nil { if p, ok := v.(*Parser); ok { return p } } return nil } // Parse creates and returns a new Parser with os.Args and supported options. // // Note that the parameter `supportedOptions` is as [option name: need argument], which means // the value item of `supportedOptions` indicates whether corresponding option name needs argument or not. // // The optional parameter `strict` specifies whether stops parsing and returns error if invalid option passed. func Parse(supportedOptions map[string]bool, option ...ParserOption) (*Parser, error) { if supportedOptions == nil { command.Init(os.Args...) return &Parser{ parsedArgs: GetArgAll(), parsedOptions: GetOptAll(), }, nil } return ParseArgs(os.Args, supportedOptions, option...) } // ParseArgs creates and returns a new Parser with given arguments and supported options. // // Note that the parameter `supportedOptions` is as [option name: need argument], which means // the value item of `supportedOptions` indicates whether corresponding option name needs argument or not. // // The optional parameter `strict` specifies whether stops parsing and returns error if invalid option passed. func ParseArgs(args []string, supportedOptions map[string]bool, option ...ParserOption) (*Parser, error) { if supportedOptions == nil { command.Init(args...) return &Parser{ parsedArgs: GetArgAll(), parsedOptions: GetOptAll(), }, nil } var parserOption ParserOption if len(option) > 0 { parserOption = option[0] } parser := &Parser{ option: parserOption, parsedArgs: make([]string, 0), parsedOptions: make(map[string]string), passedOptions: supportedOptions, supportedOptions: make(map[string]bool), commandFuncMap: make(map[string]func()), } for name, needArgument := range supportedOptions { for _, v := range strings.Split(name, ",") { parser.supportedOptions[strings.TrimSpace(v)] = needArgument } } for i := 0; i < len(args); { if option := parser.parseOption(args[i]); option != "" { array, _ := gregex.MatchString(`^(.+?)=(.+)$`, option) if len(array) == 3 { if parser.isOptionValid(array[1]) { parser.setOptionValue(array[1], array[2]) } } else { if parser.isOptionValid(option) { if parser.isOptionNeedArgument(option) { if i < len(args)-1 { parser.setOptionValue(option, args[i+1]) i += 2 continue } } else { parser.setOptionValue(option, "") i++ continue } } else { // Multiple options? if array = parser.parseMultiOption(option); len(array) > 0 { for _, v := range array { parser.setOptionValue(v, "") } i++ continue } else if parser.option.Strict { return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid option '%s'`, args[i]) } } } } else { parser.parsedArgs = append(parser.parsedArgs, args[i]) } i++ } return parser, nil } // parseMultiOption parses option to multiple valid options like: --dav. // It returns nil if given option is not multi-option. func (p *Parser) parseMultiOption(option string) []string { for i := 1; i <= len(option); i++ { s := option[:i] if p.isOptionValid(s) && !p.isOptionNeedArgument(s) { if i == len(option) { return []string{s} } array := p.parseMultiOption(option[i:]) if len(array) == 0 { return nil } return append(array, s) } } return nil } func (p *Parser) parseOption(argument string) string { array, _ := gregex.MatchString(`^\-{1,2}(.+)$`, argument) if len(array) == 2 { return array[1] } return "" } func (p *Parser) isOptionValid(name string) bool { // Case-Sensitive. if p.option.CaseSensitive { _, ok := p.supportedOptions[name] return ok } // Case-InSensitive. for optionName := range p.supportedOptions { if gstr.Equal(optionName, name) { return true } } return false } func (p *Parser) isOptionNeedArgument(name string) bool { return p.supportedOptions[name] } // setOptionValue sets the option value for name and according alias. func (p *Parser) setOptionValue(name, value string) { for optionName := range p.passedOptions { array := gstr.SplitAndTrim(optionName, ",") for _, v := range array { if strings.EqualFold(v, name) { for _, v := range array { p.parsedOptions[v] = value } return } } } } // GetOpt returns the option value named `name` as gvar.Var. func (p *Parser) GetOpt(name string, def ...interface{}) *gvar.Var { if v, ok := p.parsedOptions[name]; ok { return gvar.New(v) } if len(def) > 0 { return gvar.New(def[0]) } return nil } // GetOptAll returns all parsed options. func (p *Parser) GetOptAll() map[string]string { return p.parsedOptions } // GetArg returns the argument at `index` as gvar.Var. func (p *Parser) GetArg(index int, def ...string) *gvar.Var { if index >= 0 && index < len(p.parsedArgs) { return gvar.New(p.parsedArgs[index]) } if len(def) > 0 { return gvar.New(def[0]) } return nil } // GetArgAll returns all parsed arguments. func (p *Parser) GetArgAll() []string { return p.parsedArgs } // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (p Parser) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ "parsedArgs": p.parsedArgs, "parsedOptions": p.parsedOptions, "passedOptions": p.passedOptions, "supportedOptions": p.supportedOptions, }) }