commands/cli: Take an optional Stdin value in Parse (read as a reader argument or string argument)
Matt Bell committed
Nov 13, 2014 at 21:05 UTC
00b19f88761a4267cd9a96adb49655bb762d8906
1 file changed
+64
-38
commands/cli/parse.go
+64
-38
@@ -1,6 +1,7 @@
1
package cli
2
3
import (
4
+ "bytes"
5
"errors"
6
"fmt"
7
"os"
@@ -15,7 +16,7 @@ var ErrInvalidSubcmd = errors.New("subcommand not found")
16
// Parse parses the input commandline string (cmd, flags, and args).
17
// returns the corresponding command Request object.
18
// Parse will search each root to find the one that best matches the requested subcommand.
18
-func Parse(input []string, root *cmds.Command) (cmds.Request, *cmds.Command, []string, error) {
19
+func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *cmds.Command, []string, error) {
20
// use the root that matches the longest path (most accurately matches request)
21
path, input, cmd := parsePath(input, root)
22
opts, stringArgs, err := parseOptions(input)
@@ -27,7 +28,7 @@ func Parse(input []string, root *cmds.Command) (cmds.Request, *cmds.Command, []s
28
return nil, nil, path, ErrInvalidSubcmd
29
}
30
30
- args, err := parseArgs(stringArgs, cmd.Arguments)
31
+ args, err := parseArgs(stringArgs, stdin, cmd.Arguments)
32
if err != nil {
33
return nil, cmd, path, err
34
}
@@ -116,7 +117,21 @@ func parseOptions(input []string) (map[string]interface{}, []string, error) {
117
return opts, args, nil
118
}
119
119
-func parseArgs(stringArgs []string, arguments []cmds.Argument) ([]interface{}, error) {
120
+func parseArgs(stringArgs []string, stdin *os.File, arguments []cmds.Argument) ([]interface{}, error) {
121
+ // check if stdin is coming from terminal or is being piped in
122
+ if stdin != nil {
123
+ stat, err := stdin.Stat()
124
+ if err != nil {
125
+ return nil, err
126
+ }
127
+
128
+ // if stdin isn't a CharDevice, set it to nil
129
+ // (this means it is coming from terminal and we want to ignore it)
130
+ if (stat.Mode() & os.ModeCharDevice) != 0 {
131
+ stdin = nil
132
+ }
133
+ }
134
+
135
// count required argument definitions
136
lenRequired := 0
137
for _, argDef := range arguments {
@@ -125,56 +140,67 @@ func parseArgs(stringArgs []string, arguments []cmds.Argument) ([]interface{}, e
140
}
141
}
142
128
- args := make([]interface{}, len(stringArgs))
143
+ valCount := len(stringArgs)
144
+ if stdin != nil {
145
+ valCount += 1
146
+ }
147
+
148
+ args := make([]interface{}, 0, valCount)
149
+
150
+ argDefIndex := 0 // the index of the current argument definition
151
+ for i := 0; i < valCount; i++ {
152
+ // get the argument definiton (should be arguments[argDefIndex],
153
+ // but if argDefIndex > len(arguments) we use the last argument definition)
154
+ var argDef cmds.Argument
155
+ if argDefIndex < len(arguments) {
156
+ argDef = arguments[argDefIndex]
157
+ } else {
158
+ argDef = arguments[len(arguments)-1]
159
+ }
160
130
- valueIndex := 0 // the index of the current stringArgs value
131
- for _, argDef := range arguments {
161
// skip optional argument definitions if there aren't sufficient remaining values
133
- if len(stringArgs)-valueIndex <= lenRequired && !argDef.Required {
162
+ if valCount-i <= lenRequired && !argDef.Required {
163
continue
164
} else if argDef.Required {
165
lenRequired--
166
}
167
139
- if valueIndex >= len(stringArgs) {
140
- break
141
- }
168
+ if argDef.Type == cmds.ArgString {
169
+ if stdin == nil {
170
+ // add string values
171
+ args = append(args, stringArgs[0])
172
+ stringArgs = stringArgs[1:]
173
143
- if argDef.Variadic {
144
- for _, arg := range stringArgs[valueIndex:] {
145
- value, err := argValue(argDef, arg)
174
+ } else {
175
+ // if we have a stdin, read it in and use the data as a string value
176
+ var buf bytes.Buffer
177
+ _, err := buf.ReadFrom(stdin)
178
if err != nil {
179
return nil, err
180
}
149
- args[valueIndex] = value
150
- valueIndex++
181
+ args = append(args, buf.String())
182
+ stdin = nil
183
}
152
- } else {
153
- var err error
154
- value, err := argValue(argDef, stringArgs[valueIndex])
155
- if err != nil {
156
- return nil, err
184
+
185
+ } else if argDef.Type == cmds.ArgFile {
186
+ if stdin == nil {
187
+ // treat stringArg values as file paths
188
+ file, err := os.Open(stringArgs[0])
189
+ if err != nil {
190
+ return nil, err
191
+ }
192
+ args = append(args, file)
193
+ stringArgs = stringArgs[1:]
194
+
195
+ } else {
196
+ // if we have a stdin, use that as a reader
197
+ args = append(args, stdin)
198
+ stdin = nil
199
}
158
- args[valueIndex] = value
159
- valueIndex++
200
}
201
+
202
+ argDefIndex++
203
}
204
205
return args, nil
206
}
165
-
166
-func argValue(argDef cmds.Argument, value string) (interface{}, error) {
167
- if argDef.Type == cmds.ArgString {
168
- return value, nil
169
-
170
- } else {
171
- // NB At the time of this commit, file cleanup is performed when
172
- // Requests are cleaned up. TODO try to perform open and close at the
173
- // same level of abstraction (or at least in the same package!)
174
- in, err := os.Open(value)
175
- if err != nil {
176
- return nil, err
177
- }
178
- return in, nil
179
- }
180
-}