github.com/gogf/gf@v1.16.9/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  	"github.com/gogf/gf/errors/gcode"
    12  	"github.com/gogf/gf/errors/gerror"
    13  	"github.com/gogf/gf/internal/json"
    14  	"os"
    15  	"strings"
    16  
    17  	"github.com/gogf/gf/text/gstr"
    18  
    19  	"github.com/gogf/gf/container/gvar"
    20  
    21  	"github.com/gogf/gf/text/gregex"
    22  )
    23  
    24  // Parser for arguments.
    25  type Parser struct {
    26  	strict           bool              // Whether stops parsing and returns error if invalid option passed.
    27  	parsedArgs       []string          // As name described.
    28  	parsedOptions    map[string]string // As name described.
    29  	passedOptions    map[string]bool   // User passed supported options.
    30  	supportedOptions map[string]bool   // Option [option name : need argument].
    31  	commandFuncMap   map[string]func() // Command function map for function handler.
    32  }
    33  
    34  // Parse creates and returns a new Parser with os.Args and supported options.
    35  //
    36  // Note that the parameter <supportedOptions> is as [option name: need argument], which means
    37  // the value item of <supportedOptions> indicates whether corresponding option name needs argument or not.
    38  //
    39  // The optional parameter <strict> specifies whether stops parsing and returns error if invalid option passed.
    40  func Parse(supportedOptions map[string]bool, strict ...bool) (*Parser, error) {
    41  	return ParseWithArgs(os.Args, supportedOptions, strict...)
    42  }
    43  
    44  // ParseWithArgs creates and returns a new Parser with given arguments and supported options.
    45  //
    46  // Note that the parameter <supportedOptions> is as [option name: need argument], which means
    47  // the value item of <supportedOptions> indicates whether corresponding option name needs argument or not.
    48  //
    49  // The optional parameter <strict> specifies whether stops parsing and returns error if invalid option passed.
    50  func ParseWithArgs(args []string, supportedOptions map[string]bool, strict ...bool) (*Parser, error) {
    51  	strictParsing := false
    52  	if len(strict) > 0 {
    53  		strictParsing = strict[0]
    54  	}
    55  	parser := &Parser{
    56  		strict:           strictParsing,
    57  		parsedArgs:       make([]string, 0),
    58  		parsedOptions:    make(map[string]string),
    59  		passedOptions:    supportedOptions,
    60  		supportedOptions: make(map[string]bool),
    61  		commandFuncMap:   make(map[string]func()),
    62  	}
    63  	for name, needArgument := range supportedOptions {
    64  		for _, v := range strings.Split(name, ",") {
    65  			parser.supportedOptions[strings.TrimSpace(v)] = needArgument
    66  		}
    67  	}
    68  
    69  	for i := 0; i < len(args); {
    70  		if option := parser.parseOption(args[i]); option != "" {
    71  			array, _ := gregex.MatchString(`^(.+?)=(.+)$`, option)
    72  			if len(array) == 3 {
    73  				if parser.isOptionValid(array[1]) {
    74  					parser.setOptionValue(array[1], array[2])
    75  				}
    76  			} else {
    77  				if parser.isOptionValid(option) {
    78  					if parser.isOptionNeedArgument(option) {
    79  						if i < len(args)-1 {
    80  							parser.setOptionValue(option, args[i+1])
    81  							i += 2
    82  							continue
    83  						}
    84  					} else {
    85  						parser.setOptionValue(option, "")
    86  						i++
    87  						continue
    88  					}
    89  				} else {
    90  					// Multiple options?
    91  					if array := parser.parseMultiOption(option); len(array) > 0 {
    92  						for _, v := range array {
    93  							parser.setOptionValue(v, "")
    94  						}
    95  						i++
    96  						continue
    97  					} else if parser.strict {
    98  						return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid option '%s'`, args[i])
    99  					}
   100  				}
   101  			}
   102  		} else {
   103  			parser.parsedArgs = append(parser.parsedArgs, args[i])
   104  		}
   105  		i++
   106  	}
   107  	return parser, nil
   108  }
   109  
   110  // parseMultiOption parses option to multiple valid options like: --dav.
   111  // It returns nil if given option is not multi-option.
   112  func (p *Parser) parseMultiOption(option string) []string {
   113  	for i := 1; i <= len(option); i++ {
   114  		s := option[:i]
   115  		if p.isOptionValid(s) && !p.isOptionNeedArgument(s) {
   116  			if i == len(option) {
   117  				return []string{s}
   118  			}
   119  			array := p.parseMultiOption(option[i:])
   120  			if len(array) == 0 {
   121  				return nil
   122  			}
   123  			return append(array, s)
   124  		}
   125  	}
   126  	return nil
   127  }
   128  
   129  func (p *Parser) parseOption(argument string) string {
   130  	array, _ := gregex.MatchString(`^\-{1,2}(.+)$`, argument)
   131  	if len(array) == 2 {
   132  		return array[1]
   133  	}
   134  	return ""
   135  }
   136  
   137  func (p *Parser) isOptionValid(name string) bool {
   138  	_, ok := p.supportedOptions[name]
   139  	return ok
   140  }
   141  
   142  func (p *Parser) isOptionNeedArgument(name string) bool {
   143  	return p.supportedOptions[name]
   144  }
   145  
   146  // setOptionValue sets the option value for name and according alias.
   147  func (p *Parser) setOptionValue(name, value string) {
   148  	for optionName, _ := range p.passedOptions {
   149  		array := gstr.SplitAndTrim(optionName, ",")
   150  		for _, v := range array {
   151  			if strings.EqualFold(v, name) {
   152  				for _, v := range array {
   153  					p.parsedOptions[v] = value
   154  				}
   155  				return
   156  			}
   157  		}
   158  	}
   159  }
   160  
   161  // GetOpt returns the option value named <name>.
   162  func (p *Parser) GetOpt(name string, def ...string) string {
   163  	if v, ok := p.parsedOptions[name]; ok {
   164  		return v
   165  	}
   166  	if len(def) > 0 {
   167  		return def[0]
   168  	}
   169  	return ""
   170  }
   171  
   172  // GetOptVar returns the option value named <name> as gvar.Var.
   173  func (p *Parser) GetOptVar(name string, def ...interface{}) *gvar.Var {
   174  	if p.ContainsOpt(name) {
   175  		return gvar.New(p.GetOpt(name))
   176  	}
   177  	if len(def) > 0 {
   178  		return gvar.New(def[0])
   179  	}
   180  	return gvar.New(nil)
   181  }
   182  
   183  // GetOptAll returns all parsed options.
   184  func (p *Parser) GetOptAll() map[string]string {
   185  	return p.parsedOptions
   186  }
   187  
   188  // ContainsOpt checks whether option named <name> exist in the arguments.
   189  func (p *Parser) ContainsOpt(name string) bool {
   190  	_, ok := p.parsedOptions[name]
   191  	return ok
   192  }
   193  
   194  // GetArg returns the argument at <index>.
   195  func (p *Parser) GetArg(index int, def ...string) string {
   196  	if index < len(p.parsedArgs) {
   197  		return p.parsedArgs[index]
   198  	}
   199  	if len(def) > 0 {
   200  		return def[0]
   201  	}
   202  	return ""
   203  }
   204  
   205  // GetArgVar returns the argument at <index> as gvar.Var.
   206  func (p *Parser) GetArgVar(index int, def ...string) *gvar.Var {
   207  	return gvar.New(p.GetArg(index, def...))
   208  }
   209  
   210  // GetArgAll returns all parsed arguments.
   211  func (p *Parser) GetArgAll() []string {
   212  	return p.parsedArgs
   213  }
   214  
   215  // MarshalJSON implements the interface MarshalJSON for json.Marshal.
   216  func (p *Parser) MarshalJSON() ([]byte, error) {
   217  	return json.Marshal(map[string]interface{}{
   218  		"parsedArgs":       p.parsedArgs,
   219  		"parsedOptions":    p.parsedOptions,
   220  		"passedOptions":    p.passedOptions,
   221  		"supportedOptions": p.supportedOptions,
   222  	})
   223  }