kevinschoon-pomo/vendor/github.com/jawher/mow.cli/spec_tk.go

224 lines
4.2 KiB
Go

package cli
import (
"strings"
"fmt"
)
type uTokenType string
const (
utPos uTokenType = "Pos"
utOpenPar uTokenType = "OpenPar"
utClosePar uTokenType = "ClosePar"
utOpenSq uTokenType = "OpenSq"
utCloseSq uTokenType = "CloseSq"
utChoice uTokenType = "Choice"
utOptions uTokenType = "Options"
utRep uTokenType = "Rep"
utShortOpt uTokenType = "ShortOpt"
utLongOpt uTokenType = "LongOpt"
utOptSeq uTokenType = "OptSeq"
utOptValue uTokenType = "OptValue"
utDoubleDash uTokenType = "DblDash"
)
type uToken struct {
typ uTokenType
val string
pos int
}
func (t *uToken) String() string {
return fmt.Sprintf("%s('%s')@%d", t.typ, t.val, t.pos)
}
type parseError struct {
input string
msg string
pos int
}
func (t *parseError) ident() string {
return strings.Map(func(c rune) rune {
switch c {
case '\t':
return c
default:
return ' '
}
}, t.input[:t.pos])
}
func (t *parseError) Error() string {
return fmt.Sprintf("Parse error at position %d:\n%s\n%s^ %s",
t.pos, t.input, t.ident(), t.msg)
}
func uTokenize(usage string) ([]*uToken, *parseError) {
pos := 0
res := []*uToken{}
var (
tk = func(t uTokenType, v string) {
res = append(res, &uToken{t, v, pos})
}
tkp = func(t uTokenType, v string, p int) {
res = append(res, &uToken{t, v, p})
}
err = func(msg string) *parseError {
return &parseError{usage, msg, pos}
}
)
eof := len(usage)
for pos < eof {
switch c := usage[pos]; c {
case ' ':
pos++
case '\t':
pos++
case '[':
tk(utOpenSq, "[")
pos++
case ']':
tk(utCloseSq, "]")
pos++
case '(':
tk(utOpenPar, "(")
pos++
case ')':
tk(utClosePar, ")")
pos++
case '|':
tk(utChoice, "|")
pos++
case '.':
start := pos
pos++
if pos >= eof || usage[pos] != '.' {
return nil, err("Unexpected end of usage, was expecting '..'")
}
pos++
if pos >= eof || usage[pos] != '.' {
return nil, err("Unexpected end of usage, was expecting '.'")
}
tkp(utRep, "...", start)
pos++
case '-':
start := pos
pos++
if pos >= eof {
return nil, err("Unexpected end of usage, was expecting an option name")
}
switch o := usage[pos]; {
case isLetter(o):
pos++
for ; pos < eof; pos++ {
ok := isLetter(usage[pos])
if !ok {
break
}
}
typ := utShortOpt
if pos-start > 2 {
typ = utOptSeq
start++
}
opt := usage[start:pos]
tkp(typ, opt, start)
if pos < eof && usage[pos] == '-' {
return nil, err("Invalid syntax")
}
case o == '-':
pos++
if pos == eof || usage[pos] == ' ' {
tkp(utDoubleDash, "--", start)
continue
}
for pos0 := pos; pos < eof; pos++ {
ok := isOkLongOpt(usage[pos], pos == pos0)
if !ok {
break
}
}
opt := usage[start:pos]
if len(opt) == 2 {
return nil, err("Was expecting a long option name")
}
tkp(utLongOpt, opt, start)
}
case '=':
start := pos
pos++
if pos >= eof || usage[pos] != '<' {
return nil, err("Unexpected end of usage, was expecting '=<'")
}
closed := false
for ; pos < eof; pos++ {
closed = usage[pos] == '>'
if closed {
break
}
}
if !closed {
return nil, err("Unclosed option value")
}
if pos-start == 2 {
return nil, err("Was expecting an option value")
}
pos++
value := usage[start:pos]
tkp(utOptValue, value, start)
default:
switch {
case isUppercase(c):
start := pos
for pos = pos + 1; pos < eof; pos++ {
if !isOkPos(usage[pos]) {
break
}
}
s := usage[start:pos]
typ := utPos
if s == "OPTIONS" {
typ = utOptions
}
tkp(typ, s, start)
default:
return nil, err("Unexpected input")
}
}
}
return res, nil
}
func isLowercase(c uint8) bool {
return c >= 'a' && c <= 'z'
}
func isUppercase(c uint8) bool {
return c >= 'A' && c <= 'Z'
}
func isOkPos(c uint8) bool {
return isUppercase(c) || isDigit(c) || c == '_'
}
func isLetter(c uint8) bool {
return isLowercase(c) || isUppercase(c)
}
func isDigit(c uint8) bool {
return c >= '0' && c <= '9'
}
func isOkLongOpt(c uint8, first bool) bool {
return isLetter(c) || isDigit(c) || c == '_' || (!first && c == '-')
}