1
1
mirror of https://github.com/srid/rib.git synced 2024-11-27 01:12:09 +03:00
Haskell static site generator based on Shake (superseded by Ema)
Go to file
2020-02-02 12:00:25 -05:00
.github/workflows Add macOS to CI matrix (#68) 2019-12-31 10:53:06 -05:00
assets Update README example 2019-11-23 19:09:31 -05:00
src Replace concurrently_ with forkIO 2020-01-29 14:12:34 -05:00
.gitignore Vastly simplify configuration (#19) 2019-07-14 11:17:48 -04:00
CHANGELOG.md Update nixpkgs; and shake to 0.18.5 2020-02-02 12:00:25 -05:00
CONTRIBUTING.md Handle parsing errors in Pandoc metadata 2019-11-26 21:58:43 -05:00
default.nix Update nixpkgs; and shake to 0.18.5 2020-02-02 12:00:25 -05:00
DEVELOPMENT.md Add DEVELOPMENT.md 2020-01-08 15:20:26 -05:00
LICENSE Create LICENSE 2019-11-23 13:50:52 -05:00
README.md Ability to customize the output filepath (#94) 2020-01-22 10:58:24 -05:00
rib.cabal Update nixpkgs; and shake to 0.18.5 2020-02-02 12:00:25 -05:00

Logo

rib

BSD3 Hackage built with nix Zulip chat

Rib is a Haskell library for writing your own static site generator.

How does it compare to Hakyll?

  • Uses the Shake build system at its core.
  • Allows writing Haskell DSL to define HTML (Lucid) & CSS (Clay)
  • Built-in support for Pandoc and MMark, while also supporting custom parsers (eg: Dhall, TOML)
  • Remain as simple as possible to use (see example below)
  • Nix-based environment for reproducibility
  • ghcid and fsnotify for "hot reload"

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:

-- | This will be our type representing generated pages.
--
-- Each `Source` specifies the parser type to use. Rib provides `MMark` and
-- `Pandoc`; but you may define your own as well.
data Page
  = Page_Index [Source MMark]
  | Page_Single (Source MMark)

-- | 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 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

-- | Shake action for generating the static site
generateSite :: Action ()
generateSite = do
  -- Copy over the static files
  Rib.buildStaticFiles [[relfile|static/**|]]
  -- Build individual sources, generating .html for each.
  -- The function `buildHtmlMulti` takes the following arguments:
  -- - Function that will parse the file (here we use mmark)
  -- - File patterns to build
  -- - Function that will generate the HTML (see below)
  srcs <-
    Rib.forEvery [[relfile|*.md|]] $ \srcPath ->
      Rib.buildHtml srcPath MMark.parse $ renderPage . Page_Single
  -- Write an index.html linking to the aforementioned files.
  Rib.writeHtml [relfile|index.html|] $
    renderPage (Page_Index srcs)

-- | 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_Single src -> toHtml $ title $ getMeta src
    style_ [type_ "text/css"] $ C.render pageStyle
  body_ $ do
    with div_ [id_ "thesite"] $ do
      with div_ [class_ "header"] $
        with a_ [href_ "/"] "Back to Home"
      case page of
        Page_Index srcs -> div_ $ forM_ srcs $ \src ->
          with li_ [class_ "pages"] $ do
            let meta = getMeta src
            b_ $ with a_ [href_ (Rib.sourceUrl src)] $ toHtml $ title meta
            maybe mempty renderMarkdown $ description meta
        Page_Single src ->
          with article_ [class_ "post"] $ do
            h1_ $ toHtml $ title $ getMeta src
            MMark.render $ Rib.sourceVal src
  where
    renderMarkdown =
      MMark.render . either (error . T.unpack) id . MMark.parsePure "<none>"

-- | Define your site CSS here
pageStyle :: Css
pageStyle = "div#thesite" ? do
  C.margin (em 4) (pc 20) (em 1) (pc 20)
  ".header" ? do
    C.marginBottom $ em 2
  "li.pages" ? do
    C.listStyleType C.none
    C.marginTop $ em 1
    "b" ? C.fontSize (em 1.2)
    "p" ? sym C.margin (px 0)

-- | Metadata in our markdown sources
data SrcMeta
  = SrcMeta
      { title :: Text,
        -- | Description is optional, hence `Maybe`
        description :: Maybe Text
      }
  deriving (Show, Eq, Generic, FromJSON)

-- | Get metadata from Markdown's YAML block
getMeta :: Source MMark -> SrcMeta
getMeta src = case MMark.projectYaml (Rib.sourceVal src) of
  Nothing -> error "No YAML metadata"
  Just val -> case fromJSON val of
    Aeson.Error e -> error $ "JSON error: " <> e
    Aeson.Success v -> v

(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:

  1. Main.hs: Haskell source containing the DSL of the HTML/CSS of your site.
  2. a/: The source content (eg: Markdown sources and static files)
  3. 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?

  1. 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.

  2. ghcid will compile your Main.hs and run its main function.

  3. Main.hs:main in turn calls Rib.App.run which takes as argument your custom Shake action that will build the static site.

  4. 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