• Stars
    star
    204
  • Rank 192,063 (Top 4 %)
  • Language
    Haskell
  • License
    BSD 3-Clause "New...
  • Created over 6 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Static site generator built on Shake configured in Haskell

Slick

Want to get started quickly? Check out the Slick site template!

Slick is a static site generator written and configured using Haskell. It's the spiritual successor to my previous static-site generator project 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 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 for in depth help on available combinators.

If you would rather see live examples than documentation, you can check out:

Overview

Here's a quick overview of what Slick can do:

  • Slick uses the Shake build tool; the same used by ghcide! We recommend using Development.Shake.Forward; it auto-discovers which resources it should cache as you go! This means a blazing fast static site builder without all the annoying dependency tracking.
  • 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
    • Slick wraps Justus Adam's Mustache 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!

Another static site generator? What about Hakyll/Jekyll?

Yup, yet another static site generator. I've tried using Hakyll and Jekyll on different occasions and found there was too much magic going on with all of the monadic contexts for me to understand how to customize things for my use-cases. Even adding simple tags/categories to my blog seemed far more complex then it needed to be; Hakyll specifically got me really bogged down; what was the Compiler monad? How does an Item work? How do I add a custom field? Why couldn't I just edit data directly like I'm used to doing in Haskell? They seemed a bit too opinionated without giving me escape hatches to wire in my own functionality. If they're working for you, then great! But they weren't working for me, so that's where SitePipe and subsequently Slick came from.

Quick Start

Want to get started quickly? Check out the Slick site template and follow the steps there.

Example Site:

Here's an example of using slick to build an ENTIRE blog with full automatic asset caching.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import           Control.Lens
import           Control.Monad
import           Data.Aeson                 as A
import           Data.Aeson.Lens
import           Development.Shake
import           Development.Shake.Classes
import           Development.Shake.Forward
import           Development.Shake.FilePath
import           GHC.Generics               (Generic)
import           Slick
import qualified Data.Text                  as T

outputFolder :: FilePath
outputFolder = "docs/"

-- | Data for the index page
data IndexInfo =
  IndexInfo
    { posts :: [Post]
    } deriving (Generic, Show, FromJSON, ToJSON)

-- | Data for a blog post
data Post =
    Post { title   :: String
         , author  :: String
         , content :: String
         , url     :: String
         , date    :: String
         , image   :: Maybe String
         }
    deriving (Generic, Eq, Ord, Show, FromJSON, ToJSON, Binary)

