Full Code of sporto/hop for AI

master 3113fe73d78e cached
66 files
77.8 KB
20.7k tokens
1 requests
Download .txt
Repository: sporto/hop
Branch: master
Commit: 3113fe73d78e
Files: 66
Total size: 77.8 KB

Directory structure:
gitextract_i1usn_9t/

├── .editorconfig
├── .gitignore
├── .vscode/
│   └── settings.json
├── Makefile
├── assets/
│   └── logo.idraw
├── docs/
│   ├── building-routes.md
│   ├── changelog.md
│   ├── matching-routes.md
│   ├── navigating.md
│   ├── nesting-routes.md
│   ├── reverse-routing.md
│   ├── testing.md
│   ├── upgrade-2-to-3.md
│   ├── upgrade-3-to-4.md
│   ├── upgrade-4-to-5.md
│   └── upgrade-5-to-6.md
├── elm-package.json
├── examples/
│   ├── basic/
│   │   ├── .dockerignore
│   │   ├── .gitignore
│   │   ├── Main.elm
│   │   ├── elm-package.json
│   │   ├── install-packages.sh
│   │   └── readme.md
│   └── full/
│       ├── .gitignore
│       ├── dev_server.js
│       ├── elm-package.json
│       ├── install-packages.sh
│       ├── package.json
│       ├── readme.md
│       ├── src/
│       │   ├── Languages/
│       │   │   ├── Edit.elm
│       │   │   ├── Filter.elm
│       │   │   ├── List.elm
│       │   │   ├── Messages.elm
│       │   │   ├── Models.elm
│       │   │   ├── Routing.elm
│       │   │   ├── Show.elm
│       │   │   ├── Update.elm
│       │   │   └── View.elm
│       │   ├── Main.elm
│       │   ├── Messages.elm
│       │   ├── Models.elm
│       │   ├── Routing.elm
│       │   ├── Update.elm
│       │   ├── View.elm
│       │   └── index.js
│       └── webpack.config.js
├── license.md
├── package.json
├── readme.md
├── src/
│   ├── Hop/
│   │   ├── Address.elm
│   │   ├── AddressTest.elm
│   │   ├── In.elm
│   │   ├── InTest.elm
│   │   ├── Out.elm
│   │   ├── OutTest.elm
│   │   ├── TestHelper.elm
│   │   ├── Types.elm
│   │   └── Utils.elm
│   ├── Hop.elm
│   └── HopTest.elm
└── tests/
    ├── IntegrationTest.elm
    ├── Main.elm
    ├── Tests.elm
    ├── elm-package.json
    ├── install-packages.sh
    └── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2

[*.js]
indent_style = tab
indent_size = 2

[*.elm]
indent_style = space
indent_size = 4

[Makefile]
indent_style = tab
indent_size = 2


================================================
FILE: .gitignore
================================================
.DS_Store
elm-stuff
node_modules
elm.js
documentation.json
.vscode/tasks.json
npm-debug.log
.envrc
tests.js


================================================
FILE: .vscode/settings.json
================================================
// Place your settings in this file to overwrite default and user settings.
{
  "search.exclude": {
		"**/node_modules": true,
		"**/bower_components": true,
		"**/*.elmo": true,
		"documentation.json": true
	}
}


================================================
FILE: Makefile
================================================
# Start basic application example locally
basic-up:
	cd ./examples/basic && elm reactor

full-up:
	cd ./examples/full && npm run dev

# Generate documentation for preview
docs:
	elm make --docs=documentation.json

# Run unit tests locally
test-unit:
	npm test

test-unit-ci:
	npm install -g elm
	npm install -g elm-test
	elm-package install -y
	cd tests && elm-package install -y && cd ..
	elm-test

build-basic:
	cd examples/basic/ && elm make --yes Main.elm

build-full:
	cd examples/full/ && elm make --yes src/Main.elm

test-ci:
	make test-unit-ci
	make build-basic
	make build-full

.PHONY: docs test


================================================
FILE: docs/building-routes.md
================================================
# Building routes

As of version 6 Hop doesn't provide matchers anymore, instead you can use [__UrlParser__](http://package.elm-lang.org/packages/evancz/url-parser).

You build your routes by using union types:

```elm
type Route
  = HomeRoute
  | UserRoute Int
  | UserStatusRoute Int
  | NotFoundRoute
```

Then you need to create matchers for these routes:

```elm
import UrlParser exposing ((</>), format, oneOf, int, s)

matchers =
  oneOf [
    UrlParser.format HomeRoute (s "")
  , UrlParser.format UserRoute (s "users" </> int)
  , UrlParser.format UserStatusRoute (s "users" </> int </> s "status")
  ]
```

These matchers will match:

- "/"
- "users/1"
- "users/1/status"

## Order matters

The order of the matchers makes a big difference. See these examples.

Given you have some routes and matchers:

```elm
import UrlParser exposing (format, s, parse, int, oneOf, (</>))

type Route  = UserRoute Int | UserEditRoute Int

-- match 'users/1'
userMatcher = format UserRoute (s "users" </> int)

-- match '/uses/1/edit'
userEditMatcher = format UserEditRoute (s "users" </> int </> s "edit")
```

### Incorrect order

```elm
matchers = 
    oneOf [userMatcher, userEditMatcher]

parse identity matchers "users/1"
== Ok (UserRoute 1) : Result.Result String Repl.Route

parse identity matchers "users/1/edit"
== Err "The parser worked, but /edit was left over."
```

The `userEditMatcher` doesn't even run in this case. The `userMatcher` fails and stops the flow.

## Correct order

```elm
matchers = 
    oneOf [userEditMatcher, userMatcher]

parse identity matchers "users/1"
== Ok (UserRoute 1) : Result.Result String Repl.Route

parse identity matchers "users/1/edit"
== Ok (UserEditRoute 1) : Result.Result String Repl.Route
```

This works as expected, so is important to put the more specific matchers first.


================================================
FILE: docs/changelog.md
================================================
# Changelog

### 6.0.0

- Remove matchers (Use UrlParser instead)
- Rename input and output functions
- Encode and decode path segments

### 5.0.1

- Encode query string when converting location to URL.

## 5.0.0

- Update for Elm 0.17

### 4.0.3

- Fix issue where root path wouldn't match when using hash routing.

### 4.0.2

- Futher fix for navigating to root path, use / instead of .

### 4.0.1

- Fix https://github.com/sporto/hop/issues/20

## 4.0.0

- Support setState (No hashes)
- Most functions take a config record

## 3.0.0

- Typed values in routes
- Nested routes
- Reverse routing

### 2.1.1

- Remove unnecessary dependency to `elm-test`

### 2.1.0

- Expose `Query` and `Url` types

## 2.0.0

- Remove dependency on `Erl`.
- Change order of arguments on `addQuery`, `clearQuery`, `removeQuery` and `setQuery`

### 1.2.1

- Url is normalized before navigation i.e append `#/` if necessary

### 1.2.0 

- Added `addQuery`, changed behaviour of `setQuery`.

### 1.1.1

- Fixed issue where query string won't be set when no hash wash present


================================================
FILE: docs/matching-routes.md
================================================
# Matching routes

Create a parser using `Navigation.makeParser` combined with `Hop.makeResolver`.
There are serveral strategies you can use.

## Given you have some configuration

```
routes =
  oneOf [...]

hopConfig = 
  { ... }
```

## A parser that returns `(Route, Address)`

```
urlParserRouteAddress : Navigation.Parser ( MainRoute, Address )
urlParserRouteAddress =
    let
        parse path =
            path
                |> UrlParser.parse identity routes
                |> Result.withDefault NotFoundRoute

        solver =
            Hop.makeResolver configWithHash parse
    in
        Navigation.makeParser (.href >> solver)
```

This parser:

- Takes the `.href` from the `Location` record given by `Navigation`.
- Converts that to a normalised path (done inside `makeResolver`).
- Passes the normalised path to your `parse` function, which returns a matched route or `NotFoundRoute`.
- When run returns a tuple `(Route, Address)`.

## A parser that returns only the matched route

```
urlParserOnlyRoute : Navigation.Parser MainRoute
urlParserOnlyRoute =
    let
        parse path =
            path
                |> UrlParser.parse identity routes
                |> Result.withDefault NotFoundRoute

        solver =
            Hop.makeResolver configWithHash parse
    in
        Navigation.makeParser (.href >> solver >> fst)
```

This parser only returns the matched route. The `address` record is discarded. 
However you probably need the address record for doing things with the query later.

## A parser that returns the parser result + Address

```
urlParserResultAddress : Navigation.Parser (Result String MainRoute, Address)
urlParserResultAddress =
    let
        parse path =
            path
                |> UrlParser.parse identity routes

        solver =
            Hop.makeResolver configWithHash parse
    in
        Navigation.makeParser (.href >> solver)
```

This parser returns the result from `parse` e.g. `Result String MainRoute` and the address record.


================================================
FILE: docs/navigating.md
================================================
# Navigating

## Changing the location

Use `Hop.outputFromPath` for changing the browser location.

Add a message:

```elm
type Msg
  ...
  | NavigateTo String
```

Trigger this message from you view:

```elm
button [ onClick (NavigateTo "/users") ] [ text "Users" ]
```

React to this message in update:

```elm
NavigateTo path ->
  let
    command =
      Hop.outputFromPath routerConfig path
        |> Navigation.newUrl
  in
    ( model, command )
```

## Changing the query string

Add actions for changing the query string:

```elm
type Msg
  = ...
  | AddQuery (Dict.Dict String String)
  | SetQuery (Dict.Dict String String)
  | ClearQuery
```

Change update to respond to these actions:

```elm
import Hop exposing(addQuery, setQuery, clearQuery)

update msg model =
  case msg of
    ...

    AddQuery query ->
      let
        command =
          model.address
            |> Hop.addQuery query
            |> Hop.output routerConfig
            |> Navigation.newUrl
      in
        (model, command)
```

You need to pass the current `address` record to these functions. 
Then you use that `address` record to generate a url using `output`.

Trigger these messages from your views:

```elm
button [ onClick (AddQuery (Dict.singleton "color" "red")) ] [ text "Set query" ]
```

See details of available functions at <http://package.elm-lang.org/packages/sporto/hop/latest/Hop>


================================================
FILE: docs/nesting-routes.md
================================================
# Nesting routes

UrlParser supports nested routes:

```elm
type UserRoute
    = UsersRoute
    | UserRoute UserId

type MainRoute
    = HomeRoute
    | AboutRoute
    | UsersRoutes UserRoute
    | NotFoundRoute

usersMatchers =
    [ UrlParser.format UserRoute (int)
    , UrlParser.format UsersRoute (s "")
    ]

mainMatchers =
    [ UrlParser.format HomeRoute (s "")
    , UrlParser.format AboutRoute (s "about")
    , UrlParser.format UsersRoutes (s "users" </> (oneOf usersMatchers))
    ]

matchers =
  oneOf mainMatchers 
```

With a setup like this UrlParser will be able to match routes like:

- "" -> HomeRoute
- "/about" -> AboutRoute
- "/users" -> UsersRoutes UsersRoute
- "/users/2" -> UsersRoutes (UserRoute 2)


================================================
FILE: docs/reverse-routing.md
================================================
# Reverse routing

Reverse routing means converting a route tag back to an url e.g.

```
UserRoute 1 --> "/users/1"
```

In the current version Hop doesn't have any helpers for reverse routing. You can do this manually:

```elm
reverse : Route -> String
reverse route =
    case route of
        HomeRoute ->
            ""

        AboutRoute ->
            "about"

        UserRoute id ->
            "users/" ++ id 

        NotFoundRoute ->
            ""
```


================================================
FILE: docs/testing.md
================================================
# Testing Hop

## Unit tests

```bash
cd tests
elm package install -y

cd ..
npm i
npm test
```


================================================
FILE: docs/upgrade-2-to-3.md
================================================
# Upgrading from 2 to 3

