1package kong 2 3import ( 4 "fmt" 5 "strings" 6) 7 8// TokenType is the type of a token. 9type TokenType int 10 11// Token types. 12const ( 13 UntypedToken TokenType = iota 14 EOLToken 15 FlagToken // --<flag> 16 FlagValueToken // =<value> 17 ShortFlagToken // -<short>[<tail] 18 ShortFlagTailToken // <tail> 19 PositionalArgumentToken // <arg> 20) 21 22func (t TokenType) String() string { 23 switch t { 24 case UntypedToken: 25 return "untyped" 26 case EOLToken: 27 return "<EOL>" 28 case FlagToken: // --<flag> 29 return "long flag" 30 case FlagValueToken: // =<value> 31 return "flag value" 32 case ShortFlagToken: // -<short>[<tail] 33 return "short flag" 34 case ShortFlagTailToken: // <tail> 35 return "short flag remainder" 36 case PositionalArgumentToken: // <arg> 37 return "positional argument" 38 } 39 panic("unsupported type") 40} 41 42// Token created by Scanner. 43type Token struct { 44 Value interface{} 45 Type TokenType 46} 47 48func (t Token) String() string { 49 switch t.Type { 50 case FlagToken: 51 return fmt.Sprintf("--%v", t.Value) 52 53 case ShortFlagToken: 54 return fmt.Sprintf("-%v", t.Value) 55 56 case EOLToken: 57 return "EOL" 58 59 default: 60 return fmt.Sprintf("%v", t.Value) 61 } 62} 63 64// IsEOL returns true if this Token is past the end of the line. 65func (t Token) IsEOL() bool { 66 return t.Type == EOLToken 67} 68 69// IsAny returns true if the token's type is any of those provided. 70func (t TokenType) IsAny(types ...TokenType) bool { 71 for _, typ := range types { 72 if t == typ { 73 return true 74 } 75 } 76 return false 77} 78 79// InferredType tries to infer the type of a token. 80func (t Token) InferredType() TokenType { 81 if t.Type == UntypedToken { 82 if v, ok := t.Value.(string); ok { 83 if strings.HasPrefix(v, "--") { 84 return FlagToken 85 } else if strings.HasPrefix(v, "-") { 86 return ShortFlagToken 87 } 88 } 89 } 90 return t.Type 91} 92 93// IsValue returns true if token is usable as a parseable value. 94// 95// A parseable value is either a value typed token, or an untyped token NOT starting with a hyphen. 96func (t Token) IsValue() bool { 97 tt := t.InferredType() 98 return tt.IsAny(FlagValueToken, ShortFlagTailToken, PositionalArgumentToken) || 99 (tt == UntypedToken && !strings.HasPrefix(t.String(), "-")) 100} 101 102// Scanner is a stack-based scanner over command-line tokens. 103// 104// Initially all tokens are untyped. As the parser consumes tokens it assigns types, splits tokens, and pushes them back 105// onto the stream. 106// 107// For example, the token "--foo=bar" will be split into the following by the parser: 108// 109// [{FlagToken, "foo"}, {FlagValueToken, "bar"}] 110type Scanner struct { 111 args []Token 112} 113 114// Scan creates a new Scanner from args with untyped tokens. 115func Scan(args ...string) *Scanner { 116 s := &Scanner{} 117 for _, arg := range args { 118 s.args = append(s.args, Token{Value: arg}) 119 } 120 return s 121} 122 123// ScanFromTokens creates a new Scanner from a slice of tokens. 124func ScanFromTokens(tokens ...Token) *Scanner { 125 return &Scanner{args: tokens} 126} 127 128// Len returns the number of input arguments. 129func (s *Scanner) Len() int { 130 return len(s.args) 131} 132 133// Pop the front token off the Scanner. 134func (s *Scanner) Pop() Token { 135 if len(s.args) == 0 { 136 return Token{Type: EOLToken} 137 } 138 arg := s.args[0] 139 s.args = s.args[1:] 140 return arg 141} 142 143type expectedError struct { 144 context string 145 token Token 146} 147 148func (e *expectedError) Error() string { 149 return fmt.Sprintf("expected %s value but got %q (%s)", e.context, e.token, e.token.InferredType()) 150} 151 152// PopValue pops a value token, or returns an error. 153// 154// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>" 155func (s *Scanner) PopValue(context string) (Token, error) { 156 t := s.Pop() 157 if !t.IsValue() { 158 return t, &expectedError{context, t} 159 } 160 return t, nil 161} 162 163// PopValueInto pops a value token into target or returns an error. 164// 165// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>" 166func (s *Scanner) PopValueInto(context string, target interface{}) error { 167 t, err := s.PopValue(context) 168 if err != nil { 169 return err 170 } 171 return jsonTranscode(t.Value, target) 172} 173 174// PopWhile predicate returns true. 175func (s *Scanner) PopWhile(predicate func(Token) bool) (values []Token) { 176 for predicate(s.Peek()) { 177 values = append(values, s.Pop()) 178 } 179 return 180} 181 182// PopUntil predicate returns true. 183func (s *Scanner) PopUntil(predicate func(Token) bool) (values []Token) { 184 for !predicate(s.Peek()) { 185 values = append(values, s.Pop()) 186 } 187 return 188} 189 190// Peek at the next Token or return an EOLToken. 191func (s *Scanner) Peek() Token { 192 if len(s.args) == 0 { 193 return Token{Type: EOLToken} 194 } 195 return s.args[0] 196} 197 198// Push an untyped Token onto the front of the Scanner. 199func (s *Scanner) Push(arg interface{}) *Scanner { 200 s.PushToken(Token{Value: arg}) 201 return s 202} 203 204// PushTyped pushes a typed token onto the front of the Scanner. 205func (s *Scanner) PushTyped(arg interface{}, typ TokenType) *Scanner { 206 s.PushToken(Token{Value: arg, Type: typ}) 207 return s 208} 209 210// PushToken pushes a preconstructed Token onto the front of the Scanner. 211func (s *Scanner) PushToken(token Token) *Scanner { 212 s.args = append([]Token{token}, s.args...) 213 return s 214} 215