github.com/qiniu/x@v1.11.9/cmdline/cmdline.go (about)

     1  package cmdline
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  
     7  	. "github.com/qiniu/x/ctype"
     8  )
     9  
    10  /* ---------------------------------------------------------------------------
    11  
    12  Shell 基础规则:
    13  
    14  * 多行字符串:用 '...' 或 "..."。其中 " 会自动转义 $(var),而 ' 不会。
    15  * 普通字符串:用 [ \t] 分隔。转义符以 \ 开头。
    16  * 外部命令:`...`。
    17  
    18  七牛规则:
    19  
    20  * 外部命令: `...` 或 |...| 。
    21  * 多行字符串:用 '...' 或 ```\n...``` 或 ===\n...=== 不转义。用 "...",支持以 \ 开头的转义,也支持外部命令。
    22  * 普通字符串:用 [ \t] 分隔。转义符以 \ 开头,同时也支持外部命令。
    23  * 关于 $(var) 支持:每个命令自己执行 $(var) 的展开。不统一执行的原因是,在不同上下文需要不同的转义方式。
    24  
    25  样例:
    26  
    27  post http://rs.qiniu.com/delete/`base64 Bucket:Key`
    28  auth `qbox AccessKey SecretKey`
    29  ret  200
    30  
    31  post http://rs.qiniu.com/batch
    32  auth qboxtest
    33  form op=/delete/`base64 Bucket:Key`&op=/delete/`base64 Bucket2:Key2`
    34  ret  200
    35  
    36  post http://rs.qiniu.com/batch
    37  auth qboxtest
    38  form op=/delete/`base64 Bucket:Key`&op=/delete/`base64 Bucket:NotExistKey`
    39  ret  298
    40  json '[
    41  	{"code": 200}, {"code": 612}
    42  ]'
    43  equal $(code1) 200
    44  
    45  // -------------------------------------------------------------------------*/
    46  
    47  var (
    48  	EOF                               = errors.New("end of file")
    49  	ErrUnsupportedFeatureSubCmd       = errors.New("unsupported feature: sub command")
    50  	ErrUnsupportedFeatureMultiCmds    = errors.New("unsupported feature: multi commands")
    51  	ErrInvalidEscapeChar              = errors.New("invalid escape char")
    52  	ErrIncompleteStringExpectQuot     = errors.New("incomplete string, expect \"")
    53  	ErrIncompleteStringExpectSquot    = errors.New("incomplete string, expect '")
    54  	ErrIncompleteStringExpectBacktick = errors.New("incomplete string, expect ` or |")
    55  )
    56  
    57  var (
    58  	errEOL = errors.New("end of line")
    59  )
    60  
    61  // ---------------------------------------------------------------------------
    62  
    63  func Skip(str string, typeMask uint32) string {
    64  
    65  	for i := 0; i < len(str); i++ {
    66  		if !Is(typeMask, rune(str[i])) {
    67  			return str[i:]
    68  		}
    69  	}
    70  	return ""
    71  }
    72  
    73  func Find(str string, typeMask uint32) (n int) {
    74  
    75  	for n = 0; n < len(str); n++ {
    76  		if Is(typeMask, rune(str[n])) {
    77  			break
    78  		}
    79  	}
    80  	return
    81  }
    82  
    83  // ---------------------------------------------------------------------------
    84  
    85  // EOL = \r\n? | \n
    86  //
    87  func requireEOL(code string) (hasEOL bool, codeNext string) {
    88  
    89  	if strings.HasPrefix(code, "\r") {
    90  		if strings.HasPrefix(code[1:], "\n") {
    91  			return true, code[2:]
    92  		}
    93  	} else if !strings.HasPrefix(code, "\n") {
    94  		return false, code
    95  	}
    96  	return true, code[1:]
    97  }
    98  
    99  // ---------------------------------------------------------------------------
   100  
   101  type Parser struct {
   102  	ExecSub func(code string) (string, error)
   103  	Escape  func(c byte) string
   104  	comment bool
   105  }
   106  
   107  func NewParser() *Parser {
   108  
   109  	return &Parser{
   110  		ExecSub: defaultExecSub,
   111  		Escape:  defaultEscape,
   112  	}
   113  }
   114  
   115  func defaultExecSub(code string) (string, error) {
   116  	return "", ErrUnsupportedFeatureSubCmd
   117  }
   118  
   119  // ---------------------------------------------------------------------------
   120  
   121  const (
   122  	endOfLine    = EOL | SEMICOLON // [\r\n;]
   123  	blanks       = SPACE_BAR | TAB
   124  	blankAndEOLs = SPACE_BAR | TAB | endOfLine
   125  )
   126  
   127  const (
   128  	endMask_QuotString    = RDIV | BACKTICK | OR | QUOT         // [\\`|"]
   129  	endMask_NonquotString = RDIV | BACKTICK | OR | blankAndEOLs // [\\`| \t\r\n;]
   130  )
   131  
   132  func (p *Parser) parseString(
   133  	code string, endMask uint32) (item string, ok bool, codeNext string, err error) {
   134  
   135  	codeNext = code
   136  	for {
   137  		n := Find(codeNext, endMask)
   138  		if n > 0 {
   139  			item += codeNext[:n]
   140  			ok = true
   141  		}
   142  		if len(codeNext) == n {
   143  			codeNext = ""
   144  			if endMask == endMask_QuotString {
   145  				err = ErrIncompleteStringExpectQuot
   146  			} else {
   147  				err = EOF
   148  			}
   149  			return
   150  		}
   151  		switch codeNext[n] {
   152  		case '\\':
   153  			if len(codeNext) == n+1 {
   154  				err = ErrInvalidEscapeChar
   155  				return
   156  			}
   157  			item += p.Escape(codeNext[n+1])
   158  			codeNext = codeNext[n+2:]
   159  		case '`', '|':
   160  			c := codeNext[n]
   161  			codeNext = codeNext[n+1:]
   162  			len := strings.IndexByte(codeNext, c)
   163  			if len < 0 {
   164  				err = ErrIncompleteStringExpectBacktick
   165  				return
   166  			}
   167  			if !p.comment {
   168  				valSub, errSub := p.ExecSub(codeNext[:len])
   169  				if errSub != nil {
   170  					err = errors.New("Exec `" + codeNext[:len] + "` failed: " + errSub.Error())
   171  					return
   172  				}
   173  				item += valSub
   174  			}
   175  			codeNext = codeNext[len+1:]
   176  		case '"':
   177  			ok = true
   178  			codeNext = codeNext[n+1:]
   179  			return
   180  		default:
   181  			if Is(endOfLine, rune(codeNext[n])) {
   182  				err = errEOL
   183  			}
   184  			codeNext = codeNext[n+1:]
   185  			return
   186  		}
   187  		ok = true
   188  	}
   189  	return
   190  }
   191  
   192  func (p *Parser) parseItem(
   193  	code string, skipMask uint32) (item string, ok bool, codeNext string, err error) {
   194  
   195  	codeNext = Skip(code, skipMask)
   196  	if len(codeNext) == 0 {
   197  		err = EOF
   198  		return
   199  	}
   200  
   201  	switch codeNext[0] {
   202  	case '"':
   203  		return p.parseString(codeNext[1:], endMask_QuotString)
   204  	case '\'':
   205  		codeNext = codeNext[1:]
   206  		len := strings.IndexByte(codeNext, '\'')
   207  		if len < 0 {
   208  			err = ErrIncompleteStringExpectSquot
   209  			return
   210  		}
   211  		return codeNext[:len], true, codeNext[len+1:], nil
   212  	default:
   213  		if strings.HasPrefix(codeNext, "```") || strings.HasPrefix(codeNext, "===") {
   214  			endMark := codeNext[:3]
   215  			_, codeNext = requireEOL(codeNext[3:])
   216  			len := strings.Index(codeNext, endMark)
   217  			if len < 0 {
   218  				err = errors.New("incomplete string, expect " + endMark)
   219  				return
   220  			}
   221  			return codeNext[:len], true, codeNext[len+3:], nil
   222  		}
   223  		return p.parseString(codeNext, endMask_NonquotString)
   224  	}
   225  }
   226  
   227  func (p *Parser) ParseCmd(cmdline string) (cmd []string, err error) {
   228  
   229  	cmd, _, err = p.ParseCode(cmdline)
   230  	if err == EOF && len(cmd) > 0 {
   231  		return cmd, nil
   232  	}
   233  	if err == nil {
   234  		err = ErrUnsupportedFeatureMultiCmds
   235  	}
   236  	return
   237  }
   238  
   239  func (p *Parser) ParseCode(code string) (cmd []string, codeNext string, err error) {
   240  
   241  	item, ok, codeNext, err := p.parseItem(code, blankAndEOLs)
   242  	if !ok {
   243  		return
   244  	}
   245  	p.comment = strings.HasPrefix(item, "#")
   246  
   247  	cmd = append(cmd, item)
   248  	for err == nil {
   249  		item, ok, codeNext, err = p.parseItem(codeNext, blanks)
   250  		if ok {
   251  			cmd = append(cmd, item)
   252  		}
   253  	}
   254  	if err == errEOL {
   255  		err = nil
   256  	}
   257  	if p.comment {
   258  		cmd = nil
   259  	}
   260  	return
   261  }
   262  
   263  // ---------------------------------------------------------------------------