miller/pkg/platform/getargs_windows.go
John Kerl 8c791f5466
Static-check fixes from @lespea #1657, batch 4/n (#1706)
* Static-check fixes from @lespea #1657, batch 2/n

* Static-check fixes from @lespea #1657, batch 3/n

* Static-check fixes from @lespea #1657, batch 4/n
2024-10-27 12:00:25 -04:00

154 lines
4 KiB
Go

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