@cryptotaxi247 / kubo / commits / 00b19f887

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 -}