-
Notifications
You must be signed in to change notification settings - Fork 224
/
prompt.go
243 lines (191 loc) · 5.37 KB
/
prompt.go
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// Package prompt implements input-related functionality.
package prompt
import (
"context"
"errors"
"fmt"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/superfly/flyctl/pkg/iostreams"
"github.com/superfly/flyctl/api"
"github.com/superfly/flyctl/internal/client"
"github.com/superfly/flyctl/internal/config"
"github.com/superfly/flyctl/internal/sort"
)
func String(ctx context.Context, dst *string, msg, def string, required bool) error {
opt, err := newSurveyIO(ctx)
if err != nil {
return err
}
p := &survey.Input{
Message: msg,
Default: def,
}
opts := []survey.AskOpt{opt}
if required {
opts = append(opts, survey.WithValidator(survey.Required))
}
return survey.AskOne(p, dst, opts...)
}
func Password(ctx context.Context, dst *string, msg string, required bool) error {
opt, err := newSurveyIO(ctx)
if err != nil {
return err
}
p := &survey.Password{
Message: msg,
}
opts := []survey.AskOpt{opt}
if required {
opts = append(opts, survey.WithValidator(survey.Required))
}
return survey.AskOne(p, dst, opts...)
}
func Select(ctx context.Context, index *int, msg, def string, options ...string) error {
opt, err := newSurveyIO(ctx)
if err != nil {
return err
}
p := &survey.Select{
Message: msg,
Options: options,
PageSize: 15,
}
if def != "" {
p.Default = def
}
return survey.AskOne(p, index, opt)
}
func Confirmf(ctx context.Context, format string, a ...interface{}) (bool, error) {
return Confirm(ctx, fmt.Sprintf(format, a...))
}
func Confirm(ctx context.Context, message string) (confirm bool, err error) {
var opt survey.AskOpt
if opt, err = newSurveyIO(ctx); err != nil {
return
}
prompt := &survey.Confirm{
Message: message,
}
err = survey.AskOne(prompt, &confirm, opt)
return
}
var errNonInteractive = errors.New("prompt: non interactive")
func IsNonInteractive(err error) bool {
return errors.Is(err, errNonInteractive)
}
type NonInteractiveError string
func (e NonInteractiveError) Error() string { return string(e) }
func (NonInteractiveError) Unwrap() error { return errNonInteractive }
func newSurveyIO(ctx context.Context) (survey.AskOpt, error) {
io := iostreams.FromContext(ctx)
if !io.IsInteractive() {
return nil, errNonInteractive
}
in, ok := io.In.(terminal.FileReader)
if !ok {
return nil, errNonInteractive
}
out, ok := io.Out.(terminal.FileWriter)
if !ok {
return nil, errNonInteractive
}
return survey.WithStdio(in, out, io.ErrOut), nil
}
var errOrgSlugRequired = NonInteractiveError("org slug must be specified when not running interactively")
// Org returns the Organization the user has passed in via flag or prompts the
// user for one.
func Org(ctx context.Context, typ *api.OrganizationType) (*api.Organization, error) {
client := client.FromContext(ctx).API()
orgs, err := client.GetOrganizations(ctx, typ)
if err != nil {
return nil, err
}
sort.OrganizationsByTypeAndName(orgs)
io := iostreams.FromContext(ctx)
slug := config.FromContext(ctx).Organization
switch {
case slug == "" && len(orgs) == 1 && orgs[0].Type == "PERSONAL":
fmt.Fprintf(io.ErrOut, "automatically selected %s organization: %s\n",
strings.ToLower(orgs[0].Type), orgs[0].Name)
return &orgs[0], nil
case slug != "":
for _, org := range orgs {
if slug == org.Slug {
return &org, nil
}
}
return nil, fmt.Errorf("organization %s not found", slug)
default:
switch org, err := SelectOrg(ctx, orgs); {
case err == nil:
return org, nil
case IsNonInteractive(err):
return nil, errOrgSlugRequired
default:
return nil, err
}
}
}
func SelectOrg(ctx context.Context, orgs []api.Organization) (org *api.Organization, err error) {
var options []string
for _, org := range orgs {
options = append(options, fmt.Sprintf("%s (%s)", org.Name, org.Slug))
}
var index int
if err = Select(ctx, &index, "Select Organization:", "", options...); err == nil {
org = &orgs[index]
}
return
}
var errRegionSlugRequired = NonInteractiveError("region slug must be specified when not running interactively")
// Region returns the region the user has passed in via flag or prompts the
// user for one.
func Region(ctx context.Context) (*api.Region, error) {
client := client.FromContext(ctx).API()
regions, defaultRegion, err := client.PlatformRegions(ctx)
if err != nil {
return nil, err
}
sort.RegionsByNameAndCode(regions)
slug := config.FromContext(ctx).Region
switch {
case slug != "":
for _, region := range regions {
if slug == region.Code {
return ®ion, nil
}
}
return nil, fmt.Errorf("region %s not found", slug)
default:
var defaultRegionCode string
if defaultRegion != nil {
defaultRegionCode = defaultRegion.Code
}
switch org, err := SelectRegion(ctx, regions, defaultRegionCode); {
case err == nil:
return org, nil
case IsNonInteractive(err):
return nil, errRegionSlugRequired
default:
return nil, err
}
}
}
func SelectRegion(ctx context.Context, regions []api.Region, defaultCode string) (region *api.Region, err error) {
var defaultOption string
var options []string
for _, r := range regions {
option := fmt.Sprintf("%s (%s)", r.Name, r.Code)
if r.Code == defaultCode {
defaultOption = option
}
options = append(options, option)
}
var index int
if err = Select(ctx, &index, "Select region:", defaultOption, options...); err == nil {
region = ®ions[index]
}
return
}