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
}