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  }