A typed router in Elm, with a nice DSL built on top of parser cominators (see README for usage).
Extract an Int param
Extract a String param
Build a custom param extractor from a parser instance
Matcher for a static path.
type Route = About
matchers = [ static About "/about" ]
match matchers "/about" == Just About
Matcher for a path with one dynamic param.
type Route = Topic Int
matchers = [ dyn1 Topic "/topic/" int "/edit" ]
match matchers "/topic/1/edit" == Just (Topic 1)
Matcher for a path with two dynamic params.
type Route = SubTopic Int Int
matchers = [ dyn2 SubTopic "/topic/" int "/" int "" ]
match matchers "/topic/1/2" == Just (SubTopic 1 2)
Matcher for a path with three dynamic params.
type Route = Something String String String
matchers = [ dyn3 Something "/some/" string "/thing/" string "/here/" string "" ]
match matchers "/some/cool/thing/must-be/here/i-guess" == Just (Something "cool" "must-be" "i-guess")
Build a route from a Parser instance
Build a route from a raw matcher function
Map the result of the match
map a list of matchers from a route type to another route type. Useful for subrouting, like delegating one of the routes to another type :
-- global routing:
type Route = Home | Admin AdminRoute
matchers =
[ static Home "/" ] ++ (mapMatchers Admin adminMatchers)
-- can be delegated to a component without knowdedge of global routing:
type AdminRoute = Dashboard | Users
adminMatchers =
[ static Dashboard "/admin", static Users "/users" ]
Given a list of matchers and a path, return the first successful match of the path.
Full-featured router. A record with two properties:
fromPath
to maybe get the route from a path,toPath
to build the path from the route, typically for links in the views.A single route parser
A param parser in a route
A router is composed of a route parser, and a path generator.
module RouteParser exposing (int, string, customParam, static, dyn1, dyn2, dyn3, parserMatcher, rawMatcher, match, router, mapMatcher, mapMatchers, Matcher, Param, Router)
{-| A typed router in Elm, with a nice DSL built on top of parser cominators
(see [README](https://github.com/etaque/elm-route-parser) for usage).
# DSL for simple cases
@docs int, string, customParam, static, dyn1, dyn2, dyn3
# Other route matcher builders
@docs parserMatcher, rawMatcher, mapMatcher, mapMatchers
# Because eventually you'll have to run the router
@docs match, router
# Types
@docs Matcher, Param, Router
-}
import Combine exposing (Parser, parse, end, andThen, map, andMap, many1, while, many, skip, maybe, (<$>), (<$), (<*), (*>), (<*>), (<|>))
import Combine.Num as Num
import Maybe
import List
import RouteParser.Parser as Parser exposing (..)
{-| A single route parser
-}
type Matcher route
= M (String -> Maybe route)
{-| A param parser in a route
-}
type Param a
= P (Parser () a)
{-| A router is composed of a route parser, and a path generator.
-}
type alias Router route =
{ fromPath : String -> Maybe route
, toPath : route -> String
}
{-| Extract an Int param
-}
int : Param Int
int =
P Num.int
{-| Extract a String param
-}
string : Param String
string =
P stringParam
{-| Build a custom param extractor from a parser instance
-}
customParam : Parser () a -> Param a
customParam =
P
{-| Build a route from a raw matcher function
-}
rawMatcher : (String -> Maybe route) -> Matcher route
rawMatcher matcher =
M matcher
{-| Build a route from a Parser instance
-}
parserMatcher : Parser () route -> Matcher route
parserMatcher parser =
let
matcher path =
case parse parser path of
Ok ( _, _, route ) ->
Just route
_ ->
Nothing
in
rawMatcher matcher
staticParser : String -> route -> Parser () route
staticParser path route =
route <$ (Combine.string path *> end)
dynParser : String -> Parser () a -> (a -> route) -> Parser () route
dynParser path aParser toRoute =
map toRoute (Combine.string path *> aParser)
{-| Matcher for a static path.
type Route = About
matchers = [ static About "/about" ]
match matchers "/about" == Just About
-}
static : route -> String -> Matcher route
static route path =
staticParser path route
|> parserMatcher
{-| Matcher for a path with one dynamic param.
type Route = Topic Int
matchers = [ dyn1 Topic "/topic/" int "/edit" ]
match matchers "/topic/1/edit" == Just (Topic 1)
-}
dyn1 : (a -> route) -> String -> Param a -> String -> Matcher route
dyn1 toRoute s1 (P aParser) s2 =
dynParser s1 aParser toRoute
|> andThen (staticParser s2)
|> parserMatcher
{-| Matcher for a path with two dynamic params.
type Route = SubTopic Int Int
matchers = [ dyn2 SubTopic "/topic/" int "/" int "" ]
match matchers "/topic/1/2" == Just (SubTopic 1 2)
-}
dyn2 : (a -> b -> route) -> String -> Param a -> String -> Param b -> String -> Matcher route
dyn2 toRoute s1 (P aParser) s2 (P bParser) s3 =
dynParser s1 aParser toRoute
|> andThen (dynParser s2 bParser)
|> andThen (staticParser s3)
|> parserMatcher
{-| Matcher for a path with three dynamic params.
type Route = Something String String String
matchers = [ dyn3 Something "/some/" string "/thing/" string "/here/" string "" ]
match matchers "/some/cool/thing/must-be/here/i-guess" == Just (Something "cool" "must-be" "i-guess")
-}
dyn3 : (a -> b -> c -> route) -> String -> Param a -> String -> Param b -> String -> Param c -> String -> Matcher route
dyn3 toRoute s1 (P aParser) s2 (P bParser) s3 (P cParser) s4 =
dynParser s1 aParser toRoute
|> andThen (dynParser s2 bParser)
|> andThen (dynParser s3 cParser)
|> andThen (staticParser s4)
|> parserMatcher
{-| Map the result of the match
-}
mapMatcher : (a -> b) -> Matcher a -> Matcher b
mapMatcher mapper (M matcher) =
let
newMatcher path =
Maybe.map mapper (matcher path)
in
M newMatcher
{-| map a list of matchers from a route type to another route type.
Useful for subrouting, like delegating one of the routes to another type :
-- global routing:
type Route = Home | Admin AdminRoute
matchers =
[ static Home "/" ] ++ (mapMatchers Admin adminMatchers)
-- can be delegated to a component without knowdedge of global routing:
type AdminRoute = Dashboard | Users
adminMatchers =
[ static Dashboard "/admin", static Users "/users" ]
-}
mapMatchers : (a -> b) -> List (Matcher a) -> List (Matcher b)
mapMatchers wrapper matchers =
List.map (mapMatcher wrapper) matchers
{-| Given a list of matchers and a path, return the first successful match of the path.
-}
match : List (Matcher route) -> String -> Maybe route
match parsers url =
List.foldl (matchUrl url) Nothing parsers
matchUrl : String -> Matcher route -> Maybe route -> Maybe route
matchUrl path (M matcher) maybeRoute =
case maybeRoute of
Just _ ->
maybeRoute
Nothing ->
matcher path
{-| Full-featured router. A record with two properties:
* `fromPath` to maybe get the route from a path,
* `toPath`to build the path from the route, typically for links in the views.
-}
router : List (Matcher route) -> (route -> String) -> Router route
router routeParsers pathGenerator =
Router (match routeParsers) pathGenerator