Merge pull request #10745 from cli/kw/preview-prompter-command

Add `preview prompter` command
This commit is contained in:
Kynan Ware 2025-05-20 13:13:42 -06:00 committed by GitHub
commit a04dc73e5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 263 additions and 0 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -26,6 +26,7 @@ import (
labelCmd "github.com/cli/cli/v2/pkg/cmd/label"
orgCmd "github.com/cli/cli/v2/pkg/cmd/org"
prCmd "github.com/cli/cli/v2/pkg/cmd/pr"
previewCmd "github.com/cli/cli/v2/pkg/cmd/preview"
projectCmd "github.com/cli/cli/v2/pkg/cmd/project"
releaseCmd "github.com/cli/cli/v2/pkg/cmd/release"
repoCmd "github.com/cli/cli/v2/pkg/cmd/repo"
@ -141,6 +142,7 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) (*cobra.Command,
cmd.AddCommand(statusCmd.NewCmdStatus(f, nil))
cmd.AddCommand(codespaceCmd.NewCmdCodespace(f))
cmd.AddCommand(projectCmd.NewCmdProject(f))
cmd.AddCommand(previewCmd.NewCmdPreview(f))
// below here at the commands that require the "intelligent" BaseRepo resolver
repoResolvingCmdFactory := *f