package cli import ( "fmt" "os" "miller/src/auxents/help" "miller/src/cliutil" "miller/src/lib" "miller/src/transformers" "miller/src/types" "miller/src/version" ) // ---------------------------------------------------------------- func ParseCommandLine(args []string) ( options cliutil.TOptions, recordTransformers []transformers.IRecordTransformer, err error, ) { options = cliutil.DefaultOptions() argc := len(args) argi := 1 // Try .mlrrc overrides (then command-line on top of that). // A --norc flag (if provided) must come before all other options. // Or, they can set the environment variable MLRRC="__none__". if argc >= 2 && args[argi] == "--norc" { argi++ } else { loadMlrrcOrDie(&options) } for argi < argc /* variable increment: 1 or 2 depending on flag */ { if args[argi][0] != '-' { break // No more flag options to process } else if args[argi] == "--cpuprofile" { // Already handled in main(); ignore here. cliutil.CheckArgCount(args, argi, argc, 1) argi += 2 } else if args[argi] == "--version" { fmt.Printf("Miller %s\n", version.STRING) os.Exit(0) } else if help.ParseTerminalUsage(args[argi]) { // Most help is in the 'mlr help' auxent but there are a few shorthands // like 'mlr -h' and 'mlr -F'. os.Exit(0) } else if cliutil.ParseReaderOptions(args, argc, &argi, &options.ReaderOptions) { // handled } else if cliutil.ParseWriterOptions(args, argc, &argi, &options.WriterOptions) { // handled } else if cliutil.ParseReaderWriterOptions(args, argc, &argi, &options.ReaderOptions, &options.WriterOptions) { // handled } else if cliutil.ParseMiscOptions(args, argc, &argi, &options) { // handled } else { // unhandled fmt.Fprintf(os.Stderr, "%s: option \"%s\" not recognized.\n", "mlr", args[argi]) fmt.Fprintf(os.Stderr, "Please run \"%s --help\" for usage information.\n", "mlr") os.Exit(1) } } cliutil.ApplyReaderOptionDefaults(&options.ReaderOptions) cliutil.ApplyWriterOptionDefaults(&options.WriterOptions) // Set an optional global formatter for floating-point values if options.WriterOptions.FPOFMT != "" { err = types.SetMlrvalFloatOutputFormat(options.WriterOptions.FPOFMT) if err != nil { return options, recordTransformers, err } } recordTransformers, ignoresInput, err := parseTransformers(args, &argi, argc, &options) if err != nil { return options, recordTransformers, err } if ignoresInput { options.NoInput = true // e.g. then-chain begins with seqgen } if cliutil.DecideFinalFlatten(&options) { // E.g. '{"req": {"method": "GET", "path": "/api/check"}}' becomes // req.method=GET,req.path=/api/check. transformer, err := transformers.NewTransformerFlatten(options.WriterOptions.OFLATSEP, nil) lib.InternalCodingErrorIf(err != nil) lib.InternalCodingErrorIf(transformer == nil) recordTransformers = append(recordTransformers, transformer) } if cliutil.DecideFinalUnflatten(&options) { // E.g. req.method=GET,req.path=/api/check becomes // '{"req": {"method": "GET", "path": "/api/check"}}' transformer, err := transformers.NewTransformerUnflatten(options.WriterOptions.OFLATSEP, nil) lib.InternalCodingErrorIf(err != nil) lib.InternalCodingErrorIf(transformer == nil) recordTransformers = append(recordTransformers, transformer) } // There may already be one or more because of --from on the command line, // so append. for _, arg := range args[argi:] { options.FileNames = append(options.FileNames, arg) } // E.g. mlr -n put -v '...' if options.NoInput { options.FileNames = nil } if options.DoInPlace && (options.FileNames == nil || len(options.FileNames) == 0) { fmt.Fprintf(os.Stderr, "%s: -I option (in-place operation) requires input files.\n", "mlr") os.Exit(1) } if options.HaveRandSeed { lib.SeedRandom(int64(options.RandSeed)) } return options, recordTransformers, nil } // ---------------------------------------------------------------- // Returns a list of transformers, from the starting point in args given by *pargi. // Bumps *pargi to point to remaining post-transformer-setup args, i.e. filenames. func parseTransformers( args []string, pargi *int, argc int, options *cliutil.TOptions, ) ( transformerList []transformers.IRecordTransformer, ignoresInput bool, err error, ) { transformerList = make([]transformers.IRecordTransformer, 0) ignoresInput = false argi := *pargi // Allow then-chains to start with an initial 'then': 'mlr verb1 then verb2 then verb3' or // 'mlr then verb1 then verb2 then verb3'. Particuarly useful in backslashy scripting contexts. if (argc-argi) >= 1 && args[argi] == "then" { argi++ } if (argc - argi) < 1 { fmt.Fprintf(os.Stderr, "%s: no verb supplied.\n", "mlr") help.MainUsage(os.Stderr) os.Exit(1) } onFirst := true for { cliutil.CheckArgCount(args, argi, argc, 1) verb := args[argi] transformerSetup := transformers.LookUp(verb) if transformerSetup == nil { fmt.Fprintf(os.Stderr, "%s: verb \"%s\" not found. Please use \"%s --help\" for a list.\n", "mlr", verb, "mlr") os.Exit(1) } // E.g. then-chain begins with seqgen if onFirst && transformerSetup.IgnoresInput { ignoresInput = true } onFirst = false // It's up to the parse func to print its usage, and exit 1, on // CLI-parse failure. Also note: this assumes main reader/writer opts // are all parsed *before* transformer parse-CLI methods are invoked. transformer := transformerSetup.ParseCLIFunc( &argi, argc, args, options, ) lib.InternalCodingErrorIf(transformer == nil) transformerList = append(transformerList, transformer) // argi now points to: // * A "then", followed by the start of the next verb in the chain, if any // * Filenames after the verb (there are no more verbs listed) // * None of the above; argi == argc if argi >= argc { break } else if args[argi] != "then" { break } else { if argi == argc-1 { fmt.Fprintf(os.Stderr, "%s: missing next verb after \"then\".\n", "mlr") os.Exit(1) } else { argi++ } } } *pargi = argi return transformerList, ignoresInput, nil }