-- | given a list of posts this will build a table of contents
buildIndex :: [Post] -> Action ()
buildIndex posts' = do
  indexT <- compileTemplate' "site/templates/index.html"
  let indexInfo = IndexInfo {posts = posts'}
      indexHTML = T.unpack $ substitute indexT (toJSON indexInfo)
  writeFile' (outputFolder </> "index.html") indexHTML

-- | Find and build all posts
buildPosts :: Action [Post]
buildPosts = do
  pPaths <- getDirectoryFiles "." ["site/posts//*.md"]
  forP pPaths buildPost

-- | Load a post, process metadata, write it to output, then return the post object
-- Detects changes to either post content or template
buildPost :: FilePath -> Action Post
buildPost srcPath = cacheAction ("build" :: T.Text, srcPath) $ do
  liftIO . putStrLn $ "Rebuilding post: " <> srcPath
  postContent <- readFile' srcPath
  -- load post content and metadata as JSON blob
  postData <- markdownToHTML . T.pack $ postContent
  let postUrl = T.pack . dropDirectory1 $ srcPath -<.> "html"
      withPostUrl = _Object . at "url" ?~ String postUrl
  -- Add additional metadata we've been able to compute
  let fullPostData = withPostUrl $ postData
  template <- compileTemplate' "site/templates/post.html"
  writeFile' (outputFolder </> T.unpack postUrl) . T.unpack $ substitute template fullPostData
  -- Convert the metadata into a Post object
  convert fullPostData

-- | Copy all static files from the listed folders to their destination
copyStaticFiles :: Action ()
copyStaticFiles = do
    filepaths <- getDirectoryFiles "./site/" ["images//*", "css//*", "js//*"]
    void $ forP filepaths $ \filepath ->
        copyFileChanged ("site" </> filepath) (outputFolder </> filepath)

-- | Specific build rules for the Shake system
--   defines workflow to build the website
buildRules :: Action ()
buildRules = do
  allPosts <- buildPosts
  buildIndex allPosts
  copyStaticFiles

-- | Kick it all off
main :: IO ()
main = do
  let shOpts =
        forwardOptions $ shakeOptions
          { shakeVerbosity = Chatty
          , shakeLintInside = ["./site/"]
          }
  shakeArgsForward shOpts buildRules

Not pictured above is:

  • Using custom Pandoc readers to load other document types, there are many helpers for this in the slick library
  • Using custom build tools like sassy css or js minifiers; you can do these things using Shake directly.

Caching guide

Shake takes care of most of the tricky parts, but there're still a few things you need to know.

Cache-busting in Slick works using Development.Shake.Forward. The idea is that you can wrap actions with cacheAction, providing an unique identifier for each time it runs. Shake will track any dependencies which are triggered during the first run of that action and can use them to detect when that particular action must be re-run. Typically you'll want to cache an action for each "thing" you have to load, e.g. when you load a post, or when you build a page. You can also nest these caches if you like.

When using cacheAction Shake will automatically serialize and store the results of that action to disk so that on a later build it can simply 'hydrate' that asset without running the command. For this reason, your data models should probably implement Binary. Here's an example data model:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}

import Data.Aeson (ToJSON, FromJSON)
import Development.Shake.Classes (Binary)
import GHC.Generics (Generic)

-- | Data for a blog post
data Post =
    Post { title   :: String
         , author  :: String
         , content :: String
         , url     :: String
         , date    :: String
         , image   :: Maybe String
         }
    deriving (Generic, Eq, Ord, Show, FromJSON, ToJSON, Binary)

If you need to run arbitrary shell commands you can use cache; it will do its best to track file use during the run of the command and cache-bust on that; results may vary. It's likely better to use explicit tracking commands like readFile' when possible, (or even just use readFile' on the files you depend on, then throw away the results. It's equivalent to explicitly depending on the file contents).

Shake has many dependency tracking combinators available; whenever possible you should use the shake variants of these (e.g. copyFileChanged, readFile', writeFile', etc.). This will allow shake to detect when and what it needs to rebuild.

Note: You'll likely need to delete .shake in your working directory after editing your Main.hs file as shake can get confused if rules change without it noticing.

More Repositories

1

rasa

Extremely modular text editor built in Haskell
Haskell
612
star
2

void-space

Well-Typed Typing Tutor where you Type Types... in space... yup, you heard me
Haskell
140
star
3

wc

Beating unix `wc` in Haskell
Haskell
136
star
4

lens-regex-pcre

Text lenses using PCRE regexes
Haskell
126
star
5

SitePipe

Yet another static site generator - non-opinionated, value-level. Less magic == easier to understand
Haskell
119
star
6

eve

An extensible event-driven application framework in haskell
Haskell
109
star
7

comonads-by-example

Comonads By Example Conference talk
Haskell
89
star
8

Firefly

Simple Haskell http framework
Haskell
87
star
9

json-to-haskell

In goes JSON, out comes a complete Haskell model complete with instances! CLI and web interface available.
Haskell
83
star
10

astar-monad

A smart A* search monad transformer which supports backtracking user-state!
Haskell
81
star
11

jet

A structural editor for JSON values
Haskell
78
star
12

Advent-Of-Code-Polyglot

Examples of "Advent Of Code" solutions in many programming languages.
Python
72
star
13

mad-props

Forward-propagating Constraint Solver monad. Good for solving Sudoku, N-Queens, etc.
Haskell
66
star
14

lens-csv

Lensy interface for parsing CSV's
Haskell
42
star
15

LumberJack

A terminal-ui log watcher written in Go using the Flux architecture
Go
38
star
16

conway

Conway's game of life in 100 lines or less!
Haskell
34
star
17

tempered

Templating engine based on shell interpolation
Haskell
31
star
18

unipatterns

Helpers which allow safe partial pattern matching in lambdas
Haskell
31
star
19

session-sauce

Shell plugin for managing tmux sessions
Shell
29
star
20

slick-template

A template for quickly building sites with slick
CSS
27
star
21

grids

Arbitrary dimension type-safe grids
Haskell
26
star
22

copy-pasta

Shell
26
star
23

dumbwaiter

Extensible HTTP Web server configured entirely by a yaml file
Haskell
25
star
24

haskell-stack-travis-ci

Dead simple setup tools for running a Haskell build matrix using stack for several versions.
Shell
23
star
25

lens-filesystem

Lens interface for your filesystem
Haskell
22
star
26

selections

Haskell Package for operating with selections over an underlying functor
Haskell
22
star
27

btt-quicknav

HTML overlay for quickly navigating your computer
JavaScript
19
star
28

lens-errors

Handling errors which occur deep inside lens-chains
Haskell
17
star
29

proton

Haskell Profunctor Optics experiments
Haskell
15
star
30

Type-Tac-Toe

Type-safe tic-tac-toe using Typesafe programming in Haskell
Haskell
15
star
31

wave-function-collapse

Wave function collapse procedural generation for arbitrary graphs
Haskell
15
star
32

catalyst

There are many category theory implementations, but this one is mine
Haskell
14
star
33

update-monad

An implementation of the Update Monad and a 'Free' version from https://danelahman.github.io/papers/types13postproc.pdf
Haskell
13
star
34

Candor

A toy Parser+Compiler+Typechecker
Haskell
12
star
35

recursive-zipper

Zippers for cofree types
Haskell
12
star
36

climbing-fp-ladder

A record of examples and anecdotes as I ascend the ladder of Functional Programming
12
star
37

charter

Haskell charting library
Haskell
10
star
38

trek

Haskell
10
star
39

vimprove

A series of daily tasks/info to learn vim from beginner to expert one day at a time.
Shell
9
star
40

react-tui

Haskell
9
star
41

dont-argue

Dead-simple command line arguments for python scripts.
Python
8
star
42

advent-of-code-haskell

Advent of Code Solutions in Haskell
Haskell
7
star
43

BoxKite

A very simple blog framework that emphasizes managing posts in a plain-text directory structure. Runs on Google App Engine, but can also be exported as a static site.
Python
7
star
44

flux-monoid

A monoid which counts changing values in a sequence
Haskell
6
star
45

ffs

A Fuse-compatible Functional File System with @isovector
Haskell
6
star
46

lens-friends

Just some lens combinator experiments :)
Haskell
5
star
47

