rib
Rib is a Haskell library for writing your own static site generator.
How does it compare to Hakyll?
- Use the Shake build system
- Builtin support for using Haskell DSL to define the HTML
(Lucid) & CSS
(Clay) of your site
- Like Hakyll, Rib uses Pandoc for parsing the source
documents. It also supports MMark if you need a lightweight alternative.
- Remain as simple as possible to use (see example below)
- Optional Nix based workflow for easily reproducible environment
Rib prioritizes the use of existing tools over reinventing them, and enables
the user to compose them as they wish instead of having to write code to fit a
custom framework.
Table of Contents
Quick Preview
Here is how your code may look like if you were to generate your static site
using Rib:
-- First we shall define two datatypes to represent our pages. One, the page
-- itself. Second, the metadata associated with each document.
-- | A generated page is either an index of documents, or an individual document.
--
-- The `Document` type takes two type variables:
-- 1. The first type variable specifies the parser to use: MMark or Pandoc
-- 2. The second type variable should be your metadata record
data Page
= Page_Index [Document MMark DocMeta]
| Page_Doc (Document MMark DocMeta)
-- | Type representing the metadata in our Markdown documents
--
-- Note that if a field is not optional (i.e., not Maybe) it must be present.
data DocMeta
= DocMeta
{ title :: Text,
description :: Maybe Text
}
deriving (Show, Eq, Generic, FromJSON)
-- | Main entry point to our generator.
--
-- `Rib.run` handles CLI arguments, and takes three parameters here.
--
-- 1. Directory `a`, from which static files will be read.
-- 2. Directory `b`, under which target files will be generated.
-- 3. Shake build action to run.
--
-- In the shake build action you would expect to use the utility functions
-- provided by Rib to do the actual generation of your static site.
main :: IO ()
main = Rib.run [reldir|a|] [reldir|b|] generateSite
where
-- Shake Action for generating the static site
generateSite :: Action ()
generateSite = do
-- Copy over the static files
Rib.buildStaticFiles [[relfile|static/**|]]
-- Build individual markdown files, generating .html for each.
docs <-
Rib.buildHtmlMulti [relfile|*.md|] $
renderPage . Page_Doc
-- Build an index.html linking to the aforementioned files.
Rib.buildHtml [relfile|index.html|]
$ renderPage
$ Page_Index docs
-- Define your site HTML here
renderPage :: Page -> Html ()
renderPage page = with html_ [lang_ "en"] $ do
head_ $ do
meta_ [httpEquiv_ "Content-Type", content_ "text/html; charset=utf-8"]
title_ $ case page of
Page_Index _ -> "My website!"
Page_Doc doc -> toHtml $ title $ Rib.documentMeta doc
style_ [type_ "text/css"] $ Clay.render pageStyle
body_
$ with div_ [id_ "thesite"]
$ do
with a_ [href_ "/"] "Back to Home"
hr_ []
case page of
Page_Index docs ->
div_ $ forM_ docs $ \doc -> with li_ [class_ "links"] $ do
let meta = Rib.documentMeta doc
b_ $ with a_ [href_ (Rib.documentUrl doc)] $ toHtml $ title meta
maybe mempty Rib.renderMarkdown $
description meta
Page_Doc doc ->
with article_ [class_ "post"] $ do
h1_ $ toHtml $ title $ Rib.documentMeta doc
Rib.documentHtml doc
-- Define your site CSS here
pageStyle :: Css
pageStyle = "div#thesite" ? do
margin (em 4) (pc 20) (em 1) (pc 20)
"li.links" ? do
listStyleType none
marginTop $ em 1
"b" ? fontSize (em 1.2)
"p" ? sym margin (px 0)
(View full Main.hs
at rib-sample)
Getting Started
The easiest way to get started with Rib is to use the
template
repository, rib-sample, from Github.
Concepts
Directory structure
Let's look at what's in the template repository:
$ git clone https://github.com/srid/rib-sample.git mysite
...
$ cd mysite
$ ls -F
a/ default.nix Main.hs README.md rib-sample.cabal
The three key items here are:
Main.hs
: Haskell source containing the DSL of the HTML/CSS of your site.
a/
: The source content (eg: Markdown sources and static files)
b/
: The target directory, excluded from the git repository, will contain
generated content (i.e., the HTML files, and copied over static content)
The template repository comes with a few sample posts under a/
, and a basic
HTML layout and CSS style defined in Main.hs
.
Run the site
Now let's run them all.
Clone the sample repository locally, install Nix and
run your site as follows:
nix-shell --run 'ghcid -T main'
(Note even though the author recommends it Nix is strictly not required; you may
simply run ghcid -T main
instead of the above command if you do not wish to
use Nix.)
Running this command gives you a local HTTP server at http://localhost:8080/
(serving the generated files) that automatically reloads when either the content
(a/
) or the HTML/CSS/build-actions (Main.hs
) changes. Hot reload, in other
words.
How Rib works
How does the aforementioned nix-shell command work?
-
nix-shell
will run the given command in a shell environment with all of our
dependencies (notably the Haskell ones including the rib
library itself)
installed.
-
ghcid
will compile your Main.hs
and run its main
function.
-
Main.hs:main
in turn calls Rib.App.run
which takes as argument your custom Shake action that will build the static site.
-
Rib.App.run
: this parses the CLI arguments and runs the rib CLI "app" which
can be run in one of a few modes --- generating static files, watching the
a/
directory for changes, starting HTTP server for the b/
directory. By
default---without any explicit arguments---this will run the Shake build
action passed as argument on every file change and spin up a HTTP server.
Run that command, and visit http://localhost:8080 to view your site.
Editing workflow
Now try making some changes to the content, say a/first-post.md
. You should
see it reflected when you refresh the page. Or change the HTML or CSS of your
site in Main.hs
; this will trigger ghcid
to rebuild the Haskell source and
restart the server.
What's next?
Great, by now you should have your static site generator ready and running! What
more can you do? Surely you may have specific needs; and this usually translates
to running custom Shake actions during the build. Rib provides helper functions in Rib.Shake
to make this easier.
Rib recommends writing your Shake actions in the style of being
forward-defined
which adds to the simplicity of the entire thing.
Examples