Repository navigation
Add preview prompter command
#10745
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
Merged
Merged
Add preview prompter command
#10745
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
abd573a
feat(preview): add `preview prompter` command
BagToad 54a929d
feat(preview): enforce fixed order for prompts
BagToad 3af32fb
fix(preview): remove needless newlines
BagToad 4bc056f
Merge branch 'trunk' into kw/preview-prompter-command
BagToad c4ab455
fix(prompter): update prompter create for changes in trunk
BagToad f305cb1
fix(prompter): print to iostreams stdout
BagToad 175767c
doc(preview): add long description
BagToad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package preview | ||
|
|
||
| import ( | ||
| "github.com/MakeNowJust/heredoc" | ||
| cmdPrompter "github.com/cli/cli/v2/pkg/cmd/preview/prompter" | ||
| "github.com/cli/cli/v2/pkg/cmdutil" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func NewCmdPreview(f *cmdutil.Factory) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "preview <command>", | ||
| Short: "Execute previews for gh features", | ||
| Long: heredoc.Doc(` | ||
| Preview commands are for testing, demonstrative, and development purposes only. | ||
| They should be considered unstable and can change at any time. | ||
| `), | ||
| } | ||
|
|
||
| cmdutil.DisableAuthCheck(cmd) | ||
|
|
||
| cmd.AddCommand(cmdPrompter.NewCmdPrompter(f, nil)) | ||
|
|
||
| return cmd | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| package prompter | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/MakeNowJust/heredoc" | ||
| "github.com/cli/cli/v2/internal/gh" | ||
| "github.com/cli/cli/v2/internal/prompter" | ||
| "github.com/cli/cli/v2/pkg/cmdutil" | ||
| "github.com/cli/cli/v2/pkg/iostreams" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| type prompterOptions struct { | ||
| IO *iostreams.IOStreams | ||
| Config func() (gh.Config, error) | ||
|
|
||
| PromptsToRun []func(prompter.Prompter, *iostreams.IOStreams) error | ||
| } | ||
|
|
||
| func NewCmdPrompter(f *cmdutil.Factory, runF func(*prompterOptions) error) *cobra.Command { | ||
| opts := &prompterOptions{ | ||
| IO: f.IOStreams, | ||
| Config: f.Config, | ||
| } | ||
|
|
||
| const ( | ||
| selectPrompt = "select" | ||
| multiSelectPrompt = "multi-select" | ||
| inputPrompt = "input" | ||
| passwordPrompt = "password" | ||
| confirmPrompt = "confirm" | ||
| authTokenPrompt = "auth-token" | ||
| confirmDeletionPrompt = "confirm-deletion" | ||
| inputHostnamePrompt = "input-hostname" | ||
| markdownEditorPrompt = "markdown-editor" | ||
| ) | ||
|
|
||
| prompterTypeFuncMap := map[string]func(prompter.Prompter, *iostreams.IOStreams) error{ | ||
| selectPrompt: runSelect, | ||
| multiSelectPrompt: runMultiSelect, | ||
| inputPrompt: runInput, | ||
| passwordPrompt: runPassword, | ||
| confirmPrompt: runConfirm, | ||
| authTokenPrompt: runAuthToken, | ||
| confirmDeletionPrompt: runConfirmDeletion, | ||
| inputHostnamePrompt: runInputHostname, | ||
| markdownEditorPrompt: runMarkdownEditor, | ||
| } | ||
|
|
||
| allPromptsOrder := []string{ | ||
| selectPrompt, | ||
| multiSelectPrompt, | ||
| inputPrompt, | ||
| passwordPrompt, | ||
| confirmPrompt, | ||
| authTokenPrompt, | ||
| confirmDeletionPrompt, | ||
| inputHostnamePrompt, | ||
| markdownEditorPrompt, | ||
| } | ||
|
|
||
| cmd := &cobra.Command{ | ||
| Use: "prompter [prompt type]", | ||
| Short: "Execute a test program to preview the prompter", | ||
| Long: heredoc.Doc(` | ||
| Execute a test program to preview the prompter. | ||
| Without an argument, all prompts will be run. | ||
|
|
||
| Available prompt types: | ||
| - select | ||
| - multi-select | ||
| - input | ||
| - password | ||
| - confirm | ||
| - auth-token | ||
| - confirm-deletion | ||
| - input-hostname | ||
| - markdown-editor | ||
| `), | ||
| Args: cobra.MaximumNArgs(1), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| if runF != nil { | ||
| return runF(opts) | ||
| } | ||
|
|
||
| if len(args) == 0 { | ||
| // All prompts, in a fixed order | ||
| for _, promptType := range allPromptsOrder { | ||
| f := prompterTypeFuncMap[promptType] | ||
| opts.PromptsToRun = append(opts.PromptsToRun, f) | ||
| } | ||
| } else { | ||
| // Only the one specified | ||
| for _, arg := range args { | ||
| f, ok := prompterTypeFuncMap[arg] | ||
| if !ok { | ||
| return fmt.Errorf("unknown prompter type: %q", arg) | ||
| } | ||
| opts.PromptsToRun = append(opts.PromptsToRun, f) | ||
| } | ||
| } | ||
|
|
||
| return prompterRun(opts) | ||
| }, | ||
| } | ||
|
|
||
| return cmd | ||
| } | ||
|
|
||
| func prompterRun(opts *prompterOptions) error { | ||
| editor, err := cmdutil.DetermineEditor(opts.Config) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| p := prompter.New(editor, opts.IO) | ||
|
|
||
| for _, f := range opts.PromptsToRun { | ||
| if err := f(p, opts.IO); err != nil { | ||
| return err | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func runSelect(p prompter.Prompter, io *iostreams.IOStreams) error { | ||
| fmt.Fprintln(io.Out, "Demonstrating Single Select") | ||
| cuisines := []string{"Italian", "Greek", "Indian", "Japanese", "American"} | ||
| favorite, err := p.Select("Favorite cuisine?", "Italian", cuisines) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintf(io.Out, "Favorite cuisine: %s\n", cuisines[favorite]) | ||
| return nil | ||
| } | ||
|
|
||
| func runMultiSelect(p prompter.Prompter, io *iostreams.IOStreams) error { | ||
| fmt.Fprintln(io.Out, "Demonstrating Multi Select") | ||
| cuisines := []string{"Italian", "Greek", "Indian", "Japanese", "American"} | ||
| favorites, err := p.MultiSelect("Favorite cuisines?", []string{}, cuisines) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| for _, f := range favorites { | ||
| fmt.Fprintf(io.Out, "Favorite cuisine: %s\n", cuisines[f]) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func runInput(p prompter.Prompter, io *iostreams.IOStreams) error { | ||
| fmt.Fprintln(io.Out, "Demonstrating Text Input") | ||
| text, err := p.Input("Favorite meal?", "Breakfast") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintf(io.Out, "You typed: %s\n", text) | ||
| return nil | ||
| } | ||
|
|
||
| func runPassword(p prompter.Prompter, io *iostreams.IOStreams) error { | ||
| fmt.Fprintln(io.Out, "Demonstrating Password Input") | ||
| safeword, err := p.Password("Safe word?") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintf(io.Out, "Safe word: %s\n", safeword) | ||
| return nil | ||
| } | ||
|
|
||
| func runConfirm(p prompter.Prompter, io *iostreams.IOStreams) error { | ||
| fmt.Fprintln(io.Out, "Demonstrating Confirmation") | ||
| confirmation, err := p.Confirm("Are you sure?", true) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintf(io.Out, "Confirmation: %t\n", confirmation) | ||
| return nil | ||
| } | ||
|
|
||
| func runAuthToken(p prompter.Prompter, io *iostreams.IOStreams) error { | ||
| fmt.Fprintln(io.Out, "Demonstrating Auth Token (can't be blank)") | ||
| token, err := p.AuthToken() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintf(io.Out, "Auth token: %s\n", token) | ||
| return nil | ||
| } | ||
|
|
||
| func runConfirmDeletion(p prompter.Prompter, io *iostreams.IOStreams) error { | ||
| fmt.Fprintln(io.Out, "Demonstrating Deletion Confirmation") | ||
| err := p.ConfirmDeletion("delete-me") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintln(io.Out, "Item deleted") | ||
| return nil | ||
| } | ||
|
|
||
| func runInputHostname(p prompter.Prompter, io *iostreams.IOStreams) error { | ||
| fmt.Fprintln(io.Out, "Demonstrating Hostname") | ||
| hostname, err := p.InputHostname() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintf(io.Out, "Hostname: %s\n", hostname) | ||
| return nil | ||
| } | ||
|
|
||
| func runMarkdownEditor(p prompter.Prompter, io *iostreams.IOStreams) error { | ||
| defaultText := "default text value" | ||
|
|
||
| fmt.Fprintln(io.Out, "Demonstrating Markdown Editor with blanks allowed and default text") | ||
| editorText, err := p.MarkdownEditor("Edit your text:", defaultText, true) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintf(io.Out, "Returned text: %s\n\n", editorText) | ||
|
|
||
| fmt.Fprintln(io.Out, "Demonstrating Markdown Editor with blanks disallowed and default text") | ||
| editorText2, err := p.MarkdownEditor("Edit your text:", defaultText, false) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintf(io.Out, "Returned text: %s\n\n", editorText2) | ||
|
|
||
| fmt.Fprintln(io.Out, "Demonstrating Markdown Editor with blanks disallowed and no default text") | ||
| editorText3, err := p.MarkdownEditor("Edit your text:", "", false) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintf(io.Out, "Returned text: %s\n", editorText3) | ||
| return nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Should there be a separate section for previews that shows each of the commands within?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I follow; are you talking about adding some whitespace here to separate
cmd.AddCommand(previewCmd.NewCmdPreview(f))from other commands?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the confusion and the delay; let me elaborate.
Within the root command, the command groups created are turned into separate sections of the
gh --helpoutput:cli/pkg/cmd/root/root.go
Lines 111 to 122 in 7ceca45
Expand for
gh --helpexampleThis is handled deep in the bowels of
gh:cli/pkg/cmd/root/help.go
Lines 136 to 151 in 7ceca45
So, the suggestion was whether we should have a separate command group for preview commands or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PREVIEW COMMANDS prompter: Execute a test program to preview the prompterThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point actually. I like the separate group.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I got to implementing this and realized all the commands in
gh --helpare top level commands.Like
gh runandgh workflow, for example.But in this case, only
previewis our top-level command.Referring back to the suggested output:
Is this confusing that, for only the
previewcommand group, none of the commands listed under the group are top-level commands? Are users going to understand that you have to rungh preview prompterand notgh prompter?WDYT?
Maybe we can merge this PR without this, and then decide later how we'd want that to look?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😶 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a fair point about users potentially being confused / missing these commands need to be
gh preview.I agree we should move forward with this PR as-is and revisit as we see how
previewexperiment proceeds.Currently,
previewwill show up underADDITIONAL COMMANDSsection: