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 }