github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/commands/cli/parse.go (about) 1 package cli 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path" 8 "runtime" 9 "strings" 10 11 cmds "github.com/ipfs/go-ipfs/commands" 12 files "github.com/ipfs/go-ipfs/commands/files" 13 u "github.com/ipfs/go-ipfs/util" 14 ) 15 16 // Parse parses the input commandline string (cmd, flags, and args). 17 // returns the corresponding command Request object. 18 func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *cmds.Command, []string, error) { 19 path, opts, stringVals, cmd, err := parseOpts(input, root) 20 if err != nil { 21 return nil, nil, path, err 22 } 23 24 optDefs, err := root.GetOptions(path) 25 if err != nil { 26 return nil, cmd, path, err 27 } 28 29 req, err := cmds.NewRequest(path, opts, nil, nil, cmd, optDefs) 30 if err != nil { 31 return nil, cmd, path, err 32 } 33 34 // if -r is provided, and it is associated with the package builtin 35 // recursive path option, allow recursive file paths 36 recursiveOpt := req.Option(cmds.RecShort) 37 recursive := false 38 if recursiveOpt != nil && recursiveOpt.Definition() == cmds.OptionRecursivePath { 39 recursive, _, err = recursiveOpt.Bool() 40 if err != nil { 41 return req, nil, nil, u.ErrCast() 42 } 43 } 44 45 stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive, root) 46 if err != nil { 47 return req, cmd, path, err 48 } 49 req.SetArguments(stringArgs) 50 51 file := files.NewSliceFile("", "", fileArgs) 52 req.SetFiles(file) 53 54 err = cmd.CheckArguments(req) 55 if err != nil { 56 return req, cmd, path, err 57 } 58 59 return req, cmd, path, nil 60 } 61 62 // Parse a command line made up of sub-commands, short arguments, long arguments and positional arguments 63 func parseOpts(args []string, root *cmds.Command) ( 64 path []string, 65 opts map[string]interface{}, 66 stringVals []string, 67 cmd *cmds.Command, 68 err error, 69 ) { 70 path = make([]string, 0, len(args)) 71 stringVals = make([]string, 0, len(args)) 72 optDefs := map[string]cmds.Option{} 73 opts = map[string]interface{}{} 74 cmd = root 75 76 // parseFlag checks that a flag is valid and saves it into opts 77 // Returns true if the optional second argument is used 78 parseFlag := func(name string, arg *string, mustUse bool) (bool, error) { 79 if _, ok := opts[name]; ok { 80 return false, fmt.Errorf("Duplicate values for option '%s'", name) 81 } 82 83 optDef, found := optDefs[name] 84 if !found { 85 err = fmt.Errorf("Unrecognized option '%s'", name) 86 return false, err 87 } 88 89 if optDef.Type() == cmds.Bool { 90 if mustUse { 91 return false, fmt.Errorf("Option '%s' takes no arguments, but was passed '%s'", name, *arg) 92 } 93 opts[name] = "" 94 return false, nil 95 } else { 96 if arg == nil { 97 return true, fmt.Errorf("Missing argument for option '%s'", name) 98 } 99 opts[name] = *arg 100 return true, nil 101 } 102 } 103 104 optDefs, err = root.GetOptions(path) 105 if err != nil { 106 return 107 } 108 109 consumed := false 110 for i, arg := range args { 111 switch { 112 case consumed: 113 // arg was already consumed by the preceding flag 114 consumed = false 115 continue 116 117 case arg == "--": 118 // treat all remaining arguments as positional arguments 119 stringVals = append(stringVals, args[i+1:]...) 120 return 121 122 case strings.HasPrefix(arg, "--"): 123 // arg is a long flag, with an optional argument specified 124 // using `=' or in args[i+1] 125 var slurped bool 126 var next *string 127 split := strings.SplitN(arg, "=", 2) 128 if len(split) == 2 { 129 slurped = false 130 arg = split[0] 131 next = &split[1] 132 } else { 133 slurped = true 134 if i+1 < len(args) { 135 next = &args[i+1] 136 } else { 137 next = nil 138 } 139 } 140 consumed, err = parseFlag(arg[2:], next, len(split) == 2) 141 if err != nil { 142 return 143 } 144 if !slurped { 145 consumed = false 146 } 147 148 case strings.HasPrefix(arg, "-") && arg != "-": 149 // args is one or more flags in short form, followed by an optional argument 150 // all flags except the last one have type bool 151 for arg = arg[1:]; len(arg) != 0; arg = arg[1:] { 152 var rest *string 153 var slurped bool 154 mustUse := false 155 if len(arg) > 1 { 156 slurped = false 157 str := arg[1:] 158 if len(str) > 0 && str[0] == '=' { 159 str = str[1:] 160 mustUse = true 161 } 162 rest = &str 163 } else { 164 slurped = true 165 if i+1 < len(args) { 166 rest = &args[i+1] 167 } else { 168 rest = nil 169 } 170 } 171 var end bool 172 end, err = parseFlag(arg[0:1], rest, mustUse) 173 if err != nil { 174 return 175 } 176 if end { 177 consumed = slurped 178 break 179 } 180 } 181 182 default: 183 // arg is a sub-command or a positional argument 184 sub := cmd.Subcommand(arg) 185 if sub != nil { 186 cmd = sub 187 path = append(path, arg) 188 optDefs, err = root.GetOptions(path) 189 if err != nil { 190 return 191 } 192 } else { 193 stringVals = append(stringVals, arg) 194 } 195 } 196 } 197 return 198 } 199 200 func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool, root *cmds.Command) ([]string, []files.File, error) { 201 // ignore stdin on Windows 202 if runtime.GOOS == "windows" { 203 stdin = nil 204 } 205 206 // check if stdin is coming from terminal or is being piped in 207 if stdin != nil { 208 if term, err := isTerminal(stdin); err != nil { 209 return nil, nil, err 210 } else if term { 211 stdin = nil // set to nil so we ignore it 212 } 213 } 214 215 // count required argument definitions 216 numRequired := 0 217 for _, argDef := range argDefs { 218 if argDef.Required { 219 numRequired++ 220 } 221 } 222 223 // count number of values provided by user. 224 // if there is at least one ArgDef, we can safely trigger the inputs loop 225 // below to parse stdin. 226 numInputs := len(inputs) 227 if len(argDefs) > 0 && argDefs[len(argDefs)-1].SupportsStdin && stdin != nil { 228 numInputs += 1 229 } 230 231 // if we have more arg values provided than argument definitions, 232 // and the last arg definition is not variadic (or there are no definitions), return an error 233 notVariadic := len(argDefs) == 0 || !argDefs[len(argDefs)-1].Variadic 234 if notVariadic && len(inputs) > len(argDefs) { 235 suggestions := suggestUnknownCmd(inputs, root) 236 237 if len(suggestions) > 1 { 238 return nil, nil, fmt.Errorf("Unknown Command \"%s\"\n\nDid you mean any of these?\n\n\t%s", inputs[0], strings.Join(suggestions, "\n\t")) 239 } else if len(suggestions) > 0 { 240 return nil, nil, fmt.Errorf("Unknown Command \"%s\"\n\nDid you mean this?\n\n\t%s", inputs[0], suggestions[0]) 241 } else { 242 return nil, nil, fmt.Errorf("Unknown Command \"%s\"\n", inputs[0]) 243 } 244 } 245 246 stringArgs := make([]string, 0, numInputs) 247 fileArgs := make([]files.File, 0, numInputs) 248 249 argDefIndex := 0 // the index of the current argument definition 250 for i := 0; i < numInputs; i++ { 251 argDef := getArgDef(argDefIndex, argDefs) 252 253 // skip optional argument definitions if there aren't sufficient remaining inputs 254 for numInputs-i <= numRequired && !argDef.Required { 255 argDefIndex++ 256 argDef = getArgDef(argDefIndex, argDefs) 257 } 258 if argDef.Required { 259 numRequired-- 260 } 261 262 var err error 263 if argDef.Type == cmds.ArgString { 264 if stdin == nil || !argDef.SupportsStdin { 265 // add string values 266 stringArgs, inputs = appendString(stringArgs, inputs) 267 268 } else { 269 if len(inputs) > 0 { 270 // don't use stdin if we have inputs 271 stdin = nil 272 } else { 273 // if we have a stdin, read it in and use the data as a string value 274 stringArgs, stdin, err = appendStdinAsString(stringArgs, stdin) 275 if err != nil { 276 return nil, nil, err 277 } 278 } 279 } 280 } else if argDef.Type == cmds.ArgFile { 281 if stdin == nil || !argDef.SupportsStdin { 282 // treat stringArg values as file paths 283 fileArgs, inputs, err = appendFile(fileArgs, inputs, argDef, recursive) 284 if err != nil { 285 return nil, nil, err 286 } 287 288 } else { 289 if len(inputs) > 0 { 290 // don't use stdin if we have inputs 291 stdin = nil 292 } else { 293 // if we have a stdin, create a file from it 294 fileArgs, stdin = appendStdinAsFile(fileArgs, stdin) 295 } 296 } 297 } 298 299 argDefIndex++ 300 } 301 302 // check to make sure we didn't miss any required arguments 303 if len(argDefs) > argDefIndex { 304 for _, argDef := range argDefs[argDefIndex:] { 305 if argDef.Required { 306 return nil, nil, fmt.Errorf("Argument '%s' is required", argDef.Name) 307 } 308 } 309 } 310 311 return stringArgs, fileArgs, nil 312 } 313 314 func getArgDef(i int, argDefs []cmds.Argument) *cmds.Argument { 315 if i < len(argDefs) { 316 // get the argument definition (usually just argDefs[i]) 317 return &argDefs[i] 318 319 } else if len(argDefs) > 0 { 320 // but if i > len(argDefs) we use the last argument definition) 321 return &argDefs[len(argDefs)-1] 322 } 323 324 // only happens if there aren't any definitions 325 return nil 326 } 327 328 func appendString(args, inputs []string) ([]string, []string) { 329 return append(args, inputs[0]), inputs[1:] 330 } 331 332 func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, error) { 333 buf := new(bytes.Buffer) 334 335 _, err := buf.ReadFrom(stdin) 336 if err != nil { 337 return nil, nil, err 338 } 339 340 input := strings.TrimSpace(buf.String()) 341 return append(args, strings.Split(input, "\n")...), nil, nil 342 } 343 344 func appendFile(args []files.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]files.File, []string, error) { 345 fpath := inputs[0] 346 347 if fpath == "." { 348 cwd, err := os.Getwd() 349 if err != nil { 350 return nil, nil, err 351 } 352 fpath = cwd 353 } 354 stat, err := os.Lstat(fpath) 355 if err != nil { 356 return nil, nil, err 357 } 358 359 if stat.IsDir() { 360 if !argDef.Recursive { 361 err = fmt.Errorf("Invalid path '%s', argument '%s' does not support directories", 362 fpath, argDef.Name) 363 return nil, nil, err 364 } 365 if !recursive { 366 err = fmt.Errorf("'%s' is a directory, use the '-%s' flag to specify directories", 367 fpath, cmds.RecShort) 368 return nil, nil, err 369 } 370 } 371 372 arg, err := files.NewSerialFile(path.Base(fpath), fpath, stat) 373 if err != nil { 374 return nil, nil, err 375 } 376 return append(args, arg), inputs[1:], nil 377 } 378 379 func appendStdinAsFile(args []files.File, stdin *os.File) ([]files.File, *os.File) { 380 arg := files.NewReaderFile("", "", stdin, nil) 381 return append(args, arg), nil 382 } 383 384 // isTerminal returns true if stdin is a Stdin pipe (e.g. `cat file | ipfs`), 385 // and false otherwise (e.g. nothing is being piped in, so stdin is 386 // coming from the terminal) 387 func isTerminal(stdin *os.File) (bool, error) { 388 stat, err := stdin.Stat() 389 if err != nil { 390 return false, err 391 } 392 393 // if stdin is a CharDevice, return true 394 return ((stat.Mode() & os.ModeCharDevice) != 0), nil 395 }