@@ -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.
3032Download 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 , "\n Downloaded to\n " )
188- fmt .Fprintf (Out , "%s\n " , solution .Dir )
189- return nil
190- },
200+ }
201+ fmt .Fprintf (Err , "\n Downloaded to\n " )
202+ fmt .Fprintf (Out , "%s\n " , solution .Dir )
203+ return nil
191204}
192205
193206type 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
229242func init () {
230243 RootCmd .AddCommand (downloadCmd )
231- initDownloadCmd ( )
244+ setupDownloadFlags ( downloadCmd . Flags () )
232245}
0 commit comments