Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rules editor create/update tweaks #229

Merged
merged 3 commits into from
Apr 2, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion internal/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cli
import (
"fmt"

"github.com/AlecAivazis/survey/v2"
"github.com/auth0/auth0-cli/internal/prompt"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -55,6 +57,56 @@ func (f *Flag) SelectU(cmd *cobra.Command, value interface{}, options []string,
return selectFlag(cmd, f, value, options, defaultValue, true)
}

func (f *Flag) EditorPrompt(cmd *cobra.Command, value *string, initialValue, filename string, infoFn func()) error {
out, err := prompt.CaptureInputViaEditor(
initialValue,
filename,
infoFn,
)
if err != nil {
return err
}

*value = out
return nil
}

func (f *Flag) EditorPromptU(cmd *cobra.Command, value *string, initialValue, filename string, infoFn func()) error {
response := map[string]interface{}{}

questions := []*survey.Question{
{
Name: f.Name,
Prompt: &prompt.Editor{
BlankAllowed: true,
Editor: &survey.Editor{
Help: f.Help,
Message: f.Name,
FileName: filename,
Default: initialValue,
HideDefault: true,
AppendDefault: true,
},
},
},
}

if err := survey.Ask(questions, &response); err != nil {
return err
}

// Since we have BlankAllowed=true, an empty answer means we'll use the
// initialValue provided since this path is for the Update path.
answer, ok := response[f.Name].(string)
if ok && answer != "" {
*value = answer
} else {
*value = initialValue
}

return nil
}

func (f *Flag) RegisterString(cmd *cobra.Command, value *string, defaultValue string) {
registerString(cmd, f, value, defaultValue, false)
}
Expand Down Expand Up @@ -152,7 +204,6 @@ func shouldAsk(cmd *cobra.Command, f *Flag, isUpdate bool) bool {
if !f.IsRequired && !f.AlwaysPrompt {
return false
}

return shouldPromptWhenFlagless(cmd, f.LongForm)
}

Expand Down
35 changes: 24 additions & 11 deletions internal/cli/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/auth0/auth0-cli/internal/ansi"
"github.com/auth0/auth0-cli/internal/auth0"
"github.com/auth0/auth0-cli/internal/prompt"
"github.com/spf13/cobra"
"gopkg.in/auth0.v5/management"
)
Expand Down Expand Up @@ -39,6 +38,14 @@ var (
Help: "Enable (or disable) a rule.",
}

ruleScript = Flag{
Name: "Script",
LongForm: "script",
ShortForm: "s",
Help: "Script contents for the rule.",
IsRequired: true,
}