Hop has changed in many ways in version 3. Please review the [Getting started guide](https://github.com/sporto/hop/blob/master/docs/getting-started.md). Following is an overview of the major changes.

## Routes

Now routes can take values, instead of:

```elm
type Route
  = Home
  | Users
  | User
  | Token
```

You can have values attached:

```elm
type Route
  = Home
  | Users
  | User Int
  | Token String
```

Routes are now defined using matchers. So instead of

```elm
routes =
  [ ("/users/:int", User) ]
```

You do:

```elm
import Hop.Matchers exposing(match2)

userMatcher =
  match2 User "/users/" int

matchers =
  [userMatcher]
```

This is so we can have stronger types e.g. `User Int`.

## Actions

Hop.signal now returns a tuple with `(Route, Location)`. Your application needs to map this to an action. e.g.

```elm

type Action
  = HopAction ()
  | ApplyRoute ( Route, Location )

taggedRouterSignal : Signal Action
taggedRouterSignal =
  Signal.map ApplyRoute router.signal
```

This is so routes (`Route`) are different type than the application actions.

## Payload

Before Hop returned a `payload` with a dictionary with matched parameters.

Now it returns the matched route with the values e.g. `User 1` and a `Location` record in the form of a tuple:

```elm
(User 1, location)
```

`location` has the information about the current path and the query:

```elm
{
  path = ["users", "1"],
  query = <Dict String String>
}
```

## Views

In your views you don't need to 'search' for the correct parameter in the payload anymore. The parameters are now in the route e.g. `User 1`.

So instead of doing:

```elm
userId =
  model.routerPayload.params
    |> Dict.get "userId"
    |> Maybe.withDefault ""
```

You simply get the id from the route:

```elm
case User userId ->
  ...
```

The query is still a dictionary. The query is now in the `location` record as shown above.


================================================
FILE: docs/upgrade-3-to-4.md
================================================
# Upgrading from 3 to 4

Version 4 introduces push state. There are two major changes:

## Config

Config now includes `hash` and `basePath`.

```elm
routerConfig : Config Route
routerConfig =
  { hash = True
  , basePath = ""
  , matchers = matchers
  , notFound = NotFoundRoute
  }
```

## Functions require the router config

Most functions in Hop now require your router config. For example instead of:

```elm
navigateTo path
addQuery query location
```

It is now:

```elm
navigateTo config path
addQuery config query location
```

This is because Hop needs to know if you are using hash or path routing.


================================================
FILE: docs/upgrade-4-to-5.md
================================================
# Upgrading from 4 to 5

Version 5 works with Navigation and Elm 0.17

## Matchers

All matchers stay the same as in version 4.

## Navigation

Navigation is now handled by the Navigation module. See example app at `examples/basic/Main.elm`.
Hop doesn't return effects / commands anymore, this should be done by passing a path to `Navigation.modifyUrl`.


================================================
FILE: docs/upgrade-5-to-6.md
================================================
# Upgrading from 5 to 6

Version 6 removes Hop matchers. Use UrlParser instead. See Building Routes document for examples.


================================================
FILE: elm-package.json
================================================
{
    "version": "6.0.1",
    "summary": "Routing and Navigation helpers for SPAs in Elm",
    "repository": "https://github.com/sporto/hop.git",
    "license": "MIT",
    "source-directories": [
        "examples",
        "src"
    ],
    "exposed-modules": [
        "Hop",
        "Hop.Types"
    ],
    "dependencies": {
        "elm-lang/core": "4.0.0 <= v < 5.0.0",
        "evancz/elm-http": "3.0.1 <= v < 4.0.0"
    },
    "elm-version": "0.16.0 <= v < 0.18.0"
}


================================================
FILE: examples/basic/.dockerignore
================================================
Dockefile
elm-stuff


================================================
FILE: examples/basic/.gitignore
================================================
index.html


================================================
FILE: examples/basic/Main.elm
================================================
module Main exposing (..)

{-|
You will need Navigation, UrlParser and Hop.

```
elm package install elm-lang/navigation
elm package install evancz/url-parser
elm package install sporto/hop
```
-}

import Html exposing (..)
import Html.Attributes exposing (class)
import Html.Events exposing (onClick)
import Dict
import Navigation
import UrlParser exposing ((</>))
import Hop
import Hop.Types exposing (Config, Address, Query)


-- ROUTES


{-|
Define your routes as union types.
You need to provide a route for when the current URL doesn't match any known route i.e. NotFoundRoute.
-}
type Route
    = AboutRoute
    | MainRoute
    | NotFoundRoute


{-|
Define route matchers.
See `docs/building-routes.md` for more examples.
-}
routes : UrlParser.Parser (Route -> a) a
routes =
    UrlParser.oneOf
        [ UrlParser.format MainRoute (UrlParser.s "")
        , UrlParser.format AboutRoute (UrlParser.s "about")
        ]


{-|
Define your router configuration.

Use `hash = True` for hash routing e.g. `#/users/1`.
Use `hash = False` for push state e.g. `/users/1`.

The `basePath` is only used for path routing.
This is useful if you application is not located at the root of a url e.g. `/app/v1/users/1` where `/app/v1` is the base path.
-}
hopConfig : Config
hopConfig =
    { hash = True
    , basePath = ""
    }



-- MESSAGES


{-|
Add messages for navigation and changing the query.
-}
type Msg
    = NavigateTo String
    | SetQuery Query



-- MODEL


{-|
Add the current route and address to your model.

- `Route` is your Route union type defined above.
- `Hop.Address` is record to aid with changing the query string.

`route` will be used for determining the current route in the views.

`address` is needed because:

- Some navigation functions in Hop need this information to rebuild the current address.
- Your views might need information about the current query string.

-}
type alias Model =
    { address : Address
    , route : Route
    }


{-|
Respond to navigation messages in update i.e. NavigateTo and SetQuery
-}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case (Debug.log "msg" msg) of
        NavigateTo path ->
            let
                command =
                    -- First generate the URL using your config (`outputFromPath`).
                    -- Then generate a command using Navigation.newUrl.
                    Hop.outputFromPath hopConfig path
                        |> Navigation.newUrl
            in
                ( model, command )

        SetQuery query ->
            let
                command =
                    -- First modify the current stored address record (setting the query)
                    -- Then generate a URL using Hop.output
                    -- Finally, create a command using Navigation.newUrl
                    model.address
                        |> Hop.setQuery query
                        |> Hop.output hopConfig
                        |> Navigation.newUrl
            in
                ( model, command )


{-|
Create a URL Parser for Navigation
-}
urlParser : Navigation.Parser ( Route, Address )
urlParser =
    let
        -- A parse function takes the normalised path from Hop after taking
        -- in consideration the basePath and the hash.
        -- This function then returns a result.
        parse path =
            -- First we parse using UrlParser.parse.
            -- Then we return the parsed route or NotFoundRoute if the parsed failed.
            -- You can choose to return the parse return directly.
            path
                |> UrlParser.parse identity routes
                |> Result.withDefault NotFoundRoute

        resolver =
            -- Create a function that parses and formats the URL
            -- This function takes 2 arguments: The Hop Config and the parse function.
            Hop.makeResolver hopConfig parse
    in
        -- Create a Navigation URL parser
        Navigation.makeParser (.href >> resolver)


{-|
Navigation will call urlUpdate when the address changes.
This function gets the result from `urlParser`, which is a tuple with (Route, Hop.Types.Address)

Address is a record that has:

```elm
{
  path: List String,
  query: Hop.Types.Query
}
```

- `path` is an array of strings that has the current path e.g. `["users", "1"]` for `"/users/1"`
- `query` Is dictionary of String String. You can access this information in your views to show the relevant content.

We store these two things in our model. We keep the address because it is needed for matching a query string.

-}
urlUpdate : ( Route, Address ) -> Model -> ( Model, Cmd Msg )
urlUpdate ( route, address ) model =
    ( { model | route = route, address = address }, Cmd.none )



-- VIEWS


view : Model -> Html Msg
view model =
    div []
        [ menu model
        , pageView model
        ]


menu : Model -> Html Msg
menu model =
    div []
        [ div []
            [ button
                [ class "btnMain"
                , onClick (NavigateTo "")
                ]
                [ text "Main" ]
            , button
                [ class "btnAbout"
                , onClick (NavigateTo "about")
                ]
                [ text "About" ]
            , button
                [ class "btnQuery"
                , onClick (SetQuery (Dict.singleton "keyword" "el/m"))
                ]
                [ text "Set query string" ]
            , currentQuery model
            ]
        ]


currentQuery : Model -> Html msg
currentQuery model =
    let
        query =
            toString model.address.query
    in
        span [ class "labelQuery" ]
            [ text query ]


{-|
Views can decide what to show using `model.route`.

-}
pageView : Model -> Html msg
pageView model =
    case model.route of
        MainRoute ->
            div [] [ h2 [ class "title" ] [ text "Main" ] ]

        AboutRoute ->
            div [] [ h2 [ class "title" ] [ text "About" ] ]

        NotFoundRoute ->
            div [] [ h2 [ class "title" ] [ text "Not found" ] ]



-- APP


{-|
Your init function will receive an initial payload from Navigation, this payload is the initial matched location.
Here we store the `route` and `address` in our model.
-}
init : ( Route, Address ) -> ( Model, Cmd Msg )
init ( route, address ) =
    ( Model address route, Cmd.none )


{-|
Wire everything using Navigation.
-}
main : Program Never
main =
    Navigation.program urlParser
        { init = init
        , view = view
        , update = update
        , urlUpdate = urlUpdate
        , subscriptions = (always Sub.none)
        }


================================================
FILE: examples/basic/elm-package.json
================================================
{
    "version": "1.0.0",
    "summary": "helpful summary of your project, less than 80 characters",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        "../../src/"
    ],
    "exposed-modules": [],
    "dependencies": {
        "elm-lang/core": "4.0.0 <= v < 5.0.0",
        "elm-lang/html": "1.0.0 <= v < 2.0.0",
        "elm-lang/navigation": "1.0.0 <= v < 2.0.0",
        "evancz/elm-http": "3.0.1 <= v < 4.0.0",
        "evancz/url-parser": "1.0.0 <= v < 2.0.0"
    },
    "elm-version": "0.16.0 <= v < 0.18.0"
}


================================================
FILE: examples/basic/install-packages.sh
================================================
#!/bin/bash

n=0
until [ $n -ge 5 ]
do
  elm package install -y && break
  n=$[$n+1]
  sleep 15
done


================================================
FILE: examples/basic/readme.md
================================================
# Basic Hop Example

- Install packages `elm package install -y`
- Run `elm reactor`
- Open `http://localhost:8000/Main.elm`


================================================
FILE: examples/full/.gitignore
================================================
index.html


================================================
FILE: examples/full/dev_server.js
================================================
var path    = require('path');
var express = require('express');
var http    = require('http');
var webpack = require('webpack');
var config  = require('./webpack.config');

var app      = express();
var compiler = webpack(config);
var host     = 'localhost';
var port     = 3000;

app.use(require('webpack-dev-middleware')(compiler, {
  // contentBase: 'src',
  noInfo: true,
  publicPath: config.output.publicPath,
  inline: true,
  stats: { colors: true },
}))

app.get('/app', function(req, res) {
  res.sendFile(path.join(__dirname, 'public/index.html'));
});

app.get('/app/*', function(req, res) {
  res.sendFile(path.join(__dirname, 'public/index.html'));
});

// When hitting / redirect to app
app.get('/', function(req, res) {
  res.redirect('/app');
});

// Server images
app.use(express.static('public'));

var server = http.createServer(app);

server.listen(port, function(err) {
  if (err) {
    console.log(err);
    return;
  }

  var addr = server.address();

  console.log('Listening at http://%s:%d', addr.address, addr.port);
})


================================================
FILE: examples/full/elm-package.json
================================================
{
    "version": "1.0.0",
    "summary": "helpful summary of your project, less than 80 characters",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        "./src",
        "../../src/"
    ],
    "exposed-modules": [],
    "dependencies": {
        "elm-lang/core": "4.0.0 <= v < 5.0.0",
        "elm-lang/html": "1.0.0 <= v < 2.0.0",
        "elm-lang/navigation": "1.0.0 <= v < 2.0.0",
        "evancz/elm-http": "3.0.1 <= v < 4.0.0",
        "evancz/url-parser": "1.0.0 <= v < 2.0.0"
    },
    "elm-version": "0.16.0 <= v < 0.18.0"
}


