mmark-0.0.1.1: Strict markdown processor for writers

Text.MMark

Description

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:

https://github.com/jgm/CommonMark

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.

Synopsis

# Parsing

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.

Instances

 Source # Methodsrnf :: MMark -> () #

data MMarkErr Source #

MMark custom parse errors.

Constructors

 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

Instances

 Source # Methods Source # Methodsgfoldl :: (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 #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 # Source # Methods(<) :: MMarkErr -> MMarkErr -> Bool #(>) :: MMarkErr -> MMarkErr -> Bool # Source # Methods Source # MethodsshowList :: [MMarkErr] -> ShowS # Source # Associated Typestype Rep MMarkErr :: * -> * # Methodsto :: Rep MMarkErr x -> MMarkErr # Source # Methodsrnf :: MMarkErr -> () # Source # Methods type Rep MMarkErr Source # type Rep MMarkErr = D1 (MetaData "MMarkErr" "Text.MMark.Parser" "mmark-0.0.1.1-F14QMINEeCX3m2kXIGfOdd" 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)))))

Arguments

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

Arguments

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

# Extensions

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.

Instances

 Source # Methodsstimes :: Integral b => b -> Extension -> Extension # Source # Methodsmconcat :: [Extension] -> Extension #

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.

# Scanning

Arguments

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

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

# Rendering

render :: MMark -> Html () Source #

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

• to lazy Text with renderText
• to lazy ByteString with renderBS
• directly to file with renderToFile