slick-0.1.0.0: README.md
# Slick
Slick is a static site generator written and configured using Haskell. It's the spiritual successor to my previous
static-site generator project [SitePipe](https://github.com/chrispenner/SitePipe/); but is faster, simpler, and more
easily used in combination with other tools.
Slick provides a small set of tools and combinators for building static
websites on top of the [Shake](https://shakebuild.com/) build system. Shake is
adaptable, fast, reliable, and caches aggressively so it's a sensible tool for
static-site builds, but figuring out how to get started can be a bit abstract. Slick aims to answer the question of
'how do I get a site building?' while giving you the necessary tools and examples to figure out how to accomplish your
goals.
See the [hackage docs](https://hackage.haskell.org/package/slick) for in depth help on available combinators.
Also check out the [example site](https://github.com/ChrisPenner/Slick/blob/master/example-site/app/Main.hs)!
# Overview
Here's a quick overview of what Slick can do:
- Slick provides helpers for loading in blog-post-like things using Pandoc
under the hood;
- This means that if Pandoc can read it, you can use it with Slick!
- Write your blog posts in Markdown or LaTeX and render it to
syntax-highlighted HTML!
- Slick processes Pandoc (and LaTeX) metadata into a usable form (as an
Aeson Value object) which you can manipulate as you please.
- Slick provides combinators for rendering [Mustache templates](https://mustache.github.io/)
- Slick wraps Justus Adam's [Mustache](http://hackage.haskell.org/package/mustache-2.3.0/docs/Text-Mustache.html)
library and provides cached template rendering with awareness of changes to templates, partials, and Mustache
objects.
- It's a thin wrapper so you can still use things like Mustache functions, etc. if you like!
- Provides only the individual tools without opinions about how to wire them up; if you want to load blog posts from
a database and render them out using Blaze html; well go ahead, we can help with that!
- Provides caching of arbitrary (JSON serializable) objects using Shake resulting in super-fast rebuild times!
# Example Site:
Here's an example of using slick to render out the posts for a pretty simple blog;
```haskell
module Main where
import qualified Data.Text as T
import Development.Shake
import Development.Shake.FilePath
import Data.Foldable
import Slick
-- convert a source filepath to a build filepath
-- e.g. site/css/style.css -> build/css/style.css
srcToBuild :: FilePath -> FilePath
srcToBuild path = "build" </> dropDirectory1 path
main' :: IO ()
main' =
shakeArgs shakeOptions $ do
-- Require all the things we need to build the site
-- For this simplified example we'll just copy static assets and build a page for each post
"site" ~> need ["static", "posts"]
-- Require all static assets
"static" ~> do
staticFiles <- getDirectoryFiles "." ["site/css//*", "site/js//*", "site/images//*"]
let copyStaticFile path = copyFileChanged path (srcToBuild path)
traverse_ copyStaticFile staticFiles
-- Find and require every post to be built
-- this uses the `~>` 'phony' rule because it doesn't actually write any files on its own
"posts" ~> do
postPaths <- getDirectoryFiles "site/posts" ["*.md"]
-- We tell shake we need to build each individual post
-- We require each post separately so that Shake can cache them individually
need (((-<.> "html") . srcToBuild) <$> postPaths)
-- rule for actually building posts
"build/posts//*.html" %> \out -> do
-- Recover the path where the source file for the post should be
let srcPath = (dropDirectory1 out) -<.> "md"
fileContents <- readFile' srcPath
-- Load a markdown source file into an Aeson Value
-- The 'content' key contains an html-rendered string
-- Any metadata from a yaml block is loaded into the appropriate keys in the Aeson object
-- e.g. author, date, tags, etc.
postData <- markdownToHTML . T.pack $ fileContents
-- Load a mustache template using using cache if available
template <- compileTemplate' "site/templates/post.html"
-- Fill in the template using the post metadata/content
writeFile' out . T.unpack $ substitute template postData
```
Not pictured above is:
- Deserializing post metadata into an object which implements `FromJSON`
- Using custom Pandoc readers to load other document types
- Using `jsonCache`s to cache intermediate JSON results to improve build times and simplify logic.