================================================
FILE: examples/full/install-packages.sh
================================================
#!/bin/bash

n=0
until [ $n -ge 5 ]
do
  elm package install -y && break
  n=$[$n+1]
  sleep 15
done


================================================
FILE: examples/full/package.json
================================================
{
  "name": "full",
  "version": "1.0.0",
  "description": "",
  "main": "elm.js",
  "scripts": {
    "dev": "node dev_server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ace-css": "^1.0.1",
    "css-loader": "^0.23.1",
    "elm-webpack-loader": "^3.0.1",
    "express": "^4.13.4",
    "file-loader": "^0.8.5",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.14",
    "webpack-dev-middleware": "^1.5.1"
  }
}


================================================
FILE: examples/full/readme.md
================================================
# Full Hop Example

This example uses push state.

To run:

```
elm package install -y
npm i
npm run dev
```

Open http://localhost:3000

## Webpack config

The Webpack config included in this example is not ready for production. Please refer to the Webpack site.


================================================
FILE: examples/full/src/Languages/Edit.elm
================================================
module Languages.Edit exposing (..)

import Html exposing (..)
import Html.Events exposing (on, targetValue)
import Html.Attributes exposing (href, style, src, value, name)
import Json.Decode as Json
import Languages.Models exposing (..)
import Languages.Messages exposing (..)


styles : Html.Attribute a
styles =
    style
        [ ( "float", "left" )
        ]


view : Language -> Html Msg
view language =
    div [ styles ]
        [ h2 [] [ text language.name ]
        , form []
            [ input
                [ value language.name
                , name "name"
                , on "input" (Json.map (Update language.id "name") targetValue)
                ]
                []
            ]
        ]


================================================
FILE: examples/full/src/Languages/Filter.elm
================================================
module Languages.Filter exposing (..)

import Html exposing (..)
import Html.Events exposing (onClick)
import Html.Attributes exposing (id, class, href, style)
import Dict
import Languages.Messages exposing (..)


type alias ViewModel =
    {}


styles : Html.Attribute a
styles =
    style
        [ ( "float", "left" )
        , ( "margin-left", "2rem" )
        , ( "margin-right", "2rem" )
        ]


view : ViewModel -> Html Msg
view model =
    div [ styles ]
        [ h2 [] [ text "Filter" ]
        , btn "btnSetQuery" "SetQuery" (SetQuery (Dict.singleton "latests" "true"))
        , div []
            [ h3 [] [ text "Kind" ]
            , div []
                [ btn "btnAll" "All" (AddQuery (Dict.singleton "typed" ""))
                , btn "btnDynamic" "Dynamic" (AddQuery (Dict.singleton "typed" "dynamic"))
                , btn "btnStatic" "Static" (AddQuery (Dict.singleton "typed" "static"))
                ]
            ]
        ]


btn : String -> String -> Msg -> Html Msg
btn viewId label action =
    button [ id viewId, class "btn btn-primary btn-small inline-block mr1", onClick action ]
        [ text label ]


================================================
FILE: examples/full/src/Languages/List.elm
================================================
module Languages.List exposing (..)

import Html exposing (..)
import Html.Events exposing (onClick)
import Html.Attributes exposing (class, href, style)
import Hop.Types exposing (Address)
import Dict
import Languages.Models exposing (..)
import Languages.Messages exposing (..)


type alias ViewModel =
    { languages : List Language
    , address : Address
    }


styles : Html.Attribute a
styles =
    style
        [ ( "float", "left" )
        , ( "margin-right", "2rem" )
        ]


hasTag : String -> Language -> Bool
hasTag tag language =
    List.any (\t -> t == tag) language.tags


filteredLanguages : ViewModel -> List Language
filteredLanguages model =
    let
        typed =
            model.address.query
                |> Dict.get "typed"
                |> Maybe.withDefault ""
    in
        case typed of
            "" ->
                model.languages

            _ ->
                List.filter (hasTag typed) model.languages


view : ViewModel -> Html Msg
view model =
    div [ styles ]
        [ h2 [] [ text "Languages" ]
        , table []
            [ tbody [] (tableRows (filteredLanguages model)) ]
        ]


tableRows : List Language -> List (Html Msg)
tableRows collection =
    List.map rowView collection


rowView : Language -> Html Msg
rowView language =
    tr []
        [ td [] [ text (toString language.id) ]
        , td []
            [ text language.name
            ]
        , td []
            [ actionBtn (Show language.id) "Show"
            , actionBtn (Edit language.id) "Edit"
            ]
        ]


actionBtn : Msg -> String -> Html Msg
actionBtn action label =
    button [ class "regular btn btn-small", onClick action ]
        [ text label ]


================================================
FILE: examples/full/src/Languages/Messages.elm
================================================
module Languages.Messages exposing (..)

import Dict
import Languages.Models exposing (..)


type alias Prop =
    String


type alias Value =
    String


type Msg
    = Show LanguageId
    | Edit LanguageId
    | Update LanguageId Prop Value
    | AddQuery (Dict.Dict String String)
    | SetQuery (Dict.Dict String String)


================================================
FILE: examples/full/src/Languages/Models.elm
================================================
module Languages.Models exposing (..)


type alias LanguageId =
    Int


type alias Language =
    { id : LanguageId
    , name : String
    , image : String
    , tags : List String
    }



-- ROUTING


type Route
    = LanguagesRoute
    | LanguageRoute LanguageId
    | LanguageEditRoute LanguageId


languages : List Language
languages =
    [ { id = 1
      , name = "Elm"
      , image = "elm"
      , tags = [ "functional", "browser", "static" ]
      }
    , { id = 2
      , name = "JavaScript"
      , image = "js"
      , tags = [ "functional", "oo", "browser", "dynamic", "prototypical" ]
      }
    , { id = 3
      , name = "Go"
      , image = "go"
      , tags = [ "oo", "google", "static" ]
      }
    , { id = 4
      , name = "Rust"
      , image = "rust"
      , tags = [ "functional", "mozilla", "static" ]
      }
    , { id = 5
      , name = "Elixir"
      , image = "elixir"
      , tags = [ "functional", "erlang", "dynamic" ]
      }
    , { id = 6
      , name = "Ruby"
      , image = "ruby"
      , tags = [ "oo", "japan", "1996", "dynamic", "classical" ]
      }
    , { id = 7
      , name = "Python"
      , image = "python"
      , tags = [ "oo", "dynamic", "classical" ]
      }
    , { id = 8
      , name = "Swift"
      , image = "swift"
      , tags = [ "functional", "apple", "static", "classical" ]
      }
    , { id = 9
      , name = "Haskell"
      , image = "haskell"
      , tags = [ "functional", "static" ]
      }
    , { id = 10
      , name = "Java"
      , image = "java"
      , tags = [ "oo", "static", "classical" ]
      }
    , { id = 11
      , name = "C#"
      , image = "csharp"
      , tags = [ "oo", "microsoft", "static", "classical" ]
      }
    , { id = 12
      , name = "PHP"
      , image = "php"
      , tags = [ "oo", "server", "1994", "dynamic", "classical" ]
      }
    ]


================================================
FILE: examples/full/src/Languages/Routing.elm
================================================
module Languages.Routing exposing (..)

import Languages.Models exposing (..)
import UrlParser exposing ((</>), int, s, string)


matchers : List (UrlParser.Parser (Route -> a) a)
matchers =
    [ UrlParser.format LanguageEditRoute (int </> s "edit")
    , UrlParser.format LanguageRoute (int)
    , UrlParser.format LanguagesRoute (s "")
    ]


toS : a -> String
toS =
    toString


reverseWithPrefix : Route -> String
reverseWithPrefix route =
    "/languages" ++ (reverse route)


reverse : Route -> String
reverse route =
    case route of
        LanguagesRoute ->
            "/"

        LanguageRoute id ->
            "/" ++ (toS id)

        LanguageEditRoute id ->
            "/" ++ (toS id) ++ "/edit"


================================================
FILE: examples/full/src/Languages/Show.elm
================================================
module Languages.Show exposing (..)

import Html exposing (..)
import Html.Attributes exposing (id, href, style, src)
import Languages.Models exposing (..)
import Languages.Messages exposing (..)


styles : Html.Attribute a
styles =
    style
        [ ( "float", "left" )
        ]


view : Language -> Html Msg
view language =
    div [ styles ]
        [ h2 [ id "titleLanguage" ] [ text language.name ]
        , img [ src ("/images/" ++ language.image ++ ".png") ] []
        , tags language
        ]


tags : Language -> Html Msg
tags language =
    div [] (List.map tag language.tags)


tag : String -> Html Msg
tag tagName =
    span []
        [ text (tagName ++ ", ")
        ]


================================================
FILE: examples/full/src/Languages/Update.elm
================================================
module Languages.Update exposing (..)

import Debug
import Navigation
import Hop exposing (output, outputFromPath, addQuery, setQuery)
import Hop.Types exposing (Config, Address)
import Routing
import Languages.Models exposing (..)
import Languages.Messages exposing (Msg(..))
import Languages.Routing


type alias UpdateModel =
    { languages : List Language
    , address : Address
    }


routerConfig : Config
routerConfig =
    Routing.config


navigationCmd : String -> Cmd a
navigationCmd path =
    path
        |> outputFromPath routerConfig
        |> Navigation.modifyUrl


update : Msg -> UpdateModel -> ( UpdateModel, Cmd Msg )
update message model =
    case Debug.log "message" message of
        Show id ->
            let
                path =
                    Languages.Routing.reverseWithPrefix (Languages.Models.LanguageRoute id)
            in
                ( model, navigationCmd path )

        Edit id ->
            let
                path =
                    Languages.Routing.reverseWithPrefix (Languages.Models.LanguageEditRoute id)
            in
                ( model, navigationCmd path )

        Update id prop value ->
            let
                udpatedLanguages =
                    List.map (updateWithId id prop value) model.languages
            in
                ( { model | languages = udpatedLanguages }, Cmd.none )

        AddQuery query ->
            let
                command =
                    model.address
                        |> addQuery query
                        |> output routerConfig
                        |> Navigation.modifyUrl
            in
                ( model, command )

        SetQuery query ->
            let
                command =
                    model.address
                        |> setQuery query
                        |> output routerConfig
                        |> Navigation.modifyUrl
            in
                ( model, command )


updateWithId : LanguageId -> String -> String -> Language -> Language
updateWithId id prop value language =
    if id == language.id then
        case prop of
            "name" ->
                { language | name = value }

            _ ->
                language
    else
        language


================================================
FILE: examples/full/src/Languages/View.elm
================================================
module Languages.View exposing (..)

import Html exposing (..)
import Html.Attributes exposing (href, style)
import Hop.Types exposing (Address)
import Languages.Models exposing (LanguageId, Language, Route, Route(..))
import Languages.Messages exposing (..)
import Languages.Filter
import Languages.List
import Languages.Show
import Languages.Edit


type alias ViewModel =
    { languages : List Language
    , address : Address
    , route : Route
    }


containerStyle : Html.Attribute a
containerStyle =
    style
        [ ( "margin-bottom", "5rem" )
        , ( "overflow", "auto" )
        ]


view : ViewModel -> Html Msg
view model =
    div [ containerStyle ]
        [ Languages.Filter.view {}
        , Languages.List.view { languages = model.languages, address = model.address }
        , subView model
        ]


subView : ViewModel -> Html Msg
subView model =
    case model.route of
        LanguageRoute languageId ->
            let
                maybeLanguage =
                    getLanguage model.languages languageId
            in
                case maybeLanguage of
                    Just language ->
                        Languages.Show.view language

                    Nothing ->
                        notFoundView model

        LanguageEditRoute languageId ->
            let
                maybeLanguage =
                    getLanguage model.languages languageId
            in
                case maybeLanguage of
                    Just language ->
                        Languages.Edit.view language

                    _ ->
                        notFoundView model

        LanguagesRoute ->
            emptyView


emptyView : Html msg
emptyView =
    span [] []


