From b41606bf50e35e381eef5d45c3e6d1c19cec2b4d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:45:27 +0000 Subject: [PATCH 1/7] feat(cli): enhance CLI flexibility and fix option precedence - Exposed library configuration options (delimiter, case mode, strictness, etc.) as CLI flags in `cli/main.go`. - Regenerated CLI command implementations using `gosubc` to support the new flags. - Refactored `strings2` helper functions (`ToCamel`, `ToSnake`, etc.) in `permutations.go` and `types.go` to apply default options before user options, allowing users to override defaults. - Updated `process` function in `cli/main.go` to pass parsed options to `strings2` functions. --- cli/main.go | 92 +++++++++++++-- cmd/agents.md | 79 +++++++++++++ cmd/errors.go | 19 ++- cmd/strings2/camel.go | 149 ++++++++++++++++++++++-- cmd/strings2/camel_test.go | 50 +++++++- cmd/strings2/kebab.go | 149 ++++++++++++++++++++++-- cmd/strings2/kebab_test.go | 50 +++++++- cmd/strings2/main.go | 10 +- cmd/strings2/pascal.go | 149 ++++++++++++++++++++++-- cmd/strings2/pascal_test.go | 50 +++++++- cmd/strings2/root.go | 10 +- cmd/strings2/root_test.go | 2 +- cmd/strings2/snake.go | 149 ++++++++++++++++++++++-- cmd/strings2/snake_test.go | 50 +++++++- cmd/strings2/templates/camel_usage.txt | 16 ++- cmd/strings2/templates/kebab_usage.txt | 16 ++- cmd/strings2/templates/pascal_usage.txt | 16 ++- cmd/strings2/templates/snake_usage.txt | 16 ++- cmd/strings2/templates/templates.go | 2 +- permutations.go | 24 ++-- types.go | 12 +- 21 files changed, 1031 insertions(+), 79 deletions(-) create mode 100644 cmd/agents.md diff --git a/cli/main.go b/cli/main.go index 2b07ead..9d09c6b 100644 --- a/cli/main.go +++ b/cli/main.go @@ -9,7 +9,7 @@ import ( "github.com/arran4/strings2" ) -func process(input string, output string, args []string, fn func(string, ...any) (string, error)) { +func process(input string, output string, args []string, opts []any, fn func(string, ...any) (string, error)) { var in io.Reader if input == "-" { in = os.Stdin @@ -33,7 +33,7 @@ func process(input string, output string, args []string, fn func(string, ...any) os.Exit(1) } - res, err := fn(string(b)) + res, err := fn(string(b), opts...) if err != nil { fmt.Fprintf(os.Stderr, "Error processing: %v\n", err) os.Exit(1) @@ -55,15 +55,57 @@ func process(input string, output string, args []string, fn func(string, ...any) fmt.Fprintln(out, res) } +func buildOpts(delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool) []any { + var opts []any + if delimiter != "" { + opts = append(opts, strings2.OptionDelimiter(delimiter)) + } + if screaming { + opts = append(opts, strings2.OptionCaseMode(strings2.CMScreaming)) + } + if whispering { + opts = append(opts, strings2.OptionCaseMode(strings2.CMWhispering)) + } + if firstUpper { + opts = append(opts, strings2.OptionFirstUpper()) + } + if firstLower { + opts = append(opts, strings2.OptionFirstLower()) + } + if mixCaseSupport { + opts = append(opts, strings2.OptionMixCaseSupport()) + } + if noSmartAcronyms { + opts = append(opts, strings2.WithSmartAcronyms(false)) + } + if numberSplitting { + opts = append(opts, strings2.WithNumberSplitting(true)) + } + if strict { + opts = append(opts, strings2.OptionStrict()) + } + return opts +} + // Camel is a subcommand `strings2 camel` // // Flags: // // input: -i --input (default: "") Input file or - for stdin // output: -o --output (default: "") Output file or - for stdout +// delimiter: -d --delimiter (default: "") Delimiter +// screaming: -S --screaming (default: false) Screaming mode +// whispering: -w --whispering (default: false) Whispering mode +// firstUpper: -U --first-upper (default: false) First char upper +// firstLower: -l --first-lower (default: false) First char lower +// mixCaseSupport: -m --mix-case-support (default: false) Mix case support +// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms +// numberSplitting: --number-splitting (default: false) Enable number splitting +// strict: --strict (default: false) Strict UTF8 mode // args: ... String to convert if file/stdin not provided -func Camel(input string, output string, args ...string) { - process(input, output, args, strings2.ToCamel) +func Camel(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) + process(input, output, args, opts, strings2.ToCamel) } // Snake is a subcommand `strings2 snake` @@ -72,9 +114,19 @@ func Camel(input string, output string, args ...string) { // // input: -i --input (default: "") Input file or - for stdin // output: -o --output (default: "") Output file or - for stdout +// delimiter: -d --delimiter (default: "") Delimiter +// screaming: -S --screaming (default: false) Screaming mode +// whispering: -w --whispering (default: false) Whispering mode +// firstUpper: -U --first-upper (default: false) First char upper +// firstLower: -l --first-lower (default: false) First char lower +// mixCaseSupport: -m --mix-case-support (default: false) Mix case support +// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms +// numberSplitting: --number-splitting (default: false) Enable number splitting +// strict: --strict (default: false) Strict UTF8 mode // args: ... String to convert if file/stdin not provided -func Snake(input string, output string, args ...string) { - process(input, output, args, strings2.ToSnake) +func Snake(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) + process(input, output, args, opts, strings2.ToSnake) } // Kebab is a subcommand `strings2 kebab` @@ -83,9 +135,19 @@ func Snake(input string, output string, args ...string) { // // input: -i --input (default: "") Input file or - for stdin // output: -o --output (default: "") Output file or - for stdout +// delimiter: -d --delimiter (default: "") Delimiter +// screaming: -S --screaming (default: false) Screaming mode +// whispering: -w --whispering (default: false) Whispering mode +// firstUpper: -U --first-upper (default: false) First char upper +// firstLower: -l --first-lower (default: false) First char lower +// mixCaseSupport: -m --mix-case-support (default: false) Mix case support +// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms +// numberSplitting: --number-splitting (default: false) Enable number splitting +// strict: --strict (default: false) Strict UTF8 mode // args: ... String to convert if file/stdin not provided -func Kebab(input string, output string, args ...string) { - process(input, output, args, strings2.ToKebab) +func Kebab(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) + process(input, output, args, opts, strings2.ToKebab) } // Pascal is a subcommand `strings2 pascal` @@ -94,7 +156,17 @@ func Kebab(input string, output string, args ...string) { // // input: -i --input (default: "") Input file or - for stdin // output: -o --output (default: "") Output file or - for stdout +// delimiter: -d --delimiter (default: "") Delimiter +// screaming: -S --screaming (default: false) Screaming mode +// whispering: -w --whispering (default: false) Whispering mode +// firstUpper: -U --first-upper (default: false) First char upper +// firstLower: -l --first-lower (default: false) First char lower +// mixCaseSupport: -m --mix-case-support (default: false) Mix case support +// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms +// numberSplitting: --number-splitting (default: false) Enable number splitting +// strict: --strict (default: false) Strict UTF8 mode // args: ... String to convert if file/stdin not provided -func Pascal(input string, output string, args ...string) { - process(input, output, args, strings2.ToPascal) +func Pascal(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) + process(input, output, args, opts, strings2.ToPascal) } diff --git a/cmd/agents.md b/cmd/agents.md new file mode 100644 index 0000000..5ca988b --- /dev/null +++ b/cmd/agents.md @@ -0,0 +1,79 @@ + +All code under /cmd is generated code, do not place files here. + +# Go Subcommand Information + +Go Subcommand (`gosubc`) generates subcommand code for command-line interfaces (CLIs) in Go from source code comments. + +## Key Features + +- **Convention over Configuration:** Define your CLI structure with simple, intuitive code comments. +- **Zero Dependencies:** The generated code is self-contained and doesn't require any external libraries. +- **Automatic Code Generation:** `gosubc` parses your Go files and generates a complete, ready-to-use CLI. + +## Installation + +`gosubc` is a standalone tool and should not be added as a dependency in your `go.mod`. Install it using: + +```bash +go install github.com/arran4/go-subcommand/cmd/gosubc@latest +``` + +## How it works + +1. **Define Your Commands**: Create a Go file and define a function that will serve as your command. Add a comment above the function. +2. **Generate**: Run `gosubc generate` (or via `go generate`). +3. **Result**: `gosubc` creates a `cmd/` directory containing the generated CLI code. + +## Comment Syntax + +### Command Definition + +```go +// FuncName is a subcommand 'root-cmd parent child' +func FuncName() {} +``` + +### Flags + +Use a `Flags:` block or inline comments. Adhere to Go formatting. + +```go +// FuncName is a subcommand 'root-cmd parent child' +// +// Flags: +// +// username: --username -u (default: "guest") The user to greet +// count: --count -c (default: 1) Number of times +func FuncName(username string, count int) {} +``` + +### Positional Arguments + +Map positional arguments using `@N`. + +```go +// Greet is a subcommand 'app greet' +// +// Flags: +// +// name: @1 The name to greet +func Greet(name string) {} +``` + +### Variadic Arguments + +Map remaining arguments using `...`. + +```go +// Process is a subcommand 'app process' +// +// Flags: +// +// files: ... List of files +func Process(files ...string) {} +``` + +## Important Note + +Do not edit files in this directory directly if you can avoid it. They are overwritten on every generation. Modify the source code comments instead. diff --git a/cmd/errors.go b/cmd/errors.go index 6e87800..e1e1846 100644 --- a/cmd/errors.go +++ b/cmd/errors.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package cmd @@ -9,3 +9,20 @@ var ErrPrintHelp = errors.New("print help") // ErrHelp tells the user to use help. var ErrHelp = errors.New("help requested") + +// ErrExitCode Mostly used as a pass through, it's caught, but if the sub error is nil and it's not wrapped in another error, it counts as no error. +type ErrExitCode struct { + Err error + Code int +} + +func (e *ErrExitCode) Error() string { + if e.Err == nil { + return "" + } + return e.Err.Error() +} + +func (e *ErrExitCode) Unwrap() error { + return e.Err +} diff --git a/cmd/strings2/camel.go b/cmd/strings2/camel.go index aabcf6a..cdcaaea 100644 --- a/cmd/strings2/camel.go +++ b/cmd/strings2/camel.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -6,6 +6,7 @@ import ( "flag" "fmt" "os" + "strconv" "strings" "github.com/arran4/strings2/cli" @@ -15,12 +16,21 @@ var _ Cmd = (*Camel)(nil) type Camel struct { *RootCmd - Flags *flag.FlagSet - input string - output string - args []string - SubCommands map[string]Cmd - CommandAction func(c *Camel) error + Flags *flag.FlagSet + input string + output string + delimiter string + screaming bool + whispering bool + firstUpper bool + firstLower bool + mixCaseSupport bool + noSmartAcronyms bool + numberSplitting bool + strict bool + args []string + SubCommands map[string]Cmd + CommandAction func(c *Camel) error } type UsageDataCamel struct { @@ -89,6 +99,105 @@ func (c *Camel) Execute(args []string) error { } } c.output = value + + case "delimiter", "d": + if !hasValue { + if i+1 < len(args) { + value = args[i+1] + i++ + } else { + return fmt.Errorf("flag %s requires a value", name) + } + } + c.delimiter = value + + case "screaming", "S": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.screaming = b + } else { + c.screaming = true + } + + case "whispering", "w": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.whispering = b + } else { + c.whispering = true + } + + case "firstUpper", "first-upper", "U": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.firstUpper = b + } else { + c.firstUpper = true + } + + case "firstLower", "first-lower", "l": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.firstLower = b + } else { + c.firstLower = true + } + + case "mixCaseSupport", "mix-case-support", "m": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.mixCaseSupport = b + } else { + c.mixCaseSupport = true + } + + case "noSmartAcronyms", "no-smart-acronyms": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.noSmartAcronyms = b + } else { + c.noSmartAcronyms = true + } + + case "numberSplitting", "number-splitting": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.numberSplitting = b + } else { + c.numberSplitting = true + } + + case "strict": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.strict = b + } else { + c.strict = true + } case "help", "h": c.Usage() return nil @@ -133,11 +242,35 @@ func (c *RootCmd) NewCamel() *Camel { set.StringVar(&v.output, "output", "", "Output file or - for stdout") set.StringVar(&v.output, "o", "", "Output file or - for stdout") + + set.StringVar(&v.delimiter, "delimiter", "", "Delimiter") + set.StringVar(&v.delimiter, "d", "", "Delimiter") + + set.BoolVar(&v.screaming, "screaming", false, "Screaming mode") + set.BoolVar(&v.screaming, "S", false, "Screaming mode") + + set.BoolVar(&v.whispering, "whispering", false, "Whispering mode") + set.BoolVar(&v.whispering, "w", false, "Whispering mode") + + set.BoolVar(&v.firstUpper, "first-upper", false, "First char upper") + set.BoolVar(&v.firstUpper, "U", false, "First char upper") + + set.BoolVar(&v.firstLower, "first-lower", false, "First char lower") + set.BoolVar(&v.firstLower, "l", false, "First char lower") + + set.BoolVar(&v.mixCaseSupport, "mix-case-support", false, "Mix case support") + set.BoolVar(&v.mixCaseSupport, "m", false, "Mix case support") + + set.BoolVar(&v.noSmartAcronyms, "no-smart-acronyms", false, "Disable smart acronyms") + + set.BoolVar(&v.numberSplitting, "number-splitting", false, "Enable number splitting") + + set.BoolVar(&v.strict, "strict", false, "Strict UTF8 mode") set.Usage = v.Usage v.CommandAction = func(c *Camel) error { - cli.Camel(c.input, c.output, c.args...) + cli.Camel(c.input, c.output, c.delimiter, c.screaming, c.whispering, c.firstUpper, c.firstLower, c.mixCaseSupport, c.noSmartAcronyms, c.numberSplitting, c.strict, c.args...) return nil } diff --git a/cmd/strings2/camel_test.go b/cmd/strings2/camel_test.go index e1eaf61..e190c78 100644 --- a/cmd/strings2/camel_test.go +++ b/cmd/strings2/camel_test.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -22,6 +22,20 @@ func TestCamel_Execute(t *testing.T) { } args := []string{} + args = append(args, "--input") + args = append(args, "test") + args = append(args, "--output") + args = append(args, "test") + args = append(args, "--delimiter") + args = append(args, "test") + args = append(args, "--screaming") + args = append(args, "--whispering") + args = append(args, "--firstUpper") + args = append(args, "--firstLower") + args = append(args, "--mixCaseSupport") + args = append(args, "--noSmartAcronyms") + args = append(args, "--numberSplitting") + args = append(args, "--strict") err := cmd.Execute(args) if err != nil { @@ -30,4 +44,38 @@ func TestCamel_Execute(t *testing.T) { if !called { t.Error("CommandAction was not called") } + + if cmd.input != "test" { + t.Errorf("Expected input to be 'test', got '%v'", cmd.input) + } + if cmd.output != "test" { + t.Errorf("Expected output to be 'test', got '%v'", cmd.output) + } + if cmd.delimiter != "test" { + t.Errorf("Expected delimiter to be 'test', got '%v'", cmd.delimiter) + } + if cmd.screaming != true { + t.Errorf("Expected screaming to be true, got '%v'", cmd.screaming) + } + if cmd.whispering != true { + t.Errorf("Expected whispering to be true, got '%v'", cmd.whispering) + } + if cmd.firstUpper != true { + t.Errorf("Expected firstUpper to be true, got '%v'", cmd.firstUpper) + } + if cmd.firstLower != true { + t.Errorf("Expected firstLower to be true, got '%v'", cmd.firstLower) + } + if cmd.mixCaseSupport != true { + t.Errorf("Expected mixCaseSupport to be true, got '%v'", cmd.mixCaseSupport) + } + if cmd.noSmartAcronyms != true { + t.Errorf("Expected noSmartAcronyms to be true, got '%v'", cmd.noSmartAcronyms) + } + if cmd.numberSplitting != true { + t.Errorf("Expected numberSplitting to be true, got '%v'", cmd.numberSplitting) + } + if cmd.strict != true { + t.Errorf("Expected strict to be true, got '%v'", cmd.strict) + } } diff --git a/cmd/strings2/kebab.go b/cmd/strings2/kebab.go index dbec970..14c536c 100644 --- a/cmd/strings2/kebab.go +++ b/cmd/strings2/kebab.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -6,6 +6,7 @@ import ( "flag" "fmt" "os" + "strconv" "strings" "github.com/arran4/strings2/cli" @@ -15,12 +16,21 @@ var _ Cmd = (*Kebab)(nil) type Kebab struct { *RootCmd - Flags *flag.FlagSet - input string - output string - args []string - SubCommands map[string]Cmd - CommandAction func(c *Kebab) error + Flags *flag.FlagSet + input string + output string + delimiter string + screaming bool + whispering bool + firstUpper bool + firstLower bool + mixCaseSupport bool + noSmartAcronyms bool + numberSplitting bool + strict bool + args []string + SubCommands map[string]Cmd + CommandAction func(c *Kebab) error } type UsageDataKebab struct { @@ -89,6 +99,105 @@ func (c *Kebab) Execute(args []string) error { } } c.output = value + + case "delimiter", "d": + if !hasValue { + if i+1 < len(args) { + value = args[i+1] + i++ + } else { + return fmt.Errorf("flag %s requires a value", name) + } + } + c.delimiter = value + + case "screaming", "S": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.screaming = b + } else { + c.screaming = true + } + + case "whispering", "w": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.whispering = b + } else { + c.whispering = true + } + + case "firstUpper", "first-upper", "U": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.firstUpper = b + } else { + c.firstUpper = true + } + + case "firstLower", "first-lower", "l": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.firstLower = b + } else { + c.firstLower = true + } + + case "mixCaseSupport", "mix-case-support", "m": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.mixCaseSupport = b + } else { + c.mixCaseSupport = true + } + + case "noSmartAcronyms", "no-smart-acronyms": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.noSmartAcronyms = b + } else { + c.noSmartAcronyms = true + } + + case "numberSplitting", "number-splitting": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.numberSplitting = b + } else { + c.numberSplitting = true + } + + case "strict": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.strict = b + } else { + c.strict = true + } case "help", "h": c.Usage() return nil @@ -133,11 +242,35 @@ func (c *RootCmd) NewKebab() *Kebab { set.StringVar(&v.output, "output", "", "Output file or - for stdout") set.StringVar(&v.output, "o", "", "Output file or - for stdout") + + set.StringVar(&v.delimiter, "delimiter", "", "Delimiter") + set.StringVar(&v.delimiter, "d", "", "Delimiter") + + set.BoolVar(&v.screaming, "screaming", false, "Screaming mode") + set.BoolVar(&v.screaming, "S", false, "Screaming mode") + + set.BoolVar(&v.whispering, "whispering", false, "Whispering mode") + set.BoolVar(&v.whispering, "w", false, "Whispering mode") + + set.BoolVar(&v.firstUpper, "first-upper", false, "First char upper") + set.BoolVar(&v.firstUpper, "U", false, "First char upper") + + set.BoolVar(&v.firstLower, "first-lower", false, "First char lower") + set.BoolVar(&v.firstLower, "l", false, "First char lower") + + set.BoolVar(&v.mixCaseSupport, "mix-case-support", false, "Mix case support") + set.BoolVar(&v.mixCaseSupport, "m", false, "Mix case support") + + set.BoolVar(&v.noSmartAcronyms, "no-smart-acronyms", false, "Disable smart acronyms") + + set.BoolVar(&v.numberSplitting, "number-splitting", false, "Enable number splitting") + + set.BoolVar(&v.strict, "strict", false, "Strict UTF8 mode") set.Usage = v.Usage v.CommandAction = func(c *Kebab) error { - cli.Kebab(c.input, c.output, c.args...) + cli.Kebab(c.input, c.output, c.delimiter, c.screaming, c.whispering, c.firstUpper, c.firstLower, c.mixCaseSupport, c.noSmartAcronyms, c.numberSplitting, c.strict, c.args...) return nil } diff --git a/cmd/strings2/kebab_test.go b/cmd/strings2/kebab_test.go index 675eb7a..b93ceca 100644 --- a/cmd/strings2/kebab_test.go +++ b/cmd/strings2/kebab_test.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -22,6 +22,20 @@ func TestKebab_Execute(t *testing.T) { } args := []string{} + args = append(args, "--input") + args = append(args, "test") + args = append(args, "--output") + args = append(args, "test") + args = append(args, "--delimiter") + args = append(args, "test") + args = append(args, "--screaming") + args = append(args, "--whispering") + args = append(args, "--firstUpper") + args = append(args, "--firstLower") + args = append(args, "--mixCaseSupport") + args = append(args, "--noSmartAcronyms") + args = append(args, "--numberSplitting") + args = append(args, "--strict") err := cmd.Execute(args) if err != nil { @@ -30,4 +44,38 @@ func TestKebab_Execute(t *testing.T) { if !called { t.Error("CommandAction was not called") } + + if cmd.input != "test" { + t.Errorf("Expected input to be 'test', got '%v'", cmd.input) + } + if cmd.output != "test" { + t.Errorf("Expected output to be 'test', got '%v'", cmd.output) + } + if cmd.delimiter != "test" { + t.Errorf("Expected delimiter to be 'test', got '%v'", cmd.delimiter) + } + if cmd.screaming != true { + t.Errorf("Expected screaming to be true, got '%v'", cmd.screaming) + } + if cmd.whispering != true { + t.Errorf("Expected whispering to be true, got '%v'", cmd.whispering) + } + if cmd.firstUpper != true { + t.Errorf("Expected firstUpper to be true, got '%v'", cmd.firstUpper) + } + if cmd.firstLower != true { + t.Errorf("Expected firstLower to be true, got '%v'", cmd.firstLower) + } + if cmd.mixCaseSupport != true { + t.Errorf("Expected mixCaseSupport to be true, got '%v'", cmd.mixCaseSupport) + } + if cmd.noSmartAcronyms != true { + t.Errorf("Expected noSmartAcronyms to be true, got '%v'", cmd.noSmartAcronyms) + } + if cmd.numberSplitting != true { + t.Errorf("Expected numberSplitting to be true, got '%v'", cmd.numberSplitting) + } + if cmd.strict != true { + t.Errorf("Expected strict to be true, got '%v'", cmd.strict) + } } diff --git a/cmd/strings2/main.go b/cmd/strings2/main.go index 8855959..0c65f47 100644 --- a/cmd/strings2/main.go +++ b/cmd/strings2/main.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -7,6 +7,8 @@ package main import ( "fmt" "os" + + "github.com/arran4/strings2/cmd" ) var ( @@ -23,6 +25,12 @@ func main() { } if err := root.Execute(os.Args[1:]); err != nil { + if e, ok := err.(*cmd.ErrExitCode); ok { + if e.Err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", e.Err) + } + os.Exit(e.Code) + } fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } diff --git a/cmd/strings2/pascal.go b/cmd/strings2/pascal.go index dab3914..ec352bb 100644 --- a/cmd/strings2/pascal.go +++ b/cmd/strings2/pascal.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -6,6 +6,7 @@ import ( "flag" "fmt" "os" + "strconv" "strings" "github.com/arran4/strings2/cli" @@ -15,12 +16,21 @@ var _ Cmd = (*Pascal)(nil) type Pascal struct { *RootCmd - Flags *flag.FlagSet - input string - output string - args []string - SubCommands map[string]Cmd - CommandAction func(c *Pascal) error + Flags *flag.FlagSet + input string + output string + delimiter string + screaming bool + whispering bool + firstUpper bool + firstLower bool + mixCaseSupport bool + noSmartAcronyms bool + numberSplitting bool + strict bool + args []string + SubCommands map[string]Cmd + CommandAction func(c *Pascal) error } type UsageDataPascal struct { @@ -89,6 +99,105 @@ func (c *Pascal) Execute(args []string) error { } } c.output = value + + case "delimiter", "d": + if !hasValue { + if i+1 < len(args) { + value = args[i+1] + i++ + } else { + return fmt.Errorf("flag %s requires a value", name) + } + } + c.delimiter = value + + case "screaming", "S": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.screaming = b + } else { + c.screaming = true + } + + case "whispering", "w": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.whispering = b + } else { + c.whispering = true + } + + case "firstUpper", "first-upper", "U": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.firstUpper = b + } else { + c.firstUpper = true + } + + case "firstLower", "first-lower", "l": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.firstLower = b + } else { + c.firstLower = true + } + + case "mixCaseSupport", "mix-case-support", "m": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.mixCaseSupport = b + } else { + c.mixCaseSupport = true + } + + case "noSmartAcronyms", "no-smart-acronyms": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.noSmartAcronyms = b + } else { + c.noSmartAcronyms = true + } + + case "numberSplitting", "number-splitting": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.numberSplitting = b + } else { + c.numberSplitting = true + } + + case "strict": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.strict = b + } else { + c.strict = true + } case "help", "h": c.Usage() return nil @@ -133,11 +242,35 @@ func (c *RootCmd) NewPascal() *Pascal { set.StringVar(&v.output, "output", "", "Output file or - for stdout") set.StringVar(&v.output, "o", "", "Output file or - for stdout") + + set.StringVar(&v.delimiter, "delimiter", "", "Delimiter") + set.StringVar(&v.delimiter, "d", "", "Delimiter") + + set.BoolVar(&v.screaming, "screaming", false, "Screaming mode") + set.BoolVar(&v.screaming, "S", false, "Screaming mode") + + set.BoolVar(&v.whispering, "whispering", false, "Whispering mode") + set.BoolVar(&v.whispering, "w", false, "Whispering mode") + + set.BoolVar(&v.firstUpper, "first-upper", false, "First char upper") + set.BoolVar(&v.firstUpper, "U", false, "First char upper") + + set.BoolVar(&v.firstLower, "first-lower", false, "First char lower") + set.BoolVar(&v.firstLower, "l", false, "First char lower") + + set.BoolVar(&v.mixCaseSupport, "mix-case-support", false, "Mix case support") + set.BoolVar(&v.mixCaseSupport, "m", false, "Mix case support") + + set.BoolVar(&v.noSmartAcronyms, "no-smart-acronyms", false, "Disable smart acronyms") + + set.BoolVar(&v.numberSplitting, "number-splitting", false, "Enable number splitting") + + set.BoolVar(&v.strict, "strict", false, "Strict UTF8 mode") set.Usage = v.Usage v.CommandAction = func(c *Pascal) error { - cli.Pascal(c.input, c.output, c.args...) + cli.Pascal(c.input, c.output, c.delimiter, c.screaming, c.whispering, c.firstUpper, c.firstLower, c.mixCaseSupport, c.noSmartAcronyms, c.numberSplitting, c.strict, c.args...) return nil } diff --git a/cmd/strings2/pascal_test.go b/cmd/strings2/pascal_test.go index c74e83c..828f170 100644 --- a/cmd/strings2/pascal_test.go +++ b/cmd/strings2/pascal_test.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -22,6 +22,20 @@ func TestPascal_Execute(t *testing.T) { } args := []string{} + args = append(args, "--input") + args = append(args, "test") + args = append(args, "--output") + args = append(args, "test") + args = append(args, "--delimiter") + args = append(args, "test") + args = append(args, "--screaming") + args = append(args, "--whispering") + args = append(args, "--firstUpper") + args = append(args, "--firstLower") + args = append(args, "--mixCaseSupport") + args = append(args, "--noSmartAcronyms") + args = append(args, "--numberSplitting") + args = append(args, "--strict") err := cmd.Execute(args) if err != nil { @@ -30,4 +44,38 @@ func TestPascal_Execute(t *testing.T) { if !called { t.Error("CommandAction was not called") } + + if cmd.input != "test" { + t.Errorf("Expected input to be 'test', got '%v'", cmd.input) + } + if cmd.output != "test" { + t.Errorf("Expected output to be 'test', got '%v'", cmd.output) + } + if cmd.delimiter != "test" { + t.Errorf("Expected delimiter to be 'test', got '%v'", cmd.delimiter) + } + if cmd.screaming != true { + t.Errorf("Expected screaming to be true, got '%v'", cmd.screaming) + } + if cmd.whispering != true { + t.Errorf("Expected whispering to be true, got '%v'", cmd.whispering) + } + if cmd.firstUpper != true { + t.Errorf("Expected firstUpper to be true, got '%v'", cmd.firstUpper) + } + if cmd.firstLower != true { + t.Errorf("Expected firstLower to be true, got '%v'", cmd.firstLower) + } + if cmd.mixCaseSupport != true { + t.Errorf("Expected mixCaseSupport to be true, got '%v'", cmd.mixCaseSupport) + } + if cmd.noSmartAcronyms != true { + t.Errorf("Expected noSmartAcronyms to be true, got '%v'", cmd.noSmartAcronyms) + } + if cmd.numberSplitting != true { + t.Errorf("Expected numberSplitting to be true, got '%v'", cmd.numberSplitting) + } + if cmd.strict != true { + t.Errorf("Expected strict to be true, got '%v'", cmd.strict) + } } diff --git a/cmd/strings2/root.go b/cmd/strings2/root.go index f5d2a97..024359d 100644 --- a/cmd/strings2/root.go +++ b/cmd/strings2/root.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -60,7 +60,7 @@ type RootCmd struct { func (c *RootCmd) Usage() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) - c.FlagSet.PrintDefaults() + c.PrintDefaults() fmt.Fprintln(os.Stderr, " Commands:") for name := range c.Commands { fmt.Fprintf(os.Stderr, " %s\n", name) @@ -69,7 +69,7 @@ func (c *RootCmd) Usage() { func (c *RootCmd) UsageRecursive() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) - c.FlagSet.PrintDefaults() + c.PrintDefaults() fmt.Fprintln(os.Stderr, " Commands:") fmt.Fprintf(os.Stderr, " %s\n", "camel") fmt.Fprintf(os.Stderr, " %s\n", "kebab") @@ -130,10 +130,10 @@ func NewRoot(name, version, commit, date string) (*RootCmd, error) { } func (c *RootCmd) Execute(args []string) error { - if err := c.FlagSet.Parse(args); err != nil { + if err := c.Parse(args); err != nil { return NewUserError(err, fmt.Sprintf("flag parse error %s", err.Error())) } - remainingArgs := c.FlagSet.Args() + remainingArgs := c.Args() if len(remainingArgs) < 1 { c.Usage() return nil diff --git a/cmd/strings2/root_test.go b/cmd/strings2/root_test.go index 6d4868c..87ca9a9 100644 --- a/cmd/strings2/root_test.go +++ b/cmd/strings2/root_test.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main diff --git a/cmd/strings2/snake.go b/cmd/strings2/snake.go index 31fb548..10bbb18 100644 --- a/cmd/strings2/snake.go +++ b/cmd/strings2/snake.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -6,6 +6,7 @@ import ( "flag" "fmt" "os" + "strconv" "strings" "github.com/arran4/strings2/cli" @@ -15,12 +16,21 @@ var _ Cmd = (*Snake)(nil) type Snake struct { *RootCmd - Flags *flag.FlagSet - input string - output string - args []string - SubCommands map[string]Cmd - CommandAction func(c *Snake) error + Flags *flag.FlagSet + input string + output string + delimiter string + screaming bool + whispering bool + firstUpper bool + firstLower bool + mixCaseSupport bool + noSmartAcronyms bool + numberSplitting bool + strict bool + args []string + SubCommands map[string]Cmd + CommandAction func(c *Snake) error } type UsageDataSnake struct { @@ -89,6 +99,105 @@ func (c *Snake) Execute(args []string) error { } } c.output = value + + case "delimiter", "d": + if !hasValue { + if i+1 < len(args) { + value = args[i+1] + i++ + } else { + return fmt.Errorf("flag %s requires a value", name) + } + } + c.delimiter = value + + case "screaming", "S": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.screaming = b + } else { + c.screaming = true + } + + case "whispering", "w": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.whispering = b + } else { + c.whispering = true + } + + case "firstUpper", "first-upper", "U": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.firstUpper = b + } else { + c.firstUpper = true + } + + case "firstLower", "first-lower", "l": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.firstLower = b + } else { + c.firstLower = true + } + + case "mixCaseSupport", "mix-case-support", "m": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.mixCaseSupport = b + } else { + c.mixCaseSupport = true + } + + case "noSmartAcronyms", "no-smart-acronyms": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.noSmartAcronyms = b + } else { + c.noSmartAcronyms = true + } + + case "numberSplitting", "number-splitting": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.numberSplitting = b + } else { + c.numberSplitting = true + } + + case "strict": + if hasValue { + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid boolean value for flag %s: %s", name, value) + } + c.strict = b + } else { + c.strict = true + } case "help", "h": c.Usage() return nil @@ -133,11 +242,35 @@ func (c *RootCmd) NewSnake() *Snake { set.StringVar(&v.output, "output", "", "Output file or - for stdout") set.StringVar(&v.output, "o", "", "Output file or - for stdout") + + set.StringVar(&v.delimiter, "delimiter", "", "Delimiter") + set.StringVar(&v.delimiter, "d", "", "Delimiter") + + set.BoolVar(&v.screaming, "screaming", false, "Screaming mode") + set.BoolVar(&v.screaming, "S", false, "Screaming mode") + + set.BoolVar(&v.whispering, "whispering", false, "Whispering mode") + set.BoolVar(&v.whispering, "w", false, "Whispering mode") + + set.BoolVar(&v.firstUpper, "first-upper", false, "First char upper") + set.BoolVar(&v.firstUpper, "U", false, "First char upper") + + set.BoolVar(&v.firstLower, "first-lower", false, "First char lower") + set.BoolVar(&v.firstLower, "l", false, "First char lower") + + set.BoolVar(&v.mixCaseSupport, "mix-case-support", false, "Mix case support") + set.BoolVar(&v.mixCaseSupport, "m", false, "Mix case support") + + set.BoolVar(&v.noSmartAcronyms, "no-smart-acronyms", false, "Disable smart acronyms") + + set.BoolVar(&v.numberSplitting, "number-splitting", false, "Enable number splitting") + + set.BoolVar(&v.strict, "strict", false, "Strict UTF8 mode") set.Usage = v.Usage v.CommandAction = func(c *Snake) error { - cli.Snake(c.input, c.output, c.args...) + cli.Snake(c.input, c.output, c.delimiter, c.screaming, c.whispering, c.firstUpper, c.firstLower, c.mixCaseSupport, c.noSmartAcronyms, c.numberSplitting, c.strict, c.args...) return nil } diff --git a/cmd/strings2/snake_test.go b/cmd/strings2/snake_test.go index 6e74249..61a0459 100644 --- a/cmd/strings2/snake_test.go +++ b/cmd/strings2/snake_test.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package main @@ -22,6 +22,20 @@ func TestSnake_Execute(t *testing.T) { } args := []string{} + args = append(args, "--input") + args = append(args, "test") + args = append(args, "--output") + args = append(args, "test") + args = append(args, "--delimiter") + args = append(args, "test") + args = append(args, "--screaming") + args = append(args, "--whispering") + args = append(args, "--firstUpper") + args = append(args, "--firstLower") + args = append(args, "--mixCaseSupport") + args = append(args, "--noSmartAcronyms") + args = append(args, "--numberSplitting") + args = append(args, "--strict") err := cmd.Execute(args) if err != nil { @@ -30,4 +44,38 @@ func TestSnake_Execute(t *testing.T) { if !called { t.Error("CommandAction was not called") } + + if cmd.input != "test" { + t.Errorf("Expected input to be 'test', got '%v'", cmd.input) + } + if cmd.output != "test" { + t.Errorf("Expected output to be 'test', got '%v'", cmd.output) + } + if cmd.delimiter != "test" { + t.Errorf("Expected delimiter to be 'test', got '%v'", cmd.delimiter) + } + if cmd.screaming != true { + t.Errorf("Expected screaming to be true, got '%v'", cmd.screaming) + } + if cmd.whispering != true { + t.Errorf("Expected whispering to be true, got '%v'", cmd.whispering) + } + if cmd.firstUpper != true { + t.Errorf("Expected firstUpper to be true, got '%v'", cmd.firstUpper) + } + if cmd.firstLower != true { + t.Errorf("Expected firstLower to be true, got '%v'", cmd.firstLower) + } + if cmd.mixCaseSupport != true { + t.Errorf("Expected mixCaseSupport to be true, got '%v'", cmd.mixCaseSupport) + } + if cmd.noSmartAcronyms != true { + t.Errorf("Expected noSmartAcronyms to be true, got '%v'", cmd.noSmartAcronyms) + } + if cmd.numberSplitting != true { + t.Errorf("Expected numberSplitting to be true, got '%v'", cmd.numberSplitting) + } + if cmd.strict != true { + t.Errorf("Expected strict to be true, got '%v'", cmd.strict) + } } diff --git a/cmd/strings2/templates/camel_usage.txt b/cmd/strings2/templates/camel_usage.txt index 7eff533..aef74ee 100644 --- a/cmd/strings2/templates/camel_usage.txt +++ b/cmd/strings2/templates/camel_usage.txt @@ -1,12 +1,22 @@ -{{/* Generated by github.com/arran4/go-subcommand/cmd/gosubc */}}Usage: strings2 camel [flags...] [args...] +{{/* Do not modify: Generated by github.com/arran4/go-subcommand/cmd/gosubc */-}} +Usage: strings2 camel [flags...] [args...] Subcommands: help Print this help message usage Print this usage message Flags: - --input, -i string Input file or - for stdin - --output, -o string Output file or - for stdout + --input, -i string Input file or - for stdin + --output, -o string Output file or - for stdout + --delimiter, -d string Delimiter + --screaming, -S (default: false) Screaming mode + --whispering, -w (default: false) Whispering mode + --first-upper, -U (default: false) First char upper + --first-lower, -l (default: false) First char lower + --mix-case-support, -m (default: false) Mix case support + --no-smart-acronyms (default: false) Disable smart acronyms + --number-splitting (default: false) Enable number splitting + --strict (default: false) Strict UTF8 mode Positional Arguments: args String to convert if file/stdin not provided diff --git a/cmd/strings2/templates/kebab_usage.txt b/cmd/strings2/templates/kebab_usage.txt index 08b810a..2eb63ff 100644 --- a/cmd/strings2/templates/kebab_usage.txt +++ b/cmd/strings2/templates/kebab_usage.txt @@ -1,12 +1,22 @@ -{{/* Generated by github.com/arran4/go-subcommand/cmd/gosubc */}}Usage: strings2 kebab [flags...] [args...] +{{/* Do not modify: Generated by github.com/arran4/go-subcommand/cmd/gosubc */-}} +Usage: strings2 kebab [flags...] [args...] Subcommands: help Print this help message usage Print this usage message Flags: - --input, -i string Input file or - for stdin - --output, -o string Output file or - for stdout + --input, -i string Input file or - for stdin + --output, -o string Output file or - for stdout + --delimiter, -d string Delimiter + --screaming, -S (default: false) Screaming mode + --whispering, -w (default: false) Whispering mode + --first-upper, -U (default: false) First char upper + --first-lower, -l (default: false) First char lower + --mix-case-support, -m (default: false) Mix case support + --no-smart-acronyms (default: false) Disable smart acronyms + --number-splitting (default: false) Enable number splitting + --strict (default: false) Strict UTF8 mode Positional Arguments: args String to convert if file/stdin not provided diff --git a/cmd/strings2/templates/pascal_usage.txt b/cmd/strings2/templates/pascal_usage.txt index 01e3345..dacb506 100644 --- a/cmd/strings2/templates/pascal_usage.txt +++ b/cmd/strings2/templates/pascal_usage.txt @@ -1,12 +1,22 @@ -{{/* Generated by github.com/arran4/go-subcommand/cmd/gosubc */}}Usage: strings2 pascal [flags...] [args...] +{{/* Do not modify: Generated by github.com/arran4/go-subcommand/cmd/gosubc */-}} +Usage: strings2 pascal [flags...] [args...] Subcommands: help Print this help message usage Print this usage message Flags: - --input, -i string Input file or - for stdin - --output, -o string Output file or - for stdout + --input, -i string Input file or - for stdin + --output, -o string Output file or - for stdout + --delimiter, -d string Delimiter + --screaming, -S (default: false) Screaming mode + --whispering, -w (default: false) Whispering mode + --first-upper, -U (default: false) First char upper + --first-lower, -l (default: false) First char lower + --mix-case-support, -m (default: false) Mix case support + --no-smart-acronyms (default: false) Disable smart acronyms + --number-splitting (default: false) Enable number splitting + --strict (default: false) Strict UTF8 mode Positional Arguments: args String to convert if file/stdin not provided diff --git a/cmd/strings2/templates/snake_usage.txt b/cmd/strings2/templates/snake_usage.txt index 03e82e9..3cd9b99 100644 --- a/cmd/strings2/templates/snake_usage.txt +++ b/cmd/strings2/templates/snake_usage.txt @@ -1,12 +1,22 @@ -{{/* Generated by github.com/arran4/go-subcommand/cmd/gosubc */}}Usage: strings2 snake [flags...] [args...] +{{/* Do not modify: Generated by github.com/arran4/go-subcommand/cmd/gosubc */-}} +Usage: strings2 snake [flags...] [args...] Subcommands: help Print this help message usage Print this usage message Flags: - --input, -i string Input file or - for stdin - --output, -o string Output file or - for stdout + --input, -i string Input file or - for stdin + --output, -o string Output file or - for stdout + --delimiter, -d string Delimiter + --screaming, -S (default: false) Screaming mode + --whispering, -w (default: false) Whispering mode + --first-upper, -U (default: false) First char upper + --first-lower, -l (default: false) First char lower + --mix-case-support, -m (default: false) Mix case support + --no-smart-acronyms (default: false) Disable smart acronyms + --number-splitting (default: false) Enable number splitting + --strict (default: false) Strict UTF8 mode Positional Arguments: args String to convert if file/stdin not provided diff --git a/cmd/strings2/templates/templates.go b/cmd/strings2/templates/templates.go index 45cfbb5..2b4ce08 100644 --- a/cmd/strings2/templates/templates.go +++ b/cmd/strings2/templates/templates.go @@ -1,4 +1,4 @@ -// Generated by github.com/arran4/go-subcommand/cmd/gosubc +// Code generated by github.com/arran4/go-subcommand/cmd/gosubc. DO NOT EDIT. package templates diff --git a/permutations.go b/permutations.go index 1c23ebf..d0c0cf3 100644 --- a/permutations.go +++ b/permutations.go @@ -5,44 +5,52 @@ package strings2 // ToCamel converts an input string (auto-detected format) to camelCase. func ToCamel(input string, opts ...any) (string, error) { // Camel: Delimiter "", FirstLower, AllTitle - return ToFormattedString(input, append(opts, OptionDelimiter(""), OptionFirstLower(), OptionCaseMode(CMAllTitle))...) + defaults := []any{OptionDelimiter(""), OptionFirstLower(), OptionCaseMode(CMAllTitle)} + return ToFormattedString(input, append(defaults, opts...)...) } // ToSnake converts an input string (auto-detected format) to snake_case. func ToSnake(input string, opts ...any) (string, error) { // Snake: Delimiter "_" - return ToFormattedString(input, append(opts, OptionDelimiter("_"))...) + defaults := []any{OptionDelimiter("_")} + return ToFormattedString(input, append(defaults, opts...)...) } // ToKebab converts an input string (auto-detected format) to kebab-case. func ToKebab(input string, opts ...any) (string, error) { // Kebab: Delimiter "-" - return ToFormattedString(input, append(opts, OptionDelimiter("-"))...) + defaults := []any{OptionDelimiter("-")} + return ToFormattedString(input, append(defaults, opts...)...) } // ToPascal converts an input string (auto-detected format) to PascalCase. func ToPascal(input string, opts ...any) (string, error) { // Pascal: Delimiter "", FirstUpper, AllTitle - return ToFormattedString(input, append(opts, OptionDelimiter(""), OptionFirstUpper(), OptionCaseMode(CMAllTitle))...) + defaults := []any{OptionDelimiter(""), OptionFirstUpper(), OptionCaseMode(CMAllTitle)} + return ToFormattedString(input, append(defaults, opts...)...) } // FromWordsToY // FromWordsToCamel converts words to camelCase. func FromWordsToCamel(words []Word, opts ...Option) (string, error) { - return WordsToFormattedCase(words, append(convertOptions(opts), OptionDelimiter(""), OptionFirstLower(), OptionCaseMode(CMAllTitle))...) + defaults := []any{OptionDelimiter(""), OptionFirstLower(), OptionCaseMode(CMAllTitle)} + return WordsToFormattedCase(words, append(defaults, convertOptions(opts)...)...) } func FromWordsToSnake(words []Word, opts ...Option) (string, error) { - return WordsToFormattedCase(words, append(convertOptions(opts), OptionDelimiter("_"))...) + defaults := []any{OptionDelimiter("_")} + return WordsToFormattedCase(words, append(defaults, convertOptions(opts)...)...) } func FromWordsToKebab(words []Word, opts ...Option) (string, error) { - return WordsToFormattedCase(words, append(convertOptions(opts), OptionDelimiter("-"))...) + defaults := []any{OptionDelimiter("-")} + return WordsToFormattedCase(words, append(defaults, convertOptions(opts)...)...) } func FromWordsToPascal(words []Word, opts ...Option) (string, error) { - return WordsToFormattedCase(words, append(convertOptions(opts), OptionDelimiter(""), OptionFirstUpper(), OptionCaseMode(CMAllTitle))...) + defaults := []any{OptionDelimiter(""), OptionFirstUpper(), OptionCaseMode(CMAllTitle)} + return WordsToFormattedCase(words, append(defaults, convertOptions(opts)...)...) } // FromXToWords diff --git a/types.go b/types.go index 1e58b8b..1bf1568 100644 --- a/types.go +++ b/types.go @@ -478,20 +478,24 @@ func splitMixCase(input, delimiter string) string { // ToKebabCase converts words into kebab-case format. func ToKebabCase(words []Word, opts ...Option) (string, error) { - return WordsToFormattedCase(words, append(convertOptions(opts), OptionDelimiter("-"))...) + defaults := []any{OptionDelimiter("-")} + return WordsToFormattedCase(words, append(defaults, convertOptions(opts)...)...) } // ToSnakeCase converts words into snake_case format. func ToSnakeCase(words []Word, opts ...Option) (string, error) { - return WordsToFormattedCase(words, append(convertOptions(opts), OptionDelimiter("_"))...) + defaults := []any{OptionDelimiter("_")} + return WordsToFormattedCase(words, append(defaults, convertOptions(opts)...)...) } // ToPascalCase converts words into PascalCase format. func ToPascalCase(words []Word, opts ...Option) (string, error) { - return WordsToFormattedCase(words, append(convertOptions(opts), OptionDelimiter(""), OptionFirstUpper(), OptionCaseMode(CMAllTitle))...) + defaults := []any{OptionDelimiter(""), OptionFirstUpper(), OptionCaseMode(CMAllTitle)} + return WordsToFormattedCase(words, append(defaults, convertOptions(opts)...)...) } // ToCamelCase converts words into camelCase format. func ToCamelCase(words []Word, opts ...Option) (string, error) { - return WordsToFormattedCase(words, append(convertOptions(opts), OptionDelimiter(""), OptionFirstLower(), OptionCaseMode(CMAllTitle))...) + defaults := []any{OptionDelimiter(""), OptionFirstLower(), OptionCaseMode(CMAllTitle)} + return WordsToFormattedCase(words, append(defaults, convertOptions(opts)...)...) } From ae02043c17dda479bab9f3edd621c32ef5ffb57b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 03:00:39 +0000 Subject: [PATCH 2/7] feat(cli): enhance CLI flexibility and fix option precedence - Exposed library configuration options (delimiter, case mode, strictness, etc.) as CLI flags in `cli/main.go`. - Regenerated CLI command implementations using `gosubc` to support the new flags. - Refactored `strings2` helper functions (`ToCamel`, `ToSnake`, etc.) in `permutations.go` and `types.go` to apply default options before user options, allowing users to override defaults. - Updated `process` function in `cli/main.go` to pass parsed options to `strings2` functions as variadic arguments. - Added a section to `README.md` about the CLI mode. --- README.md | 9 +++ README.md.orig | 88 ++++++++++++++++++++++++ cli/main.go | 10 +-- cli/main.go.orig | 172 ++++++++++++++++++++++++++++++++++++++++++++++ patch.diff | 45 ++++++++++++ patch_readme.diff | 18 +++++ 6 files changed, 337 insertions(+), 5 deletions(-) create mode 100644 README.md.orig create mode 100644 cli/main.go.orig create mode 100644 patch.diff create mode 100644 patch_readme.diff diff --git a/README.md b/README.md index 27efea8..b2ed22a 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,15 @@ fmt.Println(strings2.ToKebabCase(words, strings2.OptionDelimiter("|"))) fmt.Println(strings2.ToSnakeCase(words, strings2.OptionCaseMode(strings2.CMScreaming))) ``` +### CLI Mode + +The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). + +```bash +# Screaming snake case +strings2 snake --screaming "hello world" +``` + Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. ## License diff --git a/README.md.orig b/README.md.orig new file mode 100644 index 0000000..27efea8 --- /dev/null +++ b/README.md.orig @@ -0,0 +1,88 @@ +# strings2 + +[![Test Status](https://github.com/arran4/strings2/actions/workflows/test.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/test.yml) +[![Vet Status](https://github.com/arran4/strings2/actions/workflows/vet.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/vet.yml) +[![Lint Status](https://github.com/arran4/strings2/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/golangci-lint.yml) +[![Fmt Status](https://github.com/arran4/strings2/actions/workflows/fmt.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/fmt.yml) +[![Go Reference](https://pkg.go.dev/badge/github.com/arran4/strings2.svg)](https://pkg.go.dev/github.com/arran4/strings2) + +strings2 provides utilities for converting slices of words into various casing conventions. It is intended to supplement Go's standard library `strings` package with helpers for creating formats such as `camelCase`, `PascalCase`, `snake_case` and `kebab-case`. + +## Installation + +``` +go get github.com/arran4/strings2 +``` + +Add the module to your project and import it: + +```go +import "github.com/arran4/strings2" +``` + +## Usage + +Words must implement `fmt.Stringer`. The package defines several helper types which satisfy this interface: + +```go +words := []strings2.Word{ + strings2.SingleCaseWord("hello"), + strings2.SingleCaseWord("world"), +} +``` + +### Parsing + +The library includes a robust parser to convert strings into typed `Word` objects, distinguishing between acronyms, casing, and delimiters. + +```go +// Auto-detect format and parse +words, err := strings2.Parse("helloWorld") +// Result: [SingleCaseWord("hello"), FirstUpperCaseWord("World")] + +// Parse specific format +words = strings2.ParseSnakeCase("hello_world") + +// Configure parser +words, err = strings2.Parse("N.E.W. World", strings2.ParserSmartAcronyms(true)) +``` + +### Case Conversion Functions + +```go +strings2.ToCamelCase(words) // "helloWorld" +strings2.ToPascalCase(words) // "HelloWorld" +strings2.ToKebabCase(words) // "hello-world" +strings2.ToSnakeCase(words) // "hello_world" +``` + +### Customising Formatting + +Behaviour can be tuned with options passed to each function. Some commonly used options include: + +- `OptionDelimiter(string)` – change the delimiter used between words. +- `OptionCaseMode(CaseMode)` – set the case transformation mode. Modes include: + - `CMVerbatim` + - `CMFirstTitle` + - `CMAllTitle` + - `CMFirstLower` + - `CMWhispering` + - `CMScreaming` +- `OptionFirstUpper()` – force the result to start with an uppercase letter. +- `OptionFirstLower()` – force the result to start with a lowercase letter. + +Examples: + +```go +// Custom delimiter +fmt.Println(strings2.ToKebabCase(words, strings2.OptionDelimiter("|"))) + +// Screaming snake case +fmt.Println(strings2.ToSnakeCase(words, strings2.OptionCaseMode(strings2.CMScreaming))) +``` + +Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. + +## License + +This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details. diff --git a/cli/main.go b/cli/main.go index 9d09c6b..f5e9090 100644 --- a/cli/main.go +++ b/cli/main.go @@ -9,7 +9,7 @@ import ( "github.com/arran4/strings2" ) -func process(input string, output string, args []string, opts []any, fn func(string, ...any) (string, error)) { +func process(input string, output string, args []string, fn func(string, ...any) (string, error), opts ...any) { var in io.Reader if input == "-" { in = os.Stdin @@ -105,7 +105,7 @@ func buildOpts(delimiter string, screaming bool, whispering bool, firstUpper boo // args: ... String to convert if file/stdin not provided func Camel(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) - process(input, output, args, opts, strings2.ToCamel) + process(input, output, args, strings2.ToCamel, opts...) } // Snake is a subcommand `strings2 snake` @@ -126,7 +126,7 @@ func Camel(input string, output string, delimiter string, screaming bool, whispe // args: ... String to convert if file/stdin not provided func Snake(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) - process(input, output, args, opts, strings2.ToSnake) + process(input, output, args, strings2.ToSnake, opts...) } // Kebab is a subcommand `strings2 kebab` @@ -147,7 +147,7 @@ func Snake(input string, output string, delimiter string, screaming bool, whispe // args: ... String to convert if file/stdin not provided func Kebab(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) - process(input, output, args, opts, strings2.ToKebab) + process(input, output, args, strings2.ToKebab, opts...) } // Pascal is a subcommand `strings2 pascal` @@ -168,5 +168,5 @@ func Kebab(input string, output string, delimiter string, screaming bool, whispe // args: ... String to convert if file/stdin not provided func Pascal(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) - process(input, output, args, opts, strings2.ToPascal) + process(input, output, args, strings2.ToPascal, opts...) } diff --git a/cli/main.go.orig b/cli/main.go.orig new file mode 100644 index 0000000..9d09c6b --- /dev/null +++ b/cli/main.go.orig @@ -0,0 +1,172 @@ +package cli + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/arran4/strings2" +) + +func process(input string, output string, args []string, opts []any, fn func(string, ...any) (string, error)) { + var in io.Reader + if input == "-" { + in = os.Stdin + } else if input != "" { + f, err := os.Open(input) + if err != nil { + fmt.Fprintf(os.Stderr, "Error opening input file: %v\n", err) + os.Exit(1) + } + defer f.Close() + in = f + } else if len(args) > 0 { + in = strings.NewReader(strings.Join(args, " ")) + } else { + in = os.Stdin + } + + b, err := io.ReadAll(in) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err) + os.Exit(1) + } + + res, err := fn(string(b), opts...) + if err != nil { + fmt.Fprintf(os.Stderr, "Error processing: %v\n", err) + os.Exit(1) + } + + var out io.Writer + if output == "-" || output == "" { + out = os.Stdout + } else { + f, err := os.Create(output) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err) + os.Exit(1) + } + defer f.Close() + out = f + } + + fmt.Fprintln(out, res) +} + +func buildOpts(delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool) []any { + var opts []any + if delimiter != "" { + opts = append(opts, strings2.OptionDelimiter(delimiter)) + } + if screaming { + opts = append(opts, strings2.OptionCaseMode(strings2.CMScreaming)) + } + if whispering { + opts = append(opts, strings2.OptionCaseMode(strings2.CMWhispering)) + } + if firstUpper { + opts = append(opts, strings2.OptionFirstUpper()) + } + if firstLower { + opts = append(opts, strings2.OptionFirstLower()) + } + if mixCaseSupport { + opts = append(opts, strings2.OptionMixCaseSupport()) + } + if noSmartAcronyms { + opts = append(opts, strings2.WithSmartAcronyms(false)) + } + if numberSplitting { + opts = append(opts, strings2.WithNumberSplitting(true)) + } + if strict { + opts = append(opts, strings2.OptionStrict()) + } + return opts +} + +// Camel is a subcommand `strings2 camel` +// +// Flags: +// +// input: -i --input (default: "") Input file or - for stdin +// output: -o --output (default: "") Output file or - for stdout +// delimiter: -d --delimiter (default: "") Delimiter +// screaming: -S --screaming (default: false) Screaming mode +// whispering: -w --whispering (default: false) Whispering mode +// firstUpper: -U --first-upper (default: false) First char upper +// firstLower: -l --first-lower (default: false) First char lower +// mixCaseSupport: -m --mix-case-support (default: false) Mix case support +// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms +// numberSplitting: --number-splitting (default: false) Enable number splitting +// strict: --strict (default: false) Strict UTF8 mode +// args: ... String to convert if file/stdin not provided +func Camel(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) + process(input, output, args, opts, strings2.ToCamel) +} + +// Snake is a subcommand `strings2 snake` +// +// Flags: +// +// input: -i --input (default: "") Input file or - for stdin +// output: -o --output (default: "") Output file or - for stdout +// delimiter: -d --delimiter (default: "") Delimiter +// screaming: -S --screaming (default: false) Screaming mode +// whispering: -w --whispering (default: false) Whispering mode +// firstUpper: -U --first-upper (default: false) First char upper +// firstLower: -l --first-lower (default: false) First char lower +// mixCaseSupport: -m --mix-case-support (default: false) Mix case support +// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms +// numberSplitting: --number-splitting (default: false) Enable number splitting +// strict: --strict (default: false) Strict UTF8 mode +// args: ... String to convert if file/stdin not provided +func Snake(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) + process(input, output, args, opts, strings2.ToSnake) +} + +// Kebab is a subcommand `strings2 kebab` +// +// Flags: +// +// input: -i --input (default: "") Input file or - for stdin +// output: -o --output (default: "") Output file or - for stdout +// delimiter: -d --delimiter (default: "") Delimiter +// screaming: -S --screaming (default: false) Screaming mode +// whispering: -w --whispering (default: false) Whispering mode +// firstUpper: -U --first-upper (default: false) First char upper +// firstLower: -l --first-lower (default: false) First char lower +// mixCaseSupport: -m --mix-case-support (default: false) Mix case support +// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms +// numberSplitting: --number-splitting (default: false) Enable number splitting +// strict: --strict (default: false) Strict UTF8 mode +// args: ... String to convert if file/stdin not provided +func Kebab(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) + process(input, output, args, opts, strings2.ToKebab) +} + +// Pascal is a subcommand `strings2 pascal` +// +// Flags: +// +// input: -i --input (default: "") Input file or - for stdin +// output: -o --output (default: "") Output file or - for stdout +// delimiter: -d --delimiter (default: "") Delimiter +// screaming: -S --screaming (default: false) Screaming mode +// whispering: -w --whispering (default: false) Whispering mode +// firstUpper: -U --first-upper (default: false) First char upper +// firstLower: -l --first-lower (default: false) First char lower +// mixCaseSupport: -m --mix-case-support (default: false) Mix case support +// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms +// numberSplitting: --number-splitting (default: false) Enable number splitting +// strict: --strict (default: false) Strict UTF8 mode +// args: ... String to convert if file/stdin not provided +func Pascal(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) + process(input, output, args, opts, strings2.ToPascal) +} diff --git a/patch.diff b/patch.diff new file mode 100644 index 0000000..0a0b266 --- /dev/null +++ b/patch.diff @@ -0,0 +1,45 @@ +--- cli/main.go ++++ cli/main.go +@@ -9,7 +9,7 @@ + "github.com/arran4/strings2" + ) + +-func process(input string, output string, args []string, opts []any, fn func(string, ...any) (string, error)) { ++func process(input string, output string, args []string, fn func(string, ...any) (string, error), opts ...any) { + var in io.Reader + if input == "-" { + in = os.Stdin +@@ -83,7 +83,7 @@ + // args: ... String to convert if file/stdin not provided + func Camel(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) +- process(input, output, args, opts, strings2.ToCamel) ++ process(input, output, args, strings2.ToCamel, opts...) + } + + // Snake is a subcommand `strings2 snake` +@@ -103,7 +103,7 @@ + // args: ... String to convert if file/stdin not provided + func Snake(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) +- process(input, output, args, opts, strings2.ToSnake) ++ process(input, output, args, strings2.ToSnake, opts...) + } + + // Kebab is a subcommand `strings2 kebab` +@@ -123,7 +123,7 @@ + // args: ... String to convert if file/stdin not provided + func Kebab(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) +- process(input, output, args, opts, strings2.ToKebab) ++ process(input, output, args, strings2.ToKebab, opts...) + } + + // Pascal is a subcommand `strings2 pascal` +@@ -143,6 +143,6 @@ + // args: ... String to convert if file/stdin not provided + func Pascal(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { + opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) +- process(input, output, args, opts, strings2.ToPascal) ++ process(input, output, args, strings2.ToPascal, opts...) + } diff --git a/patch_readme.diff b/patch_readme.diff new file mode 100644 index 0000000..ecb261b --- /dev/null +++ b/patch_readme.diff @@ -0,0 +1,18 @@ +--- README.md ++++ README.md +@@ -48,6 +48,15 @@ + fmt.Println(strings2.ToSnakeCase(words, strings2.OptionCaseMode(strings2.CMScreaming))) + ``` + ++### CLI Mode ++ ++The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). ++ ++```bash ++# Screaming snake case ++strings2 snake --screaming "hello world" ++``` ++ + Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. + + ## License From f516b0ddb26447ba15f8e4fbcdd2e1e7b64002ff Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 03:06:58 +0000 Subject: [PATCH 3/7] feat(cli): enhance CLI flexibility and fix option precedence - Exposed library configuration options (delimiter, case mode, strictness, etc.) as CLI flags in `cli/main.go`. - Regenerated CLI command implementations using `gosubc` to support the new flags. - Refactored `strings2` helper functions (`ToCamel`, `ToSnake`, etc.) in `permutations.go` and `types.go` to apply default options before user options, allowing users to override defaults. - Updated `process` function in `cli/main.go` to pass parsed options to `strings2` functions as variadic arguments. - Added a section to `README.md` about the CLI mode. From fba26e19534fba2a1ce068cdb7a78bbcbc4fb726 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 04:43:24 +0000 Subject: [PATCH 4/7] feat(cli): enhance CLI flexibility and fix option precedence - Exposed library configuration options (delimiter, case mode, strictness, etc.) as CLI flags in `cli/main.go`. - Regenerated CLI command implementations using `gosubc` to support the new flags. - Refactored `strings2` helper functions (`ToCamel`, `ToSnake`, etc.) in `permutations.go` and `types.go` to apply default options before user options, allowing users to override defaults. - Updated `process` function in `cli/main.go` to pass parsed options to `strings2` functions as variadic arguments. - Added a section to `README.md` about the CLI mode. - Added a TODO to `README.md` to support slices for flags when `gosubc` supports it. --- README.md | 4 ++++ README.md.orig | 9 +++++++++ patch_todo.diff | 13 +++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 patch_todo.diff diff --git a/README.md b/README.md index b2ed22a..8b7d495 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,10 @@ strings2 snake --screaming "hello world" Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. +## TODO + +- Support slices for flags when the gosubc version supports it. + ## License This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details. diff --git a/README.md.orig b/README.md.orig index 27efea8..b2ed22a 100644 --- a/README.md.orig +++ b/README.md.orig @@ -81,6 +81,15 @@ fmt.Println(strings2.ToKebabCase(words, strings2.OptionDelimiter("|"))) fmt.Println(strings2.ToSnakeCase(words, strings2.OptionCaseMode(strings2.CMScreaming))) ``` +### CLI Mode + +The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). + +```bash +# Screaming snake case +strings2 snake --screaming "hello world" +``` + Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. ## License diff --git a/patch_todo.diff b/patch_todo.diff new file mode 100644 index 0000000..adc3d64 --- /dev/null +++ b/patch_todo.diff @@ -0,0 +1,13 @@ +--- README.md ++++ README.md +@@ -58,6 +58,10 @@ + + Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. + ++## TODO ++ ++- Support slices for flags when the gosubc version supports it. ++ + ## License + + This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details. From 64435b9c3685e408d994a01b87a8dcc47713d065 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 04:46:57 +0000 Subject: [PATCH 5/7] feat(cli): enhance CLI flexibility and fix option precedence - Exposed library configuration options (delimiter, case mode, strictness, etc.) as CLI flags in `cli/main.go`. - Regenerated CLI command implementations using `gosubc` to support the new flags. - Refactored `strings2` helper functions (`ToCamel`, `ToSnake`, etc.) in `permutations.go` and `types.go` to apply default options before user options, allowing users to override defaults. - Updated `process` function in `cli/main.go` to pass parsed options to `strings2` functions as variadic arguments. - Added a section to `README.md` about the CLI mode. - Added a TODO to `README.md` to support slices for flags when `gosubc` supports it. - Removed accidentally committed `.diff` and `.orig` files. --- README.md.orig | 97 -------------------------- cli/main.go.orig | 172 ---------------------------------------------- patch.diff | 45 ------------ patch_readme.diff | 18 ----- patch_todo.diff | 13 ---- 5 files changed, 345 deletions(-) delete mode 100644 README.md.orig delete mode 100644 cli/main.go.orig delete mode 100644 patch.diff delete mode 100644 patch_readme.diff delete mode 100644 patch_todo.diff diff --git a/README.md.orig b/README.md.orig deleted file mode 100644 index b2ed22a..0000000 --- a/README.md.orig +++ /dev/null @@ -1,97 +0,0 @@ -# strings2 - -[![Test Status](https://github.com/arran4/strings2/actions/workflows/test.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/test.yml) -[![Vet Status](https://github.com/arran4/strings2/actions/workflows/vet.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/vet.yml) -[![Lint Status](https://github.com/arran4/strings2/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/golangci-lint.yml) -[![Fmt Status](https://github.com/arran4/strings2/actions/workflows/fmt.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/fmt.yml) -[![Go Reference](https://pkg.go.dev/badge/github.com/arran4/strings2.svg)](https://pkg.go.dev/github.com/arran4/strings2) - -strings2 provides utilities for converting slices of words into various casing conventions. It is intended to supplement Go's standard library `strings` package with helpers for creating formats such as `camelCase`, `PascalCase`, `snake_case` and `kebab-case`. - -## Installation - -``` -go get github.com/arran4/strings2 -``` - -Add the module to your project and import it: - -```go -import "github.com/arran4/strings2" -``` - -## Usage - -Words must implement `fmt.Stringer`. The package defines several helper types which satisfy this interface: - -```go -words := []strings2.Word{ - strings2.SingleCaseWord("hello"), - strings2.SingleCaseWord("world"), -} -``` - -### Parsing - -The library includes a robust parser to convert strings into typed `Word` objects, distinguishing between acronyms, casing, and delimiters. - -```go -// Auto-detect format and parse -words, err := strings2.Parse("helloWorld") -// Result: [SingleCaseWord("hello"), FirstUpperCaseWord("World")] - -// Parse specific format -words = strings2.ParseSnakeCase("hello_world") - -// Configure parser -words, err = strings2.Parse("N.E.W. World", strings2.ParserSmartAcronyms(true)) -``` - -### Case Conversion Functions - -```go -strings2.ToCamelCase(words) // "helloWorld" -strings2.ToPascalCase(words) // "HelloWorld" -strings2.ToKebabCase(words) // "hello-world" -strings2.ToSnakeCase(words) // "hello_world" -``` - -### Customising Formatting - -Behaviour can be tuned with options passed to each function. Some commonly used options include: - -- `OptionDelimiter(string)` – change the delimiter used between words. -- `OptionCaseMode(CaseMode)` – set the case transformation mode. Modes include: - - `CMVerbatim` - - `CMFirstTitle` - - `CMAllTitle` - - `CMFirstLower` - - `CMWhispering` - - `CMScreaming` -- `OptionFirstUpper()` – force the result to start with an uppercase letter. -- `OptionFirstLower()` – force the result to start with a lowercase letter. - -Examples: - -```go -// Custom delimiter -fmt.Println(strings2.ToKebabCase(words, strings2.OptionDelimiter("|"))) - -// Screaming snake case -fmt.Println(strings2.ToSnakeCase(words, strings2.OptionCaseMode(strings2.CMScreaming))) -``` - -### CLI Mode - -The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). - -```bash -# Screaming snake case -strings2 snake --screaming "hello world" -``` - -Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. - -## License - -This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details. diff --git a/cli/main.go.orig b/cli/main.go.orig deleted file mode 100644 index 9d09c6b..0000000 --- a/cli/main.go.orig +++ /dev/null @@ -1,172 +0,0 @@ -package cli - -import ( - "fmt" - "io" - "os" - "strings" - - "github.com/arran4/strings2" -) - -func process(input string, output string, args []string, opts []any, fn func(string, ...any) (string, error)) { - var in io.Reader - if input == "-" { - in = os.Stdin - } else if input != "" { - f, err := os.Open(input) - if err != nil { - fmt.Fprintf(os.Stderr, "Error opening input file: %v\n", err) - os.Exit(1) - } - defer f.Close() - in = f - } else if len(args) > 0 { - in = strings.NewReader(strings.Join(args, " ")) - } else { - in = os.Stdin - } - - b, err := io.ReadAll(in) - if err != nil { - fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err) - os.Exit(1) - } - - res, err := fn(string(b), opts...) - if err != nil { - fmt.Fprintf(os.Stderr, "Error processing: %v\n", err) - os.Exit(1) - } - - var out io.Writer - if output == "-" || output == "" { - out = os.Stdout - } else { - f, err := os.Create(output) - if err != nil { - fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err) - os.Exit(1) - } - defer f.Close() - out = f - } - - fmt.Fprintln(out, res) -} - -func buildOpts(delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool) []any { - var opts []any - if delimiter != "" { - opts = append(opts, strings2.OptionDelimiter(delimiter)) - } - if screaming { - opts = append(opts, strings2.OptionCaseMode(strings2.CMScreaming)) - } - if whispering { - opts = append(opts, strings2.OptionCaseMode(strings2.CMWhispering)) - } - if firstUpper { - opts = append(opts, strings2.OptionFirstUpper()) - } - if firstLower { - opts = append(opts, strings2.OptionFirstLower()) - } - if mixCaseSupport { - opts = append(opts, strings2.OptionMixCaseSupport()) - } - if noSmartAcronyms { - opts = append(opts, strings2.WithSmartAcronyms(false)) - } - if numberSplitting { - opts = append(opts, strings2.WithNumberSplitting(true)) - } - if strict { - opts = append(opts, strings2.OptionStrict()) - } - return opts -} - -// Camel is a subcommand `strings2 camel` -// -// Flags: -// -// input: -i --input (default: "") Input file or - for stdin -// output: -o --output (default: "") Output file or - for stdout -// delimiter: -d --delimiter (default: "") Delimiter -// screaming: -S --screaming (default: false) Screaming mode -// whispering: -w --whispering (default: false) Whispering mode -// firstUpper: -U --first-upper (default: false) First char upper -// firstLower: -l --first-lower (default: false) First char lower -// mixCaseSupport: -m --mix-case-support (default: false) Mix case support -// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms -// numberSplitting: --number-splitting (default: false) Enable number splitting -// strict: --strict (default: false) Strict UTF8 mode -// args: ... String to convert if file/stdin not provided -func Camel(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { - opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) - process(input, output, args, opts, strings2.ToCamel) -} - -// Snake is a subcommand `strings2 snake` -// -// Flags: -// -// input: -i --input (default: "") Input file or - for stdin -// output: -o --output (default: "") Output file or - for stdout -// delimiter: -d --delimiter (default: "") Delimiter -// screaming: -S --screaming (default: false) Screaming mode -// whispering: -w --whispering (default: false) Whispering mode -// firstUpper: -U --first-upper (default: false) First char upper -// firstLower: -l --first-lower (default: false) First char lower -// mixCaseSupport: -m --mix-case-support (default: false) Mix case support -// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms -// numberSplitting: --number-splitting (default: false) Enable number splitting -// strict: --strict (default: false) Strict UTF8 mode -// args: ... String to convert if file/stdin not provided -func Snake(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { - opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) - process(input, output, args, opts, strings2.ToSnake) -} - -// Kebab is a subcommand `strings2 kebab` -// -// Flags: -// -// input: -i --input (default: "") Input file or - for stdin -// output: -o --output (default: "") Output file or - for stdout -// delimiter: -d --delimiter (default: "") Delimiter -// screaming: -S --screaming (default: false) Screaming mode -// whispering: -w --whispering (default: false) Whispering mode -// firstUpper: -U --first-upper (default: false) First char upper -// firstLower: -l --first-lower (default: false) First char lower -// mixCaseSupport: -m --mix-case-support (default: false) Mix case support -// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms -// numberSplitting: --number-splitting (default: false) Enable number splitting -// strict: --strict (default: false) Strict UTF8 mode -// args: ... String to convert if file/stdin not provided -func Kebab(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { - opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) - process(input, output, args, opts, strings2.ToKebab) -} - -// Pascal is a subcommand `strings2 pascal` -// -// Flags: -// -// input: -i --input (default: "") Input file or - for stdin -// output: -o --output (default: "") Output file or - for stdout -// delimiter: -d --delimiter (default: "") Delimiter -// screaming: -S --screaming (default: false) Screaming mode -// whispering: -w --whispering (default: false) Whispering mode -// firstUpper: -U --first-upper (default: false) First char upper -// firstLower: -l --first-lower (default: false) First char lower -// mixCaseSupport: -m --mix-case-support (default: false) Mix case support -// noSmartAcronyms: --no-smart-acronyms (default: false) Disable smart acronyms -// numberSplitting: --number-splitting (default: false) Enable number splitting -// strict: --strict (default: false) Strict UTF8 mode -// args: ... String to convert if file/stdin not provided -func Pascal(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { - opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) - process(input, output, args, opts, strings2.ToPascal) -} diff --git a/patch.diff b/patch.diff deleted file mode 100644 index 0a0b266..0000000 --- a/patch.diff +++ /dev/null @@ -1,45 +0,0 @@ ---- cli/main.go -+++ cli/main.go -@@ -9,7 +9,7 @@ - "github.com/arran4/strings2" - ) - --func process(input string, output string, args []string, opts []any, fn func(string, ...any) (string, error)) { -+func process(input string, output string, args []string, fn func(string, ...any) (string, error), opts ...any) { - var in io.Reader - if input == "-" { - in = os.Stdin -@@ -83,7 +83,7 @@ - // args: ... String to convert if file/stdin not provided - func Camel(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { - opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) -- process(input, output, args, opts, strings2.ToCamel) -+ process(input, output, args, strings2.ToCamel, opts...) - } - - // Snake is a subcommand `strings2 snake` -@@ -103,7 +103,7 @@ - // args: ... String to convert if file/stdin not provided - func Snake(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { - opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) -- process(input, output, args, opts, strings2.ToSnake) -+ process(input, output, args, strings2.ToSnake, opts...) - } - - // Kebab is a subcommand `strings2 kebab` -@@ -123,7 +123,7 @@ - // args: ... String to convert if file/stdin not provided - func Kebab(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { - opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) -- process(input, output, args, opts, strings2.ToKebab) -+ process(input, output, args, strings2.ToKebab, opts...) - } - - // Pascal is a subcommand `strings2 pascal` -@@ -143,6 +143,6 @@ - // args: ... String to convert if file/stdin not provided - func Pascal(input string, output string, delimiter string, screaming bool, whispering bool, firstUpper bool, firstLower bool, mixCaseSupport bool, noSmartAcronyms bool, numberSplitting bool, strict bool, args ...string) { - opts := buildOpts(delimiter, screaming, whispering, firstUpper, firstLower, mixCaseSupport, noSmartAcronyms, numberSplitting, strict) -- process(input, output, args, opts, strings2.ToPascal) -+ process(input, output, args, strings2.ToPascal, opts...) - } diff --git a/patch_readme.diff b/patch_readme.diff deleted file mode 100644 index ecb261b..0000000 --- a/patch_readme.diff +++ /dev/null @@ -1,18 +0,0 @@ ---- README.md -+++ README.md -@@ -48,6 +48,15 @@ - fmt.Println(strings2.ToSnakeCase(words, strings2.OptionCaseMode(strings2.CMScreaming))) - ``` - -+### CLI Mode -+ -+The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). -+ -+```bash -+# Screaming snake case -+strings2 snake --screaming "hello world" -+``` -+ - Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. - - ## License diff --git a/patch_todo.diff b/patch_todo.diff deleted file mode 100644 index adc3d64..0000000 --- a/patch_todo.diff +++ /dev/null @@ -1,13 +0,0 @@ ---- README.md -+++ README.md -@@ -58,6 +58,10 @@ - - Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. - -+## TODO -+ -+- Support slices for flags when the gosubc version supports it. -+ - ## License - - This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details. From e1193c746065a6b0d5cb96dcffa0745185f3fca1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 04:53:11 +0000 Subject: [PATCH 6/7] feat(cli): enhance CLI flexibility and fix option precedence - Exposed library configuration options (delimiter, case mode, strictness, etc.) as CLI flags in `cli/main.go`. - Regenerated CLI command implementations using `gosubc` to support the new flags. - Refactored `strings2` helper functions (`ToCamel`, `ToSnake`, etc.) in `permutations.go` and `types.go` to apply default options before user options, allowing users to override defaults. - Updated `process` function in `cli/main.go` to pass parsed options to `strings2` functions as variadic arguments. - Added a section to `README.md` about the CLI mode, demonstrating flag usage. - Added a TODO to `README.md` to support slices for flags when `gosubc` supports it. - Removed accidentally committed `.diff` and `.orig` files. --- README.md | 25 ++++++++- README.md.orig | 127 +++++++++++++++++++++++++++++++++++++++++++++ patch_readme2.diff | 37 +++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 README.md.orig create mode 100644 patch_readme2.diff diff --git a/README.md b/README.md index 8b7d495..eaee53f 100644 --- a/README.md +++ b/README.md @@ -86,8 +86,31 @@ fmt.Println(strings2.ToSnakeCase(words, strings2.OptionCaseMode(strings2.CMScrea The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). ```bash -# Screaming snake case +strings2 camel "hello world" +# Result: helloWorld + strings2 snake --screaming "hello world" +# Result: HELLO_WORLD + +strings2 kebab --first-upper "hello world" +# Result: Hello-world +``` + +You can pipe input into the CLI as well: +```bash +echo "hello world" | strings2 pascal +# Result: HelloWorld +``` + +Available flags across commands: +- `--delimiter`, `-d` (string): Override the delimiter +- `--screaming`, `-S`: Enforce uppercase formatting +- `--whispering`, `-w`: Enforce lowercase formatting +- `--first-upper`, `-U`: Capitalize the first letter +- `--first-lower`, `-l`: Lowercase the first letter +- `--mix-case-support`, `-m`: Enable splitting of mixed case words +- `--no-smart-acronyms`: Disable acronym preservation +- `--number-splitting`: Enable letter-digit boundary splitting ``` Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. diff --git a/README.md.orig b/README.md.orig new file mode 100644 index 0000000..c1762e2 --- /dev/null +++ b/README.md.orig @@ -0,0 +1,127 @@ +# strings2 + +[![Test Status](https://github.com/arran4/strings2/actions/workflows/test.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/test.yml) +[![Vet Status](https://github.com/arran4/strings2/actions/workflows/vet.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/vet.yml) +[![Lint Status](https://github.com/arran4/strings2/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/golangci-lint.yml) +[![Fmt Status](https://github.com/arran4/strings2/actions/workflows/fmt.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/fmt.yml) +[![Go Reference](https://pkg.go.dev/badge/github.com/arran4/strings2.svg)](https://pkg.go.dev/github.com/arran4/strings2) + +strings2 provides utilities for converting slices of words into various casing conventions. It is intended to supplement Go's standard library `strings` package with helpers for creating formats such as `camelCase`, `PascalCase`, `snake_case` and `kebab-case`. + +## Installation + +``` +go get github.com/arran4/strings2 +``` + +Add the module to your project and import it: + +```go +import "github.com/arran4/strings2" +``` + +## Usage + +Words must implement `fmt.Stringer`. The package defines several helper types which satisfy this interface: + +```go +words := []strings2.Word{ + strings2.SingleCaseWord("hello"), + strings2.SingleCaseWord("world"), +} +``` + +### Parsing + +The library includes a robust parser to convert strings into typed `Word` objects, distinguishing between acronyms, casing, and delimiters. + +```go +// Auto-detect format and parse +words, err := strings2.Parse("helloWorld") +// Result: [SingleCaseWord("hello"), FirstUpperCaseWord("World")] + +// Parse specific format +words = strings2.ParseSnakeCase("hello_world") + +// Configure parser +words, err = strings2.Parse("N.E.W. World", strings2.ParserSmartAcronyms(true)) +``` + +### Case Conversion Functions + +```go +strings2.ToCamelCase(words) // "helloWorld" +strings2.ToPascalCase(words) // "HelloWorld" +strings2.ToKebabCase(words) // "hello-world" +strings2.ToSnakeCase(words) // "hello_world" +``` + +### Customising Formatting + +Behaviour can be tuned with options passed to each function. Some commonly used options include: + +- `OptionDelimiter(string)` – change the delimiter used between words. +- `OptionCaseMode(CaseMode)` – set the case transformation mode. Modes include: + - `CMVerbatim` + - `CMFirstTitle` + - `CMAllTitle` + - `CMFirstLower` + - `CMWhispering` + - `CMScreaming` +- `OptionFirstUpper()` – force the result to start with an uppercase letter. +- `OptionFirstLower()` – force the result to start with a lowercase letter. + +Examples: + +```go +// Custom delimiter +fmt.Println(strings2.ToKebabCase(words, strings2.OptionDelimiter("|"))) + +// Screaming snake case +fmt.Println(strings2.ToSnakeCase(words, strings2.OptionCaseMode(strings2.CMScreaming))) +``` + +### CLI Mode + +The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). + +```bash +strings2 camel "hello world" +# Result: helloWorld + +strings2 snake --screaming "hello world" +# Result: HELLO_WORLD + +strings2 kebab --first-upper "hello world" +# Result: Hello-world +``` + +You can pipe input into the CLI as well: +```bash +echo "hello world" | strings2 pascal +# Result: HelloWorld +``` + +Available flags across commands: +- `--delimiter`, `-d` (string): Override the delimiter +- `--screaming`, `-S`: Enforce uppercase formatting +- `--whispering`, `-w`: Enforce lowercase formatting +- `--first-upper`, `-U`: Capitalize the first letter +- `--first-lower`, `-l`: Lowercase the first letter +- `--mix-case-support`, `-m`: Enable splitting of mixed case words +- `--no-smart-acronyms`: Disable acronym preservation +- `--number-splitting`: Enable letter-digit boundary splitting + +# Screaming snake case +strings2 snake --screaming "hello world" +``` + +Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. + +## TODO + +- Support slices for flags when the gosubc version supports it. + +## License + +This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details. diff --git a/patch_readme2.diff b/patch_readme2.diff new file mode 100644 index 0000000..220d47e --- /dev/null +++ b/patch_readme2.diff @@ -0,0 +1,37 @@ +--- README.md ++++ README.md +@@ -53,8 +53,30 @@ + + The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). + + ```bash +-# Screaming snake case +-strings2 snake --screaming "hello world" ++strings2 camel "hello world" ++# Result: helloWorld ++ ++strings2 snake --screaming "hello world" ++# Result: HELLO_WORLD ++ ++strings2 kebab --first-upper "hello world" ++# Result: Hello-world ++``` ++ ++You can pipe input into the CLI as well: ++```bash ++echo "hello world" | strings2 pascal ++# Result: HelloWorld ++``` ++ ++Available flags across commands: ++- `--delimiter`, `-d` (string): Override the delimiter ++- `--screaming`, `-S`: Enforce uppercase formatting ++- `--whispering`, `-w`: Enforce lowercase formatting ++- `--first-upper`, `-U`: Capitalize the first letter ++- `--first-lower`, `-l`: Lowercase the first letter ++- `--mix-case-support`, `-m`: Enable splitting of mixed case words ++- `--no-smart-acronyms`: Disable acronym preservation ++- `--number-splitting`: Enable letter-digit boundary splitting + ``` + + Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. From e136f64e947fa57c93faa977a86c84506d4aeb0a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 04:58:20 +0000 Subject: [PATCH 7/7] feat(cli): enhance CLI flexibility and fix option precedence - Exposed library configuration options (delimiter, case mode, strictness, etc.) as CLI flags in `cli/main.go`. - Regenerated CLI command implementations using `gosubc` to support the new flags. - Refactored `strings2` helper functions (`ToCamel`, `ToSnake`, etc.) in `permutations.go` and `types.go` to apply default options before user options, allowing users to override defaults. - Updated `process` function in `cli/main.go` to pass parsed options to `strings2` functions as variadic arguments. - Added a section to `README.md` about the CLI mode, demonstrating flag usage. - Added a TODO to `README.md` to support slices for flags when `gosubc` supports it. - Removed accidentally committed `.diff` and `.orig` files. --- README.md.orig | 127 --------------------------------------------- patch_readme2.diff | 37 ------------- 2 files changed, 164 deletions(-) delete mode 100644 README.md.orig delete mode 100644 patch_readme2.diff diff --git a/README.md.orig b/README.md.orig deleted file mode 100644 index c1762e2..0000000 --- a/README.md.orig +++ /dev/null @@ -1,127 +0,0 @@ -# strings2 - -[![Test Status](https://github.com/arran4/strings2/actions/workflows/test.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/test.yml) -[![Vet Status](https://github.com/arran4/strings2/actions/workflows/vet.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/vet.yml) -[![Lint Status](https://github.com/arran4/strings2/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/golangci-lint.yml) -[![Fmt Status](https://github.com/arran4/strings2/actions/workflows/fmt.yml/badge.svg)](https://github.com/arran4/strings2/actions/workflows/fmt.yml) -[![Go Reference](https://pkg.go.dev/badge/github.com/arran4/strings2.svg)](https://pkg.go.dev/github.com/arran4/strings2) - -strings2 provides utilities for converting slices of words into various casing conventions. It is intended to supplement Go's standard library `strings` package with helpers for creating formats such as `camelCase`, `PascalCase`, `snake_case` and `kebab-case`. - -## Installation - -``` -go get github.com/arran4/strings2 -``` - -Add the module to your project and import it: - -```go -import "github.com/arran4/strings2" -``` - -## Usage - -Words must implement `fmt.Stringer`. The package defines several helper types which satisfy this interface: - -```go -words := []strings2.Word{ - strings2.SingleCaseWord("hello"), - strings2.SingleCaseWord("world"), -} -``` - -### Parsing - -The library includes a robust parser to convert strings into typed `Word` objects, distinguishing between acronyms, casing, and delimiters. - -```go -// Auto-detect format and parse -words, err := strings2.Parse("helloWorld") -// Result: [SingleCaseWord("hello"), FirstUpperCaseWord("World")] - -// Parse specific format -words = strings2.ParseSnakeCase("hello_world") - -// Configure parser -words, err = strings2.Parse("N.E.W. World", strings2.ParserSmartAcronyms(true)) -``` - -### Case Conversion Functions - -```go -strings2.ToCamelCase(words) // "helloWorld" -strings2.ToPascalCase(words) // "HelloWorld" -strings2.ToKebabCase(words) // "hello-world" -strings2.ToSnakeCase(words) // "hello_world" -``` - -### Customising Formatting - -Behaviour can be tuned with options passed to each function. Some commonly used options include: - -- `OptionDelimiter(string)` – change the delimiter used between words. -- `OptionCaseMode(CaseMode)` – set the case transformation mode. Modes include: - - `CMVerbatim` - - `CMFirstTitle` - - `CMAllTitle` - - `CMFirstLower` - - `CMWhispering` - - `CMScreaming` -- `OptionFirstUpper()` – force the result to start with an uppercase letter. -- `OptionFirstLower()` – force the result to start with a lowercase letter. - -Examples: - -```go -// Custom delimiter -fmt.Println(strings2.ToKebabCase(words, strings2.OptionDelimiter("|"))) - -// Screaming snake case -fmt.Println(strings2.ToSnakeCase(words, strings2.OptionCaseMode(strings2.CMScreaming))) -``` - -### CLI Mode - -The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). - -```bash -strings2 camel "hello world" -# Result: helloWorld - -strings2 snake --screaming "hello world" -# Result: HELLO_WORLD - -strings2 kebab --first-upper "hello world" -# Result: Hello-world -``` - -You can pipe input into the CLI as well: -```bash -echo "hello world" | strings2 pascal -# Result: HelloWorld -``` - -Available flags across commands: -- `--delimiter`, `-d` (string): Override the delimiter -- `--screaming`, `-S`: Enforce uppercase formatting -- `--whispering`, `-w`: Enforce lowercase formatting -- `--first-upper`, `-U`: Capitalize the first letter -- `--first-lower`, `-l`: Lowercase the first letter -- `--mix-case-support`, `-m`: Enable splitting of mixed case words -- `--no-smart-acronyms`: Disable acronym preservation -- `--number-splitting`: Enable letter-digit boundary splitting - -# Screaming snake case -strings2 snake --screaming "hello world" -``` - -Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options. - -## TODO - -- Support slices for flags when the gosubc version supports it. - -## License - -This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details. diff --git a/patch_readme2.diff b/patch_readme2.diff deleted file mode 100644 index 220d47e..0000000 --- a/patch_readme2.diff +++ /dev/null @@ -1,37 +0,0 @@ ---- README.md -+++ README.md -@@ -53,8 +53,30 @@ - - The library also provides a command-line interface that exposes all these options, ensuring that the CLI mode has as much flexibility as the code (without being obligated to use smart defaults). - - ```bash --# Screaming snake case --strings2 snake --screaming "hello world" -+strings2 camel "hello world" -+# Result: helloWorld -+ -+strings2 snake --screaming "hello world" -+# Result: HELLO_WORLD -+ -+strings2 kebab --first-upper "hello world" -+# Result: Hello-world -+``` -+ -+You can pipe input into the CLI as well: -+```bash -+echo "hello world" | strings2 pascal -+# Result: HelloWorld -+``` -+ -+Available flags across commands: -+- `--delimiter`, `-d` (string): Override the delimiter -+- `--screaming`, `-S`: Enforce uppercase formatting -+- `--whispering`, `-w`: Enforce lowercase formatting -+- `--first-upper`, `-U`: Capitalize the first letter -+- `--first-lower`, `-l`: Lowercase the first letter -+- `--mix-case-support`, `-m`: Enable splitting of mixed case words -+- `--no-smart-acronyms`: Disable acronym preservation -+- `--number-splitting`: Enable letter-digit boundary splitting - ``` - - Options are composable so multiple behaviours can be applied at once. See the documentation in `types.go` for details on further options.