ruleTemplateOptions = pickerOptions{
{"Empty rule", ruleTemplateEmptyRule},
{"Add email to access token", ruleTemplateAddEmailToAccessToken},
Expand Down Expand Up @@ -100,6 +107,7 @@ func createRuleCmd(cli *cli) *cobra.Command {
var inputs struct {
Name string
Template string
Script string
Enabled bool
}

Expand All @@ -126,20 +134,20 @@ auth0 rules create -n "My Rule" -t "Empty rule" --enabled=false`,
// TODO(cyx): we can re-think this once we have
// `--stdin` based commands. For now we don't have
// those yet, so keeping this simple.
script, err := prompt.CaptureInputViaEditor(
err := ruleScript.EditorPrompt(
cmd,
&inputs.Script,
ruleTemplateOptions.getValue(inputs.Template),
inputs.Name+".*.js",
func() {
cli.renderer.Infof("%s once you close the editor, the rule will be saved. To cancel, CTRL+C.", ansi.Faint("Hint:"))
},
cli.ruleEditorHint,
)
if err != nil {
return fmt.Errorf("Failed to capture input from the editor: %w", err)
}

rule := &management.Rule{
Name: &inputs.Name,
Script: auth0.String(script),
Script: auth0.String(inputs.Script),
Enabled: &inputs.Enabled,
}

Expand Down Expand Up @@ -251,6 +259,7 @@ func updateRuleCmd(cli *cli) *cobra.Command {
var inputs struct {
ID string
Name string
Script string
Enabled bool
}

Expand Down Expand Up @@ -291,12 +300,12 @@ auth0 rules update <rule-id> -n "My Updated Rule" --enabled=false`,
// TODO(cyx): we can re-think this once we have
// `--stdin` based commands. For now we don't have
// those yet, so keeping this simple.
script, err := prompt.CaptureInputViaEditor(
err = ruleScript.EditorPromptU(
cmd,
&inputs.Script,
rule.GetScript(),
rule.GetName()+".*.js",
func() {
cli.renderer.Infof("%s once you close the editor, the rule will be saved. To cancel, CTRL+C.", ansi.Faint("Hint:"))
},
cli.ruleEditorHint,
)
if err != nil {
return fmt.Errorf("Failed to capture input from the editor: %w", err)
Expand All @@ -312,7 +321,7 @@ auth0 rules update <rule-id> -n "My Updated Rule" --enabled=false`,
// display.
rule = &management.Rule{
Name: &inputs.Name,
Script: &script,
Script: &inputs.Script,
Enabled: &inputs.Enabled,
}

Expand Down Expand Up @@ -352,3 +361,7 @@ func (c *cli) rulePickerOptions() (pickerOptions, error) {

return opts, nil
}

func (c *cli) ruleEditorHint() {
c.renderer.Infof("%s once you close the editor, the rule will be saved. To cancel, CTRL+C.", ansi.Faint("Hint:"))
}
135 changes: 135 additions & 0 deletions internal/prompt/surveyext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package prompt

import (
"path/filepath"

"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
)

// EXTENDED to enable different prompting behavior
type Editor struct {
*survey.Editor
EditorCommand string
BlankAllowed bool
}

func (e *Editor) editorCommand() string {
if e.EditorCommand == "" {
return getDefaultEditor()
}

return e.EditorCommand
}

// EXTENDED to change prompt text
var EditorQuestionTemplate = `
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
{{- if .ShowAnswer}}
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
{{- else }}
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
{{- if and .Default (not .HideDefault)}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
{{- color "cyan"}}[(e) to launch {{ .EditorCommand }}{{- if .BlankAllowed }}, enter to skip{{ end }}] {{color "reset"}}
{{- end}}`

// EXTENDED to pass editor name (to use in prompt)
type EditorTemplateData struct {
survey.Editor
EditorCommand string
BlankAllowed bool
Answer string
ShowAnswer bool
ShowHelp bool
Config *survey.PromptConfig
}

// EXTENDED to augment prompt text and keypress handling
func (e *Editor) prompt(initialValue string, config *survey.PromptConfig) (interface{}, error) {
err := e.Render(
EditorQuestionTemplate,
// EXTENDED to support printing editor in prompt and BlankAllowed
EditorTemplateData{
Editor: *e.Editor,
BlankAllowed: e.BlankAllowed,
EditorCommand: filepath.Base(e.editorCommand()),
Config: config,
},
)
if err != nil {
return "", err
}

// start reading runes from the standard in
rr := e.NewRuneReader()
_ = rr.SetTermMode()
defer func() { _ = rr.RestoreTermMode() }()

cursor := e.NewCursor()
cursor.Hide()
defer cursor.Show()

for {
// EXTENDED to handle the e to edit / enter to skip behavior + BlankAllowed
r, _, err := rr.ReadRune()
if err != nil {
return "", err
}
if r == 'e' {
break
}
if r == '\r' || r == '\n' {
if e.BlankAllowed {
return "", nil
} else {
continue
}
}
if r == terminal.KeyInterrupt {
return "", terminal.InterruptErr
}
if r == terminal.KeyEndTransmission {
break
}
if string(r) == config.HelpInput && e.Help != "" {
err = e.Render(
EditorQuestionTemplate,
EditorTemplateData{
// EXTENDED to support printing editor in prompt, BlankAllowed
Editor: *e.Editor,
BlankAllowed: e.BlankAllowed,
EditorCommand: filepath.Base(e.editorCommand()),
ShowHelp: true,
Config: config,
},
)
if err != nil {
return "", err
}
}
continue
}

text, err := CaptureInputViaEditor(initialValue, e.FileName, nil)
if err != nil {
return "", err
}

// check length, return default value on empty
if len(text) == 0 && !e.AppendDefault {
return e.Default, nil
}

return text, nil
}

// EXTENDED This is straight copypasta from survey to get our overridden prompt called.
func (e *Editor) Prompt(config *survey.PromptConfig) (interface{}, error) {
initialValue := ""
if e.Default != "" && e.AppendDefault {
initialValue = e.Default
}
return e.prompt(initialValue, config)
}