notFoundView : ViewModel -> Html msg
notFoundView model =
    div []
        [ text "Not Found"
        ]


getLanguage : List Language -> LanguageId -> Maybe Language
getLanguage languages id =
    languages
        |> List.filter (\lang -> lang.id == id)
        |> List.head


================================================
FILE: examples/full/src/Main.elm
================================================
module Main exposing (..)

import Navigation
import Hop
import Hop.Types exposing (Address)
import Messages exposing (..)
import Models exposing (..)
import Update exposing (..)
import View exposing (..)
import Routing
import String
import UrlParser


urlParser : Navigation.Parser ( Route, Address )
urlParser =
    let
        parse path =
            path
                |> UrlParser.parse identity Routing.routes
                |> Result.withDefault NotFoundRoute

        matcher =
            Hop.makeResolver Routing.config parse
    in
        Navigation.makeParser (.href >> matcher)


urlUpdate : ( Route, Address ) -> AppModel -> ( AppModel, Cmd Msg )
urlUpdate ( route, address ) model =
    let
        _ =
            Debug.log "urlUpdate address" address
    in
        ( { model | route = route, address = address }, Cmd.none )


init : ( Route, Address ) -> ( AppModel, Cmd Msg )
init ( route, address ) =
    ( newAppModel route address, Cmd.none )


main : Program Never
main =
    Navigation.program urlParser
        { init = init
        , view = view
        , update = update
        , urlUpdate = urlUpdate
        , subscriptions = (always Sub.none)
        }


================================================
FILE: examples/full/src/Messages.elm
================================================
module Messages exposing (..)

import Hop.Types exposing (Query)
import Languages.Messages


type Msg
    = SetQuery Query
    | LanguagesMsg Languages.Messages.Msg
    | ShowHome
    | ShowLanguages
    | ShowAbout


================================================
FILE: examples/full/src/Models.elm
================================================
module Models exposing (..)

import Hop.Types exposing (Address, newAddress)
import Languages.Models exposing (Language, languages)


type Route
    = HomeRoute
    | AboutRoute
    | LanguagesRoutes Languages.Models.Route
    | NotFoundRoute


type alias AppModel =
    { languages : List Language
    , address : Address
    , route : Route
    , selectedLanguage : Maybe Language
    }


newAppModel : Route -> Address -> AppModel
newAppModel route address =
    { languages = languages
    , address = address
    , route = route
    , selectedLanguage = Maybe.Nothing
    }


================================================
FILE: examples/full/src/Routing.elm
================================================
module Routing exposing (..)

import Models exposing (..)
import Hop.Types exposing (Config)
import Languages.Routing
import UrlParser exposing ((</>), oneOf, int, s)
import Languages.Routing


matchers : List (UrlParser.Parser (Route -> a) a)
matchers =
    [ UrlParser.format HomeRoute (s "")
    , UrlParser.format AboutRoute (s "about")
    , UrlParser.format LanguagesRoutes (s "languages" </> (oneOf Languages.Routing.matchers))
    ]


routes : UrlParser.Parser (Route -> a) a
routes =
    oneOf matchers


config : Config
config =
    { basePath = "/app"
    , hash = False
    }

reverse : Route -> String
reverse route =
    case route of
        HomeRoute ->
            ""

        AboutRoute ->
            "about"

        LanguagesRoutes subRoute ->
            "languages" ++ Languages.Routing.reverse subRoute

        NotFoundRoute ->
            ""


================================================
FILE: examples/full/src/Update.elm
================================================
module Update exposing (..)

import Debug
import Navigation
import Hop exposing (output, outputFromPath, setQuery)
import Hop.Types exposing (Config)
import Messages exposing (..)
import Models exposing (..)
import Routing
import Languages.Update
import Languages.Models


navigationCmd : String -> Cmd a
navigationCmd path =
    path
        |> outputFromPath Routing.config
        |> Navigation.newUrl


routerConfig : Config
routerConfig =
    Routing.config


update : Msg -> AppModel -> ( AppModel, Cmd Msg )
update message model =
    case Debug.log "message" message of
        LanguagesMsg subMessage ->
            let
                updateModel =
                    { languages = model.languages
                    , address = model.address
                    }

                ( updatedModel, cmd ) =
                    Languages.Update.update subMessage updateModel
            in
                ( { model | languages = updatedModel.languages }, Cmd.map LanguagesMsg cmd )

        SetQuery query ->
            let
                command =
                    model.address
                        |> setQuery query
                        |> output routerConfig
                        |> Navigation.newUrl
            in
                ( model, command )

        ShowHome ->
            let
                path =
                    Routing.reverse HomeRoute
            in
                ( model, navigationCmd path )

        ShowLanguages ->
            let
                path =
                    Routing.reverse (LanguagesRoutes Languages.Models.LanguagesRoute)
            in
                ( model, navigationCmd path )

        ShowAbout ->
            let
                path =
                    Routing.reverse AboutRoute
            in
                ( model, navigationCmd path )


================================================
FILE: examples/full/src/View.elm
================================================
module View exposing (..)

import Html exposing (..)
import Html.App
import Html.Events exposing (onClick)
import Html.Attributes exposing (id, class, href, style)
import Models exposing (..)
import Messages exposing (..)
import Languages.View


view : AppModel -> Html Msg
view model =
    div []
        [ menu model
        , pageView model
        ]


menu : AppModel -> Html Msg
menu model =
    div [ class "p2 white bg-black" ]
        [ div []
            [ menuLink ShowHome "btnHome" "Home"
            , text "|"
            , menuLink ShowLanguages "btnLanguages" "Languages"
            , text "|"
            , menuLink ShowAbout "btnAbout" "About"
            ]
        ]


menuLink : Msg -> String -> String -> Html Msg
menuLink message viewId label =
    a
        [ id viewId
        , href "javascript://"
        , onClick message
        , class "white px2"
        ]
        [ text label ]


pageView : AppModel -> Html Msg
pageView model =
    case model.route of
        HomeRoute ->
            div [ class "p2" ]
                [ h1 [ id "title", class "m0" ] [ text "Home" ]
                , div [] [ text "Click on Languages to start routing" ]
                ]

        AboutRoute ->
            div [ class "p2" ]
                [ h1 [ id "title", class "m0" ] [ text "About" ]
                ]

        LanguagesRoutes languagesRoute ->
            let
                viewModel =
                    { languages = model.languages
                    , route = languagesRoute
                    , address = model.address
                    }
            in
                div [ class "p2" ]
                    [ h1 [ id "title", class "m0" ] [ text "Languages" ]
                    , Html.App.map LanguagesMsg (Languages.View.view viewModel)
                    ]

        NotFoundRoute ->
            notFoundView model


notFoundView : AppModel -> Html msg
notFoundView model =
    div []
        [ text "Not Found"
        ]


================================================
FILE: examples/full/src/index.js
================================================
'use strict';

require('ace-css/css/ace.css');

var Elm = require('./Main.elm');
var mountNode = document.getElementById('main');

var app = Elm.Main.embed(mountNode);


================================================
FILE: examples/full/webpack.config.js
================================================
var path = require("path");

/*
publicPath is used for finding the bundles during dev
e.g. http://localhost:3000/bundles/app.js
When the index.html is served using the webpack server then just specify the path.
When index.html is served using a framework e.g. from Rails, Phoenix or Go
then you must specify the full url where the webpack dev server is running e.g. http://localhost:4000/bundles/
This path is also used for resolving relative assets e.g. fonts from css. So for production and staging this path has to be
overriden. See webpack.prod.config.js
*/
var publicPath = '/bundles/'

module.exports = {
  entry: {
    app: [
      './src/index.js'
    ]
  },

  output: {
    path: path.resolve(__dirname + '/dist'),
    filename: '[name].js',
    publicPath:  publicPath,
  },

  module: {
    loaders: [
      {
        test: /\.(css|scss)$/,
        loaders: [
          'style-loader',
          'css-loader',
        ]
      },
      {
        test:    /\.html$/,
        exclude: /node_modules/,
        loader:  'file?name=[name].[ext]',
      },
      {
        test:    /\.elm$/,
        exclude: [/elm-stuff/, /node_modules/],
        loader:  'elm-webpack',
      },
      {
        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'url-loader?limit=10000&minetype=application/font-woff',
      },
      {
        test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'file-loader',
      },
    ],

    noParse: /\.elm$/,
  },

};


================================================
FILE: license.md
================================================
The MIT License (MIT)

Copyright (c) 2015-present, Sebastian Porto

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: package.json
================================================
{
  "name": "Hop",
  "version": "1.0.0",
  "description": "",
  "main": "",
  "scripts": {
    "test": "elm-test"
  },
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "elm-test": "^0.17.3"
  }
}


================================================
FILE: readme.md
================================================
# Hop: Navigation and routing helpers for Elm SPAs

