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 // ---------------------------------------------------------------------------