cli/parser.go (141 lines of code) (raw):

// Copyright (c) 2009-present, Alibaba Cloud All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "strings" ) type flagDetector interface { detectFlag(name string) (*Flag, error) detectFlagByShorthand(ch rune) (*Flag, error) } type Parser struct { current int args []string detector flagDetector currentFlag *Flag } func NewParser(args []string, detector flagDetector) *Parser { return &Parser{ args: args, current: 0, detector: detector, currentFlag: nil, } } func (p *Parser) ReadNextArg() (arg string, more bool, err error) { for { arg, _, more, err = p.readNext() if err != nil { return } if !more { return } if arg != "" { return } } } func (p *Parser) GetRemains() []string { return p.args[p.current:] } func (p *Parser) GetCurrent() int { return p.current } func (p *Parser) ReadAll() ([]string, error) { r := make([]string, 0) for { arg, _, more, err := p.readNext() if err != nil { return r, err } if arg != "" { r = append(r, arg) } if !more { return r, nil } } } func (p *Parser) readNext() (arg string, flag *Flag, more bool, err error) { if p.current >= len(p.args) { more = false return } s := p.args[p.current] p.current++ more = true value := "" flag, value, err = p.parseCommandArg(s) if err != nil { return } if flag != nil { err = flag.setIsAssigned() if err != nil { return } } // fmt.Printf(">>> current=%v\n flag=%v\n value=%s\n err=%s\n", p.currentFlag, flag, value, err) if flag == nil { // parse with value xxxx if p.currentFlag != nil { // value need to feed to previous flag xxx err = p.currentFlag.assign(value) if err != nil { return } if !p.currentFlag.needValue() { // if current flag is feeds close it // fmt.Printf("$$$ clear %s\n", p.currentFlag.AssignedMode) p.currentFlag = nil } } else { arg = value // this is a arg } } else { // parse with flag --xxx or -x if p.currentFlag != nil { err = p.currentFlag.validate() if err != nil { return } p.currentFlag = nil } if value != "" { // pattern --xx=aa, -x:aa, -xxx=bb err = flag.assign(value) if err != nil { return } } else { // pattern --xx -- yy if flag.needValue() { p.currentFlag = flag } } } return } func (p *Parser) parseCommandArg(s string) (flag *Flag, value string, err error) { prefix, v, ok := SplitStringWithPrefix(s, "=:") if ok { value = v } if strings.HasPrefix(prefix, "-") { // 特殊处理 SSL 证书等以多个连字符开头的情况 if strings.HasPrefix(prefix, "---") { // 对于以三个或更多连字符开头的参数,视为值而非 flag value = s } else if strings.HasPrefix(prefix, "--") { // 恰好以两个连字符开头的处理为长格式 flag if len(prefix) > 2 { flag, err = p.detector.detectFlag(prefix[2:]) } else { err = fmt.Errorf("not support '--' in command line") } } else if len(prefix) == 2 { // 对于 -x 格式的短 flag flag, err = p.detector.detectFlagByShorthand(rune(prefix[1])) } else { err = fmt.Errorf("not support flag form %s", prefix) } } else { value = s } return } // SplitStringWithPrefix TODO can use function string.SplitN to replace func SplitStringWithPrefix(s string, splitters string) (string, string, bool) { i := strings.IndexAny(s, splitters) if i < 0 { return s, "", false } return s[:i], s[i+1:], true }