Add stages 6 and 7

This commit is contained in:
Richard Feldman
2016-03-06 07:17:20 -08:00
parent 879788d86a
commit e9b33a36ef
22 changed files with 909 additions and 0 deletions

14
stages/6/README.md Normal file
View File

@@ -0,0 +1,14 @@
Stage 5
=======
To run tests:
```bash
npm test
```
To engage Auto-Rebuilding:
```bash
npm run watch
```

18
stages/6/elm-package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"version": "1.0.0",
"summary": "Like GitHub, but for Elm stuff.",
"repository": "https://github.com/rtfeldman/elm-workshop.git",
"license": "BSD-3-Clause",
"source-directories": [
"src"
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "3.0.0 <= v < 4.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0"
},
"elm-version": "0.16.0 <= v < 0.17.0"
}

25
stages/6/index.html Normal file
View File

@@ -0,0 +1,25 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>ElmHub</title>
<script type="text/javascript" src="elm.js"></script>
<!-- Uncomment the below line to enable elm-reactor support. -->
<!-- <script type="text/javascript" src="/_reactor/debug.js"></script> -->
<link rel="stylesheet" href="style.css">
</head>
<body>
</body>
<script type="text/javascript">
var app = Elm.fullscreen(Elm.Main, {});
// Uncomment this line and comment out the above to enable elm-reactor support.
// var app = Elm.fullscreenDebug("ElmHub", "Main.elm");
</script>
</html>

25
stages/6/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "elm-hub",
"version": "1.0.0",
"description": "Like GitHub, but for Elm stuff.",
"scripts": {
"build": "elm-make src/Main.elm --output elm.js",
"watch": "../../node_modules/.bin/elm-live src/Main.elm --open -- --output=elm.js",
"test": "node test.js",
"install": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rtfeldman/elm-workshop.git"
},
"author": "Richard Feldman",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/rtfeldman/elm-workshop/issues"
},
"homepage": "https://github.com/rtfeldman/elm-workshop#readme",
"devDependencies": {
"elm-live": "2.0.4",
"elm-test": "0.16.1-alpha3"
}
}

143
stages/6/src/ElmHub.elm Normal file
View File

