Skip to content

Commit

Permalink
feat: prompt user to fill values
Browse files Browse the repository at this point in the history
Signed-off-by: Yves Brissaud <[email protected]>
  • Loading branch information
eunomie committed Oct 9, 2024
1 parent 14ed38f commit 3faef8a
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 31 deletions.
13 changes: 12 additions & 1 deletion examples/alpine-hello.runx.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
actions:
- id: hello
- id: hello:user
desc: Say hello to the current user
type: run
env:
- USER
cmd: --rm {{.Ref}} echo hello {{env "USER"}}

- id: hello
desc: Say hello!
type: run
opts:
- name: name
desc: User's name
prompt: Please enter your name
required: true
cmd: >
--rm {{.Ref}} echo hello {{opt "name"}}
11 changes: 10 additions & 1 deletion internal/commands/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,20 @@ func run(ctx context.Context, out io.Writer, rk *runkit.RunKit, action string) e
return err
}

opts, err := prompt.Ask(runnable.Action)
if err != nil {
return err
}

if err = runnable.SetOptionValues(opts); err != nil {
return err
}

_, _ = fmt.Fprintln(out, tui.Markdown(fmt.Sprintf(`
> **Running the following command:**
>
> %s
`, runnable)))
`, runnable.Command)))

return runnable.Run(ctx)
}
Expand Down
55 changes: 55 additions & 0 deletions internal/prompt/prompt.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package prompt

import (
"cmp"
"errors"
"strings"

"github.com/charmbracelet/huh"
Expand Down Expand Up @@ -44,3 +46,56 @@ func envStr(env []string) string {
}
return " (required env: " + strings.Join(env, ", ") + ")"
}

func Ask(action runkit.Action) (map[string]string, error) {
if len(action.Options) == 0 {
return nil, nil
}

var (
err error
opts = map[string]string{}
form *huh.Form
fields []huh.Field
)

for _, opt := range action.Options {
opt := opt
if len(opt.Values) == 0 {
fields = append(fields,
huh.NewInput().
Title(cmp.Or(opt.Prompt, cmp.Or(opt.Description, opt.Name))).
Key(opt.Name).
Validate(checkRequired(opt.Required)))
} else {
fields = append(fields,
huh.NewSelect[string]().
Title(cmp.Or(opt.Prompt, cmp.Or(opt.Description, opt.Name))).
Key(opt.Name).
Validate(checkRequired(opt.Required)).
Options(pizza.Map(opt.Values, func(str string) huh.Option[string] {
return huh.NewOption(str, str)
})...))
}
}

form = huh.NewForm(huh.NewGroup(fields...))
if err = form.Run(); err != nil {
return nil, err
}

for _, opt := range action.Options {
opts[opt.Name] = form.GetString(opt.Name)
}

return opts, nil
}

func checkRequired(isRequired bool) func(string) error {
return func(str string) error {
if str == "" && isRequired {
return errors.New("required")
}
return nil
}
}
73 changes: 44 additions & 29 deletions runkit/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,19 @@ import (
type (
Runnable struct {
Command string
Args string
command string
args string
data TemplateData
Action Action
}

TemplateData struct {
Ref string
Env map[string]string
Ref string
Env map[string]string
Opts map[string]string
}
)

func GetRunnable(rk *RunKit, action string) (*Runnable, error) {
if rk == nil || len(rk.Config.Actions) == 0 {
return nil, fmt.Errorf("no available configuration is nil")
}
return rk.GetRunnable(action)
}

func (rk *RunKit) GetRunnable(action string) (*Runnable, error) {
for _, a := range rk.Config.Actions {
if a.ID == action {
Expand All @@ -46,48 +43,66 @@ func (action Action) GetRunnable(ref string) (*Runnable, error) {
return nil, fmt.Errorf("unsupported action type %s", action.Type)
}

data := TemplateData{
Ref: ref,
Env: map[string]string{},
runnable := Runnable{
Action: action,
command: "docker run",
data: TemplateData{
Ref: ref,
Env: map[string]string{},
},
}

for _, env := range action.Env {
if v, ok := os.LookupEnv(env); !ok {
return nil, fmt.Errorf("environment variable %q is required", env)
} else {
data.Env[env] = v
runnable.data.Env[env] = v
}
}

runnable := Runnable{
Command: "docker run",
}
return &runnable, nil
}

tmpl, err := template.New(action.ID).Funcs(template.FuncMap{
func (r *Runnable) compute() error {
tmpl, err := template.New("runx").Funcs(template.FuncMap{
"env": func(envName string) string {
return data.Env[envName]
return r.data.Env[envName]
},
"opt": func(optName string) string {
return r.data.Opts[optName]
},
}).Parse(action.Command)
}).Parse(r.Action.Command)
if err != nil {
return nil, err
return err
}

out := strings.Builder{}
err = tmpl.Execute(&out, data)
err = tmpl.Execute(&out, r.data)
if err != nil {
return nil, err
return err
}
runnable.Args = out.String()
r.args = out.String()

return &runnable, nil
return nil
}

func (r Runnable) String() string {
return fmt.Sprintf("%s %s", r.Command, r.Args)
func (r *Runnable) SetOptionValues(opts map[string]string) error {
r.data.Opts = opts

if err := r.compute(); err != nil {
return err
}

r.Command = fmt.Sprintf("%s %s", r.command, r.args)
return nil
}

func (r Runnable) Run(ctx context.Context) error {
parsedCmd, err := syntax.NewParser().Parse(strings.NewReader(r.String()), "")
func (r *Runnable) Run(ctx context.Context) error {
if r.Command == "" {
return fmt.Errorf("command not set")
}

parsedCmd, err := syntax.NewParser().Parse(strings.NewReader(r.Command), "")
if err != nil {
return err
}
Expand Down
9 changes: 9 additions & 0 deletions runkit/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ type (
Type ActionType `yaml:"type" json:"type"`
Command string `yaml:"cmd" json:"cmd,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
Options []Opt `yaml:"opts,omitempty" json:"opts,omitempty"`
}

Opt struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"desc" json:"desc,omitempty"`
Prompt string `yaml:"prompt,omitempty" json:"prompt,omitempty"`
Required bool `yaml:"required,omitempty" json:"required,omitempty"`
Values []string `yaml:"values,omitempty" json:"values,omitempty"`
}

ActionType string
Expand Down

0 comments on commit 3faef8a

Please sign in to comment.