// ================================================================ // Handling single quotes and double quotes is different on Windows unless // particular care is taken, which is what this file does. // ================================================================ //go:build windows // +build windows package platform import ( "fmt" "os" "path/filepath" "strings" shellquote "github.com/kballard/go-shellquote" "golang.org/x/sys/windows" ) // GetArgs returns a copy of os.Args, as-is except for any arg wrapped in single quotes. // This is for compatibility Linux/Unix/MacOS. // // For example: // // mlr --icsv --ojson put '$filename = $basename . ".ext"' ..\data\foo.csv // // The Windows way to say that is // // mlr --icsv --ojson put "$filename = $basename . """.ext"""" ..\data\foo.csv // // This function makes it possible to say the former, or the latter. func GetArgs() []string { // If this code is running in MSYS2: MSYS2 does the right thing already and // we won't try to improve on that. This is true regardless of whether we // were compiled inside MSYS2 or outside; what matters is whether we're // _running_ inside MSYS2 or outside. (I.e. this is necessarily a run-time // check, not a compile-time check.) msystem := os.Getenv("MSYSTEM") if msystem != "" { return os.Args } //printArgs(os.Args, "ORIGINAL") regrouped, ok := regroupForSingleQuote(os.Args) if !ok { return os.Args } //printArgs(regrouped, "REGROUPED") // Things on the command line include: args[0], which is fine as-is; // various flags like -x or --xyz which are fine as-is; DSL expressions // such as '$a = $b . "ccc"'. We don't want to give back the result of // shellquote.Split since that will remove the backslashes from things like // ..\data\foo\dat or C:\foo\bar.baz. rawCommandLine := windows.UTF16PtrToString(windows.GetCommandLine()) splitArgs, err := shellquote.Split(rawCommandLine) if err != nil { fmt.Fprintf( os.Stderr, "mlr: internal error: could not parse Windows raw command line: %v\n", err, ) } retargs := make([]string, 0) // TODO err/stetret if lens uneq for i, oldArg := range regrouped { if strings.HasPrefix(oldArg, "'") && strings.HasSuffix(oldArg, "'") { retargs = append(retargs, splitArgs[i]) } else { retargs = append(retargs, regrouped[i]) } } //printArgs(retargs, "NEW") globbed := make([]string, 0) for i := range retargs { // Expand things like *.csv matches, err := filepath.Glob(retargs[i]) if matches != nil && err == nil { globbed = append(globbed, matches...) } else { globbed = append(globbed, retargs[i]) } } //printArgs(globbed, "NEW") return globbed } // ---------------------------------------------------------------- func printArgs(args []string, description string) { fmt.Printf("%s:\n", description) for i, arg := range args { fmt.Printf("%d %s\n", i, arg) } fmt.Println() } // ---------------------------------------------------------------- func regroupForSingleQuote(inargs []string) ([]string, bool) { outargs := make([]string, 0, len(inargs)) inside := false var concat string // TODO: comment // TODO: UT this all for _, inarg := range inargs { if !inside { if !strings.HasPrefix(inarg, "'") { // Current arg is not single-quoted, and not inside a single-quoted region outargs = append(outargs, inarg) } else { // Start of single-quoted region if strings.HasSuffix(inarg, "'") { // Start and end of single-quoted region, like '$y=$x' outargs = append(outargs, inarg) } else { // Start but not end of single-quoted region, like '$y= inside = true concat = inarg } } } else { if !strings.HasSuffix(inarg, "'") { // Continuation of single-quoted region concat = concat + " " + inarg } else { // End of single-quoted region inside = false concat = concat + " " + inarg outargs = append(outargs, concat) concat = "" } } } // TODO: error not bool? if inside { return nil, false } return outargs, true }