A hello-world site is not very interesting. Let’s write an application to track moods, i.e., a mood tracking app. We will record our moods in plain-text (a CSV file) and view them through an Ema app.
The first step is to think about the various pages and define their corresponding route types. Since our application will have an index page (displaying mood summary) and pages specific to the individual days, we will use an ADT with two constructors:
data Route = Route_Index -- /index.html | Route_Date Date -- /date/YYYY-MM-DD.html deriving stock (Show, Eq, Ord, Generic) deriveGeneric ''Route deriveIsRoute ''Route [t|'|]
We must derive
IsRoute to enrich our route type with three capabilities:
RouteModel: associate a value type (Model type) that will be used for encoding and decoding routes (see next point)
routePrism: produce Route Prism (a
Prism') that we can use to encode routes to URLs and vice versa.
routeUniverse: generate a list of routes to statically generate
Here, we use TemplateHaskell to derive
IsRoute generically, instead of hand-writing the instance. We can of course also derive
IsRoute manually. In fact, we must do it for the
Date sub-route type (because it is not an ADT, like
Route above, shaped for Generic deriving):
import Data.Time import Optics.Core (prism') -- | Isomorphic to `Data.Time.Calendar.Day` newtype Date = Date (Integer, Int, Int) deriving stock (Show, Eq, Ord, Generic) instance IsRoute Date where type RouteModel Date = () routePrism () = Ema.toPrism_ $ prism' ( \(Date (y, m, d)) -> formatTime defaultTimeLocale "%Y-%m-%d.html" $ fromGregorian y m d ) ( fmap (Date . toGregorian) . parseTimeM False defaultTimeLocale "%Y-%m-%d.html" ) routeUniverse () =  -- need model for this
We don’t need any special Model type to encode a
RouteModelis a unit. But we’ll modify this in next step (to implement
toPrism_is an Ema function that converts the optics-core
Prism'into a coercible
Prism_type that Ema internally uses. A route prism knows how to encode and decode the
Dayroute. Our route
Prism'is built using
We will implement
routeUniversein the next step of the tutorial
The result is that we can use the function
routeUrl to get the URL to our routes. Let’s see this in action in GHCi (run
bin/repl in the template repository):
ghci> -- First get hold of the route Prism, which is passed to `siteOutput` ghci> let rp = fromPrism_ $ routePrism @Route () ghci> Ema.routeUrl rp Route_Index "" -- The 'index.html' is dropped as it is redundant in HTML. ghci> Ema.routeUrl rp $ Route_Day $ Date (2022, 04, 23) "date/2022-04-23.html"
You also can use optics operators to directly operate on route prisms.
-- NOTE: Using `rp` from GHCi session above ghci> import Optics.Core ghci> review rp Route_Index "index.html" ghci> preview rp "2022-04-23.html" Nothing ghci> preview rp "date/2022-04-23.html" Just (Route_Date (Date (2022,4,23)))
See Route type for details.
Next, we will explain how to define our
moods.csv model and render it .