Skip to content

Commit 7846ff6

Browse files
authored
feat: generate command from YAML file (#244)
1 parent 1eac18e commit 7846ff6

File tree

19 files changed

+537
-209
lines changed

19 files changed

+537
-209
lines changed

.gitignore

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ todo
77
.env
88
*.log
99
/local
10-
config/config.yml
11-
config/config.*.yml
12-
config/wallets
13-
config/templates
10+
config/config.local.yml
1411
__debug_*
1512
*.sqlite*
1613
deployment/.env

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ linters-settings:
211211

212212
issues:
213213
exclude-use-default: false
214+
exclude-generated: lax
214215
exclude-rules:
215216
- path: _test.go
216217
linters:

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,10 @@ build-telegram:
4848
build-http:
4949
go build -o build/pagu-http ./cmd/http
5050

51+
### Generating commands
52+
gen:
53+
go run ./internal/generator/main.go \
54+
"./internal/engine/command/crowdfund/crowdfund.yml"
55+
5156
###
52-
.PHONY: devtools mock proto fmt check test build build-cli build-discord build-grpc build-telegram build-http
57+
.PHONY: devtools mock proto fmt check test build build-cli build-discord build-grpc build-telegram build-http gen

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
github.com/spf13/cobra v1.8.1
1414
github.com/stretchr/testify v1.10.0
1515
go.uber.org/mock v0.5.0
16+
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
1617
golang.org/x/text v0.21.0
1718
google.golang.org/grpc v1.69.2
1819
google.golang.org/protobuf v1.36.1
@@ -56,7 +57,6 @@ require (
5657
github.com/valyala/fasttemplate v1.2.2 // indirect
5758
github.com/x448/float16 v0.8.4 // indirect
5859
golang.org/x/crypto v0.31.0 // indirect
59-
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
6060
golang.org/x/net v0.33.0 // indirect
6161
golang.org/x/sys v0.28.0 // indirect
6262
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 // indirect

internal/engine/command/command.go

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"strings"
88

99
"github.com/pagu-project/pagu/internal/entity"
10-
"github.com/pagu-project/pagu/pkg/color"
10+
"github.com/pagu-project/pagu/pkg/utils"
1111
)
1212

1313
const failedTemplate = `
@@ -31,45 +31,81 @@ var (
3131
type InputBox int
3232

3333
const (
34-
InputBoxText InputBox = iota
35-
InputBoxMultilineText
36-
InputBoxInteger
37-
InputBoxFloat
38-
InputBoxFile
39-
InputBoxToggle
40-
InputBoxChoice
34+
InputBoxText InputBox = 1
35+
InputBoxMultilineText InputBox = 2
36+
InputBoxInteger InputBox = 3
37+
InputBoxFloat InputBox = 4
38+
InputBoxFile InputBox = 5
39+
InputBoxToggle InputBox = 6
40+
InputBoxChoice InputBox = 7
4141
)
4242

43+
var inputBoxToString = map[InputBox]string{
44+
InputBoxText: "Text",
45+
InputBoxMultilineText: "MultilineText",
46+
InputBoxInteger: "Integer",
47+
InputBoxFloat: "Float",
48+
InputBoxFile: "File",
49+
InputBoxToggle: "Toggle",
50+
InputBoxChoice: "Choice",
51+
}
52+
53+
func (ib InputBox) String() string {
54+
str, ok := inputBoxToString[ib]
55+
if ok {
56+
return str
57+
}
58+
59+
return fmt.Sprintf("%d", ib)
60+
}
61+
62+
func (ib InputBox) MarshalYAML() (any, error) {
63+
return utils.MarshalEnum(ib, inputBoxToString)
64+
}
65+
66+
func (ib *InputBox) UnmarshalYAML(unmarshal func(any) error) error {
67+
var str string
68+
if err := unmarshal(&str); err != nil {
69+
return err
70+
}
71+
val, err := utils.UnmarshalEnum(str, inputBoxToString)
72+
if err != nil {
73+
return err
74+
}
75+
*ib = val
76+
77+
return nil
78+
}
79+
4380
type Choice struct {
44-
Name string
45-
Value int
81+
Name string `yaml:"name"`
82+
Value int `yaml:"value"`
4683
}
4784

4885
type Args struct {
49-
Name string
50-
Desc string
51-
InputBox InputBox
52-
Optional bool
53-
Choices []Choice
86+
Name string `yaml:"name"`
87+
Desc string `yaml:"desc"`
88+
InputBox InputBox `yaml:"input_box"`
89+
Optional bool `yaml:"optional"`
90+
Choices []Choice `yaml:"choices"`
5491
}
5592

5693
type HandlerFunc func(caller *entity.User, cmd *Command, args map[string]string) CommandResult
5794

5895
type Command struct {
59-
Emoji string
60-
Color color.ColorCode
61-
Name string
62-
Help string
63-
Args []Args
64-
AppIDs []entity.PlatformID
65-
SubCommands []*Command
66-
Middlewares []MiddlewareFunc
67-
Handler HandlerFunc
68-
TargetFlag int
96+
Emoji string `yaml:"emoji"`
97+
Name string `yaml:"name"`
98+
Help string `yaml:"help"`
99+
Args []Args `yaml:"args"`
100+
SubCommands []*Command `yaml:"sub_commands"`
101+
ResultTemplate string `yaml:"result_template"`
102+
Middlewares []MiddlewareFunc `yaml:"-"`
103+
Handler HandlerFunc `yaml:"-"`
104+
AppIDs []entity.PlatformID `yaml:"-"`
105+
TargetFlag int `yaml:"-"`
69106
}
70107

71108
type CommandResult struct {
72-
Color color.ColorCode
73109
Title string
74110
Message string
75111
Successful bool
@@ -79,7 +115,6 @@ func (cmd *Command) RenderFailedTemplate(reason string) CommandResult {
79115
msg, _ := cmd.executeTemplate(failedTemplate, map[string]any{"reason": reason})
80116

81117
return CommandResult{
82-
Color: cmd.Color,
83118
Title: fmt.Sprintf("%v %v", cmd.Name, cmd.Emoji),
84119
Message: msg,
85120
Successful: false,
@@ -90,14 +125,13 @@ func (cmd *Command) RenderErrorTemplate(err error) CommandResult {
90125
msg, _ := cmd.executeTemplate(errorTemplate, map[string]any{"err": err})
91126

92127
return CommandResult{
93-
Color: cmd.Color,
94128
Title: fmt.Sprintf("%v %v", cmd.Name, cmd.Emoji),
95129
Message: msg,
96130
Successful: false,
97131
}
98132
}
99133

100-
func (cmd *Command) RenderResultTemplate(templateContent string, keyvals ...any) CommandResult {
134+
func (cmd *Command) RenderResultTemplate(keyvals ...any) CommandResult {
101135
if len(keyvals)%2 != 0 {
102136
keyvals = append(keyvals, "!MISSING-VALUE!")
103137
}
@@ -110,13 +144,12 @@ func (cmd *Command) RenderResultTemplate(templateContent string, keyvals ...any)
110144
data[key] = val
111145
}
112146

113-
msg, err := cmd.executeTemplate(templateContent, data)
147+
msg, err := cmd.executeTemplate(cmd.ResultTemplate, data)
114148
if err != nil {
115149
return cmd.RenderErrorTemplate(err)
116150
}
117151

118152
return CommandResult{
119-
Color: cmd.Color,
120153
Title: fmt.Sprintf("%v %v", cmd.Name, cmd.Emoji),
121154
Message: msg,
122155
Successful: true,
@@ -143,7 +176,6 @@ func (cmd *Command) SuccessfulResult(msg string) CommandResult {
143176
// Deprecated: Use RenderResultTemplate.
144177
func (cmd *Command) SuccessfulResultF(msg string, a ...any) CommandResult {
145178
return CommandResult{
146-
Color: cmd.Color,
147179
Title: fmt.Sprintf("%v %v", cmd.Name, cmd.Emoji),
148180
Message: fmt.Sprintf(msg, a...),
149181
Successful: true,
@@ -158,7 +190,6 @@ func (cmd *Command) FailedResult(msg string) CommandResult {
158190
// Deprecated: Use RenderFailedTemplate.
159191
func (cmd *Command) FailedResultF(msg string, a ...any) CommandResult {
160192
return CommandResult{
161-
Color: cmd.Color,
162193
Title: fmt.Sprintf("%v %v", cmd.Name, cmd.Emoji),
163194
Message: fmt.Sprintf(msg, a...),
164195
Successful: false,
@@ -172,7 +203,6 @@ func (cmd *Command) ErrorResult(err error) CommandResult {
172203

173204
func (cmd *Command) HelpResult() CommandResult {
174205
return CommandResult{
175-
Color: cmd.Color,
176206
Title: fmt.Sprintf("%v %v", cmd.Help, cmd.Emoji),
177207
Message: cmd.HelpMessage(),
178208
Successful: false,
@@ -198,6 +228,10 @@ func (cmd *Command) HelpMessage() string {
198228
}
199229

200230
func (cmd *Command) AddSubCommand(subCmd *Command) {
231+
if subCmd == nil {
232+
return
233+
}
234+
201235
if subCmd.HasSubCommand() {
202236
subCmd.AddHelpSubCommand()
203237
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package command
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"gopkg.in/yaml.v3"
8+
)
9+
10+
func TestInputBox_MarshalYAML(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
input InputBox
14+
expected string
15+
wantErr bool
16+
}{
17+
{"Marshal Text", InputBoxText, `"Text"`, false},
18+
{"Marshal MultilineText", InputBoxMultilineText, `"MultilineText"`, false},
19+
{"Marshal Integer", InputBoxInteger, `"Integer"`, false},
20+
{"Marshal Float", InputBoxFloat, `"Float"`, false},
21+
{"Marshal File", InputBoxFile, `"File"`, false},
22+
{"Marshal Toggle", InputBoxToggle, `"Toggle"`, false},
23+
{"Marshal Choice", InputBoxChoice, `"Choice"`, false},
24+
{"Marshal Unknown", InputBox(999), ``, true},
25+
}
26+
27+
for _, tt := range tests {
28+
t.Run(tt.name, func(t *testing.T) {
29+
data, err := yaml.Marshal(tt.input)
30+
if tt.wantErr {
31+
assert.Error(t, err)
32+
} else {
33+
assert.NoError(t, err)
34+
assert.YAMLEq(t, tt.expected, string(data))
35+
}
36+
})
37+
}
38+
}
39+
40+
func TestInputBox_UnmarshalYAML(t *testing.T) {
41+
tests := []struct {
42+
name string
43+
input string
44+
expected InputBox
45+
wantErr bool
46+
}{
47+
{"Unmarshal Text", `"Text"`, InputBoxText, false},
48+
{"Unmarshal MultilineText", `"MultilineText"`, InputBoxMultilineText, false},
49+
{"Unmarshal Integer", `"Integer"`, InputBoxInteger, false},
50+
{"Unmarshal Float", `"Float"`, InputBoxFloat, false},
51+
{"Unmarshal File", `"File"`, InputBoxFile, false},
52+
{"Unmarshal Toggle", `"Toggle"`, InputBoxToggle, false},
53+
{"Unmarshal Choice", `"Choice"`, InputBoxChoice, false},
54+
{"Unmarshal Unknown", `"Unknown"`, InputBox(0), true},
55+
{"Unmarshal Invalid YAML", `123`, InputBox(0), true},
56+
}
57+
58+
for _, tt := range tests {
59+
t.Run(tt.name, func(t *testing.T) {
60+
var box InputBox
61+
err := yaml.Unmarshal([]byte(tt.input), &box)
62+
if tt.wantErr {
63+
assert.Error(t, err)
64+
} else {
65+
assert.NoError(t, err)
66+
assert.Equal(t, tt.expected, box)
67+
}
68+
})
69+
}
70+
}

internal/engine/command/crowdfund/create.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ func (c *CrowdfundCmd) createHandler(
1919
packages := []entity.Package{}
2020
err := json.Unmarshal([]byte(packagesJSON), &packages)
2121
if err != nil {
22-
return cmd.FailedResult(err.Error())
22+
return cmd.RenderErrorTemplate(err)
2323
}
2424

2525
if title == "" {
26-
return cmd.FailedResult("The title of the crowdfunding campaign cannot be empty")
26+
return cmd.RenderFailedTemplate("The title of the crowdfunding campaign cannot be empty")
2727
}
2828

2929
if len(packages) < 2 {
30-
return cmd.FailedResult("At least 3 packages are required for the crowdfunding campaign")
30+
return cmd.RenderFailedTemplate("At least 3 packages are required for the crowdfunding campaign")
3131
}
3232

3333
campaign := &entity.CrowdfundCampaign{
@@ -38,10 +38,8 @@ func (c *CrowdfundCmd) createHandler(
3838
}
3939
err = c.db.AddCrowdfundCampaign(campaign)
4040
if err != nil {
41-
return cmd.FailedResult(err.Error())
41+
return cmd.RenderErrorTemplate(err)
4242
}
4343

44-
return cmd.SuccessfulResultF(
45-
"Crowdfund campaign '%s' created successfully with %d packages",
46-
title, len(packages))
44+
return cmd.RenderResultTemplate("campaign", campaign)
4745
}

0 commit comments

Comments
 (0)