You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
box-o-sand/farse/farse.go

182 lines
3.2 KiB

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
}