parse.go 13.9 KB
Newer Older
1 2 3
package cli

import (
4
	"context"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5
	"fmt"
6
	"io"
jmank88's avatar
jmank88 committed
7
	"net/url"
8
	"os"
9
	"path"
10
	"path/filepath"
11
	"reflect"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
12
	"strings"
13

Steven Allen's avatar
Steven Allen committed
14
	osh "github.com/Kubuxu/go-os-helper"
15 16
	cmds "github.com/ipfs/go-ipfs-cmds"
	files "github.com/ipfs/go-ipfs-files"
Steven Allen's avatar
Steven Allen committed
17
	logging "github.com/ipfs/go-log"
18 19
)

Jan Winkelmann's avatar
Jan Winkelmann committed
20
var log = logging.Logger("cmds/cli")
Dominic Della Valle's avatar
Dominic Della Valle committed
21 22 23 24 25 26 27
var msgStdinInfo = "ipfs: Reading from %s; send Ctrl-d to stop."

func init() {
	if osh.IsWindows() {
		msgStdinInfo = "ipfs: Reading from %s; send Ctrl-z to stop."
	}
}
28

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
29 30
// Parse parses the input commandline string (cmd, flags, and args).
// returns the corresponding command Request object.
31 32
//
// This function never returns nil, even on error.
33
func Parse(ctx context.Context, input []string, stdin *os.File, root *cmds.Command) (*cmds.Request, error) {
34 35 36
	req := &cmds.Request{Context: ctx}

	if err := parse(req, input, root); err != nil {
37
		return req, err
38 39
	}

40 41 42
	if err := req.FillDefaults(); err != nil {
		return req, err
	}
43

44
	if err := parseArgs(req, root, stdin); err != nil {
45
		return req, err
46
	}
47

48 49 50 51 52 53 54
	// if no encoding was specified by user, default to plaintext encoding
	// (if command doesn't support plaintext, use JSON instead)
	if enc := req.Options[cmds.EncLong]; enc == "" {
		if req.Command.Encoders != nil && req.Command.Encoders[cmds.Text] != nil {
			req.SetOption(cmds.EncLong, cmds.Text)
		} else {
			req.SetOption(cmds.EncLong, cmds.JSON)
55 56 57
		}
	}

58
	return req, nil
59 60
}

61
func isHidden(req *cmds.Request) bool {
Łukasz Magiera's avatar
Łukasz Magiera committed
62
	h, ok := req.Options[cmds.Hidden].(bool)
63 64
	return h && ok
}
65

66 67 68 69
func isRecursive(req *cmds.Request) bool {
	rec, ok := req.Options[cmds.RecLong].(bool)
	return rec && ok
}
70

71 72 73 74 75 76 77 78 79 80
func getIgnoreRulesFile(req *cmds.Request) string {
	rulesFile, _ := req.Options[cmds.IgnoreRules].(string)
	return rulesFile
}

func getIgnoreRules(req *cmds.Request) []string {
	rules, _ := req.Options[cmds.Ignore].([]string)
	return rules
}

81 82 83 84 85
func stdinName(req *cmds.Request) string {
	name, _ := req.Options[cmds.StdinName].(string)
	return name
}

86 87 88 89 90 91 92 93 94 95 96 97 98
type parseState struct {
	cmdline []string
	i       int
}

func (st *parseState) done() bool {
	return st.i >= len(st.cmdline)
}

func (st *parseState) peek() string {
	return st.cmdline[st.i]
}

99 100 101 102 103 104 105 106 107 108 109 110 111
func setOpts(kv kv, kvType reflect.Kind, opts cmds.OptMap) error {

	if kvType == cmds.Strings {
		res, _ := opts[kv.Key].([]string)
		opts[kv.Key] = append(res, kv.Value.(string))
	} else if _, exists := opts[kv.Key]; !exists {
		opts[kv.Key] = kv.Value
	} else {
		return fmt.Errorf("multiple values for option %q", kv.Key)
	}
	return nil
}