[![Build Status](https://semaphoreci.com/api/v1/sporto/hop/branches/master/badge.svg)](https://semaphoreci.com/sporto/hop)

![alt Hop](https://raw.githubusercontent.com/sporto/hop/master/assets/logo.png)

__With the release of Elm 0.18 the official libraries `Navigation` and `UrlParser` have become a lot more robust and useful. They now integrate a lot of the functionality that Hop used to provide for previous versions of Elm. For example `UrlParser` now has `parseHash` and `parsePath`. Because of this I'm not convinced that Hop needs to be upgraded to Elm 0.18. I'll wait and see if this library could provide value in 0.18.__

Hop is a helper library meant to be used with:

- [__Navigation v1__](http://package.elm-lang.org/packages/elm-lang/navigation) for listening to location changes in the browser and pushing changes to it.
- [__UrlParser v1__](http://package.elm-lang.org/packages/evancz/url-parser) for constructing routes and parsing URLs.

## What Hop provides

On top of these two packages above, Hop helps with:

- Abstracting the differences between push or hash routing
- Providing helpers for working with the query string
- Encode / Decode the location path
- Encode / Decode the query string

## Getting Started

Please see this [example app](https://github.com/sporto/hop/blob/master/examples/basic/Main.elm). It explains how to wire everything in the comments.

## Docs

### [Building routes](https://github.com/sporto/hop/blob/master/docs/building-routes.md)
### [Nesting routes](https://github.com/sporto/hop/blob/master/docs/nesting-routes.md)
### [Matching routes](https://github.com/sporto/hop/blob/master/docs/matching-routes.md)
### [Navigating](https://github.com/sporto/hop/blob/master/docs/navigating.md)
### [Reverse routing](https://github.com/sporto/hop/blob/master/docs/reverse-routing.md)
### [API](http://package.elm-lang.org/packages/sporto/hop/latest/)
### [Changelog](./docs/changelog.md)
### [Testing Hop](https://github.com/sporto/hop/blob/master/docs/testing.md)

## More docs

### [Upgrade guide 5 to 6](https://github.com/sporto/hop/blob/master/docs/upgrade-5-to-6.md)
### [Upgrade guide 4 to 5](https://github.com/sporto/hop/blob/master/docs/upgrade-4-to-5.md)
### [Upgrade guide 3 to 4](https://github.com/sporto/hop/blob/master/docs/upgrade-3-to-4.md)
### [Upgrade guide 2.1 to 3.0](https://github.com/sporto/hop/blob/master/docs/upgrade-2-to-3.md)

### [Version 5 documentation](https://github.com/sporto/hop/tree/v5)
### [Version 4 documentation](https://github.com/sporto/hop/tree/v4)
### [Version 3 documentation](https://github.com/sporto/hop/tree/v3)
### [Version 2 documentation](https://github.com/sporto/hop/tree/v2)

### Hash routing

A proper url should have the query before the hash e.g. `?keyword=Ja#/users/1`,
but when using hash routing, query parameters are appended after the hash path e.g. `#/users/1?keyword=Ja`. 
This is done for aesthetics and so the router is fully controlled by the hash fragment.

## Examples

See `examples/basic` and `examples/full` folders. To run the example apps:

- Clone this repo
- Go to example folder
- Follow the readme in that folder


================================================
FILE: src/Hop/Address.elm
================================================
module Hop.Address exposing (..)

import Dict
import String
import Http exposing (uriEncode, uriDecode)
import Hop.Types exposing (..)

{-|
Get the Path
-}
getPath : Address -> String
getPath address =
    address.path
        |> List.map uriEncode
        |> String.join "/"
        |> String.append "/"


{-|
Get the query string from a Address.
Including ?
-}
getQuery : Address -> String
getQuery address =
    if Dict.isEmpty address.query then
        ""
    else
        address.query
            |> Dict.toList
            |> List.map (\( k, v ) -> ( uriEncode k, uriEncode v ))
            |> List.map (\( k, v ) -> k ++ "=" ++ v)
            |> String.join "&"
            |> String.append "?"



--------------------------------------------------------------------------------
-- PARSING
-- Parse a path into a Address
--------------------------------------------------------------------------------


parse : String -> Address
parse route =
    { path = parsePath route
    , query = parseQuery route
    }


extractPath : String -> String
extractPath route =
    route
        |> String.split "#"
        |> List.reverse
        |> List.head
        |> Maybe.withDefault ""
        |> String.split "?"
        |> List.head
        |> Maybe.withDefault ""


parsePath : String -> List String
parsePath route =
    route
        |> extractPath
        |> String.split "/"
        |> List.filter (\segment -> not (String.isEmpty segment))
        |> List.map uriDecode


extractQuery : String -> String
extractQuery route =
    route
        |> String.split "?"
        |> List.drop 1
        |> List.head
        |> Maybe.withDefault ""


parseQuery : String -> Query
parseQuery route =
    route
        |> extractQuery
        |> String.split "&"
        |> List.filter (not << String.isEmpty)
        |> List.map queryKVtoTuple
        |> Dict.fromList


{-| @priv
Convert a string to a tuple. Decode on the way.

    "k=1" --> ("k", "1")
-}
queryKVtoTuple : String -> ( String, String )
queryKVtoTuple kv =
    let
        splitted =
            kv
                |> String.split "="

        first =
            splitted
                |> List.head
                |> Maybe.withDefault ""

        firstDecoded =
            uriDecode first

        second =
            splitted
                |> List.drop 1
                |> List.head
                |> Maybe.withDefault ""

        secondDecoded =
            uriDecode second
    in
        ( firstDecoded, secondDecoded )


================================================
FILE: src/Hop/AddressTest.elm
================================================
module Hop.AddressTest exposing (..)

import Dict
import Expect
import Hop.Address as Address
import Hop.Types as Types
import Test exposing (..)


getPathTest : Test
getPathTest =
    let
        inputs =
            [ ( "it works"
              , { path = [ "users", "1" ], query = Dict.singleton "k" "1" }
              , "/users/1"
              )
            , ( "it encodes"
              , { path = [ "us/ers", "1" ], query = Dict.empty }
              , "/us%2Fers/1"
              )
            ]

        run ( testCase, address, expected ) =
            test testCase
                <| \() ->
                    let
                        actual =
                            Address.getPath address
                    in
                        Expect.equal expected actual
    in
        describe "getPath" (List.map run inputs)


getQuery : Test
getQuery =
    let
        inputs =
            [ ( "it works"
              , { path = [], query = Dict.singleton "k" "1" }
              , "?k=1"
              )
            , ( "it encoders"
              , { path = [], query = Dict.singleton "k" "a/b" }
              , "?k=a%2Fb"
              )
            ]

        run ( testCase, address, expected ) =
            test testCase
                <| \() ->
                    let
                        actual =
                            Address.getQuery address
                    in
                        Expect.equal expected actual
    in
        describe "getQuery" (List.map run inputs)


parseTest : Test
parseTest =
    let
        inputs =
            [ ( "it parses"
              , "/users/1?a=1"
              , { path = [ "users", "1" ], query = Dict.singleton "a" "1" }
              )
            , ( "it decodes"
              , "/a%2Fb/1?k=x%2Fy"
              , { path = [ "a/b", "1" ], query = Dict.singleton "k" "x/y" }
              )
            ]

        run ( testCase, location, expected ) =
            test testCase
                <| \() ->
                    let
                        actual =
                            Address.parse location
                    in
                        Expect.equal expected actual
    in
        describe "parse" (List.map run inputs)


all : Test
all =
    describe "Location"
        [ getPathTest
        , getQuery
        , parseTest
        ]


================================================
FILE: src/Hop/In.elm
================================================
module Hop.In exposing (..)

import Regex
import String
import Hop.Types exposing (Address, Config)
import Hop.Address exposing (parse)


{-| @priv
-}
ingest : Config -> String -> Address
ingest config href =
    href
        |> removeProtocol
        |> removeDomain
        |> getRelevantPathWithQuery config
        |> parse


{-| @priv
-}
removeProtocol : String -> String
removeProtocol href =
    href
        |> String.split "//"
        |> List.reverse
        |> List.head
        |> Maybe.withDefault ""


{-| @priv
-}
removeDomain : String -> String
removeDomain href =
    href
        |> String.split "/"
        |> List.tail
        |> Maybe.withDefault []
        |> String.join "/"
        |> String.append "/"


{-| @priv
-}
getRelevantPathWithQuery : Config -> String -> String
getRelevantPathWithQuery config href =
    if config.hash then
        href
            |> String.split "#"
            |> List.drop 1
            |> List.head
            |> Maybe.withDefault ""
    else
        href
            |> String.split "#"
            |> List.head
            |> Maybe.withDefault ""
            |> removeBase config


{-| @priv
Remove the basePath from a path

"/basepath/a/b?k=1" -> "/a/b?k=1"
-}
removeBase : Config -> String -> String
removeBase config pathWithQuery =
    let
        regex =
            Regex.regex config.basePath
    in
        Regex.replace (Regex.AtMost 1) regex (always "") pathWithQuery


================================================
FILE: src/Hop/InTest.elm
================================================
module Hop.InTest exposing (..)

import Dict
import Expect
import Hop.In exposing (ingest)
import Test exposing (..)

type Route
    = NotFound


config =
    { hash = True
    , basePath = ""
    }


configWithPath =
    { config | hash = False }


configWithPathAndBase =
    { configWithPath | basePath = "/app/v1" }


inputTest : Test
inputTest =
    let
        inputs =
            [ ( "it parses an empty hash"
              , config
              , "http://localhost:3000/basepath"
              , { path = [], query = Dict.empty }
              )
            , ( "it parses a hash"
              , config
              , "http://localhost:3000/basepath#/users/1"
              , { path = [ "users", "1" ], query = Dict.empty }
              )
            , ( "it parses a path"
              , configWithPath
              , "http://localhost:3000/users/1"
              , { path = [ "users", "1" ], query = Dict.empty }
              )
            , ( "it parses a path with basepath"
              , configWithPathAndBase
              , "http://localhost:3000/app/v1/users/1"
              , { path = [ "users", "1" ], query = Dict.empty }
              )
            , ( "it parses a hash with query"
              , config
              , "http://localhost:3000/basepath#/users/1?a=1"
              , { path = [ "users", "1" ], query = Dict.singleton "a" "1" }
              )
            , ( "it parses a path with query"
              , configWithPath
              , "http://localhost:3000/users/1?a=1"
              , { path = [ "users", "1" ], query = Dict.singleton "a" "1" }
              )
            , ( "it parses a path with basepath and query"
              , configWithPathAndBase
              , "http://localhost:3000/app/v1/users/1?a=1"
              , { path = [ "users", "1" ], query = Dict.singleton "a" "1" }
              )
            , ( "it decodes the query"
              , config
              , "http://localhost:3000/basepath#/?a%20b%26c%3Fd=1%202%263%3F4"
              , { path = [], query = Dict.singleton "a b&c?d" "1 2&3?4" }
              )
            ]

        run ( testCase, config, href, expected ) =
            test testCase
                <| \() ->
                    let
                        actual =
                            ingest config href
                    in
                        Expect.equal expected actual
    in
        describe "ingest" (List.map run inputs)


all : Test
all =
    describe "In"
        [ inputTest
        ]


================================================
FILE: src/Hop/Out.elm
================================================
module Hop.Out exposing (..)

import String
import Hop.Types exposing (Address, Config)
import Hop.Utils exposing (dedupSlash)
import Hop.Address exposing (parse)


{-|
Make a real path from an address record.
This will add the hash and the basePath as necessary.

    fromAddress config { path = ["users", "1"], query = Dict.empty }

    ==

    "#/users/1"

-}
output : Config -> Address -> String
output config address =
    let
        -- path -> "/a/1"
        path =
            Hop.Address.getPath address

        -- query -> "?a=1"
        query =
            Hop.Address.getQuery address

        url =
            if config.hash then
                "#" ++ path ++ query
            else if String.isEmpty config.basePath then
                path ++ query
            else if path == "/" then
                "/" ++ config.basePath ++ query
            else
                "/" ++ config.basePath ++ path ++ query

        realPath =
            dedupSlash url
    in
        if realPath == "" then
            "/"
        else
            realPath


{-|
Make a real path from a simulated path.
This will add the hash and the basePath as necessary.

    toRealPath config "/users"

    ==

    "#/users"
-}
outputFromPath : Config -> String -> String
outputFromPath config path =
    path
        |> Hop.Address.parse
        |> output config


================================================
FILE: src/Hop/OutTest.elm
================================================
module Hop.OutTest exposing (..)

import Dict
import Expect
import Hop.Out as Out
import Hop.Types exposing (newAddress)
import Hop.TestHelper exposing (configWithHash, configWithPath, configPathAndBasePath)
import Test exposing (..)


outputTest : Test
outputTest =
    let
        empty =
            newAddress

        inputs =
            [ ( "hash: it is empty when empty"
              , configWithHash
              , empty
              , ""
              , "#/"
              )
            , ( "path: it is empty when empty"
              , configWithPath
              , empty
              , ""
              , "/"
              )
            , ( "basepath: it has the basepath"
              , configPathAndBasePath
              , empty
              , ""
              , "/app/v1"
              )
            , ( "basepath: adds slash when missing"
              , { configPathAndBasePath | basePath = "app/v1" }
              , empty
              , ""
              , "/app/v1"
              )
              -- path
            , ( "hash: it adds the path"
              , configWithHash
              , { empty | path = [ "a", "b" ] }
              , "/a/b"
              , "#/a/b"
              )
            , ( "path: it adds the path"
              , configWithPath
              , { empty | path = [ "a", "b" ] }
              , "/a/b"
              , "/a/b"
              )
            , ( "path: it adds the basepath and path"
              , configPathAndBasePath
              , { empty | path = [ "a", "b" ] }
              , "/a/b"
              , "/app/v1/a/b"
              )
              -- query
            , ( "hash: it adds the query as pseudo query"
              , configWithHash
              , { empty | query = Dict.singleton "k" "1" }
              , "?k=1"
              , "#/?k=1"
              )
            , ( "path: it adds the query"
              , configWithPath
              , { empty | query = Dict.singleton "k" "1" }
              , "?k=1"
              , "/?k=1"
              )
            , ( "path: it adds the basepath query"
              , configPathAndBasePath
              , { empty | query = Dict.singleton "k" "1" }
              , "?k=1"
              , "/app/v1?k=1"
              )
              -- path and query
            , ( "hash: it adds the path and query"
              , configWithHash
              , { empty | query = Dict.singleton "k" "1", path = [ "a", "b" ] }
              , "/a/b?k=1"
              , "#/a/b?k=1"
              )
            , ( "path: it adds the path and query"
              , configWithPath
              , { empty | query = Dict.singleton "k" "1", path = [ "a", "b" ] }
              , "/a/b?k=1"
              , "/a/b?k=1"
              )
            , ( "path: it adds the basepath, path and query"
              , configPathAndBasePath
              , { empty | query = Dict.singleton "k" "1", path = [ "a", "b" ] }
              , "/a/b?k=1"
              , "/app/v1/a/b?k=1"
              )
            , ( "hash: it encodes"
              , configWithHash
              , { empty | query = Dict.singleton "a/d" "1/4", path = [ "a/b", "1" ] }
              , "/a%2Fb/1?a%2Fd=1%2F4"
              , "#/a%2Fb/1?a%2Fd=1%2F4"
              )
            ]

        run ( testCase, config, address, path, expected ) =
            [ test testCase
                <| \() ->
                    let
                        actual =
                            Out.output config address
                    in
                        Expect.equal expected actual
            , test testCase
                <| \() ->
                    let
                        actual =
                            Out.outputFromPath config path
                    in
                        Expect.equal expected actual
            ]
        
        tests =
            List.concatMap  run inputs
    in
        describe "output and outputFromPath" tests


all : Test
all =
    describe "In"
        [ outputTest
        ]


================================================
FILE: src/Hop/TestHelper.elm
================================================
module Hop.TestHelper exposing (..)

import Hop.Types exposing (Config)


configWithHash : Config
configWithHash =
    { basePath = ""
    , hash = True
    }


configWithPath : Config
configWithPath =
    { basePath = ""
    , hash = False
    }


configPathAndBasePath : Config
configPathAndBasePath =
    { basePath = "/app/v1"
    , hash = False
    }


================================================
FILE: src/Hop/Types.elm
================================================
module Hop.Types exposing (Config, Query, Address, newQuery, newAddress)

{-| Types used in Hop

#Types
@docs Config, Address, Query

#Factories
@docs newQuery, newAddress
-}

import Dict

{-| A Dict that holds query parameters

    Dict.Dict String String
-}
type alias Query =
    Dict.Dict String String

{-| A Record that represents the current location
Includes a `path` and a `query`

    {
      path: List String,
      query: Query
    }
-}
type alias Address =
    { path : List String
    , query : Query
    }

{-| Hop Configuration

- basePath: Only for pushState routing (not hash). e.g. "/app".
- hash: True for hash routing, False for pushState routing.

-}
type alias Config =
    { basePath : String
    , hash : Bool
    }

{-|
Create an empty Query record
-}
newQuery : Query
newQuery =
    Dict.empty


{-|
Create an empty Address record
-}
newAddress : Address
newAddress =
    { query = newQuery
    , path = []
    }


================================================
FILE: src/Hop/Utils.elm
================================================
module Hop.Utils exposing (..)

import Regex

dedupSlash : String -> String
dedupSlash =
    Regex.replace Regex.All (Regex.regex "/+") (\_ -> "/")


================================================
FILE: src/Hop.elm
================================================
module Hop
    exposing
        ( addQuery
        , clearQuery
        , ingest
        , makeResolver
        , output
        , outputFromPath
        , pathFromAddress
        , queryFromAddress
        , removeQuery
        , setQuery
        )

{-| Navigation and routing utilities for single page applications. See [readme](https://github.com/sporto/hop) for usage.

# Consuming an URL from the browser
@docs ingest, makeResolver

# Preparing a URL for changing the browser location
@docs output, outputFromPath

# Work with an Address record
@docs pathFromAddress, queryFromAddress

# Modify the query string
@docs addQuery, setQuery, removeQuery, clearQuery

-}

import Dict
import String
import Hop.Address
import Hop.In
import Hop.Out
import Hop.Types exposing (Address, Config, Query)


---------------------------------------
-- INGEST
---------------------------------------


{-|
Convert a raw url to an Address record. Use this function for 'normalizing' the URL before parsing it.
This conversion will take in account your basePath and hash configuration.

E.g. with path routing

    config =
        { basePath = ""
        , hash = False
        }

    ingest config "http://localhost:3000/app/languages/1?k=1"
    -->
    { path = ["app", "languages", "1" ], query = Dict.singleton "k" "1" }

E.g. with path routing and base path

    config =
        { basePath = "/app/v1"
        , hash = False
        }

    ingest config "http://localhost:3000/app/v1/languages/1?k=1"
    -->
    { path = ["languages", "1" ], query = Dict.singleton "k" "1" }

E.g. with hash routing

    config =
        { basePath = ""
        , hash = True
        }

    ingest config "http://localhost:3000/app#/languages/1?k=1"
    -->
    { path = ["languages", "1" ], query = Dict.singleton "k" "1" }
-}
ingest : Config -> String -> Address
ingest =
    Hop.In.ingest


{-|
`makeResolver` normalizes the URL using your config and then gives that normalised URL to your parser.

Use this for creating a function to give to `Navigation.makeParser`. 
See examples in `docs/matching-routes.md`.

    Hop.makeResolver hopConfig parse

`makeResolver` takes 2 arguments.

### Config e.g.

    { basePath = ""
    , hash = False
    }

### Parse function

A function that receives the normalised path and returns the result of parsing it.

    parse path =
        path
            |> UrlParser.parse identity routes
            |> Result.withDefault NotFoundRoute

You parse function will receive the path like this:

`http://example.com/users/1` --> 'users/1/'

So it won't have a leading /, but it will have a trailing /. This is because the way UrlParse works.

### Return value from resolver

After being called with a URL the resolver will return a tuple with `(parse result, address)` e.g.

    resolver =
        Hop.makeResolver hopConfig parse

    resolver "http://example.com/index.html#/users/2"

    -->

    ( UserRoute 2, { path = ["users", "2"], query = ...} )

### Example

A complete example looks like:

    urlParser : Navigation.Parser ( Route, Address )
    urlParser =
        let
            parse path =
                path
                    |> UrlParser.parse identity routes
                    |> Result.withDefault NotFoundRoute

            resolver =
                Hop.makeResolver hopConfig parse
        in
            Navigation.makeParser (.href >> resolver)

-}
makeResolver :
    Config
    -> (String -> result)
    -> String
    -> (result, Address)
makeResolver config parse rawInput =
    let
        address =
            rawInput
                |> ingest config

        parseResult =
            pathFromAddress address
                ++ "/"
                |> String.dropLeft 1
                |> parse
    in
        (parseResult, address)



---------------------------------------
-- CREATE OUTBOUND URLs
---------------------------------------


{-|
Convert an Address record to an URL to feed the browser.
This will take in account your basePath and hash config.

E.g. with path routing

    output config { path = ["languages", "1" ], query = Dict.singleton "k" "1" }
    -->
    "/languages/1?k=1"

E.g. with hash routing

    output config { path = ["languages", "1" ], query = Dict.singleton "k" "1" }
    -->
    "#/languages/1?k=1"
-}
output : Config -> Address -> String
output =
    Hop.Out.output


{-|
Convert a string to an URL to feed the browser.
This will take in account your basePath and hash config.

E.g. with path routing

    outputFromPath config "/languages/1?k=1"
    -->
    "/languages/1?k=1"

E.g. with path routing + basePath

    outputFromPath config "/languages/1?k=1"
    -->
    "/app/languages/1?k=1"

E.g. with hash routing

    output config "/languages/1?k=1"
    -->
    "#/languages/1?k=1"
-}
outputFromPath : Config -> String -> String
outputFromPath =
    Hop.Out.outputFromPath



---------------------------------------
-- WORK WITH ADDRESS
---------------------------------------


{-|
Get the path as a string from an Address record.

    address = { path = ["languages", "1" ], query = Dict.singleton "k" "1" }

    pathFromAddress address
    -->
    "/languages/1"
-}
pathFromAddress : Address -> String
pathFromAddress =
    Hop.Address.getPath


{-|
Get the query as a string from an Address record.

    address = { path = ["app"], query = Dict.singleton "k" "1" }

    queryFromAddress address
    -->
    "?k=1"
-}
queryFromAddress : Address -> String
queryFromAddress =
    Hop.Address.getQuery



-------------------------------------------------------------------------------
-- QUERY MUTATION
-------------------------------------------------------------------------------


{-|
Add query string values (patches any existing values) to an Address record.

    addQuery query address

    addQuery (Dict.Singleton "b" "2") { path = [], query = Dict.fromList [("a", "1")] }

    ==

    { path = [], query = Dict.fromList [("a", "1"), ("b", "2")] }

- query is a dictionary with keys to add

To remove a key / value pair set the value to ""
-}
addQuery : Query -> Address -> Address
addQuery query location =
    let
        updatedQuery =
            Dict.union query location.query
    in
        { location | query = updatedQuery }


{-|
Set the whole query string (removes any existing values).

    setQuery query address
-}
setQuery : Query -> Address -> Address
setQuery query location =
    { location | query = query }


{-|
Remove one key from the query string

    removeQuery key address
-}
removeQuery : String -> Address -> Address
removeQuery key location =
    let
        updatedQuery =
            Dict.remove key location.query
    in
        { location | query = updatedQuery }


{-| Clear all query string values

    clearQuery address
-}
clearQuery : Address -> Address
clearQuery location =
    { location | query = Dict.empty }


================================================
FILE: src/HopTest.elm
================================================
module HopTest exposing (..)

import Expect
import Test exposing (..)
import Hop.TestHelper exposing (configWithHash, configWithPath, configPathAndBasePath)
import Hop


makeResolverTest : Test
makeResolverTest =
    let
        inputs =
            [ ( "path"
              , configWithPath
              , "http://example.com/users/1"
              , "users/1/"
              )
            , ( "path with base"
              , configPathAndBasePath
              , "http://example.com/app/v1/users/1"
              , "users/1/"
              )
            , ( "path"
              , configWithHash
              , "http://example.com/app#/users/1"
              , "users/1/"
              )
            ]

        run ( testCase, config, href, expected ) =
            test testCase
                <| \() ->
                    let
                        resolver =
                            Hop.makeResolver config identity

                        ( actual, _ ) =
                            resolver href
                    in
                        Expect.equal expected actual
    in
        describe "makeResolver" (List.map run inputs)


all : Test
all =
    describe "Hop"
        [ makeResolverTest
        ]


================================================
FILE: tests/IntegrationTest.elm
================================================
module IntegrationTest exposing (..)

import UrlParser exposing ((</>), oneOf, int, s)
import Navigation exposing (Location)
import Expect
import String
import Test exposing (..)
import Hop.TestHelper exposing (configWithHash, configWithPath, configPathAndBasePath)
import Hop
import Hop.Types exposing (Address, Config)


type alias UserId =
    Int


type UserRoute
    = UsersRoute
    | UserRoute UserId
    | UserEditRoute UserId


type MainRoute
    = HomeRoute
    | AboutRoute
    | UsersRoutes UserRoute
    | NotFoundRoute


usersMatchers =
    [ UrlParser.format UserEditRoute (int </> s "edit")
    , UrlParser.format UserRoute (int)
    , UrlParser.format UsersRoute (s "")
    ]


mainMatchers =
    [ UrlParser.format HomeRoute (s "")
    , UrlParser.format AboutRoute (s "about")
    , UrlParser.format UsersRoutes (s "users" </> (oneOf usersMatchers))
    ]


routes =
    oneOf mainMatchers


newLocation : Location
newLocation =
    { hash = ""
    , host = "example.com"
    , hostname = "example.com"
    , href = ""
    , origin = ""
    , password = ""
    , pathname = ""
    , port_ = ""
    , protocol = "http"
    , search = ""
    , username = ""
    }


parseWithUrlParser : Config -> Location -> ( MainRoute, Address )
parseWithUrlParser currentConfig =
    let
        parse path =
            path
                |> UrlParser.parse identity routes
                |> Result.withDefault NotFoundRoute
    in
        .href >> Hop.makeResolver currentConfig parse



------------------------------
-- Example urlParsers
------------------------------


urlParserRouteAddress : Navigation.Parser ( MainRoute, Address )
urlParserRouteAddress =
    let
        parse path =
            path
                |> UrlParser.parse identity routes
                |> Result.withDefault NotFoundRoute

        solver =
            Hop.makeResolver configWithHash parse
    in
        Navigation.makeParser (.href >> solver)


urlParserOnlyRoute : Navigation.Parser MainRoute
urlParserOnlyRoute =
    let
        parse path =
            path
                |> UrlParser.parse identity routes
                |> Result.withDefault NotFoundRoute

        solver =
            Hop.makeResolver configWithHash parse
    in
        Navigation.makeParser (.href >> solver >> fst)

urlParserResultAddress : Navigation.Parser (Result String MainRoute, Address)
urlParserResultAddress =
    let
        parse path =
            path
                |> UrlParser.parse identity routes

        solver =
            Hop.makeResolver configWithHash parse
    in
        Navigation.makeParser (.href >> solver)


urlParserIntegrationTest : Test
urlParserIntegrationTest =
    let
        inputs =
            [ ( "Home page"
              , configWithPath
              , "http://example.com"
              , HomeRoute
              , "/"
              )
            , ( "Base: Home page"
              , configPathAndBasePath
              , "http://example.com/app/v1"
              , HomeRoute
              , "/app/v1"
              )
            , ( "Hash: Home page with /#"
              , configWithHash
              , "http://example.com/#"
              , HomeRoute
              , "#/"
              )
            , ( "Hash: Home page with /#/"
              , configWithHash
              , "http://example.com/#/"
              , HomeRoute
              , "#/"
              )
            , ( "Hash: Home page without hash"
              , configWithHash
              , "http://example.com"
              , HomeRoute
              , "#/"
              )
            , ( "Hash: Home page"
              , configWithHash
              , "http://example.com/index.html"
              , HomeRoute
              , "#/"
              )
              -- about
            , ( "AboutRoute"
              , configWithPath
              , "http://example.com/about"
              , AboutRoute
              , "/about"
              )
            , ( "Base: AboutRoute"
              , configPathAndBasePath
              , "http://example.com/app/v1/about"
              , AboutRoute
              , "/app/v1/about"
              )
            , ( "Hash: AboutRoute"
              , configWithHash
              , "http://example.com/#/about"
              , AboutRoute
              , "#/about"
              )
            , ( "Hash: AboutRoute with slash"
              , configWithHash
              , "http://example.com/app#/about"
              , AboutRoute
              , "#/about"
              )
              -- users
            , ( "UsersRoute"
              , configWithPath
              , "http://example.com/users"
              , UsersRoutes UsersRoute
              , "/users"
              )
            , ( "Base: UsersRoute"
              , configPathAndBasePath
              , "http://example.com/app/v1/users"
              , UsersRoutes UsersRoute
              , "/app/v1/users"
              )
            , ( "Hash: UsersRoute"
              , configWithHash
              , "http://example.com/#/users"
              , UsersRoutes UsersRoute
              , "#/users"
              )
              -- users with query
            , ( "UsersRoute"
              , configWithPath
              , "http://example.com/users?k=1"
              , UsersRoutes UsersRoute
              , "/users?k=1"
              )
            , ( "Base: UsersRoute"
              , configPathAndBasePath
              , "http://example.com/app/v1/users?k=1"
              , UsersRoutes UsersRoute
              , "/app/v1/users?k=1"
              )
            , ( "Hash: UsersRoute"
              , configWithHash
              , "http://example.com/#/users?k=1"
              , UsersRoutes UsersRoute
              , "#/users?k=1"
              )
              -- user
            , ( "UserRoute"
              , configWithPath
              , "http://example.com/users/2"
              , UsersRoutes (UserRoute 2)
              , "/users/2"
              )
            , ( "Base: UserRoute"
              , configPathAndBasePath
              , "http://example.com/app/v1/users/2"
              , UsersRoutes (UserRoute 2)
              , "/app/v1/users/2"
              )
            , ( "Hash: UserRoute"
              , configWithHash
              , "http://example.com/#/users/2"
              , UsersRoutes (UserRoute 2)
              , "#/users/2"
              )
              -- user edit
            , ( "UserRoute"
              , configWithPath
              , "http://example.com/users/2/edit"
              , UsersRoutes (UserEditRoute 2)
              , "/users/2/edit"
              )
            , ( "Base: UserRoute"
              , configPathAndBasePath
              , "http://example.com/app/v1/users/2/edit"
              , UsersRoutes (UserEditRoute 2)
              , "/app/v1/users/2/edit"
              )
            , ( "Hash: UserRoute"
              , configWithHash
              , "http://example.com/#/users/2/edit"
              , UsersRoutes (UserEditRoute 2)
              , "#/users/2/edit"
              )
            ]

        run ( testCase, currentConfig, href, expected, expectedRoundTrip ) =
            [ test testCase
                <| \() ->
                    let
                        location =
                            { newLocation | href = href }

                        ( actual, _ ) =
                            parseWithUrlParser currentConfig location
                    in
                        Expect.equal expected actual
            , test (testCase ++ " - output")
                <| \() ->
                    let
                        location =
                            { newLocation | href = href }

                        ( _, address ) =
                            parseWithUrlParser currentConfig location

                        actual =
                            Hop.output currentConfig address
                    in
                        Expect.equal expectedRoundTrip actual
            ]
    in
        describe "UrlParser integration" (List.concatMap run inputs)


all : Test
all =
    describe "Integration"
        [ urlParserIntegrationTest
        ]


================================================
FILE: tests/Main.elm
================================================
port module Main exposing (..)

import Tests
import Test.Runner.Node exposing (run)
import Json.Encode exposing (Value)


main : Program Value
main =
    run emit Tests.all


port emit : ( String, Value ) -> Cmd msg


================================================
FILE: tests/Tests.elm
================================================
module Tests exposing (..)

import HopTest
import Hop.AddressTest
import Hop.InTest
import Hop.OutTest
import IntegrationTest
import Test exposing (..)


all : Test
all =
  describe "Hop"
    [ HopTest.all
    , Hop.AddressTest.all
    , Hop.InTest.all
    , Hop.OutTest.all
    , IntegrationTest.all
    ]


================================================
FILE: tests/elm-package.json
================================================
{
    "version": "0.0.1",
    "summary": "A router for SPAs in Elm",
    "repository": "https://github.com/sporto/hop.git",
    "license": "MIT",
    "source-directories": [
        ".",
        "../src"
    ],
    "exposed-modules": [],
    "dependencies": {
        "elm-community/elm-test": "2.1.0 <= v < 3.0.0",
        "elm-lang/core": "4.0.0 <= v < 5.0.0",
        "elm-lang/navigation": "1.0.0 <= v < 2.0.0",
        "evancz/elm-http": "3.0.1 <= v < 4.0.0",
        "evancz/url-parser": "1.0.0 <= v < 2.0.0",
        "rtfeldman/node-test-runner": "2.0.0 <= v < 3.0.0"
    },
    "elm-version": "0.16.0 <= v < 0.18.0"
}

================================================
FILE: tests/install-packages.sh
================================================
#!/bin/bash

n=0
until [ $n -ge 5 ]
do
  elm package install -y && break
  n=$[$n+1]
  sleep 15
done


================================================
FILE: tests/package.json
================================================
{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "elm.js",
  "scripts": {
    "test": "elm-test"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "elm-test": "^0.17.3"
  }
}
Download .txt
gitextract_i1usn_9t/

├── .editorconfig
├── .gitignore
├── .vscode/
│   └── settings.json
├── Makefile
├── assets/
│   └── logo.idraw
├── docs/
│   ├── building-routes.md
│   ├── changelog.md
│   ├── matching-routes.md
│   ├── navigating.md
│   ├── nesting-routes.md
│   ├── reverse-routing.md
│   ├── testing.md
│   ├── upgrade-2-to-3.md
│   ├── upgrade-3-to-4.md
│   ├── upgrade-4-to-5.md
│   └── upgrade-5-to-6.md
├── elm-package.json
├── examples/
│   ├── basic/
│   │   ├── .dockerignore
│   │   ├── .gitignore
│   │   ├── Main.elm
│   │   ├── elm-package.json
│   │   ├── install-packages.sh
│   │   └── readme.md
│   └── full/
│       ├── .gitignore
│       ├── dev_server.js
│       ├── elm-package.json
│       ├── install-packages.sh
│       ├── package.json
│       ├── readme.md
│       ├── src/
│       │   ├── Languages/
│       │   │   ├── Edit.elm
│       │   │   ├── Filter.elm
│       │   │   ├── List.elm
│       │   │   ├── Messages.elm
│       │   │   ├── Models.elm
│       │   │   ├── Routing.elm
│       │   │   ├── Show.elm
│       │   │   ├── Update.elm
│       │   │   └── View.elm
│       │   ├── Main.elm
│       │   ├── Messages.elm
│       │   ├── Models.elm
│       │   ├── Routing.elm
│       │   ├── Update.elm
│       │   ├── View.elm
│       │   └── index.js
│       └── webpack.config.js
├── license.md
├── package.json
├── readme.md
├── src/
│   ├── Hop/
│   │   ├── Address.elm
│   │   ├── AddressTest.elm
│   │   ├── In.elm
│   │   ├── InTest.elm
│   │   ├── Out.elm
│   │   ├── OutTest.elm
│   │   ├── TestHelper.elm
│   │   ├── Types.elm
│   │   └── Utils.elm
│   ├── Hop.elm
│   └── HopTest.elm
└── tests/
    ├── IntegrationTest.elm
    ├── Main.elm
    ├── Tests.elm
    ├── elm-package.json
    ├── install-packages.sh
    └── package.json
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (88K chars).
[
  {
    "path": ".editorconfig",
    "chars": 222,
    "preview": "[*]\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\n\n[*.js]\nindent_style = tab\nindent_"
  },
  {
    "path": ".gitignore",
    "chars": 108,
    "preview": ".DS_Store\nelm-stuff\nnode_modules\nelm.js\ndocumentation.json\n.vscode/tasks.json\nnpm-debug.log\n.envrc\ntests.js\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 213,
    "preview": "// Place your settings in this file to overwrite default and user settings.\n{\n  \"search.exclude\": {\n\t\t\"**/node_modules\":"
  },
  {
    "path": "Makefile",
    "chars": 606,
    "preview": "# Start basic application example locally\nbasic-up:\n\tcd ./examples/basic && elm reactor\n\nfull-up:\n\tcd ./examples/full &&"
  },
  {
    "path": "docs/building-routes.md",
    "chars": 1824,
    "preview": "# Building routes\n\nAs of version 6 Hop doesn't provide matchers anymore, instead you can use [__UrlParser__](http://pack"
  },
  {
    "path": "docs/changelog.md",
    "chars": 1056,
    "preview": "# Changelog\n\n### 6.0.0\n\n- Remove matchers (Use UrlParser instead)\n- Rename input and output functions\n- Encode and decod"
  },
  {
    "path": "docs/matching-routes.md",
    "chars": 2013,
    "preview": "# Matching routes\n\nCreate a parser using `Navigation.makeParser` combined with `Hop.makeResolver`.\nThere are serveral st"
  },
  {
    "path": "docs/navigating.md",
    "chars": 1390,
    "preview": "# Navigating\n\n## Changing the location\n\nUse `Hop.outputFromPath` for changing the browser location.\n\nAdd a message:\n\n```"
  },
  {
    "path": "docs/nesting-routes.md",
    "chars": 726,
    "preview": "# Nesting routes\n\nUrlParser supports nested routes:\n\n```elm\ntype UserRoute\n    = UsersRoute\n    | UserRoute UserId\n\ntype"
  },
  {
    "path": "docs/reverse-routing.md",
    "chars": 465,
    "preview": "# Reverse routing\n\nReverse routing means converting a route tag back to an url e.g.\n\n```\nUserRoute 1 --> \"/users/1\"\n```\n"
  },
  {
    "path": "docs/testing.md",
    "chars": 96,
    "preview": "# Testing Hop\n\n## Unit tests\n\n```bash\ncd tests\nelm package install -y\n\ncd ..\nnpm i\nnpm test\n```\n"
  },
  {
    "path": "docs/upgrade-2-to-3.md",
    "chars": 1923,
    "preview": "# Upgrading from 2 to 3\n\nHop has changed in many ways in version 3. Please review the [Getting started guide](https://gi"
  },
  {
    "path": "docs/upgrade-3-to-4.md",
    "chars": 611,
    "preview": "# Upgrading from 3 to 4\n\nVersion 4 introduces push state. There are two major changes:\n\n## Config\n\nConfig now includes `"
  },
  {
    "path": "docs/upgrade-4-to-5.md",
    "chars": 354,
    "preview": "# Upgrading from 4 to 5\n\nVersion 5 works with Navigation and Elm 0.17\n\n## Matchers\n\nAll matchers stay the same as in ver"
  },
  {
    "path": "docs/upgrade-5-to-6.md",
    "chars": 123,
    "preview": "# Upgrading from 5 to 6\n\nVersion 6 removes Hop matchers. Use UrlParser instead. See Building Routes document for example"
  },
  {
    "path": "elm-package.json",
    "chars": 472,
    "preview": "{\n    \"version\": \"6.0.1\",\n    \"summary\": \"Routing and Navigation helpers for SPAs in Elm\",\n    \"repository\": \"https://gi"
  },
  {
    "path": "examples/basic/.dockerignore",
    "chars": 20,
    "preview": "Dockefile\nelm-stuff\n"
  },
  {
    "path": "examples/basic/.gitignore",
    "chars": 11,
    "preview": "index.html\n"
  },
  {
    "path": "examples/basic/Main.elm",
    "chars": 6604,
    "preview": "module Main exposing (..)\n\n{-|\nYou will need Navigation, UrlParser and Hop.\n\n```\nelm package install elm-lang/navigation"
  },
  {
    "path": "examples/basic/elm-package.json",
    "chars": 583,
    "preview": "{\n    \"version\": \"1.0.0\",\n    \"summary\": \"helpful summary of your project, less than 80 characters\",\n    \"repository\": \""
  },
  {
    "path": "examples/basic/install-packages.sh",
    "chars": 101,
    "preview": "#!/bin/bash\n\nn=0\nuntil [ $n -ge 5 ]\ndo\n  elm package install -y && break\n  n=$[$n+1]\n  sleep 15\ndone\n"
  },
  {
    "path": "examples/basic/readme.md",
    "chars": 125,
    "preview": "# Basic Hop Example\n\n- Install packages `elm package install -y`\n- Run `elm reactor`\n- Open `http://localhost:8000/Main."
  },
  {
    "path": "examples/full/.gitignore",
    "chars": 11,
    "preview": "index.html\n"
  },
  {
    "path": "examples/full/dev_server.js",
    "chars": 1049,
    "preview": "var path    = require('path');\nvar express = require('express');\nvar http    = require('http');\nvar webpack = require('w"
  },
  {
    "path": "examples/full/elm-package.json",
    "chars": 600,
    "preview": "{\n    \"version\": \"1.0.0\",\n    \"summary\": \"helpful summary of your project, less than 80 characters\",\n    \"repository\": \""
  },
  {
    "path": "examples/full/install-packages.sh",
    "chars": 101,
    "preview": "#!/bin/bash\n\nn=0\nuntil [ $n -ge 5 ]\ndo\n  elm package install -y && break\n  n=$[$n+1]\n  sleep 15\ndone\n"
  },
  {
    "path": "examples/full/package.json",
    "chars": 467,
    "preview": "{\n  \"name\": \"full\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"elm.js\",\n  \"scripts\": {\n    \"dev\": \"node dev_s"
  },
  {
    "path": "examples/full/readme.md",
    "chars": 264,
    "preview": "# Full Hop Example\n\nThis example uses push state.\n\nTo run:\n\n```\nelm package install -y\nnpm i\nnpm run dev\n```\n\nOpen http:"
  },
  {
    "path": "examples/full/src/Languages/Edit.elm",
    "chars": 716,
    "preview": "module Languages.Edit exposing (..)\n\nimport Html exposing (..)\nimport Html.Events exposing (on, targetValue)\nimport Html"
  },
  {
    "path": "examples/full/src/Languages/Filter.elm",
    "chars": 1142,
    "preview": "module Languages.Filter exposing (..)\n\nimport Html exposing (..)\nimport Html.Events exposing (onClick)\nimport Html.Attri"
  },
  {
    "path": "examples/full/src/Languages/List.elm",
    "chars": 1714,
    "preview": "module Languages.List exposing (..)\n\nimport Html exposing (..)\nimport Html.Events exposing (onClick)\nimport Html.Attribu"
  },
  {
    "path": "examples/full/src/Languages/Messages.elm",
    "chars": 326,
    "preview": "module Languages.Messages exposing (..)\n\nimport Dict\nimport Languages.Models exposing (..)\n\n\ntype alias Prop =\n    Strin"
  },
  {
    "path": "examples/full/src/Languages/Models.elm",
    "chars": 1852,
    "preview": "module Languages.Models exposing (..)\n\n\ntype alias LanguageId =\n    Int\n\n\ntype alias Language =\n    { id : LanguageId\n  "
  },
  {
    "path": "examples/full/src/Languages/Routing.elm",
    "chars": 717,
    "preview": "module Languages.Routing exposing (..)\n\nimport Languages.Models exposing (..)\nimport UrlParser exposing ((</>), int, s, "
  },
  {
    "path": "examples/full/src/Languages/Show.elm",
    "chars": 689,
    "preview": "module Languages.Show exposing (..)\n\nimport Html exposing (..)\nimport Html.Attributes exposing (id, href, style, src)\nim"
  },
  {
    "path": "examples/full/src/Languages/Update.elm",
    "chars": 2252,
    "preview": "module Languages.Update exposing (..)\n\nimport Debug\nimport Navigation\nimport Hop exposing (output, outputFromPath, addQu"
  },
  {
    "path": "examples/full/src/Languages/View.elm",
    "chars": 2001,
    "preview": "module Languages.View exposing (..)\n\nimport Html exposing (..)\nimport Html.Attributes exposing (href, style)\nimport Hop."
  },
  {
    "path": "examples/full/src/Main.elm",
    "chars": 1188,
    "preview": "module Main exposing (..)\n\nimport Navigation\nimport Hop\nimport Hop.Types exposing (Address)\nimport Messages exposing (.."
  },
  {
    "path": "examples/full/src/Messages.elm",
    "chars": 216,
    "preview": "module Messages exposing (..)\n\nimport Hop.Types exposing (Query)\nimport Languages.Messages\n\n\ntype Msg\n    = SetQuery Que"
  },
  {
    "path": "examples/full/src/Models.elm",
    "chars": 579,
    "preview": "module Models exposing (..)\n\nimport Hop.Types exposing (Address, newAddress)\nimport Languages.Models exposing (Language,"
  },
  {
    "path": "examples/full/src/Routing.elm",
    "chars": 868,
    "preview": "module Routing exposing (..)\n\nimport Models exposing (..)\nimport Hop.Types exposing (Config)\nimport Languages.Routing\nim"
  },
  {
    "path": "examples/full/src/Update.elm",
    "chars": 1828,
    "preview": "module Update exposing (..)\n\nimport Debug\nimport Navigation\nimport Hop exposing (output, outputFromPath, setQuery)\nimpor"
  },
  {
    "path": "examples/full/src/View.elm",
    "chars": 1969,
    "preview": "module View exposing (..)\n\nimport Html exposing (..)\nimport Html.App\nimport Html.Events exposing (onClick)\nimport Html.A"
  },
  {
    "path": "examples/full/src/index.js",
    "chars": 168,
    "preview": "'use strict';\n\nrequire('ace-css/css/ace.css');\n\nvar Elm = require('./Main.elm');\nvar mountNode = document.getElementById"
  },
  {
    "path": "examples/full/webpack.config.js",
    "chars": 1479,
    "preview": "var path = require(\"path\");\n\n/*\npublicPath is used for finding the bundles during dev\ne.g. http://localhost:3000/bundles"
  },
  {
    "path": "license.md",
    "chars": 1091,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015-present, Sebastian Porto\n\nPermission is hereby granted, free of charge, to any"
  },
  {
    "path": "package.json",
    "chars": 210,
    "preview": "{\n  \"name\": \"Hop\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"\",\n  \"scripts\": {\n    \"test\": \"elm-test\"\n  },\n "
  },
  {
    "path": "readme.md",
    "chars": 3195,
    "preview": "# Hop: Navigation and routing helpers for Elm SPAs\n\n[![Build Status](https://semaphoreci.com/api/v1/sporto/hop/branches/"
  },
  {
    "path": "src/Hop/Address.elm",
    "chars": 2499,
    "preview": "module Hop.Address exposing (..)\n\nimport Dict\nimport String\nimport Http exposing (uriEncode, uriDecode)\nimport Hop.Types"
  },
  {
    "path": "src/Hop/AddressTest.elm",
    "chars": 2350,
    "preview": "module Hop.AddressTest exposing (..)\n\nimport Dict\nimport Expect\nimport Hop.Address as Address\nimport Hop.Types as Types\n"
  },
  {
    "path": "src/Hop/In.elm",
    "chars": 1438,
    "preview": "module Hop.In exposing (..)\n\nimport Regex\nimport String\nimport Hop.Types exposing (Address, Config)\nimport Hop.Address e"
  },
  {
    "path": "src/Hop/InTest.elm",
    "chars": 2511,
    "preview": "module Hop.InTest exposing (..)\n\nimport Dict\nimport Expect\nimport Hop.In exposing (ingest)\nimport Test exposing (..)\n\nty"
  },
  {
    "path": "src/Hop/Out.elm",
    "chars": 1355,
    "preview": "module Hop.Out exposing (..)\n\nimport String\nimport Hop.Types exposing (Address, Config)\nimport Hop.Utils exposing (dedup"
  },
  {
    "path": "src/Hop/OutTest.elm",
    "chars": 4020,
    "preview": "module Hop.OutTest exposing (..)\n\nimport Dict\nimport Expect\nimport Hop.Out as Out\nimport Hop.Types exposing (newAddress)"
  },
  {
    "path": "src/Hop/TestHelper.elm",
    "chars": 356,
    "preview": "module Hop.TestHelper exposing (..)\n\nimport Hop.Types exposing (Config)\n\n\nconfigWithHash : Config\nconfigWithHash =\n    {"
  },
  {
    "path": "src/Hop/Types.elm",
    "chars": 941,
    "preview": "module Hop.Types exposing (Config, Query, Address, newQuery, newAddress)\n\n{-| Types used in Hop\n\n#Types\n@docs Config, Ad"
  },
  {
    "path": "src/Hop/Utils.elm",
    "chars": 148,
    "preview": "module Hop.Utils exposing (..)\n\nimport Regex\n\ndedupSlash : String -> String\ndedupSlash =\n    Regex.replace Regex.All (Re"
  },
  {
    "path": "src/Hop.elm",
    "chars": 6859,
    "preview": "module Hop\n    exposing\n        ( addQuery\n        , clearQuery\n        , ingest\n        , makeResolver\n        , output"
  },
  {
    "path": "src/HopTest.elm",
    "chars": 1226,
    "preview": "module HopTest exposing (..)\n\nimport Expect\nimport Test exposing (..)\nimport Hop.TestHelper exposing (configWithHash, co"
  },
  {
    "path": "tests/IntegrationTest.elm",
    "chars": 8178,
    "preview": "module IntegrationTest exposing (..)\n\nimport UrlParser exposing ((</>), oneOf, int, s)\nimport Navigation exposing (Locat"
  },
  {
    "path": "tests/Main.elm",
    "chars": 216,
    "preview": "port module Main exposing (..)\n\nimport Tests\nimport Test.Runner.Node exposing (run)\nimport Json.Encode exposing (Value)\n"
  },
  {
    "path": "tests/Tests.elm",
    "chars": 307,
    "preview": "module Tests exposing (..)\n\nimport HopTest\nimport Hop.AddressTest\nimport Hop.InTest\nimport Hop.OutTest\nimport Integratio"
  },
  {
    "path": "tests/elm-package.json",
    "chars": 625,
    "preview": "{\n    \"version\": \"0.0.1\",\n    \"summary\": \"A router for SPAs in Elm\",\n    \"repository\": \"https://github.com/sporto/hop.gi"
  },
  {
    "path": "tests/install-packages.sh",
    "chars": 101,
    "preview": "#!/bin/bash\n\nn=0\nuntil [ $n -ge 5 ]\ndo\n  elm package install -y && break\n  n=$[$n+1]\n  sleep 15\ndone\n"
  },
  {
    "path": "tests/package.json",
    "chars": 214,
    "preview": "{\n  \"name\": \"test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"elm.js\",\n  \"scripts\": {\n    \"test\": \"elm-test\""
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the sporto/hop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (77.8 KB), approximately 20.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!