182 lines
3.2 KiB
Go
182 lines
3.2 KiB
Go
package farse
|
|
|
|
import "fmt"
|
|
|
|
type TokenType int
|
|
|
|
const (
|
|
TokenLongFlag TokenType = iota
|
|
TokenShortFlag
|
|
TokenShortCompoundFlag
|
|
TokenIdentifier
|
|
TokenArgument
|
|
TokenInvalid
|
|
TokenSentinel
|
|
)
|
|
|
|
func (t TokenType) String() string {
|
|
switch t {
|
|
case TokenLongFlag:
|
|
return "long flag"
|
|
case TokenShortFlag:
|
|
return "short flag"
|
|
case TokenShortCompoundFlag:
|
|
return "short compound"
|
|
case TokenIdentifier:
|
|
return "identifier"
|
|
case TokenArgument:
|
|
return "argument"
|
|
case TokenInvalid:
|
|
return "invalid"
|
|
case TokenSentinel:
|
|
return "sentinel"
|
|
}
|
|
return "(x___x)"
|
|
}
|
|
|
|
type Token struct {
|
|
Value string
|
|
Type TokenType
|
|
}
|
|
|
|
type Farse struct {
|
|
known map[string]TokenType
|
|
}
|
|
|
|
func New() *Farse {
|
|
return &Farse{
|
|
known: map[string]TokenType{},
|
|
}
|
|
}
|
|
|
|
func (fl *Farse) Known() map[string]TokenType {
|
|
return fl.known
|
|
}
|
|
|
|
func (fl *Farse) AddKnown(knownStrings ...string) error {
|
|
for i, tok := range scan(knownStrings) {
|
|
if tok.Type == TokenShortCompoundFlag {
|
|
return fmt.Errorf("argument %d %q is invalid value for known identifier", i, tok.Value)
|
|
}
|
|
|
|
if len(tok.Value) == 0 {
|
|
return fmt.Errorf("argument %d is empty", i)
|
|
}
|
|
|
|
if len(tok.Value) == 1 && tok.Value == "-" {
|
|
return fmt.Errorf("argument %d %q is invalid value for known identifier", i, tok.Value)
|
|
}
|
|
|
|
if _, exists := fl.known[tok.Value]; exists {
|
|
return fmt.Errorf("argument %d %s conflicts with known", i, tok.Value)
|
|
}
|
|
|
|
fl.known[tok.Value] = tok.Type
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fl *Farse) Scan(args []string) []*Token {
|
|
ret := []*Token{}
|
|
passedSentinel := false
|
|
|
|
for _, tok := range scan(args) {
|
|
if tok.Type == TokenSentinel {
|
|
ret = append(ret, tok)
|
|
passedSentinel = true
|
|
continue
|
|
}
|
|
|
|
if passedSentinel {
|
|
tok.Type = TokenArgument
|
|
ret = append(ret, tok)
|
|
continue
|
|
}
|
|
|
|
if tok.Type == TokenShortCompoundFlag {
|
|
ret = append(ret, fl.extractCompoundTokens(tok.Value)...)
|
|
continue
|
|
}
|
|
|
|
if tok.Type == TokenIdentifier {
|
|
if _, known := fl.known[tok.Value]; !known {
|
|
tok.Type = TokenArgument
|
|
}
|
|
ret = append(ret, tok)
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func (fl *Farse) extractCompoundTokens(s string) []*Token {
|
|
ret := []*Token{}
|
|
|
|
for j, ch := range s {
|
|
last := j == (len(s) - 1)
|
|
|
|
if j == 0 && ch == '-' {
|
|
continue
|
|
}
|
|
|
|
sf := string(append([]rune("-"), ch))
|
|
|
|
if _, known := fl.known[sf]; known {
|
|
ret = append(ret, &Token{Value: sf, Type: TokenShortFlag})
|
|
|
|
if last {
|
|
return ret
|
|
}
|
|
|
|
if _, nextKnown := fl.known[string(append([]rune("-"), rune(s[j+1])))]; !nextKnown {
|
|
ret = append(ret, &Token{Value: s[j+1:], Type: TokenArgument})
|
|
return ret
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func scan(sl []string) []*Token {
|
|
ret := []*Token{}
|
|
|
|
for _, s := range sl {
|
|
tok := scanToken(s)
|
|
if tok != nil {
|
|
ret = append(ret, tok)
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func scanToken(s string) *Token {
|
|
switch len(s) {
|
|
case 0, 1:
|
|
return &Token{Value: s, Type: TokenIdentifier}
|
|
case 2:
|
|
if s == "--" {
|
|
return &Token{Value: s, Type: TokenSentinel}
|
|
}
|
|
|
|
if s[0] == '-' {
|
|
return &Token{Value: s, Type: TokenShortFlag}
|
|
}
|
|
default:
|
|
if s[0] == '-' {
|
|
if s[1] != '-' {
|
|
return &Token{Value: s, Type: TokenShortCompoundFlag}
|
|
}
|
|
|
|
return &Token{Value: s, Type: TokenLongFlag}
|
|
}
|
|
|
|
return &Token{Value: s, Type: TokenIdentifier}
|
|
}
|
|
|
|
return nil
|
|
}
|