jaunt

a jq clone in purescript
PureScript
5
star
48

json-to-haskell-web

Haskell
5
star
49

brick-filetree

A brick widget for exploring your filetree
Haskell
5
star
50

vim-committed

Sends Desktop notifications to remind you to commit.
Vim Script
4
star
51

CMPT481

Human Computer Interaction Project
JavaScript
4
star
52

rxjs-tutorial

Walkthrough of building a simple webapp using different rxjs patterns
TypeScript
4
star
53

jsonf

An educational JSON functor library for teaching recursion-schemes
Haskell
4
star
54

haskell-library-template

Template for Haskell libraries
Haskell
4
star
55

rust-advent-of-code

Rust
4
star
56

recursion-schemes-by-example

JavaScript
4
star
57

monad-suspend

Experimental Cost-Annotated Self-Yielding Coroutines
Haskell
4
star
58

game-genre-per-day

Weird and whimsical video game genres everyday!
TypeScript
4
star
59

reactive-streams

Reactive stream combinators in Haskell! Implementations of Rx primitives based on the 'machines' library
Haskell
4
star
60

professor

An experimental http server written entirely with profunctors
Haskell
4
star
61

Wirehack

A small circuit-building game built in Haskell
Haskell
3
star
62

ChrisPenner.github.io

Basic Website
HTML
3
star
63

json-to-haskell-purescript

Generate Haskell datatypes from json objects
Dhall
3
star
64

free-cached

Cache previous runs of free monads
Haskell
3
star
65

substrate

File substitution tools I need for my book
Haskell
3
star
66

cards-against-corona

Elm
3
star
67

chip8

Rust
3
star
68

type-arithmetic

Proofs of types as a semiring via Curry-Howard Isomorphism
Haskell
3
star
69

purescript-node-readline-aff

A wrapper around Node.ReadLine for use with the Aff Monad.
PureScript
3
star
70

Flow

An experimental Haskell FRP (streams) library
Haskell
3
star
71

j-lang-haskell

JLang combinators in Haskell
Haskell
3
star
72

purescript-flow

A redux-style application framework
PureScript
3
star
73

catalyst-build

Experimental build system based on composition of categories & arrows
Haskell
3
star
74

focus

cli utility for hacking and slashing data
Haskell
3
star
75

rx-prop

Propagator based reactive extensions library
Haskell
2
star
76

rsi

Structural regex based command pipelines
Haskell
2
star
77

propellant

Foray into propagator networks in Haskell
Haskell
2
star
78

concurrency-comparison

Comparison of basic concurrency primitives and tasks in Haskell and Golang
Haskell
2
star
79

mustache-shake

Build rules for compiling mustache templates using shake
Haskell
2
star
80

scavenger

A basic texting scavenger hunt using Twilio
Python
2
star
81

mailing-list-reader

Haskell
2
star
82

Kaleidoscope

Working through the Kaleidoscope llvm compiler project
Haskell
2
star
83

delve

Terminal UI File Browser
Haskell
2
star
84

free-contravariant

An exploration into free contravariant functors
Haskell
2
star
85

schemer

Uber basic scheme interpreter
Haskell
2
star
86

continuity

Composable Component Framework
Haskell
2
star
87

eve-cli

Terminal event handlers and rendering for `eve` programs
Haskell
2
star
88

sheets

Overly complex attempt at typesafe spreadsheets
Haskell
2
star
89

Simpleton-Algebraics

Learn about Functional Algebraic Types without making your head explode.
2
star
90

flags

Compiles a declarative bash script configuration into a 100% bash flags and argument parser.
Shell
2
star
91

dual-free

Library for combined free & cofree trees into a single type.
Haskell
2
star
92

cofree-zippers

Just an experiment, move along :)
Haskell
1
star
93

grids-images

Tools for interacting with images using grids
Haskell
1
star
94

overlord

Logs dashboard for all your local servers.
JavaScript
1
star
95

SPA-GAE-template

Single page application template for google app engine using react-redux
JavaScript
1
star
96

SpareSpeare

Shakespeare Filler Text Generator
Python
1
star
97

cmpt317

Python
1
star
98

test-specialization

Companion to a blog post on testing patterns in Haskell
Haskell
1
star
99

reified-dicts

Experiment to reify symbols/nats into constraints by matching them within a known set.
Haskell
1
star
100

unison-testing

Just a scrap unison codebase for testing
1
star