@@ -0,0 +1,143 @@
module ElmHub (..) where
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import StartApp
import Http
import Task exposing (Task)
import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=))
import Json.Encode
import Signal exposing (Address)
searchFeed : String -> Task x Action
searchFeed query =
let
-- See https://developer.github.com/v3/search/#example for how to customize!
url =
"https://api.github.com/search/repositories?q="
++ query
++ "+language:elm&sort=stars&order=desc"
task =
Http.get responseDecoder url
|> Task.map SetResults
in
Task.onError task (\_ -> Task.succeed (SetResults []))
responseDecoder : Decoder (List SearchResult)
responseDecoder =
"items" := Json.Decode.list searchResultDecoder
searchResultDecoder : Decoder SearchResult
searchResultDecoder =
Json.Decode.object3
SearchResult
("id" := Json.Decode.int)
("full_name" := Json.Decode.string)
("stargazers_count" := Json.Decode.int)
type alias Model =
{ query : String
, results : List SearchResult
}
type alias SearchResult =
{ id : ResultId
, name : String
, stars : Int
}
type alias ResultId =
Int
initialModel : Model
initialModel =
{ query = "tutorial"
, results = []
}
view : Address Action -> Model -> Html
view address model =
div
[ class "content" ]
[ header
[]
[ h1 [] [ text "ElmHub" ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, ul
[ class "results" ]
(List.map (viewSearchResult address) model.results)
]
onInput address wrap =
on "input" targetValue (\val -> Signal.message address (wrap val))
defaultValue str =
property "defaultValue" (Json.Encode.string str)
viewSearchResult : Address Action -> SearchResult -> Html
viewSearchResult address result =
li
[]
[ span [ class "star-count" ] [ text (toString result.stars) ]
, a
[ href ("https://github.com/" ++ result.name)
, class "result-name"
, target "_blank"
]
[ text result.name ]
, button
[ class "hide-result", onClick address (HideById result.id) ]
[ text "X" ]
]
type Action
= Search
| SetQuery String
| HideById ResultId
| SetResults (List SearchResult)
update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
Search ->
( model, Effects.task (searchFeed model.query) )
SetQuery query ->
( { model | query = query }, Effects.none )
SetResults results ->
let
newModel =
{ model | results = results }
in
( newModel, Effects.none )
HideById idToHide ->
let
newResults =
model.results
|> List.filter (\{ id } -> id /= idToHide)
newModel =
{ model | results = newResults }
in
( newModel, Effects.none )

27
stages/6/src/Main.elm Normal file
View File

@@ -0,0 +1,27 @@
module Main (..) where
import StartApp
import ElmHub exposing (..)
import Effects exposing (Effects)
import Task exposing (Task)
import Html exposing (Html)
main : Signal Html
main =
app.html
app : StartApp.App Model
app =
StartApp.start
{ view = view
, update = update
, init = ( initialModel, Effects.task (searchFeed initialModel.query) )
, inputs = []
}
port tasks : Signal (Task Effects.Never ())
port tasks =
app.tasks

91
stages/6/style.css Normal file
View File

@@ -0,0 +1,91 @@
.content {
width: 960px;
margin: 0 auto;
padding: 30px;
font-family: Helvetica, Arial, serif;
}
header {
position: relative;
padding: 6px 12px;
background-color: rgb(96, 181, 204);
}
h1 {
color: white;
font-weight: normal;
margin: 0;
}
.tagline {
color: #eee;
position: absolute;
right: 16px;
top: 12px;
font-size: 24px;
font-style: italic;
}
.results {
list-style-image: url('http://img-cache.cdn.gaiaonline.com/76bd5c99d8f2236e9d3672510e933fdf/http://i278.photobucket.com/albums/kk81/d3m3nt3dpr3p/Tiny-Star-Icon.png');
list-style-position: inside;
padding: 0;
}
.results li {
font-size: 18px;
margin-bottom: 16px;
}
.star-count {
font-weight: bold;
}
.result-name {
color: rgb(96, 181, 204);
margin-left: 16px;
text-decoration: none;
}
.result-name:hover{
text-decoration: underline;
}
.search-query {
padding: 8px;
font-size: 24px;
margin-bottom: 18px;
margin-top: 36px;
}
.search-button {
padding: 8px 16px;
font-size: 24px;
color: white;
border: 1px solid #ccc;
background-color: rgb(96, 181, 204);
margin-left: 12px
}
.search-button:hover {
color: rgb(96, 181, 204);
background-color: white;
}
.hide-result {
background-color: transparent;
border: 0;
font-weight: bold;
font-size: 18px;
margin-left: 18px;
cursor: pointer;
}
.hide-result:hover {
color: rgb(96, 181, 204);
}
button:focus, input:focus {
outline: none;
}

10
stages/6/test.js Normal file
View File

@@ -0,0 +1,10 @@
var fs = require("fs");
var execSync = require("child_process").execSync;
var path = require("path");
var testDir = path.join(__dirname, "test");
var binPath = path.join(__dirname, "..", "..", "node_modules", ".bin");
var elmTestPath = path.join(binPath, "elm-test");
var elmMakePath = path.join(binPath, "elm-make");
execSync(elmMakePath + " TestRunner.elm --yes --output /dev/null", { cwd: testDir });
execSync(elmTestPath + " TestRunner.elm", { cwd: testDir, stdio: "inherit" });

View File

@@ -0,0 +1,15 @@
module Main where
import Signal exposing (Signal)
import ElmTest exposing (consoleRunner)
import Console exposing (IO, run)
import Task
import Tests
console : IO ()
console = consoleRunner Tests.all
port runner : Signal (Task.Task x ())
port runner = run console

64
stages/6/test/Tests.elm Normal file
View File

@@ -0,0 +1,64 @@
module Tests (..) where
import ElmTest exposing (..)
import ElmHub exposing (responseDecoder)
import Json.Decode as Decode
import Json.Encode as Encode
import Check exposing (Claim, Evidence, check, claim, that, is, for)
import Check.Producer exposing (..)
import Check.Test exposing (evidenceToTest)
import String
import ElmHub exposing (..)
import Random
all : Test
all =
suite
"Decoding responses from GitHub"
[ test "they can decode empty responses"
<| let
emptyResponse =
"""{ "items": [] }"""
in
assertEqual
(Decode.decodeString responseDecoder emptyResponse)
(Ok [])
, test "they can decode responses with results in them"
<| let
response =
"""{ "items": [
{ "id": 5, "full_name": "foo", "stargazers_count": 42 },
{ "id": 3, "full_name": "bar", "stargazers_count": 77 }
] }"""
in
assertEqual
(Decode.decodeString responseDecoder response)
(Ok
[ { id = 5, name = "foo", stars = 42 }
, { id = 3, name = "bar", stars = 77 }
]
)
, (claim "they can decode individual search results"
`that` ({- TODO call encodeAndDecode -})
`is` (\( id, name, stars ) -> Ok (SearchResult id name stars))
`for` tuple3 ( int, string, int )
)
|> check 100 defaultSeed
|> evidenceToTest
]
encodeAndDecode : Int -> String -> Int -> Result String SearchResult
encodeAndDecode id name stars =
-- TODO: finish turning this into a JSON String,
-- then Decode it with searchResultDecoder
[ ( "id", Encode.int id )
, ( "full_name", Encode.string name )
, ( "stargazers_count", Encode.int stars )
]
|> Encode.object
defaultSeed =
Random.initialSeed 42

View File

@@ -0,0 +1,22 @@
{
"version": "1.0.0",
"summary": "Like GitHub, but for Elm stuff.",
"repository": "https://github.com/rtfeldman/elm-workshop.git",
"license": "BSD-3-Clause",
"source-directories": [
".",
"../src"
],
"exposed-modules": [],
"dependencies": {
"NoRedInk/elm-check": "3.0.0 <= v < 4.0.0",
"deadfoxygrandpa/elm-test": "3.1.1 <= v < 4.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
},
"elm-version": "0.16.0 <= v < 0.17.0"
}

14
stages/7/README.md Normal file
View File

@@ -0,0 +1,14 @@
Stage 5
=======
To run tests:
```bash
npm test
```
To engage Auto-Rebuilding:
```bash
npm run watch
```

19
stages/7/elm-package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"version": "1.0.0",
"summary": "Like GitHub, but for Elm stuff.",
"repository": "https://github.com/rtfeldman/elm-workshop.git",
"license": "BSD-3-Clause",
"source-directories": [
"src"
],
"exposed-modules": [],
"dependencies": {
"NoRedInk/elm-check": "3.0.0 <= v < 4.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0"
},
"elm-version": "0.16.0 <= v < 0.17.0"
}

25
stages/7/index.html Normal file
View File

@@ -0,0 +1,25 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>ElmHub</title>
<script type="text/javascript" src="elm.js"></script>
<!-- Uncomment the below line to enable elm-reactor support. -->
<!-- <script type="text/javascript" src="/_reactor/debug.js"></script> -->
<link rel="stylesheet" href="style.css">
</head>
<body>
</body>
<script type="text/javascript">
var app = Elm.fullscreen(Elm.Main, {});
// Uncomment this line and comment out the above to enable elm-reactor support.
// var app = Elm.fullscreenDebug("ElmHub", "Main.elm");
</script>
</html>

25
stages/7/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "elm-hub",
"version": "1.0.0",
"description": "Like GitHub, but for Elm stuff.",
"scripts": {
"build": "elm-make src/Main.elm --output elm.js",
"watch": "../../node_modules/.bin/elm-live src/Main.elm --open -- --output=elm.js",
"test": "node test.js",
"install": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rtfeldman/elm-workshop.git"
},
"author": "Richard Feldman",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/rtfeldman/elm-workshop/issues"
},
"homepage": "https://github.com/rtfeldman/elm-workshop#readme",
"devDependencies": {
"elm-live": "2.0.4",
"elm-test": "0.16.1-alpha3"
}
}

