Skip to content

Commit 55062c2

Browse files
author
Katrina Owen
authored
Merge pull request #640 from exercism/rm-user-config
Delete the UserConfig type
2 parents 89c39fb + b80c97c commit 55062c2

11 files changed

Lines changed: 291 additions & 397 deletions

cmd/download.go

Lines changed: 143 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/exercism/cli/config"
1515
"github.com/exercism/cli/workspace"
1616
"github.com/spf13/cobra"
17+
"github.com/spf13/pflag"
18+
"github.com/spf13/viper"
1719
)
1820

1921
// downloadCmd represents the download command
@@ -30,164 +32,175 @@ latest solution.
3032
Download other people's solutions by providing the UUID.
3133
`,
3234
RunE: func(cmd *cobra.Command, args []string) error {
33-
usrCfg, err := config.NewUserConfig()
34-
if err != nil {
35-
return err
36-
}
37-
if usrCfg.Token == "" {
38-
tokenURL := config.InferSiteURL(usrCfg.APIBaseURL) + "/my/settings"
39-
return fmt.Errorf(msgWelcomePleaseConfigure, tokenURL, BinaryName)
40-
}
35+
cfg := config.NewConfiguration()
36+
37+
v := viper.New()
38+
v.AddConfigPath(cfg.Dir)
39+
v.SetConfigName("user")
40+
v.SetConfigType("json")
41+
// Ignore error. If the file doesn't exist, that is fine.
42+
_ = v.ReadInConfig()
43+
cfg.UserViperConfig = v
4144

42-
uuid, err := cmd.Flags().GetString("uuid")
45+
return runDownload(cfg, cmd.Flags(), args)
46+
},
47+
}
48+
49+
func runDownload(cfg config.Configuration, flags *pflag.FlagSet, args []string) error {
50+
usrCfg := cfg.UserViperConfig
51+
if usrCfg.GetString("token") == "" {
52+
tokenURL := config.InferSiteURL(usrCfg.GetString("apibaseurl")) + "/my/settings"
53+
return fmt.Errorf(msgWelcomePleaseConfigure, tokenURL, BinaryName)
54+
}
55+
56+
uuid, err := flags.GetString("uuid")
57+
if err != nil {
58+
return err
59+
}
60+
exercise, err := flags.GetString("exercise")
61+
if err != nil {
62+
return err
63+
}
64+
if uuid == "" && exercise == "" {
65+
return errors.New("need an --exercise name or a solution --uuid")
66+
}
67+
68+
var slug string
69+
if uuid == "" {
70+
slug = "latest"
71+
} else {
72+
slug = uuid
73+
}
74+
url := fmt.Sprintf("%s/solutions/%s", usrCfg.GetString("apibaseurl"), slug)
75+
76+
client, err := api.NewClient(usrCfg.GetString("token"), usrCfg.GetString("apibaseurl"))
77+
if err != nil {
78+
return err
79+
}
80+
81+
req, err := client.NewRequest("GET", url, nil)
82+
if err != nil {
83+
return err
84+
}
85+
86+
track, err := flags.GetString("track")
87+
if err != nil {
88+
return err
89+
}
90+
91+
if uuid == "" {
92+
q := req.URL.Query()
93+
q.Add("exercise_id", exercise)
94+
if track != "" {
95+
q.Add("track_id", track)
96+
}
97+
req.URL.RawQuery = q.Encode()
98+
}
99+
100+
res, err := client.Do(req)
101+
if err != nil {
102+
return err
103+
}
104+
105+
var payload downloadPayload
106+
defer res.Body.Close()
107+
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
108+
return fmt.Errorf("unable to parse API response - %s", err)
109+
}
110+
111+
if res.StatusCode == http.StatusUnauthorized {
112+
siteURL := config.InferSiteURL(usrCfg.GetString("apibaseurl"))
113+
return fmt.Errorf("unauthorized request. Please run the configure command. You can find your API token at %s/my/settings", siteURL)
114+
}
115+
116+
if res.StatusCode != http.StatusOK {
117+
switch payload.Error.Type {
118+
case "track_ambiguous":
119+
return fmt.Errorf("%s: %s", payload.Error.Message, strings.Join(payload.Error.PossibleTrackIDs, ", "))
120+
default:
121+
return errors.New(payload.Error.Message)
122+
}
123+
}
124+
125+
solution := workspace.Solution{
126+
AutoApprove: payload.Solution.Exercise.AutoApprove,
127+
Track: payload.Solution.Exercise.Track.ID,
128+
Exercise: payload.Solution.Exercise.ID,
129+
ID: payload.Solution.ID,
130+
URL: payload.Solution.URL,
131+
Handle: payload.Solution.User.Handle,
132+
IsRequester: payload.Solution.User.IsRequester,
133+
}
134+
135+
dir := filepath.Join(usrCfg.GetString("workspace"), solution.Track)
136+
os.MkdirAll(dir, os.FileMode(0755))
137+
138+
var ws workspace.Workspace
139+
if solution.IsRequester {
140+
ws, err = workspace.New(dir)
43141
if err != nil {
44142
return err
45143
}
46-
exercise, err := cmd.Flags().GetString("exercise")
144+
} else {
145+
ws, err = workspace.New(filepath.Join(usrCfg.GetString("workspace"), "users", solution.Handle, solution.Track))
47146
if err != nil {
48147
return err
49148
}
50-
if uuid == "" && exercise == "" {
51-
return errors.New("need an --exercise name or a solution --uuid")
52-
}
149+
}
53150

54-
var slug string
55-
if uuid == "" {
56-
slug = "latest"
57-
} else {
58-
slug = uuid
59-
}
60-
url := fmt.Sprintf("%s/solutions/%s", usrCfg.APIBaseURL, slug)
151+
dir, err = ws.SolutionPath(solution.Exercise, solution.ID)
152+
if err != nil {
153+
return err
154+
}
61155

62-
client, err := api.NewClient(usrCfg.Token, usrCfg.APIBaseURL)
63-
if err != nil {
64-
return err
65-
}
156+
os.MkdirAll(dir, os.FileMode(0755))
66157

67-
req, err := client.NewRequest("GET", url, nil)
68-
if err != nil {
69-
return err
70-
}
158+
err = solution.Write(dir)
159+
if err != nil {
160+
return err
161+
}
71162

72-
track, err := cmd.Flags().GetString("track")
163+
for _, file := range payload.Solution.Files {
164+
url := fmt.Sprintf("%s%s", payload.Solution.FileDownloadBaseURL, file)
165+
req, err := client.NewRequest("GET", url, nil)
73166
if err != nil {
74167
return err
75168
}
76169

77-
if uuid == "" {
78-
q := req.URL.Query()
79-
q.Add("exercise_id", exercise)
80-
if track != "" {
81-
q.Add("track_id", track)
82-
}
83-
req.URL.RawQuery = q.Encode()
84-
}
85-
86170
res, err := client.Do(req)
87171
if err != nil {
88172
return err
89173
}
90-
91-
var payload downloadPayload
92174
defer res.Body.Close()
93-
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
94-
return fmt.Errorf("unable to parse API response - %s", err)
95-
}
96-
97-
if res.StatusCode == http.StatusUnauthorized {
98-
siteURL := config.InferSiteURL(usrCfg.APIBaseURL)
99-
return fmt.Errorf("unauthorized request. Please run the configure command. You can find your API token at %s/my/settings", siteURL)
100-
}
101175

102176
if res.StatusCode != http.StatusOK {
103-
switch payload.Error.Type {
104-
case "track_ambiguous":
105-
return fmt.Errorf("%s: %s", payload.Error.Message, strings.Join(payload.Error.PossibleTrackIDs, ", "))
106-
default:
107-
return errors.New(payload.Error.Message)
108-
}
177+
// TODO: deal with it
178+
continue
109179
}
110-
111-
solution := workspace.Solution{
112-
AutoApprove: payload.Solution.Exercise.AutoApprove,
113-
Track: payload.Solution.Exercise.Track.ID,
114-
Exercise: payload.Solution.Exercise.ID,
115-
ID: payload.Solution.ID,
116-
URL: payload.Solution.URL,
117-
Handle: payload.Solution.User.Handle,
118-
IsRequester: payload.Solution.User.IsRequester,
180+
// Don't bother with empty files.
181+
if res.Header.Get("Content-Length") == "0" {
182+
continue
119183
}
120184

121-
dir := filepath.Join(usrCfg.Workspace, solution.Track)
185+
// TODO: if there's a collision, interactively resolve (show diff, ask if overwrite).
186+
// TODO: handle --force flag to overwrite without asking.
187+
relativePath := filepath.FromSlash(file)
188+
dir := filepath.Join(solution.Dir, filepath.Dir(relativePath))
122189
os.MkdirAll(dir, os.FileMode(0755))
123190

124-
var ws workspace.Workspace
125-
if solution.IsRequester {
126-
ws, err = workspace.New(dir)
127-
if err != nil {
128-
return err
129-
}
130-
} else {
131-
ws, err = workspace.New(filepath.Join(usrCfg.Workspace, "users", solution.Handle, solution.Track))
132-
if err != nil {
133-
return err
134-
}
135-
}
136-
137-
dir, err = ws.SolutionPath(solution.Exercise, solution.ID)
191+
f, err := os.Create(filepath.Join(solution.Dir, relativePath))
138192
if err != nil {
139193
return err
140194
}
141-
142-
os.MkdirAll(dir, os.FileMode(0755))
143-
144-
err = solution.Write(dir)
195+
defer f.Close()
196+
_, err = io.Copy(f, res.Body)
145197
if err != nil {
146198
return err
147199
}
148-
149-
for _, file := range payload.Solution.Files {
150-
url := fmt.Sprintf("%s%s", payload.Solution.FileDownloadBaseURL, file)
151-
req, err := client.NewRequest("GET", url, nil)
152-
if err != nil {
153-
return err
154-
}
155-
156-
res, err := client.Do(req)
157-
if err != nil {
158-
return err
159-
}
160-
defer res.Body.Close()
161-
162-
if res.StatusCode != http.StatusOK {
163-
// TODO: deal with it
164-
continue
165-
}
166-
// Don't bother with empty files.
167-
if res.Header.Get("Content-Length") == "0" {
168-
continue
169-
}
170-
171-
// TODO: if there's a collision, interactively resolve (show diff, ask if overwrite).
172-
// TODO: handle --force flag to overwrite without asking.
173-
relativePath := filepath.FromSlash(file)
174-
dir := filepath.Join(solution.Dir, filepath.Dir(relativePath))
175-
os.MkdirAll(dir, os.FileMode(0755))
176-
177-
f, err := os.Create(filepath.Join(solution.Dir, relativePath))
178-
if err != nil {
179-
return err
180-
}
181-
defer f.Close()
182-
_, err = io.Copy(f, res.Body)
183-
if err != nil {
184-
return err
185-
}
186-
}
187-
fmt.Fprintf(Err, "\nDownloaded to\n")
188-
fmt.Fprintf(Out, "%s\n", solution.Dir)
189-
return nil
190-
},
200+
}
201+
fmt.Fprintf(Err, "\nDownloaded to\n")
202+
fmt.Fprintf(Out, "%s\n", solution.Dir)
203+
return nil
191204
}
192205

193206
type downloadPayload struct {
@@ -220,13 +233,13 @@ type downloadPayload struct {
220233
} `json:"error,omitempty"`
221234
}
222235

223-
func initDownloadCmd() {
224-
downloadCmd.Flags().StringP("uuid", "u", "", "the solution UUID")
225-
downloadCmd.Flags().StringP("track", "t", "", "the track ID")
226-
downloadCmd.Flags().StringP("exercise", "e", "", "the exercise slug")
236+
func setupDownloadFlags(flags *pflag.FlagSet) {
237+
flags.StringP("uuid", "u", "", "the solution UUID")
238+
flags.StringP("track", "t", "", "the track ID")
239+
flags.StringP("exercise", "e", "", "the exercise slug")
227240
}
228241

229242
func init() {
230243
RootCmd.AddCommand(downloadCmd)
231-
initDownloadCmd()
244+
setupDownloadFlags(downloadCmd.Flags())
232245
}

0 commit comments

Comments
 (0)