diff --git a/farse/cmd/farse-test/main.go b/farse/cmd/farse-test/main.go new file mode 100644 index 0000000..687d9fc --- /dev/null +++ b/farse/cmd/farse-test/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "log" + "os" + + farse "../.." +) + +func main() { + fl := farse.New() + err := fl.AddKnown( + "-a", "-n", "-q", "-m", + "--azimuth", "--noodle", "--quince", + "whoop", "fizzle", "flim", + ) + if err != nil { + log.Fatal(err) + } + + fmt.Println("known:") + for key, tokType := range fl.Known() { + fmt.Printf(" %v: %s\n", key, tokType) + } + + fmt.Println("scanned:") + for _, tok := range fl.Scan(os.Args) { + fmt.Printf(" %v: %s\n", tok.Value, tok.Type) + } +} diff --git a/farse/farse.go b/farse/farse.go new file mode 100644 index 0000000..3ac46b6 --- /dev/null +++ b/farse/farse.go @@ -0,0 +1,181 @@ +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 +}