Literate Haskell support for Markdown
February 19, 2025 ยท View on GitHub
markdown-unlit is a custom unlit program. It can be used to extract
Haskell code from Markdown files.
To use it with GHC, add
ghc-options: -pgmL markdown-unlit
to your cabal file.
Extended example
tl;dr
markdown-unlitallows you to have aREADME.md, that at the same time is a literate Haskell program.
The following steps show you how to set things up, so that:
- the Haskell code in your
README.mdgets syntax highlighted on GitHub - you can run your literate Haskell within GHCi
- you can create a Cabal
test-suitefrom yourREADME.md(no broken code examples anymore, yay!)
The complete code of this example is provided in the example subdirectory.
1. Install markdown-unlit
$ cabal update && cabal install markdown-unlit
2. Create a README.md
# nifty-library: Do nifty things (effortlessly!)
Here is a basic example:
```haskell
main :: IO ()
main = putStrLn "That was easy!"
```
Code blocks with class haskell are syntax highlighted on GitHub (like
so).
3. Create a symbolic link README.lhs -> README.md
$ ln -s README.md README.lhs
4. Run your code
At this point we can load the code into GHCi:
$ ghci -pgmL markdown-unlit README.lhs
*Main> main
That was easy!
Or better yet, pipe the required flag into a .ghci file, and forget about it:
$ echo ':set -pgmL markdown-unlit' >> .ghci
$ ghci README.lhs
*Main> main
That was easy!
5. Create a Cabal test-suite
name: nifty-library
version: 0.0.0
build-type: Simple
cabal-version: >= 1.8
library
-- nothing here yet
test-suite readme
type: exitcode-stdio-1.0
main-is: README.lhs
build-depends: base, markdown-unlit
ghc-options: -pgmL markdown-unlit
build-tool-depends: markdown-unlit:markdown-unlit
Run it like so:
$ cabal configure --enable-tests && cabal build && cabal test
Reordering code blocks
Code blocks that are tagged with top are moved to the beginning of the source
file.
Example:
## Introduction
```haskell top
{-# LANGUAGE OverloadedStrings #-}
module MyModule where
```
## Working with textual data
```haskell top
import qualified Data.Text as Text
```
```haskell
-- |
-- >>> foo
-- 3
foo :: Int
foo = Text.length "foo"
```
## Working with binary data
```haskell top
import qualified Data.ByteString as ByteString
```
```haskell
-- |
-- >>> bar
-- 3
bar :: Int
bar = ByteString.length "foo"
```
$ doctest -pgmL markdown-unlit MyModule.lhs
Examples: 2 Tried: 2 Errors: 0 Failures: 0
More fine-grained control over the ordering
Optionally, top can be followed by :n where n is a non-negative integer.
Code blocks with a smaller n move above code blocks with a larger n.
top is an alias for top:0.
Customizing
By default, markdown-unlit extracts all code that is marked with haskell,
unless it is marked with ignore as well. You can customize this by passing
-optL <pattern> to GHC.
A simple pattern is just a class name, e.g.:
-optL foo
extracts all code that is marked with foo.
A class name can be negated by prepending it with a !, e.g.
-optL !foo
extracts all code, unless it is marked with foo.
You can use + to combine two patterns with AND, e.g.
-optL foo+bar
extracts all code that is marked with both foo and bar.
If -optL is given multiple times, the patterns are combined with OR, e.g.
-optL foo -optL bar
extracts all code that is either marked with foo or bar.
Development
Limitations
- indented code blocks are not yet supported
If you want to get any limitation lifted, open a ticket or send a pull request.
Contributing
Add tests for new code, and make sure that the test suite passes with your modifications.
cabal configure --enable-tests && cabal build && cabal test
Real world examples
- attoparsec-parsec
- hspec-expectations
- wai
- servant-tutorial
- servant-cookbook
- ClickHaskell-usage
- ClickHaskell-contribution
- ClickHaskell-testing
That's it, have fun!