@@ -16,7 +16,10 @@ Conversion of 'Pandoc' documents to markdown-formatted plain text.
1616
1717Markdown: <http://daringfireball.net/projects/markdown/>
1818-}
19−module Text.Pandoc.Writers.Markdown (writeMarkdown, writePlain) where
19+module Text.Pandoc.Writers.Markdown (
20+ writeMarkdown,
21+ writeCommonMark,
22+ writePlain) where
2023import Control.Monad.Reader
2124import Control.Monad.State.Strict
2225import Data.Char (isAlphaNum)
@@ -55,15 +58,21 @@ evalMD :: PandocMonad m => MD m a -> WriterEnv -> WriterState -> m a
5558evalMD md env st = evalStateT (runReaderT md env) st
5659
5760data WriterEnv = WriterEnv { envInList :: Bool
58− , envPlain :: Bool
61+ , envVariant :: MarkdownVariant
5962 , envRefShortcutable :: Bool
6063 , envBlockLevel :: Int
6164 , envEscapeSpaces :: Bool
6265 }
6366
67+data MarkdownVariant =
68+ PlainText
69+ | Commonmark
70+ | Markdown
71+ deriving (Show, Eq)
72+
6473instance Default WriterEnv
65− where def = WriterEnv { envInList = False
66− , envPlain = False
74+ where def = WriterEnv { envInList = False
75+ , envVariant = Markdown
6776 , envRefShortcutable = True
6877 , envBlockLevel = 0
6978 , envEscapeSpaces = False
@@ -102,7 +111,12 @@ writeMarkdown opts document =
102111-- pictures, or inline formatting).
103112writePlain :: PandocMonad m => WriterOptions -> Pandoc -> m Text
104113writePlain opts document =
105− evalMD (pandocToMarkdown opts document) def{ envPlain = True } def
114+ evalMD (pandocToMarkdown opts document) def{ envVariant = PlainText } def
115+
116+-- | Convert Pandoc to Commonmark.
117+writeCommonMark :: PandocMonad m => WriterOptions -> Pandoc -> m Text
118+writeCommonMark opts document =
119+ evalMD (pandocToMarkdown opts document) def{ envVariant = Commonmark } def
106120
107121pandocTitleBlock :: Doc Text -> [Doc Text] -> Doc Text -> Doc Text
108122pandocTitleBlock tit auths dat =
@@ -187,7 +201,7 @@ pandocToMarkdown opts (Pandoc meta blocks) = do
187201 let colwidth = if writerWrapText opts == WrapAuto
188202 then Just $ writerColumns opts
189203 else Nothing
190− isPlain <- asks envPlain
204+ variant <- asks envVariant
191205 metadata <- metaToContext'
192206 (blockListToMarkdown opts)
193207 (inlineListToMarkdown opts)
@@ -196,7 +210,7 @@ pandocToMarkdown opts (Pandoc meta blocks) = do
196210 let authors' = fromMaybe [] $ getField "author" metadata
197211 let date' = fromMaybe empty $ getField "date" metadata
198212 let titleblock = case writerTemplate opts of
199− Just _ | isPlain ->
213+ Just _ | variant == PlainText ->
200214 plainTitleBlock title' authors' date'
201215 | isEnabled Ext_yaml_metadata_block opts ->
202216 yamlMetadataBlock metadata
@@ -422,7 +436,7 @@ blockToMarkdown' opts (Div attrs ils) = do
422436 attrs' = (id',classes',("markdown","1"):kvs')
423437blockToMarkdown' opts (Plain inlines) = do
424438 -- escape if para starts with ordered list marker
425− isPlain <- asks envPlain
439+ variant <- asks envVariant
426440 let escapeMarker = T.concatMap $ \x -> if x `elemText` ".()"
427441 then T.pack ['\\', x]
428442 else T.singleton x
@@ -430,17 +444,15 @@ blockToMarkdown' opts (Plain inlines) = do
430444 startsWithSpace (SoftBreak:_) = True
431445 startsWithSpace _ = False
432446 let inlines' =
433− if isPlain
447+ if variant == PlainText
434448 then inlines
435449 else case inlines of
436450 (Str t:ys)
437− | not isPlain
438− , (null ys || startsWithSpace ys)
451+ | (null ys || startsWithSpace ys)
439452 , beginsWithOrderedListMarker t
440453 -> RawInline (Format "markdown") (escapeMarker t):ys
441454 (Str t:_)
442− | not isPlain
443− , t == "+" || t == "-" ||
455+ | t == "+" || t == "-" ||
444456 (t == "%" && isEnabled Ext_pandoc_title_block opts &&
445457 isEnabled Ext_all_symbols_escapable opts)
446458 -> RawInline (Format "markdown") "\\" : inlines
@@ -465,16 +477,16 @@ blockToMarkdown' opts (LineBlock lns) =
465477 return $ (vcat $ map (hang 2 (literal "| ")) mdLines) <> blankline
466478 else blockToMarkdown opts $ linesToPara lns
467479blockToMarkdown' opts b@(RawBlock f str) = do
468− plain <- asks envPlain
480+ variant <- asks envVariant
469481 let Format fmt = f
470482 let rawAttribBlock = return $
471483 (literal "```{=" <> literal fmt <> "}") $$
472484 literal str $$
473485 (literal "```" <> literal "\n")
474486 let renderEmpty = mempty <$ report (BlockNotRendered b)
475− case () of
476− _ | plain -> renderEmpty
477− | isEnabled Ext_raw_attribute opts -> rawAttribBlock
487+ case variant of
488+ PlainText -> renderEmpty
489+ _ | isEnabled Ext_raw_attribute opts -> rawAttribBlock
478490 | f `elem` ["markdown", "markdown_github", "markdown_phpextra",
479491 "markdown_mmd", "markdown_strict"] ->
480492 return $ literal str <> literal "\n"
@@ -503,7 +515,7 @@ blockToMarkdown' opts (Header level attr inlines) = do
503515 then notesAndRefs opts
504516 else return empty
505517
506− plain <- asks envPlain
518+ variant <- asks envVariant
507519 -- we calculate the id that would be used by auto_identifiers
508520 -- so we know whether to print an explicit identifier
509521 ids <- gets stIds
@@ -521,19 +533,20 @@ blockToMarkdown' opts (Header level attr inlines) = do
521533 contents <- inlineListToMarkdown opts $
522534 -- ensure no newlines; see #3736
523535 walk lineBreakToSpace $
524− if level == 1 && plain && isEnabled Ext_gutenberg opts
525− then capitalize inlines
526− else inlines
536+ if level == 1 && variant == PlainText &&
537+ isEnabled Ext_gutenberg opts
538+ then capitalize inlines
539+ else inlines
527540 let setext = writerSetextHeaders opts
528541 hdr = nowrap $ case level of
529− 1 | plain ->
542+ 1 | variant == PlainText ->
530543 if isEnabled Ext_gutenberg opts
531544 then blanklines 3 <> contents <> blanklines 2
532545 else contents <> blankline
533546 | setext ->
534547 contents <> attr' <> cr <> literal (T.replicate (offset contents) "=") <>
535548 blankline
536− 2 | plain ->
549+ 2 | variant == PlainText ->
537550 if isEnabled Ext_gutenberg opts
538551 then blanklines 2 <> contents <> blankline
539552 else contents <> blankline
@@ -541,7 +554,7 @@ blockToMarkdown' opts (Header level attr inlines) = do
541554 contents <> attr' <> cr <> literal (T.replicate (offset contents) "-") <>
542555 blankline
543556 -- ghc interprets '#' characters in column 1 as linenum specifiers.
544− _ | plain || isEnabled Ext_literate_haskell opts ->
557+ _ | variant == PlainText || isEnabled Ext_literate_haskell opts ->
545558 contents <> blankline
546559 _ -> literal (T.replicate level "#") <> space <> contents <> attr' <> blankline
547560
@@ -571,12 +584,12 @@ blockToMarkdown' opts (CodeBlock attribs str) = return $
571584 (_,(cls:_),_) -> " " <> literal cls
572585 _ -> empty
573586blockToMarkdown' opts (BlockQuote blocks) = do
574− plain <- asks envPlain
587+ variant <- asks envVariant
575588 -- if we're writing literate haskell, put a space before the bird tracks
576589 -- so they won't be interpreted as lhs...
577590 let leader = if isEnabled Ext_literate_haskell opts
578591 then " > "
579− else if plain then " " else "> "
592+ else if variant == PlainText then " " else "> "
580593 contents <- blockListToMarkdown opts blocks
581594 return $ (prefixed leader contents) <> blankline
582595blockToMarkdown' opts t@(Table _ blkCapt specs thead tbody tfoot) = do
@@ -809,8 +822,8 @@ definitionListItemToMarkdown opts (label, defs) = do
809822 if isEnabled Ext_definition_lists opts
810823 then do
811824 let tabStop = writerTabStop opts
812− isPlain <- asks envPlain
813− let leader = if isPlain then " " else ": "
825+ variant <- asks envVariant
826+ let leader = if variant == PlainText then " " else ": "
814827 let sps = case writerTabStop opts - 3 of
815828 n | n > 0 -> literal $ T.replicate n " "
816829 _ -> literal " "
@@ -839,7 +852,7 @@ blockListToMarkdown :: PandocMonad m
839852 -> MD m (Doc Text)
840853blockListToMarkdown opts blocks = do
841854 inlist <- asks envInList
842− isPlain <- asks envPlain
855+ variant <- asks envVariant
843856 -- a) insert comment between list and indented code block, or the
844857 -- code block will be treated as a list continuation paragraph
845858 -- b) change Plain to Para unless it's followed by a RawBlock
@@ -873,7 +886,7 @@ blockListToMarkdown opts blocks = do
873886 isListBlock (OrderedList _ _) = True
874887 isListBlock (DefinitionList _) = True
875888 isListBlock _ = False
876− commentSep = if isPlain
889+ commentSep = if variant == PlainText
877890 then Null
878891 else if isEnabled Ext_raw_html opts
879892 then RawBlock "html" "<!-- -->\n"
@@ -1025,11 +1038,11 @@ inlineToMarkdown opts (Span ("",["emoji"],kvs) [Str s]) =
10251038 return $ ":" <> literal emojiname <> ":"
10261039 _ -> inlineToMarkdown opts (Str s)
10271040inlineToMarkdown opts (Span attrs ils) = do
1028− plain <- asks envPlain
1041+ variant <- asks envVariant
10291042 contents <- inlineListToMarkdown opts ils
1030− return $ case plain of
1031− True -> contents
1032− False | attrs == nullAttr -> contents
1043+ return $ case variant of
1044+ PlainText -> contents
1045+ _ | attrs == nullAttr -> contents
10331046 | isEnabled Ext_bracketed_spans opts ->
10341047 let attrs' = if attrs /= nullAttr
10351048 then attrsToMarkdown attrs
@@ -1041,20 +1054,20 @@ inlineToMarkdown opts (Span attrs ils) = do
10411054 | otherwise -> contents
10421055inlineToMarkdown _ (Emph []) = return empty
10431056inlineToMarkdown opts (Emph lst) = do
1044− plain <- asks envPlain
1057+ variant <- asks envVariant
10451058 contents <- inlineListToMarkdown opts lst
1046− return $ if plain
1047− then if isEnabled Ext_gutenberg opts
1048− then "_" <> contents <> "_"
1049− else contents
1050− else "*" <> contents <> "*"
1059+ return $ case variant of
1060+ PlainText
1061+ | isEnabled Ext_gutenberg opts -> "_" <> contents <> "_"
1062+ | otherwise -> contents
1063+ _ -> "*" <> contents <> "*"
10511064inlineToMarkdown _ (Underline []) = return empty
10521065inlineToMarkdown opts (Underline lst) = do
1053− plain <- asks envPlain
1066+ variant <- asks envVariant
10541067 contents <- inlineListToMarkdown opts lst
1055− case plain of
1056− True -> return contents
1057− False | isEnabled Ext_bracketed_spans opts ->
1068+ case variant of
1069+ PlainText -> return contents
1070+ _ | isEnabled Ext_bracketed_spans opts ->
10581071 return $ "[" <> contents <> "]" <> "{.ul}"
10591072 | isEnabled Ext_native_spans opts ->
10601073 return $ tagWithAttrs "span" ("", ["underline"], [])
@@ -1065,13 +1078,14 @@ inlineToMarkdown opts (Underline lst) = do
10651078 | otherwise -> inlineToMarkdown opts (Emph lst)
10661079inlineToMarkdown _ (Strong []) = return empty
10671080inlineToMarkdown opts (Strong lst) = do
1068− plain <- asks envPlain
1069− if plain
1070− then inlineListToMarkdown opts $
1071− if isEnabled Ext_gutenberg opts
1072− then capitalize lst
1073− else lst
1074− else do
1081+ variant <- asks envVariant
1082+ case variant of
1083+ PlainText ->
1084+ inlineListToMarkdown opts $
1085+ if isEnabled Ext_gutenberg opts
1086+ then capitalize lst
1087+ else lst
1088+ _ -> do
10751089 contents <- inlineListToMarkdown opts lst
10761090 return $ "**" <> contents <> "**"
10771091inlineToMarkdown _ (Strikeout []) = return empty
@@ -1084,7 +1098,7 @@ inlineToMarkdown opts (Strikeout lst) = do
10841098 else contents
10851099inlineToMarkdown _ (Superscript []) = return empty
10861100inlineToMarkdown opts (Superscript lst) =
1087− local (\env -> env {envEscapeSpaces = True}) $ do
1101+ local (\env -> env {envEscapeSpaces = (envVariant env == Markdown)}) $ do
10881102 contents <- inlineListToMarkdown opts lst
10891103 if isEnabled Ext_superscript opts
10901104 then return $ "^" <> contents <> "^"
@@ -1102,7 +1116,7 @@ inlineToMarkdown opts (Superscript lst) =
11021116 Nothing -> literal $ "^(" <> rendered <> ")"
11031117inlineToMarkdown _ (Subscript []) = return empty
11041118inlineToMarkdown opts (Subscript lst) =
1105− local (\env -> env {envEscapeSpaces = True}) $ do
1119+ local (\env -> env {envEscapeSpaces = (envVariant env == Markdown)}) $ do
11061120 contents <- inlineListToMarkdown opts lst
11071121 if isEnabled Ext_subscript opts
11081122 then return $ "~" <> contents <> "~"
@@ -1119,8 +1133,8 @@ inlineToMarkdown opts (Subscript lst) =
11191133 Just r -> literal $ T.pack r
11201134 Nothing -> literal $ "_(" <> rendered <> ")"
11211135inlineToMarkdown opts (SmallCaps lst) = do
1122− plain <- asks envPlain
1123− if not plain &&
1136+ variant <- asks envVariant
1137+ if variant /= PlainText &&
11241138 (isEnabled Ext_raw_html opts || isEnabled Ext_native_spans opts)
11251139 then inlineToMarkdown opts (Span ("",["smallcaps"],[]) lst)
11261140 else inlineListToMarkdown opts $ capitalize lst
@@ -1150,16 +1164,17 @@ inlineToMarkdown opts (Code attr str) = do
11501164 let attrs = if isEnabled Ext_inline_code_attributes opts && attr /= nullAttr
11511165 then attrsToMarkdown attr
11521166 else empty
1153− plain <- asks envPlain
1154− if plain
1155− then return $ literal str
1156− else return $ literal (marker <> spacer <> str <> spacer <> marker) <> attrs
1167+ variant <- asks envVariant
1168+ case variant of
1169+ PlainText -> return $ literal str
1170+ _ -> return $ literal
1171+ (marker <> spacer <> str <> spacer <> marker) <> attrs
11571172inlineToMarkdown opts (Str str) = do
1158− isPlain <- asks envPlain
1173+ variant <- asks envVariant
11591174 let str' = (if isEnabled Ext_smart opts
11601175 then unsmartify opts
11611176 else id) $
1162− if isPlain
1177+ if variant == PlainText
11631178 then str
11641179 else escapeText opts str
11651180 return $ literal str'
@@ -1174,10 +1189,10 @@ inlineToMarkdown opts (Math InlineMath str) =
11741189 | isEnabled Ext_tex_math_double_backslash opts ->
11751190 return $ "\\\\(" <> literal str <> "\\\\)"
11761191 | otherwise -> do
1177− plain <- asks envPlain
1192+ variant <- asks envVariant
11781193 texMathToInlines InlineMath str >>=
11791194 inlineListToMarkdown opts .
1180− (if plain then makeMathPlainer else id)
1195+ (if variant == PlainText then makeMathPlainer else id)
11811196inlineToMarkdown opts (Math DisplayMath str) =
11821197 case writerHTMLMathMethod opts of
11831198 WebTeX url -> (\x -> blankline <> x <> blankline) `fmap`
@@ -1196,15 +1211,15 @@ inlineToMarkdown opts il@(RawInline f str) = do
11961211 let numticks = if null tickGroups
11971212 then 1
11981213 else 1 + maximum (map T.length tickGroups)
1199− plain <- asks envPlain
1214+ variant <- asks envVariant
12001215 let Format fmt = f
12011216 let rawAttribInline = return $
12021217 literal (T.replicate numticks "`") <> literal str <>
12031218 literal (T.replicate numticks "`") <> literal "{=" <> literal fmt <> literal "}"
12041219 let renderEmpty = mempty <$ report (InlineNotRendered il)
1205− case () of
1206− _ | plain -> renderEmpty
1207− | f `elem` ["markdown", "markdown_github", "markdown_phpextra",
1220+ case variant of
1221+ PlainText -> renderEmpty
1222+ _ | f `elem` ["markdown", "markdown_github", "markdown_phpextra",
12081223 "markdown_mmd", "markdown_strict"] ->
12091224 return $ literal str
12101225 | isEnabled Ext_raw_attribute opts -> rawAttribInline
@@ -1220,8 +1235,8 @@ inlineToMarkdown opts il@(RawInline f str) = do
12201235 | otherwise -> renderEmpty
12211236 | otherwise -> renderEmpty
12221237inlineToMarkdown opts LineBreak = do
1223− plain <- asks envPlain
1224− if plain || isEnabled Ext_hard_line_breaks opts
1238+ variant <- asks envVariant
1239+ if variant == PlainText || isEnabled Ext_hard_line_breaks opts
12251240 then return cr
12261241 else return $
12271242 if isEnabled Ext_escaped_line_breaks opts
@@ -1274,7 +1289,7 @@ inlineToMarkdown opts lnk@(Link attr txt (src, tit))
12741289 (literal . T.strip) <$>
12751290 writeHtml5String opts{ writerTemplate = Nothing } (Pandoc nullMeta [Plain [lnk]])
12761291 | otherwise = do
1277− plain <- asks envPlain
1292+ variant <- asks envVariant
12781293 linktext <- inlineListToMarkdown opts txt
12791294 let linktitle = if T.null tit
12801295 then empty
@@ -1292,9 +1307,9 @@ inlineToMarkdown opts lnk@(Link attr txt (src, tit))
12921307 then literal <$> getReference attr linktext (src, tit)
12931308 else return mempty
12941309 return $ if useAuto
1295− then if plain
1296− then literal srcSuffix
1297− else "<" <> literal srcSuffix <> ">"
1310+ then case variant of
1311+ PlainText -> literal srcSuffix
1312+ _ -> "<" <> literal srcSuffix <> ">"
12981313 else if useRefLinks
12991314 then let first = "[" <> linktext <> "]"
13001315 second = if getKey linktext == getKey reftext
@@ -1303,9 +1318,9 @@ inlineToMarkdown opts lnk@(Link attr txt (src, tit))
13031318 else "[]"
13041319 else "[" <> reftext <> "]"
13051320 in first <> second
1306− else if plain
1307− then linktext
1308− else "[" <> linktext <> "](" <>
1321+ else case variant of
1322+ PlainText -> linktext
1323+ _ -> "[" <> linktext <> "](" <>
13091324 literal src <> linktitle <> ")" <>
13101325 linkAttributes opts attr
13111326inlineToMarkdown opts img@(Image attr alternate (source, tit))
@@ -1315,15 +1330,15 @@ inlineToMarkdown opts img@(Image attr alternate (source, tit))
13151330 (literal . T.strip) <$>
13161331 writeHtml5String opts{ writerTemplate = Nothing } (Pandoc nullMeta [Plain [img]])
13171332 | otherwise = do
1318− plain <- asks envPlain
1333+ variant <- asks envVariant
13191334 let txt = if null alternate || alternate == [Str source]
13201335 -- to prevent autolinks
13211336 then [Str ""]
13221337 else alternate
13231338 linkPart <- inlineToMarkdown opts (Link attr txt (source, tit))
1324− return $ if plain
1325− then "[" <> linkPart <> "]"
1326− else "!" <> linkPart
1339+ return $ case variant of
1340+ PlainText -> "[" <> linkPart <> "]"
1341+ _ -> "!" <> linkPart
13271342inlineToMarkdown opts (Note contents) = do
13281343 modify (\st -> st{ stNotes = contents : stNotes st })
13291344 st <- get