143
stages/7/src/ElmHub.elm Normal file
View File

@@ -0,0 +1,143 @@
module ElmHub (..) where
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import StartApp
import Http
import Task exposing (Task)
import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=))
import Json.Encode
import Signal exposing (Address)
searchFeed : String -> Task x Action
searchFeed query =
let
-- See https://developer.github.com/v3/search/#example for how to customize!
url =
"https://api.github.com/search/repositories?q="
++ query
++ "+language:elm&sort=stars&order=desc"
task =
Http.get responseDecoder url
|> Task.map SetResults
in
Task.onError task (\_ -> Task.succeed (SetResults []))
responseDecoder : Decoder (List SearchResult)
responseDecoder =
"items" := Json.Decode.list searchResultDecoder
searchResultDecoder : Decoder SearchResult
searchResultDecoder =
Json.Decode.object3
SearchResult
("id" := Json.Decode.int)
("full_name" := Json.Decode.string)
("stargazers_count" := Json.Decode.int)
type alias Model =
{ query : String
, results : List SearchResult
}
type alias SearchResult =
{ id : ResultId
, name : String
, stars : Int
}
type alias ResultId =
Int
initialModel : Model
initialModel =
{ query = "tutorial"
, results = []
}
view : Address Action -> Model -> Html
view address model =
div
[ class "content" ]
[ header
[]
[ h1 [] [ text "ElmHub" ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, ul
[ class "results" ]
(List.map (viewSearchResult address) model.results)
]
onInput address wrap =
on "input" targetValue (\val -> Signal.message address (wrap val))
defaultValue str =
property "defaultValue" (Json.Encode.string str)
viewSearchResult : Address Action -> SearchResult -> Html
viewSearchResult address result =
li
[]
[ span [ class "star-count" ] [ text (toString result.stars) ]
, a
[ href ("https://github.com/" ++ result.name)
, class "result-name"
, target "_blank"
]
[ text result.name ]
, button
[ class "hide-result", onClick address (HideById result.id) ]
[ text "X" ]
]
type Action
= Search
| SetQuery String
| HideById ResultId
| SetResults (List SearchResult)
update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
Search ->
( model, Effects.task (searchFeed model.query) )
SetQuery query ->
( { model | query = query }, Effects.none )
SetResults results ->
let
newModel =
{ model | results = results }
in
( newModel, Effects.none )
HideById idToHide ->
let
newResults =
model.results
|> List.filter (\{ id } -> id /= idToHide)
newModel =
{ model | results = newResults }
in
( newModel, Effects.none )

27
stages/7/src/Main.elm Normal file
View File

@@ -0,0 +1,27 @@
module Main (..) where
import StartApp
import ElmHub exposing (..)
import Effects exposing (Effects)
import Task exposing (Task)
import Html exposing (Html)
main : Signal Html
main =
app.html
app : StartApp.App Model
app =
StartApp.start
{ view = view
, update = update
, init = ( initialModel, Effects.task (searchFeed initialModel.query) )
, inputs = []
}
port tasks : Signal (Task Effects.Never ())
port tasks =
app.tasks

91
stages/7/style.css Normal file
View File

@@ -0,0 +1,91 @@
.content {
width: 960px;
margin: 0 auto;
padding: 30px;
font-family: Helvetica, Arial, serif;
}
header {
position: relative;
padding: 6px 12px;
background-color: rgb(96, 181, 204);
}
h1 {
color: white;
font-weight: normal;
margin: 0;
}
.tagline {
color: #eee;
position: absolute;
right: 16px;
top: 12px;
font-size: 24px;
font-style: italic;
}
.results {
list-style-image: url('http://img-cache.cdn.gaiaonline.com/76bd5c99d8f2236e9d3672510e933fdf/http://i278.photobucket.com/albums/kk81/d3m3nt3dpr3p/Tiny-Star-Icon.png');
list-style-position: inside;
padding: 0;
}
.results li {
font-size: 18px;
margin-bottom: 16px;
}
.star-count {
font-weight: bold;
}
.result-name {
color: rgb(96, 181, 204);
margin-left: 16px;
text-decoration: none;
}
.result-name:hover{
text-decoration: underline;
}
.search-query {
padding: 8px;
font-size: 24px;
margin-bottom: 18px;
margin-top: 36px;
}
.search-button {
padding: 8px 16px;
font-size: 24px;
color: white;
border: 1px solid #ccc;
background-color: rgb(96, 181, 204);
margin-left: 12px
}
.search-button:hover {
color: rgb(96, 181, 204);
background-color: white;
}
.hide-result {
background-color: transparent;
border: 0;
font-weight: bold;
font-size: 18px;
margin-left: 18px;
cursor: pointer;
}
.hide-result:hover {
color: rgb(96, 181, 204);
}
button:focus, input:focus {
outline: none;
}

10
stages/7/test.js Normal file
View File

@@ -0,0 +1,10 @@
var fs = require("fs");
var execSync = require("child_process").execSync;
var path = require("path");
var testDir = path.join(__dirname, "test");
var binPath = path.join(__dirname, "..", "..", "node_modules", ".bin");
var elmTestPath = path.join(binPath, "elm-test");
var elmMakePath = path.join(binPath, "elm-make");
execSync(elmMakePath + " TestRunner.elm --yes --output /dev/null", { cwd: testDir });
execSync(elmTestPath + " TestRunner.elm", { cwd: testDir, stdio: "inherit" });

View File

@@ -0,0 +1,15 @@
module Main where
import Signal exposing (Signal)
import ElmTest exposing (consoleRunner)
import Console exposing (IO, run)
import Task
import Tests
console : IO ()
console = consoleRunner Tests.all
port runner : Signal (Task.Task x ())
port runner = run console

64
stages/7/test/Tests.elm Normal file
View File

@@ -0,0 +1,64 @@
module Tests (..) where
import ElmTest exposing (..)
import ElmHub exposing (responseDecoder)
import Json.Decode as Decode
import Json.Encode as Encode
import Check exposing (Claim, Evidence, check, claim, that, is, for)
import Check.Producer exposing (..)
import Check.Test exposing (evidenceToTest)
import String
import ElmHub exposing (..)
import Random
all : Test
all =
suite
"Decoding responses from GitHub"
[ test "they can decode empty responses"
<| let
emptyResponse =
"""{ "items": [] }"""
in
assertEqual
(Decode.decodeString responseDecoder emptyResponse)
(Ok [])
, test "they can decode responses with results in them"
<| let
response =
"""{ "items": [
{ "id": 5, "full_name": "foo", "stargazers_count": 42 },
{ "id": 3, "full_name": "bar", "stargazers_count": 77 }
] }"""
in
assertEqual
(Decode.decodeString responseDecoder response)
(Ok
[ { id = 5, name = "foo", stars = 42 }
, { id = 3, name = "bar", stars = 77 }
]
)
, (claim "they can decode individual search results"
`that` (\( id, name, stars ) -> encodeAndDecode id name stars)
`is` (\( id, name, stars ) -> Ok (SearchResult id name stars))
`for` tuple3 ( int, string, int )
)
|> check 100 defaultSeed
|> evidenceToTest
]
encodeAndDecode : Int -> String -> Int -> Result String SearchResult
encodeAndDecode id name stars =
[ ( "id", Encode.int id )
, ( "full_name", Encode.string name )
, ( "stargazers_count", Encode.int stars )
]
|> Encode.object
|> Encode.encode 0
|> Decode.decodeString searchResultDecoder
defaultSeed =
Random.initialSeed 42

View File

@@ -0,0 +1,22 @@
{
"version": "1.0.0",
"summary": "Like GitHub, but for Elm stuff.",
"repository": "https://github.com/rtfeldman/elm-workshop.git",
"license": "BSD-3-Clause",
"source-directories": [
".",
"../src"
],
"exposed-modules": [],
"dependencies": {
"NoRedInk/elm-check": "3.0.0 <= v < 4.0.0",
"deadfoxygrandpa/elm-test": "3.1.1 <= v < 4.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
},
"elm-version": "0.16.0 <= v < 0.17.0"
}