Skip to content

Commit 3faef8a

Browse files
committed
feat: prompt user to fill values
Signed-off-by: Yves Brissaud <[email protected]>
1 parent 14ed38f commit 3faef8a

File tree

5 files changed

+130
-31
lines changed

5 files changed

+130
-31
lines changed

examples/alpine-hello.runx.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
actions:
2-
- id: hello
2+
- id: hello:user
33
desc: Say hello to the current user
44
type: run
55
env:
66
- USER
77
cmd: --rm {{.Ref}} echo hello {{env "USER"}}
8+
9+
- id: hello
10+
desc: Say hello!
11+
type: run
12+
opts:
13+
- name: name
14+
desc: User's name
15+
prompt: Please enter your name
16+
required: true
17+
cmd: >
18+
--rm {{.Ref}} echo hello {{opt "name"}}

internal/commands/root/root.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,20 @@ func run(ctx context.Context, out io.Writer, rk *runkit.RunKit, action string) e
127127
return err
128128
}
129129

130+
opts, err := prompt.Ask(runnable.Action)
131+
if err != nil {
132+
return err
133+
}
134+
135+
if err = runnable.SetOptionValues(opts); err != nil {
136+
return err
137+
}
138+
130139
_, _ = fmt.Fprintln(out, tui.Markdown(fmt.Sprintf(`
131140
> **Running the following command:**
132141
>
133142
> %s
134-
`, runnable)))
143+
`, runnable.Command)))
135144

136145
return runnable.Run(ctx)
137146
}

internal/prompt/prompt.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package prompt
22

33
import (
4+
"cmp"
5+
"errors"
46
"strings"
57

68
"github.com/charmbracelet/huh"
@@ -44,3 +46,56 @@ func envStr(env []string) string {
4446
}
4547
return " (required env: " + strings.Join(env, ", ") + ")"
4648
}
49+
50+
func Ask(action runkit.Action) (map[string]string, error) {
51+
if len(action.Options) == 0 {
52+
return nil, nil
53+
}
54+
55+
var (
56+
err error
57+
opts = map[string]string{}
58+
form *huh.Form
59+
fields []huh.Field
60+
)
61+
62+
for _, opt := range action.Options {
63+
opt := opt
64+
if len(opt.Values) == 0 {
65+
fields = append(fields,
66+
huh.NewInput().
67+
Title(cmp.Or(opt.Prompt, cmp.Or(opt.Description, opt.Name))).
68+
Key(opt.Name).
69+
Validate(checkRequired(opt.Required)))
70+
} else {
71+
fields = append(fields,
72+
huh.NewSelect[string]().
73+
Title(cmp.Or(opt.Prompt, cmp.Or(opt.Description, opt.Name))).
74+
Key(opt.Name).
75+
Validate(checkRequired(opt.Required)).
76+
Options(pizza.Map(opt.Values, func(str string) huh.Option[string] {
77+
return huh.NewOption(str, str)
78+
})...))
79+
}
80+
}
81+
82+
form = huh.NewForm(huh.NewGroup(fields...))
83+
if err = form.Run(); err != nil {
84+
return nil, err
85+
}
86+
87+
for _, opt := range action.Options {
88+
opts[opt.Name] = form.GetString(opt.Name)
89+
}
90+
91+
return opts, nil
92+
}
93+
94+
func checkRequired(isRequired bool) func(string) error {
95+
return func(str string) error {
96+
if str == "" && isRequired {
97+
return errors.New("required")
98+
}
99+
return nil
100+
}
101+
}

runkit/run.go

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,19 @@ import (
1515
type (
1616
Runnable struct {
1717
Command string
18-
Args string
18+
command string
19+
args string
20+
data TemplateData
21+
Action Action
1922
}
2023

2124
TemplateData struct {
22-
Ref string
23-
Env map[string]string
25+
Ref string
26+
Env map[string]string
27+
Opts map[string]string
2428
}
2529
)
2630

27-
func GetRunnable(rk *RunKit, action string) (*Runnable, error) {
28-
if rk == nil || len(rk.Config.Actions) == 0 {
29-
return nil, fmt.Errorf("no available configuration is nil")
30-
}
31-
return rk.GetRunnable(action)
32-
}
33-
3431
func (rk *RunKit) GetRunnable(action string) (*Runnable, error) {
3532
for _, a := range rk.Config.Actions {
3633
if a.ID == action {
@@ -46,48 +43,66 @@ func (action Action) GetRunnable(ref string) (*Runnable, error) {
4643
return nil, fmt.Errorf("unsupported action type %s", action.Type)
4744
}
4845

49-
data := TemplateData{
50-
Ref: ref,
51-
Env: map[string]string{},
46+
runnable := Runnable{
47+
Action: action,
48+
command: "docker run",
49+
data: TemplateData{
50+
Ref: ref,
51+
Env: map[string]string{},
52+
},
5253
}
5354

5455
for _, env := range action.Env {
5556
if v, ok := os.LookupEnv(env); !ok {
5657
return nil, fmt.Errorf("environment variable %q is required", env)
5758
} else {
58-
data.Env[env] = v
59+
runnable.data.Env[env] = v
5960
}
6061
}
6162

62-
runnable := Runnable{
63-
Command: "docker run",
64-
}
63+
return &runnable, nil
64+
}
6565

66-
tmpl, err := template.New(action.ID).Funcs(template.FuncMap{
66+
func (r *Runnable) compute() error {
67+
tmpl, err := template.New("runx").Funcs(template.FuncMap{
6768
"env": func(envName string) string {
68-
return data.Env[envName]
69+
return r.data.Env[envName]
70+
},
71+
"opt": func(optName string) string {
72+
return r.data.Opts[optName]
6973
},
70-
}).Parse(action.Command)
74+
}).Parse(r.Action.Command)
7175
if err != nil {
72-
return nil, err
76+
return err
7377
}
7478

7579
out := strings.Builder{}
76-
err = tmpl.Execute(&out, data)
80+
err = tmpl.Execute(&out, r.data)
7781
if err != nil {
78-
return nil, err
82+
return err
7983
}
80-
runnable.Args = out.String()
84+
r.args = out.String()
8185

82-
return &runnable, nil
86+
return nil
8387
}
8488

85-
func (r Runnable) String() string {
86-
return fmt.Sprintf("%s %s", r.Command, r.Args)
89+
func (r *Runnable) SetOptionValues(opts map[string]string) error {
90+
r.data.Opts = opts
91+
92+
if err := r.compute(); err != nil {
93+
return err
94+
}
95+
96+
r.Command = fmt.Sprintf("%s %s", r.command, r.args)
97+
return nil
8798
}
8899

89-
func (r Runnable) Run(ctx context.Context) error {
90-
parsedCmd, err := syntax.NewParser().Parse(strings.NewReader(r.String()), "")
100+
func (r *Runnable) Run(ctx context.Context) error {
101+
if r.Command == "" {
102+
return fmt.Errorf("command not set")
103+
}
104+
105+
parsedCmd, err := syntax.NewParser().Parse(strings.NewReader(r.Command), "")
91106
if err != nil {
92107
return err
93108
}

runkit/types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ type (
1818
Type ActionType `yaml:"type" json:"type"`
1919
Command string `yaml:"cmd" json:"cmd,omitempty"`
2020
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
21+
Options []Opt `yaml:"opts,omitempty" json:"opts,omitempty"`
22+
}
23+
24+
Opt struct {
25+
Name string `yaml:"name" json:"name"`
26+
Description string `yaml:"desc" json:"desc,omitempty"`
27+
Prompt string `yaml:"prompt,omitempty" json:"prompt,omitempty"`
28+
Required bool `yaml:"required,omitempty" json:"required,omitempty"`
29+
Values []string `yaml:"values,omitempty" json:"values,omitempty"`
2130
}
2231

2332
ActionType string

0 commit comments

Comments
 (0)