aboutsummaryrefslogtreecommitdiffstats
path: root/commands/parser.go
blob: e8146506f02f51c79fe00c5c2b8fc121a53c79d8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package commands

import (
	"strings"
)

type completionType int

const (
	NONE completionType = iota
	COMMAND
	OPERAND
	SHORT_OPTION
	OPTION_ARGUMENT
)

type parser struct {
	tokens []string
	optind int
	spec   string
	space  bool
	kind   completionType
	flag   string
	arg    string
	err    error
}

func newParser(cmd, spec string, spaceTerminated bool) (*parser, error) {
	args, err := splitCmd(cmd)
	if err != nil {
		return nil, err
	}

	p := &parser{
		tokens: args,
		optind: 0,
		spec:   spec,
		space:  spaceTerminated,
		kind:   NONE,
		flag:   "",
		arg:    "",
		err:    nil,
	}

	state := command
	for state != nil {
		state = state(p)
	}

	return p, p.err
}

func (p *parser) empty() bool {
	return len(p.tokens) == 0
}

func (p *parser) peek() string {
	return p.tokens[0]
}

func (p *parser) advance() string {
	if p.empty() {
		return ""
	}
	tok := p.tokens[0]
	p.tokens = p.tokens[1:]
	p.optind++
	return tok
}

func (p *parser) set(t completionType) {
	p.kind = t
}

func (p *parser) hasArgument() bool {
	n := len(p.flag)
	if n > 0 {
		s := string(p.flag[n-1]) + ":"
		return strings.Contains(p.spec, s)
	}
	return false
}

type stateFn func(*parser) stateFn

func command(p *parser) stateFn {
	p.set(COMMAND)
	p.advance()
	return peek(p)
}

func peek(p *parser) stateFn {
	if p.empty() {
		if p.space {
			return operand
		}
		return nil
	}
	if p.spec == "" {
		return operand
	}
	s := p.peek()
	switch {
	case s == "--":
		p.advance()
	case strings.HasPrefix(s, "-"):
		return short_option
	}
	return operand
}

func short_option(p *parser) stateFn {
	p.set(SHORT_OPTION)
	tok := p.advance()
	p.flag = tok[1:]
	if p.hasArgument() {
		return option_argument
	}
	return peek(p)
}

func option_argument(p *parser) stateFn {
	p.set(OPTION_ARGUMENT)
	p.arg = p.advance()
	if p.empty() && len(p.arg) == 0 {
		return nil
	}
	return peek(p)
}

func operand(p *parser) stateFn {
	p.set(OPERAND)
	return nil
}