-
-
Notifications
You must be signed in to change notification settings - Fork 365
Expand file tree
/
Copy pathconfigure.go
More file actions
271 lines (223 loc) · 7.13 KB
/
Copy pathconfigure.go
File metadata and controls
271 lines (223 loc) · 7.13 KB
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package cmd
import (
"bufio"
"fmt"
"os"
"strings"
"text/tabwriter"
"github.com/exercism/cli/api"
"github.com/exercism/cli/config"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
var (
viperConfig *viper.Viper
)
// configureCmd configures the command-line client with user-specific settings.
var configureCmd = &cobra.Command{
Use: "configure",
Aliases: []string{"c"},
Short: "Configure the command-line client.",
Long: `Configure the command-line client to customize it to your needs.
This lets you set up the CLI to talk to the API on your behalf,
and tells the CLI about your setup so it puts things in the right
places.
You can also override certain default settings to suit your preferences.
`,
RunE: func(cmd *cobra.Command, args []string) error {
configuration := config.NewConfig()
viperConfig.AddConfigPath(configuration.Dir)
viperConfig.SetConfigName("user")
viperConfig.SetConfigType("json")
// Ignore error. If the file doesn't exist, that is fine.
_ = viperConfig.ReadInConfig()
configuration.UserViperConfig = viperConfig
return runConfigure(configuration, cmd.Flags())
},
}
func runConfigure(configuration config.Config, flags *pflag.FlagSet) error {
cfg := configuration.UserViperConfig
// Show the existing configuration and exit.
show, err := flags.GetBool("show")
if err != nil {
return err
}
if show {
printCurrentConfig(configuration)
return nil
}
// Interactive configuration.
interactive, err := flags.GetBool("interactive")
if err != nil {
return err
}
if interactive {
tokenURL := config.SettingsURL(cfg.GetString("apibaseurl"))
fmt.Println("Find your token on", tokenURL)
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter your token: ")
token, err := reader.ReadString('\n')
token = strings.TrimSpace(token)
// If we got an EOF print a new line, so that the
// next print can be on a new line.
if err != nil {
fmt.Println()
}
if err != nil || token == "" {
tokenURL := config.SettingsURL(cfg.GetString("apibaseurl"))
return fmt.Errorf("There is no token provided. Find your token on %s, and call this command again.", tokenURL)
}
flags.Set("token", token)
}
// If the command is run 'bare' and we have no token,
// explain how to set the token.
if flags.NFlag() == 0 && cfg.GetString("token") == "" {
tokenURL := config.SettingsURL(cfg.GetString("apibaseurl"))
return fmt.Errorf("There is no token configured. Find your token on %s, and call this command again with --token=<your-token>.", tokenURL)
}
// Determine the base API URL.
baseURL, err := flags.GetString("api")
if err != nil {
return err
}
if baseURL == "" {
baseURL = cfg.GetString("apibaseurl")
}
if baseURL == "" {
baseURL = configuration.DefaultBaseURL
}
// By default we verify that
// - the configured API URL is reachable.
// - the configured token is valid.
skipVerification, err := flags.GetBool("no-verify")
if err != nil {
return err
}
// Is the API URL reachable?
if !skipVerification {
client, err := api.NewClient("", baseURL)
if err != nil {
return err
}
if err := client.IsPingable(); err != nil {
return fmt.Errorf("The base API URL '%s' cannot be reached.\n\n%s", baseURL, err)
}
}
// Finally, configure the URL.
cfg.Set("apibaseurl", baseURL)
// Determine the token.
token, err := flags.GetString("token")
if err != nil {
return err
}
if token == "" {
token = cfg.GetString("token")
}
tokenURL := config.SettingsURL(cfg.GetString("apibaseurl"))
// If we don't have a token then explain how to set it and bail.
if token == "" {
return fmt.Errorf("There is no token configured. Find your token on %s, and call this command again with --token=<your-token>.", tokenURL)
}
// Verify that the token is valid.
if !skipVerification {
client, err := api.NewClient(token, baseURL)
if err != nil {
return err
}
ok, err := client.TokenIsValid()
if err != nil {
return err
}
if !ok {
return fmt.Errorf("The token '%s' is invalid. Find your token on %s.", token, tokenURL)
}
}
// Finally, configure the token.
cfg.Set("token", token)
// Determine the workspace.
workspace, err := flags.GetString("workspace")
if err != nil {
return err
}
if workspace == "" {
workspace = cfg.GetString("workspace")
}
workspace = config.Resolve(workspace, configuration.Home)
if workspace != "" {
// If there is a non-directory here, then we cannot proceed.
if info, err := os.Lstat(workspace); !os.IsNotExist(err) && !info.IsDir() {
msg := `
There is already something at the workspace location you are configuring:
%s
Please rename it, or set a different workspace location:
%s configure %s --workspace=PATH_TO_DIFFERENT_FOLDER
`
return fmt.Errorf(msg, workspace, BinaryName, commandify(flags))
}
}
if workspace == "" {
workspace = config.DefaultWorkspaceDir(configuration)
// If it already exists don't clobber it with the default.
if _, err := os.Lstat(workspace); !os.IsNotExist(err) {
msg := `
The default Exercism workspace is
%s
There is already something there.
If it's a directory, that might be fine.
If it's a file, you will need to move it first, or choose a
different location for the workspace.
You can choose the workspace location by rerunning this command
with the --workspace flag.
%s configure %s --workspace=%s
`
return fmt.Errorf(msg, workspace, BinaryName, commandify(flags), workspace)
}
}
// Configure the workspace.
cfg.Set("workspace", workspace)
// Persist the new configuration.
if err := configuration.Save("user"); err != nil {
return err
}
fmt.Fprintln(Err, "\nYou have configured the Exercism command-line client:")
printCurrentConfig(configuration)
return nil
}
func printCurrentConfig(configuration config.Config) {
w := tabwriter.NewWriter(Err, 0, 0, 2, ' ', 0)
defer w.Flush()
v := configuration.UserViperConfig
fmt.Fprintln(w, "")
fmt.Fprintln(w, fmt.Sprintf("Config dir:\t\t%s", configuration.Dir))
fmt.Fprintln(w, fmt.Sprintf("Token:\t(-t, --token)\t%s", v.GetString("token")))
fmt.Fprintln(w, fmt.Sprintf("Workspace:\t(-w, --workspace)\t%s", v.GetString("workspace")))
fmt.Fprintln(w, fmt.Sprintf("API Base URL:\t(-a, --api)\t%s", v.GetString("apibaseurl")))
fmt.Fprintln(w, "")
}
func commandify(flags *pflag.FlagSet) string {
var cmd string
fn := func(f *pflag.Flag) {
if f.Changed {
cmd = fmt.Sprintf("%s --%s=%s", cmd, f.Name, f.Value.String())
}
}
flags.VisitAll(fn)
return strings.TrimLeft(cmd, " ")
}
func initConfigureCmd() {
viperConfig = viper.New()
setupConfigureFlags(configureCmd.Flags())
}
func setupConfigureFlags(flags *pflag.FlagSet) {
flags.StringP("token", "t", "", "authentication token used to connect to the site")
flags.StringP("workspace", "w", "", "directory for exercism exercises")
flags.StringP("api", "a", "", "API base url")
flags.BoolP("show", "s", false, "show the current configuration")
flags.BoolP("interactive", "i", false, "set configuration values with interactive prompts")
flags.BoolP("no-verify", "", false, "skip online token authorization check")
}
func init() {
RootCmd.AddCommand(configureCmd)
initConfigureCmd()
}