git.iko.soy/typeable/pandoc
on master help sign in
Merge branch 'org-meta-handling'
commita3a3e3fdbf4968d56f65b7865fa798e96e40daaa authorAlbert Krewinkel <albert@zeitkraut.de> dateMon 29 Aug 2016, 14:42 (10 years ago) parentfcbb37c88313c0
6 files changed+320−132
pandoc.cabal+1 src/Text/Pandoc/Readers/Org/Blocks.hs+3−66 src/Text/Pandoc/Readers/Org/ExportSettings.hs+5−3 src/Text/Pandoc/Readers/Org/Meta.hs+181 src/Text/Pandoc/Readers/Org/ParserState.hs+6 tests/Tests/Readers/Org.hs+124−63
pandoc.cabal+1
@@ -401,6 +401,7 @@ Library
401401 Text.Pandoc.Readers.Org.Blocks,
402402 Text.Pandoc.Readers.Org.ExportSettings,
403403 Text.Pandoc.Readers.Org.Inlines,
404+ Text.Pandoc.Readers.Org.Meta,
404405 Text.Pandoc.Readers.Org.ParserState,
405406 Text.Pandoc.Readers.Org.Parsing,
406407 Text.Pandoc.Readers.Org.Shared,
src/Text/Pandoc/Readers/Org/Blocks.hs+3−66
@@ -34,8 +34,8 @@ module Text.Pandoc.Readers.Org.Blocks
3434 ) where
3535
3636import Text.Pandoc.Readers.Org.BlockStarts
37−import Text.Pandoc.Readers.Org.ExportSettings ( exportSettings )
3837import Text.Pandoc.Readers.Org.Inlines
38+import Text.Pandoc.Readers.Org.Meta ( metaExport, metaLine )
3939import Text.Pandoc.Readers.Org.ParserState
4040import Text.Pandoc.Readers.Org.Parsing
4141import Text.Pandoc.Readers.Org.Shared
@@ -52,9 +52,7 @@ import Text.Pandoc.Shared ( compactify', compactify'DL )
5252import Control.Monad ( foldM, guard, mzero, void )
5353import Data.Char ( isSpace, toLower, toUpper)
5454import Data.List ( foldl', intersperse, isPrefixOf )
55−import qualified Data.Map as M
5655import Data.Maybe ( fromMaybe, isNothing )
57−import Network.HTTP ( urlEncode )
5856
5957--
6058-- Org headers
@@ -232,8 +230,8 @@ blockList = do
232230-- | Get the meta information safed in the state.
233231meta :: OrgParser Meta
234232meta = do
235− st <- getState
236− return $ runF (orgStateMeta st) st
233+ meta' <- metaExport
234+ runF meta' <$> getState
237235
238236blocks :: OrgParser (F Blocks)
239237blocks = mconcat <$> manyTill block (void (lookAhead headerStart) <|> eof)
@@ -631,67 +629,9 @@ exampleCode = B.codeBlockWith ("", ["example"], [])
631629specialLine :: OrgParser (F Blocks)
632630specialLine = fmap return . try $ metaLine <|> commentLine
633631
634−-- The order, in which blocks are tried, makes sure that we're not looking at
635−-- the beginning of a block, so we don't need to check for it
636−metaLine :: OrgParser Blocks
637−metaLine = mempty <$ metaLineStart <* (optionLine <|> declarationLine)
638−
639632commentLine :: OrgParser Blocks
640633commentLine = commentLineStart *> anyLine *> pure mempty
641634
642−declarationLine :: OrgParser ()
643−declarationLine = try $ do
644− key <- metaKey
645− value <- metaInlines
646− updateState $ \st ->
647− let meta' = B.setMeta key <$> value <*> pure nullMeta
648− in st { orgStateMeta = orgStateMeta st <> meta' }
649−
650−metaInlines :: OrgParser (F MetaValue)
651−metaInlines = fmap (MetaInlines . B.toList) <$> inlinesTillNewline
652−
653−metaKey :: OrgParser String
654−metaKey = map toLower <$> many1 (noneOf ": \n\r")
655− <* char ':'
656− <* skipSpaces
657−
658−optionLine :: OrgParser ()
659−optionLine = try $ do
660− key <- metaKey
661− case key of
662− "link" -> parseLinkFormat >>= uncurry addLinkFormat
663− "options" -> exportSettings
664− _ -> mzero
665−
666−addLinkFormat :: String
667− -> (String -> String)
668− -> OrgParser ()
669−addLinkFormat key formatter = updateState $ \s ->
670− let fs = orgStateLinkFormatters s
671− in s{ orgStateLinkFormatters = M.insert key formatter fs }
672−
673−parseLinkFormat :: OrgParser ((String, String -> String))
674−parseLinkFormat = try $ do
675− linkType <- (:) <$> letter <*> many (alphaNum <|> oneOf "-_") <* skipSpaces
676− linkSubst <- parseFormat
677− return (linkType, linkSubst)
678−
679−-- | An ad-hoc, single-argument-only implementation of a printf-style format
680−-- parser.
681−parseFormat :: OrgParser (String -> String)
682−parseFormat = try $ do
683− replacePlain <|> replaceUrl <|> justAppend
684− where
685− -- inefficient, but who cares
686− replacePlain = try $ (\x -> concat . flip intersperse x)
687− <$> sequence [tillSpecifier 's', rest]
688− replaceUrl = try $ (\x -> concat . flip intersperse x . urlEncode)
689− <$> sequence [tillSpecifier 'h', rest]
690− justAppend = try $ (++) <$> rest
691−
692− rest = manyTill anyChar (eof <|> () <$ oneOf "\n\r")
693− tillSpecifier c = manyTill (noneOf "\n\r") (try $ string ('%':c:""))
694−
695635
696636--
697637-- Tables
@@ -868,9 +808,6 @@ paraOrPlain = try $ do
868808 *> return (B.para <$> ils))
869809 <|> (return (B.plain <$> ils))
870810
871−inlinesTillNewline :: OrgParser (F Inlines)
872−inlinesTillNewline = trimInlinesF . mconcat <$> manyTill inline newline
873−
874811
875812--
876813-- list blocks
src/Text/Pandoc/Readers/Org/ExportSettings.hs+5−3
@@ -54,13 +54,15 @@ exportSetting = choice
5454 , ignoredSetting "<"
5555 , ignoredSetting "\\n"
5656 , archivedTreeSetting "arch" (\val es -> es { exportArchivedTrees = val })
57− , ignoredSetting "author"
57+ , booleanSetting "author" (\val es -> es { exportWithAuthor = val })
5858 , ignoredSetting "c"
59− , ignoredSetting "creator"
59+ -- org-mode allows the special value `comment` for creator, which we'll
60+ -- interpret as true as it doesn't make sense in the context of Pandoc.
61+ , booleanSetting "creator" (\val es -> es { exportWithCreator = val })
6062 , complementableListSetting "d" (\val es -> es { exportDrawers = val })
6163 , ignoredSetting "date"
6264 , ignoredSetting "e"
63− , ignoredSetting "email"
65+ , booleanSetting "email" (\val es -> es { exportWithEmail = val })
6466 , ignoredSetting "f"
6567 , integerSetting "H" (\val es -> es { exportHeadlineLevels = val })
6668 , ignoredSetting "inline"
src/Text/Pandoc/Readers/Org/Meta.hsnew file+181
@@ -0,0 +1,181 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE TupleSections #-}
3+{-
4+Copyright (C) 2014-2016 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
5+
6+This program is free software; you can redistribute it and/or modify
7+it under the terms of the GNU General Public License as published by
8+the Free Software Foundation; either version 2 of the License, or
9+(at your option) any later version.
10+
11+This program is distributed in the hope that it will be useful,
12+but WITHOUT ANY WARRANTY; without even the implied warranty of
13+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+GNU General Public License for more details.
15+
16+You should have received a copy of the GNU General Public License
17+along with this program; if not, write to the Free Software
18+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19+-}
20+
21+{- |
22+ Module : Text.Pandoc.Readers.Org.Meta
23+ Copyright : Copyright (C) 2014-2016 Albert Krewinkel
24+ License : GNU GPL, version 2 or above
25+
26+ Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
27+
28+Parsers for Org-mode meta declarations.
29+-}
30+module Text.Pandoc.Readers.Org.Meta
31+ ( metaLine
32+ , metaExport
33+ ) where
34+
35+import Text.Pandoc.Readers.Org.BlockStarts
36+import Text.Pandoc.Readers.Org.ExportSettings ( exportSettings )
37+import Text.Pandoc.Readers.Org.Inlines
38+import Text.Pandoc.Readers.Org.ParserState
39+import Text.Pandoc.Readers.Org.Parsing
40+
41+import qualified Text.Pandoc.Builder as B
42+import Text.Pandoc.Builder ( Blocks, Inlines )
43+import Text.Pandoc.Definition
44+import Text.Pandoc.Compat.Monoid ((<>))
45+
46+import Control.Monad ( mzero )
47+import Data.Char ( toLower )
48+import Data.List ( intersperse )
49+import qualified Data.Map as M
50+import Network.HTTP ( urlEncode )
51+
52+-- | Returns the current meta, respecting export options.
53+metaExport :: OrgParser (F Meta)
54+metaExport = do
55+ st <- getState
56+ let settings = orgStateExportSettings st
57+ return $ (if exportWithAuthor settings then id else removeMeta "author")
58+ . (if exportWithCreator settings then id else removeMeta "creator")
59+ . (if exportWithEmail settings then id else removeMeta "email")
60+ <$> orgStateMeta st
61+
62+removeMeta :: String -> Meta -> Meta
63+removeMeta key meta' =
64+ let metaMap = unMeta meta'
65+ in Meta $ M.delete key metaMap
66+
67+-- | Parse and handle a single line containing meta information
68+-- The order, in which blocks are tried, makes sure that we're not looking at
69+-- the beginning of a block, so we don't need to check for it
70+metaLine :: OrgParser Blocks
71+metaLine = mempty <$ metaLineStart <* (optionLine <|> declarationLine)
72+
73+declarationLine :: OrgParser ()
74+declarationLine = try $ do
75+ key <- map toLower <$> metaKey
76+ (key', value) <- metaValue key
77+ updateState $ \st ->
78+ let meta' = B.setMeta key' <$> value <*> pure nullMeta
79+ in st { orgStateMeta = meta' <> orgStateMeta st }
80+
81+metaKey :: OrgParser String
82+metaKey = map toLower <$> many1 (noneOf ": \n\r")
83+ <* char ':'
84+ <* skipSpaces
85+
86+metaValue :: String -> OrgParser (String, (F MetaValue))
87+metaValue key =
88+ let inclKey = "header-includes"
89+ in case key of
90+ "author" -> (key,) <$> metaInlinesCommaSeparated
91+ "title" -> (key,) <$> metaInlines
92+ "date" -> (key,) <$> metaInlines
93+ "header-includes" -> (key,) <$> accumulatingList key metaInlines
94+ "latex_header" -> (inclKey,) <$>
95+ accumulatingList inclKey (metaExportSnippet "latex")
96+ "latex_class" -> ("documentclass",) <$> metaString
97+ -- Org-mode expects class options to contain the surrounding brackets,
98+ -- pandoc does not.
99+ "latex_class_options" -> ("classoption",) <$>
100+ metaModifiedString (filter (`notElem` "[]"))
101+ "html_head" -> (inclKey,) <$>
102+ accumulatingList inclKey (metaExportSnippet "html")
103+ _ -> (key,) <$> metaString
104+
105+metaInlines :: OrgParser (F MetaValue)
106+metaInlines = fmap (MetaInlines . B.toList) <$> inlinesTillNewline
107+
108+metaInlinesCommaSeparated :: OrgParser (F MetaValue)
109+metaInlinesCommaSeparated = do
110+ authStrs <- (many1 (noneOf ",\n")) `sepBy1` (char ',')
111+ newline
112+ authors <- mapM (parseFromString inlinesTillNewline . (++ "\n")) authStrs
113+ let toMetaInlines = MetaInlines . B.toList
114+ return $ MetaList . map toMetaInlines <$> sequence authors
115+
116+metaString :: OrgParser (F MetaValue)
117+metaString = metaModifiedString id
118+
119+metaModifiedString :: (String -> String) -> OrgParser (F MetaValue)
120+metaModifiedString f = return . MetaString . f <$> anyLine
121+
122+-- | Read an format specific meta definition
123+metaExportSnippet :: String -> OrgParser (F MetaValue)
124+metaExportSnippet format =
125+ return . MetaInlines . B.toList . B.rawInline format <$> anyLine
126+
127+-- | Accumulate the result of the @parser@ in a list under @key@.
128+accumulatingList :: String
129+ -> OrgParser (F MetaValue)
130+ -> OrgParser (F MetaValue)
131+accumulatingList key p = do
132+ value <- p
133+ meta' <- orgStateMeta <$> getState
134+ return $ (\m v -> MetaList (curList m ++ [v])) <$> meta' <*> value
135+ where curList m = case lookupMeta key m of
136+ Just (MetaList ms) -> ms
137+ Just x -> [x]
138+ _ -> []
139+
140+--
141+-- export options
142+--
143+optionLine :: OrgParser ()
144+optionLine = try $ do
145+ key <- metaKey
146+ case key of
147+ "link" -> parseLinkFormat >>= uncurry addLinkFormat
148+ "options" -> exportSettings
149+ _ -> mzero
150+
151+addLinkFormat :: String
152+ -> (String -> String)
153+ -> OrgParser ()
154+addLinkFormat key formatter = updateState $ \s ->
155+ let fs = orgStateLinkFormatters s
156+ in s{ orgStateLinkFormatters = M.insert key formatter fs }
157+
158+parseLinkFormat :: OrgParser ((String, String -> String))
159+parseLinkFormat = try $ do
160+ linkType <- (:) <$> letter <*> many (alphaNum <|> oneOf "-_") <* skipSpaces
161+ linkSubst <- parseFormat
162+ return (linkType, linkSubst)
163+
164+-- | An ad-hoc, single-argument-only implementation of a printf-style format
165+-- parser.
166+parseFormat :: OrgParser (String -> String)
167+parseFormat = try $ do
168+ replacePlain <|> replaceUrl <|> justAppend
169+ where
170+ -- inefficient, but who cares
171+ replacePlain = try $ (\x -> concat . flip intersperse x)
172+ <$> sequence [tillSpecifier 's', rest]
173+ replaceUrl = try $ (\x -> concat . flip intersperse x . urlEncode)
174+ <$> sequence [tillSpecifier 'h', rest]
175+ justAppend = try $ (++) <$> rest
176+
177+ rest = manyTill anyChar (eof <|> () <$ oneOf "\n\r")
178+ tillSpecifier c = manyTill (noneOf "\n\r") (try $ string ('%':c:""))
179+
180+inlinesTillNewline :: OrgParser (F Inlines)
181+inlinesTillNewline = trimInlinesF . mconcat <$> manyTill inline newline
src/Text/Pandoc/Readers/Org/ParserState.hs+6
@@ -163,6 +163,9 @@ data ExportSettings = ExportSettings
163163 , exportSmartQuotes :: Bool -- ^ Parse quotes smartly
164164 , exportSpecialStrings :: Bool -- ^ Parse ellipses and dashes smartly
165165 , exportSubSuperscripts :: Bool -- ^ TeX-like syntax for sub- and superscripts
166+ , exportWithAuthor :: Bool -- ^ Include author in final meta-data
167+ , exportWithCreator :: Bool -- ^ Include creator in final meta-data
168+ , exportWithEmail :: Bool -- ^ Include email in final meta-data
166169 }
167170
168171instance Default ExportSettings where
@@ -177,6 +180,9 @@ defaultExportSettings = ExportSettings
177180 , exportSmartQuotes = True
178181 , exportSpecialStrings = True
179182 , exportSubSuperscripts = True
183+ , exportWithAuthor = True
184+ , exportWithCreator = True
185+ , exportWithEmail = True
180186 }
181187
182188
tests/Tests/Readers/Org.hs+124−63
@@ -467,7 +467,14 @@ tests =
467467 , "Author" =:
468468 "#+author: Albert /Emacs-Fanboy/ Krewinkel" =?>
469469 let author = toList . spcSep $ [ "Albert", emph "Emacs-Fanboy", "Krewinkel" ]
470− meta = setMeta "author" (MetaInlines author) $ nullMeta
470+ meta = setMeta "author" (MetaList [MetaInlines author]) $ nullMeta
471+ in Pandoc meta mempty
472+
473+ , "Multiple authors" =:
474+ "#+author: James Dewey Watson, Francis Harry Compton Crick " =?>
475+ let watson = MetaInlines $ toList "James Dewey Watson"
476+ crick = MetaInlines $ toList "Francis Harry Compton Crick"
477+ meta = setMeta "author" (MetaList [watson, crick]) $ nullMeta
471478 in Pandoc meta mempty
472479
473480 , "Date" =:
@@ -478,8 +485,8 @@ tests =
478485
479486 , "Description" =:
480487 "#+DESCRIPTION: Explanatory text" =?>
481− let description = toList . spcSep $ [ "Explanatory", "text" ]
482− meta = setMeta "description" (MetaInlines description) $ nullMeta
488+ let description = "Explanatory text"
489+ meta = setMeta "description" (MetaString description) $ nullMeta
483490 in Pandoc meta mempty
484491
485492 , "Properties drawer" =:
@@ -489,6 +496,38 @@ tests =
489496 ] =?>
490497 (mempty::Blocks)
491498
499+ , "LaTeX_headers options are translated to header-includes" =:
500+ "#+LaTeX_header: \\usepackage{tikz}" =?>
501+ let latexInlines = rawInline "latex" "\\usepackage{tikz}"
502+ inclList = MetaList [MetaInlines (toList latexInlines)]
503+ meta = setMeta "header-includes" inclList nullMeta
504+ in Pandoc meta mempty
505+
506+ , "LaTeX_class option is translated to documentclass" =:
507+ "#+LATEX_CLASS: article" =?>
508+ let meta = setMeta "documentclass" (MetaString "article") nullMeta
509+ in Pandoc meta mempty
510+
511+ , "LaTeX_class_options is translated to classoption" =:
512+ "#+LATEX_CLASS_OPTIONS: [a4paper]" =?>
513+ let meta = setMeta "classoption" (MetaString "a4paper") nullMeta
514+ in Pandoc meta mempty
515+
516+ , "LaTeX_class_options is translated to classoption" =:
517+ "#+html_head: <meta/>" =?>
518+ let html = rawInline "html" "<meta/>"
519+ inclList = MetaList [MetaInlines (toList html)]
520+ meta = setMeta "header-includes" inclList nullMeta
521+ in Pandoc meta mempty
522+
523+ , "later meta definitions take precedence" =:
524+ unlines [ "#+AUTHOR: this will not be used"
525+ , "#+author: Max"
526+ ] =?>
527+ let author = MetaInlines [Str "Max"]
528+ meta = setMeta "author" (MetaList [author]) $ nullMeta
529+ in Pandoc meta mempty
530+
492531 , "Logbook drawer" =:
493532 unlines [ " :LogBook:"
494533 , " - State \"DONE\" from \"TODO\" [2014-03-03 Mon 11:00]"
@@ -563,69 +602,91 @@ tests =
563602 ] =?>
564603 (para (link "http://example.com/foo" "" "bar"))
565604
566− , "Export option: Disable simple sub/superscript syntax" =:
567− unlines [ "#+OPTIONS: ^:nil"
568− , "a^b"
569− ] =?>
570− para "a^b"
571−
572− , "Export option: directly select drawers to be exported" =:
573− unlines [ "#+OPTIONS: d:(\"IMPORTANT\")"
574− , ":IMPORTANT:"
575− , "23"
576− , ":END:"
577− , ":BORING:"
578− , "very boring"
579− , ":END:"
580− ] =?>
581− divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "23")
582−
583− , "Export option: exclude drawers from being exported" =:
584− unlines [ "#+OPTIONS: d:(not \"BORING\")"
585− , ":IMPORTANT:"
586− , "5"
587− , ":END:"
588− , ":BORING:"
589− , "very boring"
590− , ":END:"
591− ] =?>
592− divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "5")
593605
594− , "Export option: don't include archive trees" =:
595− unlines [ "#+OPTIONS: arch:nil"
596− , "* old :ARCHIVE:"
597− ] =?>
598− (mempty ::Blocks)
606+ , testGroup "export options"
607+
608+ [ "disable simple sub/superscript syntax" =:
609+ unlines [ "#+OPTIONS: ^:nil"
610+ , "a^b"
611+ ] =?>
612+ para "a^b"
613+
614+ , "directly select drawers to be exported" =:
615+ unlines [ "#+OPTIONS: d:(\"IMPORTANT\")"
616+ , ":IMPORTANT:"
617+ , "23"
618+ , ":END:"
619+ , ":BORING:"
620+ , "very boring"
621+ , ":END:"
622+ ] =?>
623+ divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "23")
624+
625+ , "exclude drawers from being exported" =:
626+ unlines [ "#+OPTIONS: d:(not \"BORING\")"
627+ , ":IMPORTANT:"
628+ , "5"
629+ , ":END:"
630+ , ":BORING:"
631+ , "very boring"
632+ , ":END:"
633+ ] =?>
634+ divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "5")
635+
636+ , "don't include archive trees" =:
637+ unlines [ "#+OPTIONS: arch:nil"
638+ , "* old :ARCHIVE:"
639+ ] =?>
640+ (mempty ::Blocks)
641+
642+ , "include complete archive trees" =:
643+ unlines [ "#+OPTIONS: arch:t"
644+ , "* old :ARCHIVE:"
645+ , " boring"
646+ ] =?>
647+ let tagSpan t = spanWith ("", ["tag"], [("data-tag-name", t)]) mempty
648+ in mconcat [ headerWith ("old", [], mempty) 1 ("old" <> tagSpan "ARCHIVE")
649+ , para "boring"
650+ ]
599651
600− , "Export option: include complete archive trees" =:
601− unlines [ "#+OPTIONS: arch:t"
602− , "* old :ARCHIVE:"
603− , " boring"
604− ] =?>
605− let tagSpan t = spanWith ("", ["tag"], [("data-tag-name", t)]) mempty
606− in mconcat [ headerWith ("old", [], mempty) 1 ("old" <> tagSpan "ARCHIVE")
607− , para "boring"
608− ]
652+ , "include archive tree header only" =:
653+ unlines [ "#+OPTIONS: arch:headline"
654+ , "* old :ARCHIVE:"
655+ , " boring"
656+ ] =?>
657+ let tagSpan t = spanWith ("", ["tag"], [("data-tag-name", t)]) mempty
658+ in headerWith ("old", [], mempty) 1 ("old" <> tagSpan "ARCHIVE")
659+
660+ , "limit headline depth" =:
661+ unlines [ "#+OPTIONS: H:2"
662+ , "* section"
663+ , "** subsection"
664+ , "*** list item 1"
665+ , "*** list item 2"
666+ ] =?>
667+ mconcat [ headerWith ("section", [], []) 1 "section"
668+ , headerWith ("subsection", [], []) 2 "subsection"
669+ , orderedList [ para "list item 1", para "list item 2" ]
670+ ]
609671
610− , "Export option: include archive tree header only" =:
611− unlines [ "#+OPTIONS: arch:headline"
612− , "* old :ARCHIVE:"
613− , " boring"
614− ] =?>
615− let tagSpan t = spanWith ("", ["tag"], [("data-tag-name", t)]) mempty
616− in headerWith ("old", [], mempty) 1 ("old" <> tagSpan "ARCHIVE")
617−
618− , "Export option: limit headline depth" =:
619− unlines [ "#+OPTIONS: H:2"
620− , "* section"
621− , "** subsection"
622− , "*** list item 1"
623− , "*** list item 2"
624− ] =?>
625− mconcat [ headerWith ("section", [], []) 1 "section"
626− , headerWith ("subsection", [], []) 2 "subsection"
627− , orderedList [ para "list item 1", para "list item 2" ]
628− ]
672+ , "disable author export" =:
673+ unlines [ "#+OPTIONS: author:nil"
674+ , "#+AUTHOR: ShyGuy"
675+ ] =?>
676+ Pandoc nullMeta mempty
677+
678+ , "disable creator export" =:
679+ unlines [ "#+OPTIONS: creator:nil"
680+ , "#+creator: The Architect"
681+ ] =?>
682+ Pandoc nullMeta mempty
683+
684+ , "disable email export" =:
685+ unlines [ "#+OPTIONS: email:nil"
686+ , "#+email: no-mail-please@example.com"
687+ ] =?>
688+ Pandoc nullMeta mempty
689+ ]
629690 ]
630691
631692 , testGroup "Basic Blocks" $