Skip to content

Commit 48b990b

Browse files
authored
Merge pull request #24 from arran4/feature/add-strings2-cli-1191961735426199853
Add CLI using go-subcommand v0.0.17
2 parents 47be303 + e852a66 commit 48b990b

21 files changed

Lines changed: 1280 additions & 4 deletions

.github/workflows/release.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by github.com/arran4/go-subcommand/cmd/gosubc
2+
name: Release
3+
4+
permissions:
5+
contents: write
6+
7+
on:
8+
push:
9+
tags:
10+
- 'v*'
11+
12+
jobs:
13+
goreleaser:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
- name: Set up Go
21+
uses: actions/setup-go@v5
22+
with:
23+
go-version: stable
24+
- name: Login to GHCR
25+
uses: docker/login-action@v3
26+
with:
27+
registry: ghcr.io
28+
username: ${{ github.repository_owner }}
29+
password: ${{ secrets.GITHUB_TOKEN }}
30+
- name: Run GoReleaser
31+
uses: goreleaser/goreleaser-action@v5
32+
with:
33+
distribution: goreleaser
34+
version: latest
35+
args: release --clean
36+
env:
37+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.goreleaser.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Generated by github.com/arran4/go-subcommand/cmd/gosubc
2+
# .goreleaser.yml
3+
4+
before:
5+
hooks:
6+
- go mod tidy
7+
8+
builds:
9+
- id: strings2
10+
main: ./cmd/strings2
11+
binary: strings2
12+
env:
13+
- CGO_ENABLED=0
14+
goos:
15+
- linux
16+
- windows
17+
- darwin
18+
goarch:
19+
- amd64
20+
- arm64
21+
22+
archives:
23+
- format: tar.gz
24+
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
25+
files:
26+
- LICENSE
27+
- README.md
28+
29+
checksum:
30+
name_template: 'checksums.txt'
31+
32+
snapshot:
33+
name_template: "{{ .Tag }}-next"
34+
35+
changelog:
36+
sort: asc
37+
filters:
38+
exclude:
39+
- '^docs:'
40+
- '^test:'

cli/main.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"strings"
8+
9+
"github.com/arran4/strings2"
10+
)
11+
12+
func process(input string, output string, args []string, fn func(string, ...any) (string, error)) {
13+
var in io.Reader
14+
if input == "-" {
15+
in = os.Stdin
16+
} else if input != "" {
17+
f, err := os.Open(input)
18+
if err != nil {
19+
fmt.Fprintf(os.Stderr, "Error opening input file: %v\n", err)
20+
os.Exit(1)
21+
}
22+
defer f.Close()
23+
in = f
24+
} else if len(args) > 0 {
25+
in = strings.NewReader(strings.Join(args, " "))
26+
} else {
27+
in = os.Stdin
28+
}
29+
30+
b, err := io.ReadAll(in)
31+
if err != nil {
32+
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
33+
os.Exit(1)
34+
}
35+
36+
res, err := fn(string(b))
37+
if err != nil {
38+
fmt.Fprintf(os.Stderr, "Error processing: %v\n", err)
39+
os.Exit(1)
40+
}
41+
42+
var out io.Writer
43+
if output == "-" || output == "" {
44+
out = os.Stdout
45+
} else {
46+
f, err := os.Create(output)
47+
if err != nil {
48+
fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err)
49+
os.Exit(1)
50+
}
51+
defer f.Close()
52+
out = f
53+
}
54+
55+
fmt.Fprintln(out, res)
56+
}
57+
58+
// Camel is a subcommand `strings2 camel`
59+
//
60+
// Flags:
61+
//
62+
// input: -i --input (default: "") Input file or - for stdin
63+
// output: -o --output (default: "") Output file or - for stdout
64+
// args: ... String to convert if file/stdin not provided
65+
func Camel(input string, output string, args ...string) {
66+
process(input, output, args, strings2.ToCamel)
67+
}
68+
69+
// Snake is a subcommand `strings2 snake`
70+
//
71+
// Flags:
72+
//
73+
// input: -i --input (default: "") Input file or - for stdin
74+
// output: -o --output (default: "") Output file or - for stdout
75+
// args: ... String to convert if file/stdin not provided
76+
func Snake(input string, output string, args ...string) {
77+
process(input, output, args, strings2.ToSnake)
78+
}
79+
80+
// Kebab is a subcommand `strings2 kebab`
81+
//
82+
// Flags:
83+
//
84+
// input: -i --input (default: "") Input file or - for stdin
85+
// output: -o --output (default: "") Output file or - for stdout
86+
// args: ... String to convert if file/stdin not provided
87+
func Kebab(input string, output string, args ...string) {
88+
process(input, output, args, strings2.ToKebab)
89+
}
90+
91+
// Pascal is a subcommand `strings2 pascal`
92+
//
93+
// Flags:
94+
//
95+
// input: -i --input (default: "") Input file or - for stdin
96+
// output: -o --output (default: "") Output file or - for stdout
97+
// args: ... String to convert if file/stdin not provided
98+
func Pascal(input string, output string, args ...string) {
99+
process(input, output, args, strings2.ToPascal)
100+
}

cmd/errors.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Generated by github.com/arran4/go-subcommand/cmd/gosubc
2+
3+
package cmd
4+
5+
import "errors"
6+
7+
// ErrPrintHelp when returned by any function anywhere it will switch the command from whatever it is to help.
8+
var ErrPrintHelp = errors.New("print help")
9+
10+
// ErrHelp tells the user to use help.
11+
var ErrHelp = errors.New("help requested")

cmd/strings2/camel.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Generated by github.com/arran4/go-subcommand/cmd/gosubc
2+
3+
package main
4+
5+
import (
6+
"flag"
7+
"fmt"
8+
"os"
9+
"strings"
10+
11+
"github.com/arran4/strings2/cli"
12+
)
13+
14+
var _ Cmd = (*Camel)(nil)
15+
16+
type Camel struct {
17+
*RootCmd
18+
Flags *flag.FlagSet
19+
input string
20+
output string
21+
args []string
22+
SubCommands map[string]Cmd
23+
CommandAction func(c *Camel) error
24+
}
25+
26+
type UsageDataCamel struct {
27+
*Camel
28+
Recursive bool
29+
}
30+
31+
func (c *Camel) Usage() {
32+
err := executeUsage(os.Stderr, "camel_usage.txt", UsageDataCamel{c, false})
33+
if err != nil {
34+
fmt.Fprintf(os.Stderr, "Error generating usage: %s\n", err)
35+
}
36+
}
37+
38+
func (c *Camel) UsageRecursive() {
39+
err := executeUsage(os.Stderr, "camel_usage.txt", UsageDataCamel{c, true})
40+
if err != nil {
41+
fmt.Fprintf(os.Stderr, "Error generating usage: %s\n", err)
42+
}
43+
}
44+
45+
func (c *Camel) Execute(args []string) error {
46+
if len(args) > 0 {
47+
if cmd, ok := c.SubCommands[args[0]]; ok {
48+
return cmd.Execute(args[1:])
49+
}
50+
}
51+
var remainingArgs []string
52+
for i := 0; i < len(args); i++ {
53+
arg := args[i]
54+
if arg == "--" {
55+
remainingArgs = append(remainingArgs, args[i+1:]...)
56+
break
57+
}
58+
if strings.HasPrefix(arg, "-") && arg != "-" {
59+
name := arg
60+
value := ""
61+
hasValue := false
62+
if strings.Contains(arg, "=") {
63+
parts := strings.SplitN(arg, "=", 2)
64+
name = parts[0]
65+
value = parts[1]
66+
hasValue = true
67+
}
68+
trimmedName := strings.TrimLeft(name, "-")
69+
switch trimmedName {
70+
71+
case "input", "i":
72+
if !hasValue {
73+
if i+1 < len(args) {
74+
value = args[i+1]
75+
i++
76+
} else {
77+
return fmt.Errorf("flag %s requires a value", name)
78+
}
79+
}
80+
c.input = value
81+
82+
case "output", "o":
83+
if !hasValue {
84+
if i+1 < len(args) {
85+
value = args[i+1]
86+
i++
87+
} else {
88+
return fmt.Errorf("flag %s requires a value", name)
89+
}
90+
}
91+
c.output = value
92+
case "help", "h":
93+
c.Usage()
94+
return nil
95+
default:
96+
return fmt.Errorf("unknown flag: %s", name)
97+
}
98+
} else {
99+
remainingArgs = append(remainingArgs, arg)
100+
}
101+
}
102+
// Handle vararg args
103+
{
104+
varArgStart := 0
105+
if varArgStart > len(remainingArgs) {
106+
varArgStart = len(remainingArgs)
107+
}
108+
varArgs := remainingArgs[varArgStart:]
109+
c.args = varArgs
110+
}
111+
112+
if c.CommandAction != nil {
113+
if err := c.CommandAction(c); err != nil {
114+
return fmt.Errorf("camel failed: %w", err)
115+
}
116+
} else {
117+
c.Usage()
118+
}
119+
120+
return nil
121+
}
122+
123+
func (c *RootCmd) NewCamel() *Camel {
124+
set := flag.NewFlagSet("camel", flag.ContinueOnError)
125+
v := &Camel{
126+
RootCmd: c,
127+
Flags: set,
128+
SubCommands: make(map[string]Cmd),
129+
}
130+
131+
set.StringVar(&v.input, "input", "", "Input file or - for stdin")
132+
set.StringVar(&v.input, "i", "", "Input file or - for stdin")
133+
134+
set.StringVar(&v.output, "output", "", "Output file or - for stdout")
135+
set.StringVar(&v.output, "o", "", "Output file or - for stdout")
136+
set.Usage = v.Usage
137+
138+
v.CommandAction = func(c *Camel) error {
139+
140+
cli.Camel(c.input, c.output, c.args...)
141+
return nil
142+
}
143+
144+
v.SubCommands["help"] = &InternalCommand{
145+
Exec: func(args []string) error {
146+
for _, arg := range args {
147+
if arg == "-deep" {
148+
v.UsageRecursive()
149+
return nil
150+
}
151+
}
152+
v.Usage()
153+
return nil
154+
},
155+
UsageFunc: v.Usage,
156+
}
157+
v.SubCommands["usage"] = &InternalCommand{
158+
Exec: func(args []string) error {
159+
for _, arg := range args {
160+
if arg == "-deep" {
161+
v.UsageRecursive()
162+
return nil
163+
}
164+
}
165+
v.Usage()
166+
return nil
167+
},
168+
UsageFunc: v.Usage,
169+
}
170+
return v
171+
}

cmd/strings2/camel_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Generated by github.com/arran4/go-subcommand/cmd/gosubc
2+
3+
package main
4+
5+
import (
6+
"flag"
7+
"testing"
8+
)
9+
10+
func TestCamel_Execute(t *testing.T) {
11+
12+
parent := &RootCmd{
13+
FlagSet: flag.NewFlagSet("root", flag.ContinueOnError),
14+
Commands: make(map[string]Cmd),
15+
}
16+
cmd := parent.NewCamel()
17+
18+
called := false
19+
cmd.CommandAction = func(c *Camel) error {
20+
called = true
21+
return nil
22+
}
23+
24+
args := []string{}
25+
26+
err := cmd.Execute(args)
27+
if err != nil {
28+
t.Errorf("Unexpected error: %v", err)
29+
}
30+
if !called {
31+
t.Error("CommandAction was not called")
32+
}
33+
}

0 commit comments

Comments
 (0)