summaryrefslogtreecommitdiff
path: root/internal/arg/arg.go
blob: 0577d7480e6e77ed1242ee881270168d0656289b (plain)
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
package arg

import "strings"

import "github.com/posener/complete/internal/tokener"

// Arg is typed a command line argument.
type Arg struct {
	Text      string
	Completed bool
	Parsed
}

// Parsed contains information about the argument.
type Parsed struct {
	Flag     string
	HasFlag  bool
	Value    string
	Dashes   string
	HasValue bool
}

// Parse parses a typed command line argument list, and returns a list of arguments.
func Parse(line string) []Arg {
	var args []Arg
	for {
		arg, after := next(line)
		if arg.Text != "" {
			args = append(args, arg)
		}
		line = after
		if line == "" {
			break
		}
	}
	return args
}

// next returns the first argument in the line and the rest of the line.
func next(line string) (arg Arg, after string) {
	defer arg.parse()
	// Start and end of the argument term.
	var start, end int
	// Stack of quote marks met during the paring of the argument.
	var token tokener.Tokener
	// Skip prefix spaces.
	for start = 0; start < len(line); start++ {
		token.Visit(line[start])
		if !token.LastSpace() {
			break
		}
	}
	// If line is only spaces, return empty argument and empty leftovers.
	if start == len(line) {
		return
	}

	for end = start + 1; end < len(line); end++ {
		token.Visit(line[end])
		if token.LastSpace() {
			arg.Completed = true
			break
		}
	}
	arg.Text = line[start:end]
	if !arg.Completed {
		return
	}
	start2 := end

	// Skip space after word.
	for start2 < len(line) {
		token.Visit(line[start2])
		if !token.LastSpace() {
			break
		}
		start2++
	}
	after = line[start2:]
	return
}

// parse a flag from an argument. The flag can have value attached when it is given in the
// `-key=value` format.
func (a *Arg) parse() {
	if len(a.Text) == 0 {
		return
	}

	// A pure value, no flag.
	if a.Text[0] != '-' {
		a.Value = a.Text
		a.HasValue = true
		return
	}

	// Seprate the dashes from the flag name.
	dahsI := 1
	if len(a.Text) > 1 && a.Text[1] == '-' {
		dahsI = 2
	}
	a.Dashes = a.Text[:dahsI]
	a.HasFlag = true
	a.Flag = a.Text[dahsI:]

	// Empty flag
	if a.Flag == "" {
		return
	}
	// Third dash or empty flag with equal is forbidden.
	if a.Flag[0] == '-' || a.Flag[0] == '=' {
		a.Parsed = Parsed{}
		return
	}
	// The flag is valid.

	// Check if flag has a value.
	if equal := strings.IndexRune(a.Flag, '='); equal != -1 {
		a.Flag, a.Value = a.Flag[:equal], a.Flag[equal+1:]
		a.HasValue = true
		return
	}

}