Skip to content

Commit

Permalink
Merge pull request sequelize#154 from lcowell/master
Browse files Browse the repository at this point in the history
Add List command to display a list of available exercises
  • Loading branch information
lcowell committed Jan 20, 2015
2 parents e8ad540 + 49c33c7 commit c8b604d
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 0 deletions.
38 changes: 38 additions & 0 deletions api/api.go
Expand Up @@ -3,8 +3,10 @@ package api
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"

"github.com/exercism/cli/config"
)
Expand All @@ -13,6 +15,8 @@ var (
// UserAgent lets the API know where the call is being made from.
// It's set from main() so that we have access to the version.
UserAgent string

UnknownLanguageError = errors.New("the language is unknown")
)

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

// List available problems for a language
func List(language, host string) ([]string, error) {
url := fmt.Sprintf("%s/tracks/%s", host, language)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", UserAgent)
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, UnknownLanguageError
}

var payload struct {
Track Track
}
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
return nil, err
}
problems := make([]string, len(payload.Track.Problems))
prefix := language + "/"

for n, p := range payload.Track.Problems {
problems[n] = strings.TrimPrefix(p, prefix)
}

return problems, nil
}

// Unsubmit deletes a submission.
func Unsubmit(url string) error {
req, err := http.NewRequest("DELETE", url, nil)
Expand Down
42 changes: 42 additions & 0 deletions api/api_test.go
@@ -0,0 +1,42 @@
package api

import (
"io"
"net/http"
"net/http/httptest"
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestListTrack(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// check that we correctly built the URI path
assert.Equal(t, "/tracks/clojure", r.RequestURI)

f, err := os.Open("../fixtures/tracks.json")
if err != nil {
t.Fatal(err)
}
io.Copy(w, f)
f.Close()
}))
defer ts.Close()

problems, err := List("clojure", ts.URL)
assert.NoError(t, err)

assert.Equal(t, len(problems), 34)
assert.Equal(t, problems[0], "bob")
}

func TestUnknownLanguage(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}))
defer ts.Close()

_, err := List("rubbbby", ts.URL)
assert.Equal(t, err, UnknownLanguageError)
}
41 changes: 41 additions & 0 deletions cmd/list.go
@@ -0,0 +1,41 @@
package cmd

import (
"fmt"
"log"

"github.com/codegangsta/cli"
"github.com/exercism/cli/api"
"github.com/exercism/cli/config"
)

const msgExplainFetch = "In order to fetch a specific assignment, call the fetch command with a specific assignment.\n\nexercism fetch ruby matrix"

// List returns the full list of assignments for a given language
func List(ctx *cli.Context) {
c, err := config.New(ctx.GlobalString("config"))
if err != nil {
log.Fatal(err)
}
args := ctx.Args()

if len(args) != 1 {
msg := "Usage: execism list LANGUAGE"
log.Fatal(msg)
}

language := args[0]

problems, err := api.List(language, c.XAPI)
if err != nil {
if err == api.UnknownLanguageError {
log.Fatalf("The requested language '%s' is unknown", language)
}
log.Fatal(err)
}

for _, p := range problems {
fmt.Printf("%s\n", p)
}
fmt.Printf("\n%s\n\n", msgExplainFetch)
}
7 changes: 7 additions & 0 deletions exercism/main.go
Expand Up @@ -29,6 +29,7 @@ const (

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."
descDownload = "Downloads and saves a specified submission into the local system"
descList = "Lists all available assignments for a given language"
)

func main() {
Expand Down Expand Up @@ -120,6 +121,12 @@ func main() {
Usage: descDownload,
Action: cmd.Download,
},
{
Name: "list",
ShortName: "li",
Usage: descList,
Action: cmd.List,
},
}
if err := app.Run(os.Args); err != nil {

Expand Down
45 changes: 45 additions & 0 deletions fixtures/tracks.json
@@ -0,0 +1,45 @@
{
"track":{
"language":"Clojure",
"active":true,
"id":"clojure",
"slug":"clojure",
"problems":[
"clojure/bob",
"clojure/rna-transcription",
"clojure/word-count",
"clojure/anagram",
"clojure/beer-song",
"clojure/nucleotide-count",
"clojure/point-mutations",
"clojure/phone-number",
"clojure/grade-school",
"clojure/robot-name",
"clojure/leap",
"clojure/etl",
"clojure/meetup",
"clojure/space-age",
"clojure/grains",
"clojure/gigasecond",
"clojure/triangle",
"clojure/scrabble-score",
"clojure/roman-numerals",
"clojure/binary",
"clojure/prime-factors",
"clojure/raindrops",
"clojure/allergies",
"clojure/atbash-cipher",
"clojure/bank-account",
"clojure/crypto-square",
"clojure/kindergarten-garden",
"clojure/robot-simulator",
"clojure/queen-attack",
"clojure/accumulate",
"clojure/binary-search-tree",
"clojure/difference-of-squares",
"clojure/hexadecimal",
"clojure/largest-series-product"
],
"repository":"https://github.com/exercism/xclojure"
}
}

0 comments on commit c8b604d

Please sign in to comment.