112
func parse(req *cmds.Request, cmdline []string, root *cmds.Command) (err error) {
113 114 115
	var (
		path = make([]string, 0, len(cmdline))
		args = make([]string, 0, len(cmdline))
Steven Allen's avatar
Steven Allen committed
116
		opts = cmds.OptMap{}
117 118 119
		cmd  = root
	)

120 121
	st := &parseState{cmdline: cmdline}

122 123
	// get root options
	optDefs, err := root.GetOptions([]string{})
124 125 126
	if err != nil {
		return err
	}
127

128 129
L:
	// don't range so we can seek
130 131
	for !st.done() {
		param := st.peek()
Etienne Laurin's avatar
Etienne Laurin committed
132
		switch {
133 134
		case param == "--":
			// use the rest as positional arguments
135
			args = append(args, st.cmdline[st.i+1:]...)
136 137 138
			break L
		case strings.HasPrefix(param, "--"):
			// long option
139 140 141 142
			k, v, err := st.parseLongOpt(optDefs)
			if err != nil {
				return err
			}
143 144 145 146 147 148
			kvType, err := getOptType(k, optDefs)
			if err != nil {
				return err // shouldn't happen b/c k,v was parsed from optsDef
			}
			if err := setOpts(kv{Key: k, Value: v}, kvType, opts); err != nil {
				return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
149
			}
150

151 152
		case strings.HasPrefix(param, "-") && param != "-":
			// short options
153 154 155 156
			kvs, err := st.parseShortOpts(optDefs)
			if err != nil {
				return err
			}
157 158 159 160

			for _, kv := range kvs {
				kv.Key = optDefs[kv.Key].Names()[0]

161 162 163 164 165 166
				kvType, err := getOptType(kv.Key, optDefs)
				if err != nil {
					return err // shouldn't happen b/c kvs was parsed from optsDef
				}
				if err := setOpts(kv, kvType, opts); err != nil {
					return err
Etienne Laurin's avatar
Etienne Laurin committed
167
				}
168
			}
Etienne Laurin's avatar
Etienne Laurin committed
169
		default:
170
			arg := param
Etienne Laurin's avatar
Etienne Laurin committed
171
			// arg is a sub-command or a positional argument
keks's avatar
cleanup  
keks committed
172
			sub := cmd.Subcommands[arg]
Etienne Laurin's avatar
Etienne Laurin committed
173 174 175 176 177
			if sub != nil {
				cmd = sub
				path = append(path, arg)
				optDefs, err = root.GetOptions(path)
				if err != nil {
178
					return err
Etienne Laurin's avatar
Etienne Laurin committed
179
				}
180 181 182 183

				// If we've come across an external binary call, pass all the remaining
				// arguments on to it
				if cmd.External {
184
					args = append(args, st.cmdline[st.i+1:]...)
185
					break L
186
				}
Etienne Laurin's avatar
Etienne Laurin committed
187
			} else {
188
				args = append(args, arg)
189 190
				if len(path) == 0 {
					// found a typo or early argument
191
					return printSuggestions(args, root)
192
				}
Etienne Laurin's avatar
Etienne Laurin committed
193
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
194
		}
195

196
		st.i++
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
197
	}
198

199 200 201 202 203
	req.Root = root
	req.Command = cmd
	req.Path = path
	req.Arguments = args
	req.Options = opts
204

205
	return nil
206
}
207

208 209 210
func parseArgs(req *cmds.Request, root *cmds.Command, stdin *os.File) error {
	argDefs := req.Command.Arguments

211
	// count required argument definitions
212
	var numRequired int
213
	for _, argDef := range argDefs {
214
		if argDef.Required {
215
			numRequired++
216
		}
217
	}
218

219 220
	inputs := req.Arguments

221 222 223
	// count number of values provided by user.
	// if there is at least one ArgDef, we can safely trigger the inputs loop
	// below to parse stdin.
224
	numInputs := len(inputs)
225

226
	if len(argDefs) > 0 && argDefs[len(argDefs)-1].SupportsStdin && stdin != nil {
227
		numInputs += 1
228 229
	}

230 231 232
	// if we have more arg values provided than argument definitions,
	// and the last arg definition is not variadic (or there are no definitions), return an error
	notVariadic := len(argDefs) == 0 || !argDefs[len(argDefs)-1].Variadic
Christian Couder's avatar
Christian Couder committed
233
	if notVariadic && len(inputs) > len(argDefs) {
234
		return fmt.Errorf("expected %d argument(s), got %d", len(argDefs), len(inputs))
235 236
	}

237
	stringArgs := make([]string, 0, numInputs)
Łukasz Magiera's avatar
Łukasz Magiera committed
238
	fileArgs := make(map[string]files.Node)
Jeromy's avatar
Jeromy committed
239

240 241 242 243 244 245 246 247 248 249 250
	// the index of the current argument definition
	iArgDef := 0

	// remaining number of required arguments
	remRequired := numRequired

	for iInput := 0; iInput < numInputs; iInput++ {
		// remaining number of passed arguments
		remInputs := numInputs - iInput

		argDef := getArgDef(iArgDef, argDefs)
251

252
		// skip optional argument definitions if there aren't sufficient remaining inputs
253 254 255
		for remInputs <= remRequired && !argDef.Required {
			iArgDef++
			argDef = getArgDef(iArgDef, argDefs)
256 257
		}
		if argDef.Required {
258
			remRequired--
259
		}
260

261
		fillingVariadic := iArgDef+1 > len(argDefs)
Jeromy's avatar
Jeromy committed
262
		switch argDef.Type {
Steven Allen's avatar
Steven Allen committed
263
		case cmds.ArgString:
264
			if len(inputs) > 0 {
265
				stringArgs, inputs = append(stringArgs, inputs[0]), inputs[1:]
Jeromy's avatar
Jeromy committed
266
			} else if stdin != nil && argDef.SupportsStdin && !fillingVariadic {
267
				if r, err := maybeWrapStdin(stdin, msgStdinInfo); err == nil {
Łukasz Magiera's avatar
Łukasz Magiera committed
268 269 270 271
					fileArgs["stdin"], err = files.NewReaderPathFile(stdin.Name(), r, nil)
					if err != nil {
						return err
					}
Jeromy's avatar
Jeromy committed
272
					stdin = nil
Jeromy's avatar
Jeromy committed
273
				}
274
			}
Steven Allen's avatar
Steven Allen committed
275
		case cmds.ArgFile:
276
			if len(inputs) > 0 {
277
				// treat stringArg values as file paths
Jeromy's avatar
Jeromy committed
278 279
				fpath := inputs[0]
				inputs = inputs[1:]
Łukasz Magiera's avatar
Łukasz Magiera committed
280
				var file files.Node
281
				if fpath == "-" {
282 283
					r, err := maybeWrapStdin(stdin, msgStdinInfo)
					if err != nil {
284
						return err
285
					}
286

287
					fpath = stdinName(req)
Łukasz Magiera's avatar
Łukasz Magiera committed
288 289 290 291
					file, err = files.NewReaderPathFile(stdin.Name(), r, nil)
					if err != nil {
						return err
					}
jmank88's avatar
jmank88 committed
292
				} else if u := isURL(fpath); u != nil {
jmank88's avatar
jmank88 committed
293 294 295 296 297 298 299 300 301 302 303
					base := urlBase(u)
					fpath = base
					if _, ok := fileArgs[fpath]; ok {
						// Ensure a unique fpath by suffixing ' (n)'.
						for i := 1; ; i++ {
							fpath = fmt.Sprintf("%s (%d)", base, i)
							if _, ok := fileArgs[fpath]; !ok {
								break
							}
						}
					}
jmank88's avatar
jmank88 committed
304
					file = files.NewWebFile(u)
305
				} else {
Łukasz Magiera's avatar
Łukasz Magiera committed
306 307 308 309 310 311 312 313 314 315 316 317 318
					fpath = filepath.ToSlash(filepath.Clean(fpath))
					derefArgs, _ := req.Options[cmds.DerefLong].(bool)
					var err error

					switch {
					case fpath == ".":
						cwd, err := os.Getwd()
						if err != nil {
							return err
						}
						fpath = filepath.ToSlash(cwd)
						fallthrough
					case derefArgs:
319 320 321 322
						if fpath, err = filepath.EvalSymlinks(fpath); err != nil {
							return err
						}
					}
323 324 325 326 327 328 329
					rulesFile := getIgnoreRulesFile(req)
					ignoreRules := getIgnoreRules(req)
					filter, err := files.NewFilter(rulesFile, ignoreRules, isHidden(req))
					if err != nil {
						return err
					}
					nf, err := appendFile(fpath, argDef, isRecursive(req), filter)
330
					if err != nil {
331
						return err
332 333
					}

Łukasz Magiera's avatar
Łukasz Magiera committed
334
					fpath = path.Base(fpath)
335
					file = nf
336 337
				}

Łukasz Magiera's avatar
Łukasz Magiera committed
338
				fileArgs[fpath] = file
Jeromy's avatar
Jeromy committed
339 340
			} else if stdin != nil && argDef.SupportsStdin &&
				argDef.Required && !fillingVariadic {
341 342
				r, err := maybeWrapStdin(stdin, msgStdinInfo)
				if err != nil {
343
					return err
344
				}
345

346
				fileArgs[stdinName(req)], err = files.NewReaderPathFile(stdin.Name(), r, nil)
Łukasz Magiera's avatar
Łukasz Magiera committed
347 348 349
				if err != nil {
					return err
				}
350 351
			}
		}
352

353 354 355 356 357 358 359
		iArgDef++
	}

	if iArgDef == len(argDefs)-1 && stdin != nil &&
		req.Command.Arguments[iArgDef].SupportsStdin {
		// handle this one at runtime, pretend it's there
		iArgDef++
360 361
	}

362
	// check to make sure we didn't miss any required arguments
363 364
	if len(argDefs) > iArgDef {
		for _, argDef := range argDefs[iArgDef:] {
365
			if argDef.Required {
366 367 368 369 370 371 372
				return fmt.Errorf("argument %q is required", argDef.Name)
			}
		}
	}

	req.Arguments = stringArgs
	if len(fileArgs) > 0 {
Łukasz Magiera's avatar
Łukasz Magiera committed
373
		req.Files = files.NewMapDirectory(fileArgs)
374 375 376 377 378
	}

	return nil
}

jmank88's avatar
jmank88 committed
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
// isURL returns a url.URL for valid http:// and https:// URLs, otherwise it returns nil.
func isURL(path string) *url.URL {
	u, err := url.Parse(path)
	if err != nil {
		return nil
	}
	if u.Host == "" {
		return nil
	}
	switch u.Scheme {
	default:
		return nil
	case "http", "https":
		return u
	}
}

jmank88's avatar
jmank88 committed
396 397 398 399 400 401 402
func urlBase(u *url.URL) string {
	if u.Path == "" {
		return u.Host
	}
	return path.Base(u.Path)
}

403 404 405 406 407 408 409 410 411
func splitkv(opt string) (k, v string, ok bool) {
	split := strings.SplitN(opt, "=", 2)
	if len(split) == 2 {
		return split[0], split[1], true
	} else {
		return opt, "", false
	}
}

Steven Allen's avatar
Steven Allen committed
412
func parseOpt(opt, value string, opts map[string]cmds.Option) (interface{}, error) {
413 414
	optDef, ok := opts[opt]
	if !ok {
415
		return nil, fmt.Errorf("unknown option %q", opt)
416 417 418 419
	}

	v, err := optDef.Parse(value)
	if err != nil {
420
		return nil, err
421
	}
422
	return v, nil
423 424 425 426 427 428 429
}

type kv struct {
	Key   string
	Value interface{}
}

Steven Allen's avatar
Steven Allen committed
430
func (st *parseState) parseShortOpts(optDefs map[string]cmds.Option) ([]kv, error) {
431
	k, vStr, ok := splitkv(st.cmdline[st.i][1:])
keks's avatar
keks committed
432
	kvs := make([]kv, 0, len(k))
433 434 435

	if ok {
		// split at = successful
436 437 438 439 440 441
		v, err := parseOpt(k, vStr, optDefs)
		if err != nil {
			return nil, err
		}

		kvs = append(kvs, kv{Key: k, Value: v})
442 443

	} else {
keks's avatar
keks committed
444 445
	LOOP:
		for j := 0; j < len(k); {
446
			flag := k[j : j+1]
keks's avatar
keks committed
447
			od, ok := optDefs[flag]
448

keks's avatar
keks committed
449 450
			switch {
			case !ok:
451
				return nil, fmt.Errorf("unknown option %q", k)
452

Steven Allen's avatar
Steven Allen committed
453
			case od.Type() == cmds.Bool:
454 455 456 457 458 459 460
				// single char flags for bools
				kvs = append(kvs, kv{
					Key:   flag,
					Value: true,
				})
				j++

keks's avatar
keks committed
461
			case j < len(k)-1:
462 463 464
				// single char flag for non-bools (use the rest of the flag as value)
				rest := k[j+1:]

465 466 467 468 469 470
				v, err := parseOpt(flag, rest, optDefs)
				if err != nil {
					return nil, err
				}

				kvs = append(kvs, kv{Key: flag, Value: v})
keks's avatar
keks committed
471
				break LOOP
472

473
			case st.i < len(st.cmdline)-1:
474
				// single char flag for non-bools (use the next word as value)
475 476 477 478 479 480 481
				st.i++
				v, err := parseOpt(flag, st.cmdline[st.i], optDefs)
				if err != nil {
					return nil, err
				}

				kvs = append(kvs, kv{Key: flag, Value: v})
keks's avatar
keks committed
482
				break LOOP
483

keks's avatar
keks committed
484
			default:
485
				return nil, fmt.Errorf("missing argument for option %q", k)
486
			}
487 488 489
		}
	}

490
	return kvs, nil
Jeromy's avatar
Jeromy committed
491 492
}

Steven Allen's avatar
Steven Allen committed
493
func (st *parseState) parseLongOpt(optDefs map[string]cmds.Option) (string, interface{}, error) {
494
	k, v, ok := splitkv(st.peek()[2:])
495
	if !ok {
496 497 498 499
		optDef, ok := optDefs[k]
		if !ok {
			return "", nil, fmt.Errorf("unknown option %q", k)
		}
Steven Allen's avatar
Steven Allen committed
500
		if optDef.Type() == cmds.Bool {
501 502 503 504
			return k, true, nil
		} else if st.i < len(st.cmdline)-1 {
			st.i++
			v = st.peek()
505
		} else {
506
			return "", nil, fmt.Errorf("missing argument for option %q", k)
507 508 509
		}
	}

510 511
	optval, err := parseOpt(k, v, optDefs)
	return k, optval, err
512
}
513

Steven Allen's avatar
Steven Allen committed
514
func getArgDef(i int, argDefs []cmds.Argument) *cmds.Argument {
515 516 517 518 519 520 521 522 523 524 525 526 527
	if i < len(argDefs) {
		// get the argument definition (usually just argDefs[i])
		return &argDefs[i]

	} else if len(argDefs) > 0 {
		// but if i > len(argDefs) we use the last argument definition)
		return &argDefs[len(argDefs)-1]
	}

	// only happens if there aren't any definitions
	return nil
}

Jeromy's avatar
Jeromy committed
528
const notRecursiveFmtStr = "'%s' is a directory, use the '-%s' flag to specify directories"
529
const dirNotSupportedFmtStr = "invalid path '%s', argument '%s' does not support directories"
Jeromy's avatar
Jeromy committed
530

531
func appendFile(fpath string, argDef *cmds.Argument, recursive bool, filter *files.Filter) (files.Node, error) {
Jeromy's avatar
Jeromy committed
532
	stat, err := os.Lstat(fpath)
533
	if err != nil {
Jeromy's avatar
Jeromy committed
534
		return nil, err
535 536 537 538
	}

	if stat.IsDir() {
		if !argDef.Recursive {
Jeromy's avatar
Jeromy committed
539
			return nil, fmt.Errorf(dirNotSupportedFmtStr, fpath, argDef.Name)
540 541
		}
		if !recursive {
542
			return nil, fmt.Errorf(notRecursiveFmtStr, fpath, cmds.RecShort)
543
		}
544 545 546 547 548 549 550 551
	} else if (stat.Mode() & os.ModeNamedPipe) != 0 {
		// Special case pipes that are provided directly on the command line
		// We do this here instead of go-ipfs-files, as we need to differentiate between
		// recursive(unsupported) and direct(supported) mode
		file, err := os.Open(fpath)
		if err != nil {
			return nil, err
		}
552

553
		return files.NewReaderFile(file), nil
554
	}
555
	return files.NewSerialFileWithFilter(fpath, filter, stat)
556
}
557 558

// Inform the user if a file is waiting on input
559
func maybeWrapStdin(f *os.File, msg string) (io.ReadCloser, error) {
560
	isTty, err := isTty(f)
561
	if err != nil {
562
		return nil, err
563 564
	}

565
	if isTty {
566
		return newMessageReader(f, fmt.Sprintf(msg, f.Name())), nil
567 568
	}

569
	return f, nil
570
}
571 572 573 574 575 576 577 578 579 580

func isTty(f *os.File) (bool, error) {
	fInfo, err := f.Stat()
	if err != nil {
		log.Error(err)
		return false, err
	}

	return (fInfo.Mode() & os.ModeCharDevice) != 0, nil
}
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597

type messageReader struct {
	r       io.ReadCloser
	done    bool
	message string
}

func newMessageReader(r io.ReadCloser, msg string) io.ReadCloser {
	return &messageReader{
		r:       r,
		message: msg,
	}
}

func (r *messageReader) Read(b []byte) (int, error) {
	if !r.done {
		fmt.Fprintln(os.Stderr, r.message)
598
		r.done = true
599 600 601 602 603 604 605 606
	}

	return r.r.Read(b)
}

func (r *messageReader) Close() error {
	return r.r.Close()
}
607 608 609 610 611 612 613

func getOptType(k string, optDefs map[string]cmds.Option) (reflect.Kind, error) {
	if opt, ok := optDefs[k]; ok {
		return opt.Type(), nil
	}
	return reflect.Invalid, fmt.Errorf("unknown option %q", k)
}