File: prompter.go

package info (click to toggle)
golang-github-cli-go-gh-v2 2.6.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 496 kB
  • sloc: makefile: 2
file content (134 lines) | stat: -rw-r--r-- 3,485 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Package prompter provides various methods for prompting the user with
// questions for input.
package prompter

import (
	"fmt"
	"io"
	"strings"

	"github.com/AlecAivazis/survey/v2"
	"github.com/cli/go-gh/v2/pkg/text"
)

// Prompter provides methods for prompting the user.
type Prompter struct {
	stdin  FileReader
	stdout FileWriter
	stderr FileWriter
}

// FileWriter provides a minimal writable interface for stdout and stderr.
type FileWriter interface {
	io.Writer
	Fd() uintptr
}

// FileReader provides a minimal readable interface for stdin.
type FileReader interface {
	io.Reader
	Fd() uintptr
}

// New instantiates a new Prompter.
func New(stdin FileReader, stdout FileWriter, stderr FileWriter) *Prompter {
	return &Prompter{
		stdin:  stdin,
		stdout: stdout,
		stderr: stderr,
	}
}

// Select prompts the user to select an option from a list of options.
func (p *Prompter) Select(prompt, defaultValue string, options []string) (int, error) {
	var result int
	q := &survey.Select{
		Message:  prompt,
		Options:  options,
		PageSize: 20,
		Filter:   latinMatchingFilter,
	}
	if defaultValue != "" {
		for _, o := range options {
			if o == defaultValue {
				q.Default = defaultValue
				break
			}
		}
	}
	err := p.ask(q, &result)
	return result, err
}

// MultiSelect prompts the user to select multiple options from a list of options.
func (p *Prompter) MultiSelect(prompt string, defaultValues, options []string) ([]int, error) {
	var result []int
	q := &survey.MultiSelect{
		Message:  prompt,
		Options:  options,
		PageSize: 20,
		Filter:   latinMatchingFilter,
	}
	if len(defaultValues) > 0 {
		validatedDefault := []string{}
		for _, x := range defaultValues {
			for _, y := range options {
				if x == y {
					validatedDefault = append(validatedDefault, x)
				}
			}
		}
		q.Default = validatedDefault
	}
	err := p.ask(q, &result)
	return result, err
}

// Input prompts the user to input a single-line string.
func (p *Prompter) Input(prompt, defaultValue string) (string, error) {
	var result string
	err := p.ask(&survey.Input{
		Message: prompt,
		Default: defaultValue,
	}, &result)
	return result, err
}

// Password prompts the user to input a single-line string without echoing the input.
func (p *Prompter) Password(prompt string) (string, error) {
	var result string
	err := p.ask(&survey.Password{
		Message: prompt,
	}, &result)
	return result, err
}

// Confirm prompts the user to confirm a yes/no question.
func (p *Prompter) Confirm(prompt string, defaultValue bool) (bool, error) {
	var result bool
	err := p.ask(&survey.Confirm{
		Message: prompt,
		Default: defaultValue,
	}, &result)
	return result, err
}

func (p *Prompter) ask(q survey.Prompt, response interface{}, opts ...survey.AskOpt) error {
	opts = append(opts, survey.WithStdio(p.stdin, p.stdout, p.stderr))
	err := survey.AskOne(q, response, opts...)
	if err == nil {
		return nil
	}
	return fmt.Errorf("could not prompt: %w", err)
}

// latinMatchingFilter returns whether the value matches the input filter.
// The strings are compared normalized in case.
// The filter's diactritics are kept as-is, but the value's are normalized,
// so that a missing diactritic in the filter still returns a result.
func latinMatchingFilter(filter, value string, index int) bool {
	filter = strings.ToLower(filter)
	value = strings.ToLower(value)
	// include this option if it matches.
	return strings.Contains(value, filter) || strings.Contains(text.RemoveDiacritics(value), filter)
}