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