diff --git a/cmd/sqlcmd/sqlcmd.go b/cmd/sqlcmd/sqlcmd.go index 7d69b24b..227cdbd6 100644 --- a/cmd/sqlcmd/sqlcmd.go +++ b/cmd/sqlcmd/sqlcmd.go @@ -294,6 +294,9 @@ func Execute(version string) { // We need to rewrite the arguments to add -i and -v in front of each space-delimited value to be Cobra-friendly. // For flags like -r we need to inject the default value if the user omits it func convertOsArgs(args []string) (cargs []string) { + // Pre-process to handle special help flag cases + args = preprocessHelpFlags(args) + flag := "" first := true for i, a := range args { @@ -323,6 +326,63 @@ func convertOsArgs(args []string) (cargs []string) { return } +// preprocessHelpFlags handles special cases for help flags to improve user experience: +// 1. Converts "-help" to "--help" since "-help" would be parsed as "-h elp" +// 2. Converts "-h" without an argument to "-?" to show help, since many users expect +// "-h" to show help (common in other CLI tools), even though in sqlcmd "-h" is +// traditionally used to set header count. When "-h" has a following number, it's +// left as-is to maintain compatibility with the traditional behavior. +func preprocessHelpFlags(args []string) []string { + result := make([]string, 0, len(args)) + for i := 0; i < len(args); i++ { + arg := args[i] + + // Convert "-help" to "--help" + if arg == "-help" { + result = append(result, "--help") + continue + } + + // Handle "-h" without an argument: convert to "-?" to show help + if arg == "-h" { + // Check if next arg exists + hasNextArg := i+1 < len(args) + nextIsNumber := false + nextIsFlag := false + if hasNextArg { + nextArg := args[i+1] + // Check if next arg looks like a number (including negative numbers) + // This must be checked before checking if it's a flag, because + // negative numbers start with '-' + _, err := strconv.Atoi(nextArg) + nextIsNumber = err == nil + + // Check if next arg is a flag (starts with '-' but is not a number) + if !nextIsNumber && len(nextArg) > 0 && nextArg[0] == '-' { + nextIsFlag = true + } + } + + // If -h is followed by a number, keep it as-is (header count) + // Otherwise, convert to -? (show help) and consume any non-numeric, non-flag argument + if !nextIsNumber { + result = append(result, "-?") + // If there was a following non-numeric, non-flag argument, consume it + // (Don't consume flags, as they should be processed normally) + if hasNextArg && !nextIsFlag { + i++ + } + } else { + result = append(result, arg) + } + continue + } + + result = append(result, arg) + } + return result +} + // If args[i] is the given flag and args[i+1] is another flag, returns the value to append after the flag func checkDefaultValue(args []string, i int) (val string) { flags := map[rune]string{ diff --git a/cmd/sqlcmd/sqlcmd_test.go b/cmd/sqlcmd/sqlcmd_test.go index 511816b2..d04d625b 100644 --- a/cmd/sqlcmd/sqlcmd_test.go +++ b/cmd/sqlcmd/sqlcmd_test.go @@ -72,6 +72,15 @@ func TestValidCommandLineToArgsConversion(t *testing.T) { {[]string{"-h", "2", "-?"}, func(args SQLCmdArguments) bool { return args.Help && args.Headers == 2 }}, + {[]string{"-h"}, func(args SQLCmdArguments) bool { + return args.Help + }}, + {[]string{"-help"}, func(args SQLCmdArguments) bool { + return args.Help + }}, + {[]string{"-h", "abc"}, func(args SQLCmdArguments) bool { + return args.Help + }}, {[]string{"-u", "-A"}, func(args SQLCmdArguments) bool { return args.UnicodeOutputFile && args.DedicatedAdminConnection }}, @@ -584,6 +593,36 @@ func TestConvertOsArgs(t *testing.T) { []string{"-X", "-k2"}, []string{"-X", "0", "-k2"}, }, + { + "-h without argument converts to -? for help", + []string{"-h"}, + []string{"-?"}, + }, + { + "-h with number keeps -h for headers", + []string{"-h", "5"}, + []string{"-h", "5"}, + }, + { + "-h with negative number keeps -h for headers", + []string{"-h", "-1"}, + []string{"-h", "-1"}, + }, + { + "-help converts to --help", + []string{"-help"}, + []string{"--help"}, + }, + { + "-h followed by flag converts to -? for help", + []string{"-h", "-E"}, + []string{"-?", "-E"}, + }, + { + "-h followed by non-numeric converts to -? for help (consuming the non-numeric arg)", + []string{"-h", "abc"}, + []string{"-?"}, + }, } for _, c := range tests { t.Run(c.name, func(t *testing.T) {