-
-
Notifications
You must be signed in to change notification settings - Fork 361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
improve open cmd #788
improve open cmd #788
Changes from 7 commits
e42a157
4a7c94f
24d789a
d3d82c4
49c072d
a80741c
d0d0655
764d72f
76cf131
ce6efb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,9 +1,19 @@ | ||||
package cmd | ||||
|
||||
import ( | ||||
"encoding/json" | ||||
"errors" | ||||
"fmt" | ||||
"net/http" | ||||
"strings" | ||||
|
||||
"github.com/exercism/cli/api" | ||||
"github.com/exercism/cli/browser" | ||||
"github.com/exercism/cli/config" | ||||
"github.com/exercism/cli/workspace" | ||||
"github.com/spf13/cobra" | ||||
"github.com/spf13/pflag" | ||||
"github.com/spf13/viper" | ||||
) | ||||
|
||||
// openCmd opens the designated exercise in the browser. | ||||
|
@@ -15,17 +25,182 @@ var openCmd = &cobra.Command{ | |||
|
||||
Pass the path to the directory that contains the solution you want to see on the website. | ||||
`, | ||||
Args: cobra.ExactArgs(1), | ||||
RunE: func(cmd *cobra.Command, args []string) error { | ||||
metadata, err := workspace.NewExerciseMetadata(args[0]) | ||||
cfg := config.NewConfig() | ||||
|
||||
v := viper.New() | ||||
v.AddConfigPath(cfg.Dir) | ||||
v.SetConfigName("user") | ||||
v.SetConfigType("json") | ||||
// Ignore error. If the file doesn't exist, that is fine. | ||||
_ = v.ReadInConfig() | ||||
cfg.UserViperConfig = v | ||||
|
||||
return runOpen(cfg, cmd.Flags(), args) | ||||
}, | ||||
} | ||||
|
||||
func runOpen(cfg config.Config, flags *pflag.FlagSet, args []string) error { | ||||
var url string | ||||
usrCfg := cfg.UserViperConfig | ||||
trackID, err := flags.GetString("track") | ||||
if err != nil { | ||||
return err | ||||
} | ||||
|
||||
exerciseSlug, err := flags.GetString("exercise") | ||||
if err != nil { | ||||
return err | ||||
} | ||||
|
||||
teamID, err := flags.GetString("team") | ||||
if err != nil { | ||||
return err | ||||
} | ||||
|
||||
remote, err := flags.GetBool("remote") | ||||
if err != nil { | ||||
return err | ||||
} | ||||
|
||||
var path string | ||||
if len(args) > 0 { | ||||
path = args[0] | ||||
} | ||||
|
||||
if exerciseSlug == "" { | ||||
if path == "" { | ||||
return fmt.Errorf("must provide an --exercise slug or an exercise path") | ||||
} | ||||
// if no --exercise is given, use original functionality | ||||
metadata, err := workspace.NewExerciseMetadata(path) | ||||
if err != nil { | ||||
return err | ||||
} | ||||
browser.Open(metadata.URL) | ||||
return nil | ||||
}, | ||||
} | ||||
|
||||
if remote { | ||||
apiURL := fmt.Sprintf("%s/solutions/latest", usrCfg.GetString("apibaseurl")) | ||||
|
||||
client, err := api.NewClient(usrCfg.GetString("token"), usrCfg.GetString("apibaseurl")) | ||||
if err != nil { | ||||
return err | ||||
} | ||||
|
||||
req, err := client.NewRequest("GET", apiURL, nil) | ||||
if err != nil { | ||||
return err | ||||
} | ||||
|
||||
q := req.URL.Query() | ||||
q.Add("exercise_id", exerciseSlug) | ||||
if trackID != "" { | ||||
q.Add("track_id", trackID) | ||||
} | ||||
if teamID != "" { | ||||
q.Add("team_id", teamID) | ||||
} | ||||
req.URL.RawQuery = q.Encode() | ||||
|
||||
res, err := client.Do(req) | ||||
if err != nil { | ||||
return err | ||||
} | ||||
|
||||
defer res.Body.Close() | ||||
var payload openPayload | ||||
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil { | ||||
return fmt.Errorf("unable to parse API response - %s", err) | ||||
} | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to use the api error decoder helper? Line 78 in c3525cf
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to decode the json anyways for the solution url. The helper only returns the error. Would it be beneficial to introduce a helper that decodes the entire api response? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm. Maybe, but let's hold off on that for now. |
||||
|
||||
if res.StatusCode != http.StatusOK { | ||||
switch payload.Error.Type { | ||||
case "track_ambiguous": | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we can make the error decoder helper smarter and add this logic into it. Line 78 in c3525cf
(If so, let's do that in a separate PR and get that into master quickly so we can use it elsewhere). |
||||
return fmt.Errorf("%s: %s", payload.Error.Message, strings.Join(payload.Error.PossibleTrackIDs, ", ")) | ||||
kytrinyx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
default: | ||||
return errors.New(payload.Error.Message) | ||||
} | ||||
} | ||||
|
||||
url = payload.Solution.URL | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's open the URL and return here so that we can out-dent the |
||||
|
||||
browser.Open(url) | ||||
return nil | ||||
} | ||||
|
||||
ws, err := workspace.New(usrCfg.GetString("workspace")) | ||||
if err != nil { | ||||
return err | ||||
} | ||||
exercises, err := ws.Exercises() | ||||
if err != nil { | ||||
return err | ||||
} | ||||
|
||||
matchingExerciseMeta := make([]*workspace.ExerciseMetadata, 0, len(exercises)) | ||||
for _, exercise := range exercises { | ||||
metaDir := exercise.MetadataDir() | ||||
meta, err := workspace.NewExerciseMetadata(metaDir) | ||||
if err != nil { | ||||
return err | ||||
} | ||||
|
||||
if meta.ExerciseSlug != exerciseSlug { | ||||
continue | ||||
} | ||||
|
||||
if trackID != "" && meta.Track != trackID { | ||||
continue | ||||
} | ||||
|
||||
if meta.Team != teamID { | ||||
continue | ||||
} | ||||
|
||||
matchingExerciseMeta = append(matchingExerciseMeta, meta) | ||||
} | ||||
|
||||
switch len(matchingExerciseMeta) { | ||||
case 0: | ||||
return fmt.Errorf("No matching exercise found") | ||||
case 1: | ||||
url = matchingExerciseMeta[0].URL | ||||
break | ||||
default: | ||||
tracks := make([]string, 0, len(matchingExerciseMeta)) | ||||
for _, exercise := range matchingExerciseMeta { | ||||
tracks = append(tracks, exercise.Track) | ||||
} | ||||
|
||||
return fmt.Errorf("Please specify a track ID: %s", strings.Join(tracks, ", ")) | ||||
} | ||||
|
||||
browser.Open(url) | ||||
return nil | ||||
} | ||||
|
||||
type openPayload struct { | ||||
Solution struct { | ||||
ID string `json:"id"` | ||||
URL string `json:"url"` | ||||
} `json:"solution"` | ||||
Error struct { | ||||
Type string `json:"type"` | ||||
Message string `json:"message"` | ||||
PossibleTrackIDs []string `json:"possible_track_ids"` | ||||
} `json:"error,omitempty"` | ||||
} | ||||
|
||||
func setupOpenFlags(flags *pflag.FlagSet) { | ||||
flags.BoolP("remote", "r", false, "checks for remote solutions") | ||||
flags.StringP("track", "t", "", "the track id") | ||||
flags.StringP("exercise", "e", "", "the exercise slug") | ||||
flags.StringP("team", "T", "", "the team slug") | ||||
} | ||||
|
||||
func init() { | ||||
RootCmd.AddCommand(openCmd) | ||||
setupOpenFlags(openCmd.Flags()) | ||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package cmd | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to add tests after this is reviewed, to check that functionality is how we want it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this would be easier to understand if we did something like this: