mmark- Strict markdown processor for writers

Copyright© 2017 Mark Karpov
LicenseBSD 3 clause
MaintainerMark Karpov <>
Safe HaskellNone




MMark (read “em-mark”) is a strict markdown processor for writers. “Strict” means that not every input is considered a valid markdown document and parse errors are possible and even desirable, because they allow to spot markup issues without searching for them in rendered document. If a markdown document passes MMark parser, then it'll most certainly produce HTML without quirks. This feature makes it a good choice for writers and bloggers.

MMark and Common Mark

MMark mostly tries to follow the Common Mark specification as given here:

However, due to the fact that we do not allow inputs that do not make sense, and also try to guard against common silly mistakes (like writing ##My header and having it rendered as a paragraph starting with hashes) MMark obviously can't follow the specification precisely. In particular, parsing of inlines differs considerably from Common Mark.

Another difference between Common Mark and MMark is that the latter supports more (pun alert) common markdown extensions out-of-the-box. In particular, MMark supports:

  • parsing of an optional YAML block
  • strikeout using ~~this~~ syntax
  • superscript using ^this^ syntax
  • subscript using ~this~ syntax
  • automatic assignment of ids to headers
  • PHP-style footnotes, e.g. [^1] (NOT YET)
  • “pipe” tables (as used on GitHub) (NOT YET)

One do not need to enable or tweak anything for these to work, they are built-in features.

The readme contains a more detailed description of differences between Common Mark and MMark.

How to use the library

The module is intended to be imported qualified:

import Text.MMark (MMark)
import qualified Text.MMark as MMark

Working with MMark happens in three stages:

  1. Parsing of markdown document.
  2. Applying extensions, which optionally may require scanning of previously parsed document (for example to build a table of contents).
  3. Rendering of HTML document.

The structure of the documentation below corresponds to these stages and should clarify the details.

Other modules of interest

This module contains all the “core” functionality you may need. However, one of the main selling points of MMark is that it's possible to write your own extensions which stay highly composable (if done right), so proliferation of third-party extensions is to be expected and encouraged. To write an extension of your own import the Text.MMark.Extension module, which has some documentation focusing on extension writing.



data MMark Source #

Representation of complete markdown document. You can't look inside of MMark on purpose. The only way to influence an MMark document you obtain as a result of parsing is via the extension mechanism.


NFData MMark Source # 


rnf :: MMark -> () #

data MMarkErr Source #

MMark custom parse errors.


YamlParseError String

YAML error that occurred during parsing of a YAML block

NonFlankingDelimiterRun (NonEmpty Char)

This delimiter run should be in left- or right- flanking position


Eq MMarkErr Source # 
Data MMarkErr Source # 


gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> MMarkErr -> c MMarkErr #

gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c MMarkErr #

toConstr :: MMarkErr -> Constr #

dataTypeOf :: MMarkErr -> DataType #

dataCast1 :: Typeable (* -> *) t => (forall d. Data d => c (t d)) -> Maybe (c MMarkErr) #

dataCast2 :: Typeable (* -> * -> *) t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c MMarkErr) #

gmapT :: (forall b. Data b => b -> b) -> MMarkErr -> MMarkErr #

gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> MMarkErr -> r #

gmapQr :: (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> MMarkErr -> r #

gmapQ :: (forall d. Data d => d -> u) -> MMarkErr -> [u] #

gmapQi :: Int -> (forall d. Data d => d -> u) -> MMarkErr -> u #

gmapM :: Monad m => (forall d. Data d => d -> m d) -> MMarkErr -> m MMarkErr #

gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> MMarkErr -> m MMarkErr #

gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> MMarkErr -> m MMarkErr #

Ord MMarkErr Source # 
Read MMarkErr Source # 
Show MMarkErr Source # 
Generic MMarkErr Source # 

Associated Types

type Rep MMarkErr :: * -> * #


from :: MMarkErr -> Rep MMarkErr x #

to :: Rep MMarkErr x -> MMarkErr #

NFData MMarkErr Source # 


rnf :: MMarkErr -> () #

ShowErrorComponent MMarkErr Source # 
type Rep MMarkErr Source # 
type Rep MMarkErr = D1 (MetaData "MMarkErr" "Text.MMark.Parser" "mmark-" False) ((:+:) (C1 (MetaCons "YamlParseError" PrefixI False) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 String))) (C1 (MetaCons "NonFlankingDelimiterRun" PrefixI False) (S1 (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (NonEmpty Char)))))

parse Source #


:: String

File name (only to be used in error messages), may be empty

-> Text

Input to parse

-> Either (NonEmpty (ParseError Char MMarkErr)) MMark

Parse errors or parsed document

Parse a markdown document in the form of a strict Text value and either report parse errors or return a MMark document. Note that the parser has the ability to report multiple parse errors at once.

parseErrorsPretty Source #


:: Text

Original input for parser

-> NonEmpty (ParseError Char MMarkErr)

Collection of parse errors

-> String

Result of pretty-printing

Pretty-print a collection of parse errors returned from parse.

Pro tip: if you would like to pretty-print a single ParseError, use parseErrorPretty_ (mkPos 4), because Common Mark suggests that we should assume tab width 4, and that's what we do in the parser.


data Extension Source #

An extension. You can apply extensions with useExtension and useExtensions functions. The Text.MMark.Extension module provides tools for extension creation.

Note that Extension is an instance of Semigroup and Monoid, i.e. you can combine several extensions into one. Since the (<>) operator is right-associative and mconcat is a right fold under the hood, the expression

l <> r

means that the extension r will be applied before the extension l, similar to how Endo works. This may seem counter-intuitive, but only with this logic we get consistency of ordering with more complex expressions:

e2 <> e1 <> e0 == e2 <> (e1 <> e0)

Here, e0 will be applied first, then e1, then e2. The same applies to expressions involving mconcat—extensions closer to beginning of the list passed to mconcat will be applied later.

useExtension :: Extension -> MMark -> MMark Source #

Apply an Extension to an MMark document. The order in which you apply Extensions does matter. Extensions you apply first take effect first. The extension system is designed in such a way that in many cases the order doesn't matter, but sometimes the difference is important.

useExtensions :: [Extension] -> MMark -> MMark Source #

Apply several Extensions to an MMark document.

This is a simple shortcut:

useExtensions exts = useExtension (mconcat exts)

As mentioned in the docs for useExtension, the order in which you apply extensions matters. Extensions closer to beginning of the list are applied later, i.e. the last extension in the list is applied first.


runScanner Source #


:: MMark

Document to scan

-> Fold Bni a

Fold to use

-> a

Result of scanning

Scan an MMark document efficiently in one pass. This uses the excellent Fold type, which see.

Take a look at the Text.MMark.Extension module if you want to create scanners of your own.

projectYaml :: MMark -> Maybe Value Source #

Extract contents of optional YAML block that may have been parsed.


render :: MMark -> Html () Source #

Render a MMark markdown document. You can then render Html () to various things: