Skip to content

Commit c840884

Browse files
authored
feat: add cli package (#126)
2 parents a27c4d0 + 16de343 commit c840884

17 files changed

+1615
-227
lines changed

.github/workflows/label-checker.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
# Add labels based on PR title
4747
GH_PR_TITLE=$(gh pr view --json title --jq .title)
4848
gh label list --json name --jq ".[].name" | while read -r LINE; do
49-
grep -Eo "^${LINE:?}" <<<"${GH_PR_TITLE:?}" || true # NOTE: Ignore the return value of grep because we just want to output the string
49+
awk -F: "/^${LINE-}(\([^\)]+\))?:/ {print \$1}" <<<"${GH_PR_TITLE:?}" | grep -Eo "^${LINE:?}" || true # NOTE: Ignore the return value of grep because we just want to output the string
5050
done | xargs -t -I{} gh pr edit --add-label {}
5151
5252
# If any of the labels are present, exit with success

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Thumbs.db
99
/.tmp/
1010

1111
# codecov
12-
coverage.txt
12+
coverage.txt*
1313
coverage.html
1414

1515
# env

.test.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# UTIL_GO_CLI_TRACE=true
2+
UTIL_GO_CLI_DEBUG=true

Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,15 @@ lint: githooks ## Run secretlint, go mod tidy, golangci-lint
5757
.PHONY: test
5858
test: githooks ## Run go test and display coverage
5959
@[ -x "${DOTLOCAL_DIR}/bin/godotnev" ] || GOBIN="${DOTLOCAL_DIR}/bin" go install github.com/joho/godotenv/cmd/godotenv@latest
60+
6061
# Unit testing
61-
godotenv -f .test.env go test -v -race -p=4 -parallel=8 -timeout=300s -cover -coverprofile=./coverage.txt ./... && go tool cover -func=./coverage.txt
62+
godotenv -f .test.env go test -v -race -p=4 -parallel=8 -timeout=300s -cover -coverprofile=./coverage.txt.tmp ./... ; grep -Ev "\.deprecated\.go" ./coverage.txt.tmp > ./coverage.txt ; go tool cover -func=./coverage.txt
63+
6264
# Unit testing (with external modules)
63-
cd grpc && godotenv -f .test.env go test -v -race -p=4 -parallel=8 -timeout=300s -cover -coverprofile=./coverage.txt ./... && go tool cover -func=./coverage.txt
65+
cd grpc ; godotenv -f .test.env go test -v -race -p=4 -parallel=8 -timeout=300s -cover -coverprofile=./coverage.txt.tmp ./... ; grep -Ev "\.deprecated\.go" ./coverage.txt.tmp > ./coverage.txt ; go tool cover -func=./coverage.txt
66+
6467
# Integration testing
65-
cd integrationtest && godotenv -f .test.env go test -v -race -p=4 -parallel=8 -timeout=300s -cover -coverprofile=./coverage.txt ./... && go tool cover -func=./coverage.txt
68+
cd integrationtest ; godotenv -f .test.env go test -v -race -p=4 -parallel=8 -timeout=300s -cover -coverprofile=./coverage.txt.tmp ./... ; grep -Ev "\.deprecated\.go" ./coverage.txt.tmp > ./coverage.txt ; go tool cover -func=./coverage.txt
6669

6770
.PHONY: bench
6871
bench: ## Run benchmarks

exp/cli/check_commands.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package cliz
2+
3+
import errorz "github.com/kunitsucom/util.go/errors"
4+
5+
func (cmd *Command) checkSubCommands() error {
6+
if err := cmd.checkDuplicateSubCommands(); err != nil {
7+
return errorz.Errorf("%s: %w", cmd.Name, err)
8+
}
9+
10+
return nil
11+
}
12+
13+
func (cmd *Command) checkDuplicateSubCommands() error {
14+
names := make(map[string]bool)
15+
16+
for _, cmd := range cmd.SubCommands {
17+
name := cmd.Name
18+
19+
TraceLog.Printf("checkDuplicateSubCommands: %s", name)
20+
21+
if name != "" && names[name] {
22+
return errorz.Errorf("sub command: %s: %w", name, ErrDuplicateSubCommand)
23+
}
24+
names[name] = true
25+
}
26+
27+
for _, subcmd := range cmd.SubCommands {
28+
if err := subcmd.checkDuplicateSubCommands(); err != nil {
29+
return errorz.Errorf("%w", err)
30+
}
31+
}
32+
return nil
33+
}

exp/cli/check_commands_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package cliz
2+
3+
import (
4+
"errors"
5+
"testing"
6+
)
7+
8+
func TestCommand_checkCommands(t *testing.T) {
9+
t.Parallel()
10+
t.Run("success,", func(t *testing.T) {
11+
t.Parallel()
12+
c := &Command{
13+
Name: "main-cli",
14+
SubCommands: []*Command{
15+
{
16+
Name: "sub-cmd",
17+
},
18+
},
19+
}
20+
21+
if err := c.checkSubCommands(); err != nil {
22+
t.Fatalf("❌: %+v", err)
23+
}
24+
})
25+
26+
t.Run("failure,", func(t *testing.T) {
27+
t.Parallel()
28+
29+
c := &Command{
30+
Name: "main-cli",
31+
SubCommands: []*Command{
32+
{
33+
Name: "sub-cmd",
34+
SubCommands: []*Command{
35+
{
36+
Name: "sub-sub-cmd",
37+
},
38+
{
39+
Name: "sub-sub-cmd",
40+
},
41+
},
42+
},
43+
},
44+
}
45+
46+
if err := c.checkSubCommands(); !errors.Is(err, ErrDuplicateSubCommand) {
47+
t.Fatalf("❌: err != ErrDuplicateSubCommand: %+v", err)
48+
}
49+
})
50+
}

exp/cli/check_options.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package cliz
2+
3+
import errorz "github.com/kunitsucom/util.go/errors"
4+
5+
func (cmd *Command) checkOptions() error {
6+
// NOTE: duplicate check
7+
if err := cmd.checkDuplicateOptions(make(map[string]bool)); err != nil {
8+
return errorz.Errorf("%s: %w", cmd.Name, err)
9+
}
10+
11+
return nil
12+
}
13+
14+
//nolint:cyclop
15+
func (cmd *Command) checkDuplicateOptions(envs map[string]bool) error {
16+
names := make(map[string]bool)
17+
shorts := make(map[string]bool)
18+
19+
for _, opt := range cmd.Options {
20+
if name := opt.GetName(); name != "" {
21+
TraceLog.Printf("checkDuplicateOptions: %s: option: %s", cmd.Name, name)
22+
if names[name] {
23+
err := ErrDuplicateOptionName
24+
return errorz.Errorf("option: %s%s: %w", longOptionPrefix, name, err)
25+
}
26+
names[name] = true
27+
}
28+
29+
if short := opt.GetShort(); short != "" {
30+
if shorts[short] {
31+
return errorz.Errorf("short option: %s%s: %w", shortOptionPrefix, short, ErrDuplicateOptionName)
32+
}
33+
shorts[short] = true
34+
}
35+
36+
if env := opt.GetEnvironment(); env != "" {
37+
if envs[env] {
38+
return errorz.Errorf("environment: %s: %w", env, ErrDuplicateOptionName)
39+
}
40+
envs[env] = true
41+
}
42+
}
43+
44+
for _, subcmd := range cmd.SubCommands {
45+
if err := subcmd.checkDuplicateOptions(envs); err != nil {
46+
return errorz.Errorf("%s: %w", subcmd.Name, err)
47+
}
48+
}
49+
50+
return nil
51+
}

exp/cli/check_options_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package cliz
2+
3+
import (
4+
"errors"
5+
"testing"
6+
)
7+
8+
func TestCommand_checkOptions(t *testing.T) {
9+
t.Parallel()
10+
t.Run("success,", func(t *testing.T) {
11+
t.Parallel()
12+
c := &Command{
13+
Name: "main-cli",
14+
SubCommands: []*Command{
15+
{
16+
Name: "sub-cmd",
17+
Options: []Option{
18+
&StringOption{
19+
Name: "foo",
20+
},
21+
},
22+
},
23+
},
24+
}
25+
26+
if err := c.checkOptions(); err != nil {
27+
t.Fatalf("❌: %+v", err)
28+
}
29+
})
30+
31+
t.Run("failure,ErrDuplicateOptionName,name", func(t *testing.T) {
32+
t.Parallel()
33+
c := &Command{
34+
Name: "main-cli",
35+
SubCommands: []*Command{
36+
{
37+
Name: "sub-cmd",
38+
Options: []Option{
39+
&StringOption{
40+
Name: "foo",
41+
},
42+
&StringOption{
43+
Name: "foo",
44+
},
45+
},
46+
},
47+
},
48+
}
49+
50+
if err := c.checkOptions(); !errors.Is(err, ErrDuplicateOptionName) {
51+
t.Fatalf("❌: err != ErrDuplicateOptionName: %+v", err)
52+
}
53+
})
54+
55+
t.Run("failure,ErrDuplicateOptionName,short", func(t *testing.T) {
56+
t.Parallel()
57+
c := &Command{
58+
Name: "main-cli",
59+
SubCommands: []*Command{
60+
{
61+
Name: "sub-cmd",
62+
Options: []Option{
63+
&StringOption{
64+
Short: "f",
65+
},
66+
&StringOption{
67+
Short: "f",
68+
},
69+
},
70+
},
71+
},
72+
}
73+
74+
{
75+
err := c.checkOptions()
76+
if !errors.Is(err, ErrDuplicateOptionName) {
77+
t.Fatalf("❌: err != ErrDuplicateOptionName: %+v", err)
78+
}
79+
}
80+
})
81+
82+
t.Run("failure,ErrDuplicateOptionName,environment", func(t *testing.T) {
83+
t.Parallel()
84+
c := &Command{
85+
Name: "main-cli",
86+
SubCommands: []*Command{
87+
{
88+
Name: "sub-cmd",
89+
Options: []Option{
90+
&StringOption{
91+
Environment: "FOO",
92+
},
93+
&StringOption{
94+
Environment: "FOO",
95+
},
96+
},
97+
},
98+
},
99+
}
100+
101+
{
102+
err := c.checkOptions()
103+
if !errors.Is(err, ErrDuplicateOptionName) {
104+
t.Fatalf("❌: err != ErrDuplicateOptionName: %+v", err)
105+
}
106+
}
107+
})
108+
}

0 commit comments

Comments
 (0)