Skip to content

Commit 7b5cc33

Browse files
committed
Add command to list available assignments
* lists available exercises for a given language and explains how to fetch them * returns error for unknown languages * Resolves issue #89 * Fetches the track for a specific language using the /tracks/:language endopoint on the XAPI.
1 parent e6672ec commit 7b5cc33

5 files changed

Lines changed: 173 additions & 0 deletions

File tree

api/api.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package api
33
import (
44
"bytes"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"net/http"
9+
"strings"
810

911
"github.com/exercism/cli/config"
1012
)
@@ -13,6 +15,8 @@ var (
1315
// UserAgent lets the API know where the call is being made from.
1416
// It's set from main() so that we have access to the version.
1517
UserAgent string
18+
19+
UnknownLanguageError = errors.New("the language is unknown")
1620
)
1721

1822
// PayloadError represents an error message from the API.
@@ -128,6 +132,40 @@ func Submit(url string, iter *Iteration) (*Submission, error) {
128132
return ps.Submission, nil
129133
}
130134

135+
// List available problems for a language
136+
func List(language, host string) ([]string, error) {
137+
url := fmt.Sprintf("%s/tracks/%s", host, language)
138+
139+
req, err := http.NewRequest("GET", url, nil)
140+
if err != nil {
141+
return nil, err
142+
}
143+
req.Header.Set("User-Agent", UserAgent)
144+
res, err := http.DefaultClient.Do(req)
145+
if err != nil {
146+
return nil, err
147+
}
148+
defer res.Body.Close()
149+
if res.StatusCode != http.StatusOK {
150+
return nil, UnknownLanguageError
151+
}
152+
153+
var payload struct {
154+
Track Track
155+
}
156+
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
157+
return nil, err
158+
}
159+
problems := make([]string, len(payload.Track.Problems))
160+
prefix := language + "/"
161+
162+
for n, p := range payload.Track.Problems {
163+
problems[n] = strings.TrimPrefix(p, prefix)
164+
}
165+
166+
return problems, nil
167+
}
168+
131169
// Unsubmit deletes a submission.
132170
func Unsubmit(url string) error {
133171
req, err := http.NewRequest("DELETE", url, nil)

api/api_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package api
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"net/http/httptest"
7+
"os"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestListTrack(t *testing.T) {
14+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15+
// check that we correctly built the URI path
16+
assert.Equal(t, "/tracks/clojure", r.RequestURI)
17+
18+
f, err := os.Open("../fixtures/tracks.json")
19+
if err != nil {
20+
t.Fatal(err)
21+
}
22+
io.Copy(w, f)
23+
f.Close()
24+
}))
25+
defer ts.Close()
26+
27+
problems, err := List("clojure", ts.URL)
28+
assert.NoError(t, err)
29+
30+
assert.Equal(t, len(problems), 34)
31+
assert.Equal(t, problems[0], "bob")
32+
}
33+
34+
func TestUnknownLanguage(t *testing.T) {
35+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36+
http.NotFound(w, r)
37+
}))
38+
defer ts.Close()
39+
40+
_, err := List("rubbbby", ts.URL)
41+
assert.Equal(t, err, UnknownLanguageError)
42+
}

cmd/list.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/codegangsta/cli"
8+
"github.com/exercism/cli/api"
9+
"github.com/exercism/cli/config"
10+
)
11+
12+
const msgExplainFetch = "In order to fetch a specific assignment, call the fetch command with a specific assignment.\n\nexercism fetch ruby matrix"
13+
14+
// List returns the full list of assignments for a given language
15+
func List(ctx *cli.Context) {
16+
c, err := config.New(ctx.GlobalString("config"))
17+
if err != nil {
18+
log.Fatal(err)
19+
}
20+
args := ctx.Args()
21+
22+
if len(args) != 1 {
23+
msg := "Usage: execism list LANGUAGE"
24+
log.Fatal(msg)
25+
}
26+
27+
language := args[0]
28+
29+
problems, err := api.List(language, c.XAPI)
30+
if err != nil {
31+
if err == api.UnknownLanguageError {
32+
log.Fatalf("The requested language '%s' is unknown", language)
33+
}
34+
log.Fatal(err)
35+
}
36+
37+
for _, p := range problems {
38+
fmt.Printf("%s\n", p)
39+
}
40+
fmt.Printf("\n%s\n\n", msgExplainFetch)
41+
}

exercism/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929

3030
descLongRestore = "Restore will pull the latest revisions of exercises that have already been submitted. It will *not* overwrite existing files. If you have made changes to a file and have not submitted it, and you're trying to restore the last submitted version, first move that file out of the way, then call restore."
3131
descDownload = "Downloads and saves a specified submission into the local system"
32+
descList = "Lists all available assignments for a given language"
3233
)
3334

3435
func main() {
@@ -120,6 +121,12 @@ func main() {
120121
Usage: descDownload,
121122
Action: cmd.Download,
122123
},
124+
{
125+
Name: "list",
126+
ShortName: "li",
127+
Usage: descList,
128+
Action: cmd.List,
129+
},
123130
}
124131
if err := app.Run(os.Args); err != nil {
125132

fixtures/tracks.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"track":{
3+
"language":"Clojure",
4+
"active":true,
5+
"id":"clojure",
6+
"slug":"clojure",
7+
"problems":[
8+
"clojure/bob",
9+
"clojure/rna-transcription",
10+
"clojure/word-count",
11+
"clojure/anagram",
12+
"clojure/beer-song",
13+
"clojure/nucleotide-count",
14+
"clojure/point-mutations",
15+
"clojure/phone-number",
16+
"clojure/grade-school",
17+
"clojure/robot-name",
18+
"clojure/leap",
19+
"clojure/etl",
20+
"clojure/meetup",
21+
"clojure/space-age",
22+
"clojure/grains",
23+
"clojure/gigasecond",
24+
"clojure/triangle",
25+
"clojure/scrabble-score",
26+
"clojure/roman-numerals",
27+
"clojure/binary",
28+
"clojure/prime-factors",
29+
"clojure/raindrops",
30+
"clojure/allergies",
31+
"clojure/atbash-cipher",
32+
"clojure/bank-account",
33+
"clojure/crypto-square",
34+
"clojure/kindergarten-garden",
35+
"clojure/robot-simulator",
36+
"clojure/queen-attack",
37+
"clojure/accumulate",
38+
"clojure/binary-search-tree",
39+
"clojure/difference-of-squares",
40+
"clojure/hexadecimal",
41+
"clojure/largest-series-product"
42+
],
43+
"repository":"https://github.com/exercism/xclojure"
44+
}
45+
}

0 commit comments

Comments
 (0)