git.iko.soy/typeable/pandoc
on master help sign in
Break-up org reader test file

The org reader test file had grown large, to the point that editor performance was negatively affected in some cases. The tests are spread over multiple submodules, and re-combined into a tasty TestTree in the main org reader test file.

commit2d443ecb07491f81e35b60d9c6cdd9041ab1c4dd authorAlbert Krewinkel <albert@zeitkraut.de> dateSun 24 Dec 2017, 22:48 (9 years ago) parent0fd7ed0
14 files changed+2019−1864
pandoc.cabal+12 test/Tests/Readers/Org.hs+9−1864 test/Tests/Readers/Org/Block.hs+192 test/Tests/Readers/Org/Block/CodeBlock.hs+194 test/Tests/Readers/Org/Block/Figure.hs+57 test/Tests/Readers/Org/Block/Header.hs+182 test/Tests/Readers/Org/Block/List.hs+244 test/Tests/Readers/Org/Block/Table.hs+150 test/Tests/Readers/Org/Directive.hs+128 test/Tests/Readers/Org/Inline.hs+516 test/Tests/Readers/Org/Inline/Note.hs+87 test/Tests/Readers/Org/Inline/Smart.hs+46 test/Tests/Readers/Org/Meta.hs+173 test/Tests/Readers/Org/Shared.hs+29
pandoc.cabal+12
@@ -630,6 +630,18 @@ test-suite test-pandoc
630630 Tests.Readers.JATS
631631 Tests.Readers.Markdown
632632 Tests.Readers.Org
633+ Tests.Readers.Org.Block
634+ Tests.Readers.Org.Block.CodeBlock
635+ Tests.Readers.Org.Block.Figure
636+ Tests.Readers.Org.Block.Header
637+ Tests.Readers.Org.Block.List
638+ Tests.Readers.Org.Block.Table
639+ Tests.Readers.Org.Directive
640+ Tests.Readers.Org.Inline
641+ Tests.Readers.Org.Inline.Note
642+ Tests.Readers.Org.Inline.Smart
643+ Tests.Readers.Org.Meta
644+ Tests.Readers.Org.Shared
633645 Tests.Readers.RST
634646 Tests.Readers.Docx
635647 Tests.Readers.Odt
test/Tests/Readers/Org.hs+9−1864
@@ -1,1871 +1,16 @@
11{-# LANGUAGE OverloadedStrings #-}
22module Tests.Readers.Org (tests) where
33
4−import Data.List (intersperse)
5−import Data.Text (Text)
6−import qualified Data.Text as T
7−import Test.Tasty
8−import Tests.Helpers
9−import Text.Pandoc
10−import Text.Pandoc.Builder
11−import Text.Pandoc.Shared (underlineSpan)
12−
13−org :: Text -> Pandoc
14−org = purely $ readOrg def{ readerExtensions = getDefaultExtensions "org" }
15−
16−orgSmart :: Text -> Pandoc
17−orgSmart = purely $ readOrg def { readerExtensions =
18− enableExtension Ext_smart $ getDefaultExtensions "org" }
19−
20−infix 4 =:
21−(=:) :: ToString c
22− => String -> (Text, c) -> TestTree
23−(=:) = test org
24−
25−spcSep :: [Inlines] -> Inlines
26−spcSep = mconcat . intersperse space
27−
28−simpleTable' :: Int
29− -> [Blocks]
30− -> [[Blocks]]
31− -> Blocks
32−simpleTable' n = table "" (replicate n (AlignDefault, 0.0))
33−
34−-- | Create a span for the given tag.
35−tagSpan :: String -> Inlines
36−tagSpan t = spanWith ("", ["tag"], [("tag-name", t)]) . smallcaps $ str t
4+import Test.Tasty (TestTree, testGroup)
5+import qualified Tests.Readers.Org.Block as Block
6+import qualified Tests.Readers.Org.Directive as Directive
7+import qualified Tests.Readers.Org.Inline as Inline
8+import qualified Tests.Readers.Org.Meta as Meta
379
3810tests :: [TestTree]
3911tests =
40− [ testGroup "Inlines" $
41− [ "Plain String" =:
42− "Hello, World" =?>
43− para (spcSep [ "Hello,", "World" ])
44−
45− , "Emphasis" =:
46− "/Planet Punk/" =?>
47− para (emph . spcSep $ ["Planet", "Punk"])
48−
49− , "Strong" =:
50− "*Cider*" =?>
51− para (strong "Cider")
52−
53− , "Strong Emphasis" =:
54− "/*strength*/" =?>
55− para (emph . strong $ "strength")
56−
57− , "Emphasized Strong preceded by space" =:
58− " */super/*" =?>
59− para (strong . emph $ "super")
60−
61− , "Underline" =:
62− "_underline_" =?>
63− para (underlineSpan $ "underline")
64−
65− , "Strikeout" =:
66− "+Kill Bill+" =?>
67− para (strikeout . spcSep $ [ "Kill", "Bill" ])
68−
69− , "Verbatim" =:
70− "=Robot.rock()=" =?>
71− para (code "Robot.rock()")
72−
73− , "Code" =:
74− "~word for word~" =?>
75− para (code "word for word")
76−
77− , "Math $..$" =:
78− "$E=mc^2$" =?>
79− para (math "E=mc^2")
80−
81− , "Math $$..$$" =:
82− "$$E=mc^2$$" =?>
83− para (displayMath "E=mc^2")
84−
85− , "Math \\[..\\]" =:
86− "\\[E=ℎν\\]" =?>
87− para (displayMath "E=ℎν")
88−
89− , "Math \\(..\\)" =:
90− "\\(σ_x σ_p ≥ \\frac{ℏ}{2}\\)" =?>
91− para (math "σ_x σ_p ≥ \\frac{ℏ}{2}")
92−
93− , "Symbol" =:
94− "A * symbol" =?>
95− para (str "A" <> space <> str "*" <> space <> "symbol")
96−
97− , "Superscript simple expression" =:
98− "2^-λ" =?>
99− para (str "2" <> superscript "-λ")
100−
101− , "Superscript multi char" =:
102− "2^{n-1}" =?>
103− para (str "2" <> superscript "n-1")
104−
105− , "Subscript simple expression" =:
106− "a_n" =?>
107− para (str "a" <> subscript "n")
108−
109− , "Subscript multi char" =:
110− "a_{n+1}" =?>
111− para (str "a" <> subscript "n+1")
112−
113− , "Linebreak" =:
114− "line \\\\ \nbreak" =?>
115− para ("line" <> linebreak <> "break")
116−
117− , "Inline note" =:
118− "[fn::Schreib mir eine E-Mail]" =?>
119− para (note $ para "Schreib mir eine E-Mail")
120−
121− , "Markup-chars not occuring on word break are symbols" =:
122− T.unlines [ "this+that+ +so+on"
123− , "seven*eight* nine*"
124− , "+not+funny+"
125− ] =?>
126− para ("this+that+ +so+on" <> softbreak <>
127− "seven*eight* nine*" <> softbreak <>
128− strikeout "not+funny")
129−
130− , "No empty markup" =:
131− "// ** __ <> == ~~ $$" =?>
132− para (spcSep [ "//", "**", "__", "<>", "==", "~~", "$$" ])
133−
134− , "Adherence to Org's rules for markup borders" =:
135− "/t/& a/ / ./r/ (*l*) /e/! /b/." =?>
136− para (spcSep [ emph $ "t/&" <> space <> "a"
137− , "/"
138− , "./r/"
139− , "(" <> strong "l" <> ")"
140− , emph "e" <> "!"
141− , emph "b" <> "."
142− ])
143−
144− , "Quotes are allowed border chars" =:
145− "/'yep/ *sure\"*" =?>
146− para (emph "'yep" <> space <> strong "sure\"")
147−
148− , "Spaces are forbidden border chars" =:
149− "/nada /" =?>
150− para "/nada /"
151−
152− , "Markup should work properly after a blank line" =:
153− T.unlines ["foo", "", "/bar/"] =?>
154− (para $ text "foo") <> (para $ emph $ text "bar")
155−
156− , "Inline math must stay within three lines" =:
157− T.unlines [ "$a", "b", "c$", "$d", "e", "f", "g$" ] =?>
158− para ((math "a\nb\nc") <> softbreak <>
159− "$d" <> softbreak <> "e" <> softbreak <>
160− "f" <> softbreak <> "g$")
161−
162− , "Single-character math" =:
163− "$a$ $b$! $c$?" =?>
164− para (spcSep [ math "a"
165− , "$b$!"
166− , (math "c") <> "?"
167− ])
168−
169− , "Markup may not span more than two lines" =:
170− "/this *is +totally\nnice+ not*\nemph/" =?>
171− para ("/this" <> space <>
172− strong ("is" <> space <>
173− strikeout ("totally" <>
174− softbreak <> "nice") <>
175− space <> "not") <>
176− softbreak <> "emph/")
177−
178− , "Sub- and superscript expressions" =:
179− T.unlines [ "a_(a(b)(c)d)"
180− , "e^(f(g)h)"
181− , "i_(jk)l)"
182− , "m^()n"
183− , "o_{p{q{}r}}"
184− , "s^{t{u}v}"
185− , "w_{xy}z}"
186− , "1^{}2"
187− , "3_{{}}"
188− , "4^(a(*b(c*)d))"
189− ] =?>
190− para (mconcat $ intersperse softbreak
191− [ "a" <> subscript "(a(b)(c)d)"
192− , "e" <> superscript "(f(g)h)"
193− , "i" <> (subscript "(jk)") <> "l)"
194− , "m" <> (superscript "()") <> "n"
195− , "o" <> subscript "p{q{}r}"
196− , "s" <> superscript "t{u}v"
197− , "w" <> (subscript "xy") <> "z}"
198− , "1" <> (superscript "") <> "2"
199− , "3" <> subscript "{}"
200− , "4" <> superscript ("(a(" <> strong "b(c" <> ")d))")
201− ])
202− , "Verbatim text can contain equal signes (=)" =:
203− "=is_subst = True=" =?>
204− para (code "is_subst = True")
205−
206− , testGroup "Images"
207− [ "Image" =:
208− "[[./sunset.jpg]]" =?>
209− (para $ image "./sunset.jpg" "" "")
210−
211− , "Image with explicit file: prefix" =:
212− "[[file:sunrise.jpg]]" =?>
213− (para $ image "sunrise.jpg" "" "")
214−
215− , "Multiple images within a paragraph" =:
216− T.unlines [ "[[file:sunrise.jpg]]"
217− , "[[file:sunset.jpg]]"
218− ] =?>
219− (para $ (image "sunrise.jpg" "" "")
220− <> softbreak
221− <> (image "sunset.jpg" "" ""))
222−
223− , "Image with html attributes" =:
224− T.unlines [ "#+ATTR_HTML: :width 50%"
225− , "[[file:guinea-pig.gif]]"
226− ] =?>
227− (para $ imageWith ("", [], [("width", "50%")]) "guinea-pig.gif" "" "")
228− ]
229−
230− , "Explicit link" =:
231− "[[http://zeitlens.com/][pseudo-random /nonsense/]]" =?>
232− (para $ link "http://zeitlens.com/" ""
233− ("pseudo-random" <> space <> emph "nonsense"))
234−
235− , "Self-link" =:
236− "[[http://zeitlens.com/]]" =?>
237− (para $ link "http://zeitlens.com/" "" "http://zeitlens.com/")
238−
239− , "Absolute file link" =:
240− "[[/url][hi]]" =?>
241− (para $ link "file:///url" "" "hi")
242−
243− , "Link to file in parent directory" =:
244− "[[../file.txt][moin]]" =?>
245− (para $ link "../file.txt" "" "moin")
246−
247− , "Empty link (for gitit interop)" =:
248− "[[][New Link]]" =?>
249− (para $ link "" "" "New Link")
250−
251− , "Image link" =:
252− "[[sunset.png][file:dusk.svg]]" =?>
253− (para $ link "sunset.png" "" (image "dusk.svg" "" ""))
254−
255− , "Image link with non-image target" =:
256− "[[http://example.com][./logo.png]]" =?>
257− (para $ link "http://example.com" "" (image "./logo.png" "" ""))
258−
259− , "Plain link" =:
260− "Posts on http://zeitlens.com/ can be funny at times." =?>
261− (para $ spcSep [ "Posts", "on"
262− , link "http://zeitlens.com/" "" "http://zeitlens.com/"
263− , "can", "be", "funny", "at", "times."
264− ])
265−
266− , "Angle link" =:
267− "Look at <http://moltkeplatz.de> for fnords." =?>
268− (para $ spcSep [ "Look", "at"
269− , link "http://moltkeplatz.de" "" "http://moltkeplatz.de"
270− , "for", "fnords."
271− ])
272−
273− , "Absolute file link" =:
274− "[[file:///etc/passwd][passwd]]" =?>
275− (para $ link "file:///etc/passwd" "" "passwd")
276−
277− , "File link" =:
278− "[[file:target][title]]" =?>
279− (para $ link "target" "" "title")
280−
281− , "Anchor" =:
282− "<<anchor>> Link here later." =?>
283− (para $ spanWith ("anchor", [], []) mempty <>
284− "Link" <> space <> "here" <> space <> "later.")
285−
286− , "Inline code block" =:
287− "src_emacs-lisp{(message \"Hello\")}" =?>
288− (para $ codeWith ( ""
289− , [ "commonlisp" ]
290− , [ ("org-language", "emacs-lisp") ])
291− "(message \"Hello\")")
292−
293− , "Inline code block with arguments" =:
294− "src_sh[:export both :results output]{echo 'Hello, World'}" =?>
295− (para $ codeWith ( ""
296− , [ "bash" ]
297− , [ ("org-language", "sh")
298− , ("export", "both")
299− , ("results", "output")
300− ]
301− )
302− "echo 'Hello, World'")
303−
304− , "Inline code block with toggle" =:
305− "src_sh[:toggle]{echo $HOME}" =?>
306− (para $ codeWith ( ""
307− , [ "bash" ]
308− , [ ("org-language", "sh")
309− , ("toggle", "yes")
310− ]
311− )
312− "echo $HOME")
313−
314− , "Citation" =:
315− "[@nonexistent]" =?>
316− let citation = Citation
317− { citationId = "nonexistent"
318− , citationPrefix = []
319− , citationSuffix = []
320− , citationMode = NormalCitation
321− , citationNoteNum = 0
322− , citationHash = 0}
323− in (para $ cite [citation] "[@nonexistent]")
324−
325− , "Citation containing text" =:
326− "[see @item1 p. 34-35]" =?>
327− let citation = Citation
328− { citationId = "item1"
329− , citationPrefix = [Str "see"]
330− , citationSuffix = [Space ,Str "p.",Space,Str "34-35"]
331− , citationMode = NormalCitation
332− , citationNoteNum = 0
333− , citationHash = 0}
334− in (para $ cite [citation] "[see @item1 p. 34-35]")
335−
336− , "Org-ref simple citation" =:
337− "cite:pandoc" =?>
338− let citation = Citation
339− { citationId = "pandoc"
340− , citationPrefix = mempty
341− , citationSuffix = mempty
342− , citationMode = AuthorInText
343− , citationNoteNum = 0
344− , citationHash = 0
345− }
346− in (para $ cite [citation] "cite:pandoc")
347−
348− , "Org-ref simple citation with underscores" =:
349− "cite:pandoc_org_ref" =?>
350− let citation = Citation
351− { citationId = "pandoc_org_ref"
352− , citationPrefix = mempty
353− , citationSuffix = mempty
354− , citationMode = AuthorInText
355− , citationNoteNum = 0
356− , citationHash = 0
357− }
358− in (para $ cite [citation] "cite:pandoc_org_ref")
359−
360− , "Org-ref simple citation succeeded by comma" =:
361− "cite:pandoc," =?>
362− let citation = Citation
363− { citationId = "pandoc"
364− , citationPrefix = mempty
365− , citationSuffix = mempty
366− , citationMode = AuthorInText
367− , citationNoteNum = 0
368− , citationHash = 0
369− }
370− in (para $ cite [citation] "cite:pandoc" <> str ",")
371−
372− , "Org-ref simple citation succeeded by dot" =:
373− "cite:pandoc." =?>
374− let citation = Citation
375− { citationId = "pandoc"
376− , citationPrefix = mempty
377− , citationSuffix = mempty
378− , citationMode = AuthorInText
379− , citationNoteNum = 0
380− , citationHash = 0
381− }
382− in (para $ cite [citation] "cite:pandoc" <> str ".")
383−
384− , "Org-ref simple citation succeeded by colon" =:
385− "cite:pandoc:" =?>
386− let citation = Citation
387− { citationId = "pandoc"
388− , citationPrefix = mempty
389− , citationSuffix = mempty
390− , citationMode = AuthorInText
391− , citationNoteNum = 0
392− , citationHash = 0
393− }
394− in (para $ cite [citation] "cite:pandoc" <> str ":")
395−
396− , "Org-ref simple citep citation" =:
397− "citep:pandoc" =?>
398− let citation = Citation
399− { citationId = "pandoc"
400− , citationPrefix = mempty
401− , citationSuffix = mempty
402− , citationMode = NormalCitation
403− , citationNoteNum = 0
404− , citationHash = 0
405− }
406− in (para $ cite [citation] "citep:pandoc")
407−
408− , "Org-ref extended citation" =:
409− "[[citep:Dominik201408][See page 20::, for example]]" =?>
410− let citation = Citation
411− { citationId = "Dominik201408"
412− , citationPrefix = toList "See page 20"
413− , citationSuffix = toList ", for example"
414− , citationMode = NormalCitation
415− , citationNoteNum = 0
416− , citationHash = 0
417− }
418− in (para $ cite [citation] "[[citep:Dominik201408][See page 20::, for example]]")
419−
420− , testGroup "Berkeley-style citations" $
421− let pandocCite = Citation
422− { citationId = "Pandoc"
423− , citationPrefix = mempty
424− , citationSuffix = mempty
425− , citationMode = NormalCitation
426− , citationNoteNum = 0
427− , citationHash = 0
428− }
429− pandocInText = pandocCite { citationMode = AuthorInText }
430− dominikCite = Citation
431− { citationId = "Dominik201408"
432− , citationPrefix = mempty
433− , citationSuffix = mempty
434− , citationMode = NormalCitation
435− , citationNoteNum = 0
436− , citationHash = 0
437− }
438− dominikInText = dominikCite { citationMode = AuthorInText }
439− in [
440− "Berkeley-style in-text citation" =:
441− "See @Dominik201408." =?>
442− (para $ "See "
443− <> cite [dominikInText] "@Dominik201408"
444− <> ".")
445−
446− , "Berkeley-style parenthetical citation list" =:
447− "[(cite): see; @Dominik201408;also @Pandoc; and others]" =?>
448− let pandocCite' = pandocCite {
449− citationPrefix = toList "also"
450− , citationSuffix = toList "and others"
451− }
452− dominikCite' = dominikCite {
453− citationPrefix = toList "see"
454− }
455− in (para $ cite [dominikCite', pandocCite'] "")
456−
457− , "Berkeley-style plain citation list" =:
458− "[cite: See; @Dominik201408; and @Pandoc; and others]" =?>
459− let pandocCite' = pandocInText {
460− citationPrefix = toList "and"
461− }
462− in (para $ "See "
463− <> cite [dominikInText] ""
464− <> "," <> space
465− <> cite [pandocCite'] ""
466− <> "," <> space <> "and others")
467− ]
468−
469− , "Inline LaTeX symbol" =:
470− "\\dots" =?>
471− para "…"
472−
473− , "Inline LaTeX command" =:
474− "\\textit{Emphasised}" =?>
475− para (emph "Emphasised")
476−
477− , "Inline LaTeX command with spaces" =:
478− "\\emph{Emphasis mine}" =?>
479− para (emph "Emphasis mine")
480−
481− , "Inline LaTeX math symbol" =:
482− "\\tau" =?>
483− para (emph "τ")
484−
485− , "Unknown inline LaTeX command" =:
486− "\\notacommand{foo}" =?>
487− para (rawInline "latex" "\\notacommand{foo}")
488−
489− , "Export snippet" =:
490− "@@html:<kbd>M-x org-agenda</kbd>@@" =?>
491− para (rawInline "html" "<kbd>M-x org-agenda</kbd>")
492−
493− , "MathML symbol in LaTeX-style" =:
494− "There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: '\\nbsp')." =?>
495− para ("There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: ' ').")
496−
497− , "MathML symbol in LaTeX-style, including braces" =:
498− "\\Aacute{}stor" =?>
499− para "Ástor"
500−
501− , "MathML copy sign" =:
502− "\\copy" =?>
503− para "©"
504−
505− , "MathML symbols, space separated" =:
506− "\\ForAll \\Auml" =?>
507− para "∀ Ä"
508−
509− , "LaTeX citation" =:
510− "\\cite{Coffee}" =?>
511− let citation = Citation
512− { citationId = "Coffee"
513− , citationPrefix = []
514− , citationSuffix = []
515− , citationMode = NormalCitation
516− , citationNoteNum = 0
517− , citationHash = 0}
518− in (para . cite [citation] $ rawInline "latex" "\\cite{Coffee}")
519−
520− , "Macro" =:
521− T.unlines [ "#+MACRO: HELLO /Hello, $1/"
522− , "{{{HELLO(World)}}}"
523− ] =?>
524− para (emph "Hello, World")
525−
526− , "Macro repeting its argument" =:
527− T.unlines [ "#+MACRO: HELLO $1$1"
528− , "{{{HELLO(moin)}}}"
529− ] =?>
530− para "moinmoin"
531−
532− , "Macro called with too few arguments" =:
533− T.unlines [ "#+MACRO: HELLO Foo $1 $2 Bar"
534− , "{{{HELLO()}}}"
535− ] =?>
536− para "Foo Bar"
537− ]
538−
539− , testGroup "Meta Information" $
540− [ "Comment" =:
541− "# Nothing to see here" =?>
542− (mempty::Blocks)
543−
544− , "Not a comment" =:
545− "#-tag" =?>
546− para "#-tag"
547−
548− , "Comment surrounded by Text" =:
549− T.unlines [ "Before"
550− , "# Comment"
551− , "After"
552− ] =?>
553− mconcat [ para "Before"
554− , para "After"
555− ]
556−
557− , "Title" =:
558− "#+TITLE: Hello, World" =?>
559− let titleInline = toList $ "Hello," <> space <> "World"
560− meta = setMeta "title" (MetaInlines titleInline) $ nullMeta
561− in Pandoc meta mempty
562−
563− , "Author" =:
564− "#+author: Albert /Emacs-Fanboy/ Krewinkel" =?>
565− let author = toList . spcSep $ [ "Albert", emph "Emacs-Fanboy", "Krewinkel" ]
566− meta = setMeta "author" (MetaList [MetaInlines author]) $ nullMeta
567− in Pandoc meta mempty
568−
569− , "Multiple authors" =:
570− "#+author: James Dewey Watson, Francis Harry Compton Crick " =?>
571− let watson = MetaInlines $ toList "James Dewey Watson"
572− crick = MetaInlines $ toList "Francis Harry Compton Crick"
573− meta = setMeta "author" (MetaList [watson, crick]) $ nullMeta
574− in Pandoc meta mempty
575−
576− , "Date" =:
577− "#+Date: Feb. *28*, 2014" =?>
578− let date = toList . spcSep $ [ "Feb.", (strong "28") <> ",", "2014" ]
579− meta = setMeta "date" (MetaInlines date) $ nullMeta
580− in Pandoc meta mempty
581−
582− , "Description" =:
583− "#+DESCRIPTION: Explanatory text" =?>
584− let description = "Explanatory text"
585− meta = setMeta "description" (MetaString description) $ nullMeta
586− in Pandoc meta mempty
587−
588− , "Properties drawer" =:
589− T.unlines [ " :PROPERTIES:"
590− , " :setting: foo"
591− , " :END:"
592− ] =?>
593− (mempty::Blocks)
594−
595− , "LaTeX_headers options are translated to header-includes" =:
596− "#+LaTeX_header: \\usepackage{tikz}" =?>
597− let latexInlines = rawInline "latex" "\\usepackage{tikz}"
598− inclList = MetaList [MetaInlines (toList latexInlines)]
599− meta = setMeta "header-includes" inclList nullMeta
600− in Pandoc meta mempty
601−
602− , "LaTeX_class option is translated to documentclass" =:
603− "#+LATEX_CLASS: article" =?>
604− let meta = setMeta "documentclass" (MetaString "article") nullMeta
605− in Pandoc meta mempty
606−
607− , "LaTeX_class_options is translated to classoption" =:
608− "#+LATEX_CLASS_OPTIONS: [a4paper]" =?>
609− let meta = setMeta "classoption" (MetaString "a4paper") nullMeta
610− in Pandoc meta mempty
611−
612− , "LaTeX_class_options is translated to classoption" =:
613− "#+html_head: <meta/>" =?>
614− let html = rawInline "html" "<meta/>"
615− inclList = MetaList [MetaInlines (toList html)]
616− meta = setMeta "header-includes" inclList nullMeta
617− in Pandoc meta mempty
618−
619− , "later meta definitions take precedence" =:
620− T.unlines [ "#+AUTHOR: this will not be used"
621− , "#+author: Max"
622− ] =?>
623− let author = MetaInlines [Str "Max"]
624− meta = setMeta "author" (MetaList [author]) $ nullMeta
625− in Pandoc meta mempty
626−
627− , "Logbook drawer" =:
628− T.unlines [ " :LogBook:"
629− , " - State \"DONE\" from \"TODO\" [2014-03-03 Mon 11:00]"
630− , " :END:"
631− ] =?>
632− (mempty::Blocks)
633−
634− , "Drawer surrounded by text" =:
635− T.unlines [ "Before"
636− , ":PROPERTIES:"
637− , ":END:"
638− , "After"
639− ] =?>
640− para "Before" <> para "After"
641−
642− , "Drawer markers must be the only text in the line" =:
643− T.unlines [ " :LOGBOOK: foo"
644− , " :END: bar"
645− ] =?>
646− para (":LOGBOOK: foo" <> softbreak <> ":END: bar")
647−
648− , "Drawers can be arbitrary" =:
649− T.unlines [ ":FOO:"
650− , "/bar/"
651− , ":END:"
652− ] =?>
653− divWith (mempty, ["FOO", "drawer"], mempty) (para $ emph "bar")
654−
655− , "Anchor reference" =:
656− T.unlines [ "<<link-here>> Target."
657− , ""
658− , "[[link-here][See here!]]"
659− ] =?>
660− (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
661− para (link "#link-here" "" ("See" <> space <> "here!")))
662−
663− , "Search links are read as emph" =:
664− "[[Wally][Where's Wally?]]" =?>
665− (para (emph $ "Where's" <> space <> "Wally?"))
666−
667− , "Link to nonexistent anchor" =:
668− T.unlines [ "<<link-here>> Target."
669− , ""
670− , "[[link$here][See here!]]"
671− ] =?>
672− (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
673− para (emph ("See" <> space <> "here!")))
674−
675− , "Link abbreviation" =:
676− T.unlines [ "#+LINK: wp https://en.wikipedia.org/wiki/%s"
677− , "[[wp:Org_mode][Wikipedia on Org-mode]]"
678− ] =?>
679− (para (link "https://en.wikipedia.org/wiki/Org_mode" ""
680− ("Wikipedia" <> space <> "on" <> space <> "Org-mode")))
681−
682− , "Link abbreviation, defined after first use" =:
683− T.unlines [ "[[zl:non-sense][Non-sense articles]]"
684− , "#+LINK: zl http://zeitlens.com/tags/%s.html"
685− ] =?>
686− (para (link "http://zeitlens.com/tags/non-sense.html" ""
687− ("Non-sense" <> space <> "articles")))
688−
689− , "Link abbreviation, URL encoded arguments" =:
690− T.unlines [ "#+link: expl http://example.com/%h/foo"
691− , "[[expl:Hello, World!][Moin!]]"
692− ] =?>
693− (para (link "http://example.com/Hello%2C%20World%21/foo" "" "Moin!"))
694−
695− , "Link abbreviation, append arguments" =:
696− T.unlines [ "#+link: expl http://example.com/"
697− , "[[expl:foo][bar]]"
698− ] =?>
699− (para (link "http://example.com/foo" "" "bar"))
700−
701−
702− , testGroup "export options"
703−
704− [ "disable simple sub/superscript syntax" =:
705− T.unlines [ "#+OPTIONS: ^:nil"
706− , "a^b"
707− ] =?>
708− para "a^b"
709−
710− , "directly select drawers to be exported" =:
711− T.unlines [ "#+OPTIONS: d:(\"IMPORTANT\")"
712− , ":IMPORTANT:"
713− , "23"
714− , ":END:"
715− , ":BORING:"
716− , "very boring"
717− , ":END:"
718− ] =?>
719− divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "23")
720−
721− , "exclude drawers from being exported" =:
722− T.unlines [ "#+OPTIONS: d:(not \"BORING\")"
723− , ":IMPORTANT:"
724− , "5"
725− , ":END:"
726− , ":BORING:"
727− , "very boring"
728− , ":END:"
729− ] =?>
730− divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "5")
731−
732− , "don't include archive trees" =:
733− T.unlines [ "#+OPTIONS: arch:nil"
734− , "* old :ARCHIVE:"
735− ] =?>
736− (mempty ::Blocks)
737−
738− , "include complete archive trees" =:
739− T.unlines [ "#+OPTIONS: arch:t"
740− , "* old :ARCHIVE:"
741− , " boring"
742− ] =?>
743− mconcat [ headerWith ("old", [], mempty) 1
744− ("old" <> space <> tagSpan "ARCHIVE")
745− , para "boring"
746− ]
747−
748− , "include archive tree header only" =:
749− T.unlines [ "#+OPTIONS: arch:headline"
750− , "* old :ARCHIVE:"
751− , " boring"
752− ] =?>
753− headerWith ("old", [], mempty) 1 ("old" <> space <> tagSpan "ARCHIVE")
754−
755− , "limit headline depth" =:
756− T.unlines [ "#+OPTIONS: H:2"
757− , "* top-level section"
758− , "** subsection"
759− , "*** list item 1"
760− , "*** list item 2"
761− ] =?>
762− mconcat [ headerWith ("top-level-section", [], []) 1 "top-level section"
763− , headerWith ("subsection", [], []) 2 "subsection"
764− , orderedList [ para "list item 1", para "list item 2" ]
765− ]
766−
767− , "turn all headlines into lists" =:
768− T.unlines [ "#+OPTIONS: H:0"
769− , "first block"
770− , "* top-level section 1"
771− , "** subsection"
772− , "* top-level section 2"
773− ] =?>
774− mconcat [ para "first block"
775− , orderedList
776− [ (para "top-level section 1" <>
777− orderedList [ para "subsection" ])
778− , para "top-level section 2" ]
779− ]
780−
781− , "preserve linebreaks as hard breaks" =:
782− T.unlines [ "#+OPTIONS: \\n:t"
783− , "first"
784− , "second"
785− ] =?>
786− para ("first" <> linebreak <> "second")
787−
788− , "disable author export" =:
789− T.unlines [ "#+OPTIONS: author:nil"
790− , "#+AUTHOR: ShyGuy"
791− ] =?>
792− Pandoc nullMeta mempty
793−
794− , "disable creator export" =:
795− T.unlines [ "#+OPTIONS: creator:nil"
796− , "#+creator: The Architect"
797− ] =?>
798− Pandoc nullMeta mempty
799−
800− , "disable email export" =:
801− T.unlines [ "#+OPTIONS: email:nil"
802− , "#+email: no-mail-please@example.com"
803− ] =?>
804− Pandoc nullMeta mempty
805−
806− , "disable inclusion of todo keywords" =:
807− T.unlines [ "#+OPTIONS: todo:nil"
808− , "** DONE todo export"
809− ] =?>
810− headerWith ("todo-export", [], []) 2 "todo export"
811−
812− , "remove tags from headlines" =:
813− T.unlines [ "#+OPTIONS: tags:nil"
814− , "* Headline :hello:world:"
815− ] =?>
816− headerWith ("headline", [], mempty) 1 "Headline"
817− ]
818− ]
819−
820− , testGroup "Basic Blocks" $
821− [ "Paragraph" =:
822− "Paragraph\n" =?>
823− para "Paragraph"
824−
825− , testGroup "headers" $
826− [ "First Level Header" =:
827− "* Headline\n" =?>
828− headerWith ("headline", [], []) 1 "Headline"
829−
830− , "Third Level Header" =:
831− "*** Third Level Headline\n" =?>
832− headerWith ("third-level-headline", [], [])
833− 3
834− ("Third" <> space <> "Level" <> space <> "Headline")
835−
836− , "Compact Headers with Paragraph" =:
837− T.unlines [ "* First Level"
838− , "** Second Level"
839− , " Text"
840− ] =?>
841− mconcat [ headerWith ("first-level", [], [])
842− 1
843− ("First" <> space <> "Level")
844− , headerWith ("second-level", [], [])
845− 2
846− ("Second" <> space <> "Level")
847− , para "Text"
848− ]
849−
850− , "Separated Headers with Paragraph" =:
851− T.unlines [ "* First Level"
852− , ""
853− , "** Second Level"
854− , ""
855− , " Text"
856− ] =?>
857− mconcat [ headerWith ("first-level", [], [])
858− 1
859− ("First" <> space <> "Level")
860− , headerWith ("second-level", [], [])
861− 2
862− ("Second" <> space <> "Level")
863− , para "Text"
864− ]
865−
866− , "Headers not preceded by a blank line" =:
867− T.unlines [ "** eat dinner"
868− , "Spaghetti and meatballs tonight."
869− , "** walk dog"
870− ] =?>
871− mconcat [ headerWith ("eat-dinner", [], [])
872− 2
873− ("eat" <> space <> "dinner")
874− , para $ spcSep [ "Spaghetti", "and", "meatballs", "tonight." ]
875− , headerWith ("walk-dog", [], [])
876− 2
877− ("walk" <> space <> "dog")
878− ]
879−
880− , testGroup "Todo keywords"
881− [ "Header with known todo keyword" =:
882− "* TODO header" =?>
883− let todoSpan = spanWith ("", ["todo", "TODO"], []) "TODO"
884− in headerWith ("header", [], []) 1 (todoSpan <> space <> "header")
885−
886− , "Header marked as done" =:
887− "* DONE header" =?>
888− let todoSpan = spanWith ("", ["done", "DONE"], []) "DONE"
889− in headerWith ("header", [], []) 1 (todoSpan <> space <> "header")
890−
891− , "Header with unknown todo keyword" =:
892− "* WAITING header" =?>
893− headerWith ("waiting-header", [], []) 1 "WAITING header"
894−
895− , "Custom todo keywords" =:
896− T.unlines [ "#+TODO: WAITING CANCELLED"
897− , "* WAITING compile"
898− , "* CANCELLED lunch"
899− ] =?>
900− let todoSpan = spanWith ("", ["todo", "WAITING"], []) "WAITING"
901− doneSpan = spanWith ("", ["done", "CANCELLED"], []) "CANCELLED"
902− in headerWith ("compile", [], []) 1 (todoSpan <> space <> "compile")
903− <> headerWith ("lunch", [], []) 1 (doneSpan <> space <> "lunch")
904−
905− , "Custom todo keywords with multiple done-states" =:
906− T.unlines [ "#+TODO: WAITING | DONE CANCELLED "
907− , "* WAITING compile"
908− , "* CANCELLED lunch"
909− , "* DONE todo-feature"
910− ] =?>
911− let waiting = spanWith ("", ["todo", "WAITING"], []) "WAITING"
912− cancelled = spanWith ("", ["done", "CANCELLED"], []) "CANCELLED"
913− done = spanWith ("", ["done", "DONE"], []) "DONE"
914− in headerWith ("compile", [], []) 1 (waiting <> space <> "compile")
915− <> headerWith ("lunch", [], []) 1 (cancelled <> space <> "lunch")
916− <> headerWith ("todo-feature", [], []) 1 (done <> space <> "todo-feature")
917− ]
918−
919− , "Tagged headers" =:
920− T.unlines [ "* Personal :PERSONAL:"
921− , "** Call Mom :@PHONE:"
922− , "** Call John :@PHONE:JOHN: "
923− ] =?>
924− mconcat [ headerWith ("personal", [], [])
925− 1
926− ("Personal " <> tagSpan "PERSONAL")
927− , headerWith ("call-mom", [], [])
928− 2
929− ("Call Mom " <> tagSpan "@PHONE")
930− , headerWith ("call-john", [], [])
931− 2
932− ("Call John " <> tagSpan "@PHONE" <> "\160" <> tagSpan "JOHN")
933− ]
934−
935− , "Untagged header containing colons" =:
936− "* This: is not: tagged" =?>
937− headerWith ("this-is-not-tagged", [], []) 1 "This: is not: tagged"
938−
939− , "Header starting with strokeout text" =:
940− T.unlines [ "foo"
941− , ""
942− , "* +thing+ other thing"
943− ] =?>
944− mconcat [ para "foo"
945− , headerWith ("thing-other-thing", [], [])
946− 1
947− ((strikeout "thing") <> " other thing")
948− ]
949−
950− , "Comment Trees" =:
951− T.unlines [ "* COMMENT A comment tree"
952− , " Not much going on here"
953− , "** This will be dropped"
954− , "* Comment tree above"
955− ] =?>
956− headerWith ("comment-tree-above", [], []) 1 "Comment tree above"
957−
958− , "Nothing but a COMMENT header" =:
959− "* COMMENT Test" =?>
960− (mempty::Blocks)
961−
962− , "Tree with :noexport:" =:
963− T.unlines [ "* Should be ignored :archive:noexport:old:"
964− , "** Old stuff"
965− , " This is not going to be exported"
966− ] =?>
967− (mempty::Blocks)
968−
969− , "Subtree with :noexport:" =:
970− T.unlines [ "* Exported"
971− , "** This isn't exported :noexport:"
972− , "*** This neither"
973− , "** But this is"
974− ] =?>
975− mconcat [ headerWith ("exported", [], []) 1 "Exported"
976− , headerWith ("but-this-is", [], []) 2 "But this is"
977− ]
978−
979− , "Preferences are treated as header attributes" =:
980− T.unlines [ "* foo"
981− , " :PROPERTIES:"
982− , " :custom_id: fubar"
983− , " :bar: baz"
984− , " :END:"
985− ] =?>
986− headerWith ("fubar", [], [("bar", "baz")]) 1 "foo"
987−
988−
989− , "Headers marked with a unnumbered property get a class of the same name" =:
990− T.unlines [ "* Not numbered"
991− , " :PROPERTIES:"
992− , " :UNNUMBERED: t"
993− , " :END:"
994− ] =?>
995− headerWith ("not-numbered", ["unnumbered"], []) 1 "Not numbered"
996− ]
997− , "Paragraph starting with an asterisk" =:
998− "*five" =?>
999− para "*five"
1000−
1001− , "Paragraph containing asterisk at beginning of line" =:
1002− T.unlines [ "lucky"
1003− , "*star"
1004− ] =?>
1005− para ("lucky" <> softbreak <> "*star")
1006−
1007− , "Example block" =:
1008− T.unlines [ ": echo hello"
1009− , ": echo dear tester"
1010− ] =?>
1011− codeBlockWith ("", ["example"], []) "echo hello\necho dear tester\n"
1012−
1013− , "Example block surrounded by text" =:
1014− T.unlines [ "Greetings"
1015− , ": echo hello"
1016− , ": echo dear tester"
1017− , "Bye"
1018− ] =?>
1019− mconcat [ para "Greetings"
1020− , codeBlockWith ("", ["example"], [])
1021− "echo hello\necho dear tester\n"
1022− , para "Bye"
1023− ]
1024−
1025− , "Horizontal Rule" =:
1026− T.unlines [ "before"
1027− , "-----"
1028− , "after"
1029− ] =?>
1030− mconcat [ para "before"
1031− , horizontalRule
1032− , para "after"
1033− ]
1034−
1035− , "Not a Horizontal Rule" =:
1036− "----- em and en dash" =?>
1037− para "\8212\8211 em and en dash"
1038−
1039− , "Comment Block" =:
1040− T.unlines [ "#+BEGIN_COMMENT"
1041− , "stuff"
1042− , "bla"
1043− , "#+END_COMMENT"] =?>
1044− (mempty::Blocks)
1045−
1046− , testGroup "Figures" $
1047− [ "Figure" =:
1048− T.unlines [ "#+caption: A very courageous man."
1049− , "#+name: goodguy"
1050− , "[[file:edward.jpg]]"
1051− ] =?>
1052− para (image "edward.jpg" "fig:goodguy" "A very courageous man.")
1053−
1054− , "Figure with no name" =:
1055− T.unlines [ "#+caption: I've been through the desert on this"
1056− , "[[file:horse.png]]"
1057− ] =?>
1058− para (image "horse.png" "fig:" "I've been through the desert on this")
1059−
1060− , "Figure with `fig:` prefix in name" =:
1061− T.unlines [ "#+caption: Used as a metapher in evolutionary biology."
1062− , "#+name: fig:redqueen"
1063− , "[[./the-red-queen.jpg]]"
1064− ] =?>
1065− para (image "./the-red-queen.jpg" "fig:redqueen"
1066− "Used as a metapher in evolutionary biology.")
1067−
1068− , "Figure with HTML attributes" =:
1069− T.unlines [ "#+CAPTION: mah brain just explodid"
1070− , "#+NAME: lambdacat"
1071− , "#+ATTR_HTML: :style color: blue :role button"
1072− , "[[file:lambdacat.jpg]]"
1073− ] =?>
1074− let kv = [("style", "color: blue"), ("role", "button")]
1075− name = "fig:lambdacat"
1076− caption = "mah brain just explodid"
1077− in para (imageWith (mempty, mempty, kv) "lambdacat.jpg" name caption)
1078−
1079− , "Labelled figure" =:
1080− T.unlines [ "#+CAPTION: My figure"
1081− , "#+LABEL: fig:myfig"
1082− , "[[file:blub.png]]"
1083− ] =?>
1084− let attr = ("fig:myfig", mempty, mempty)
1085− in para (imageWith attr "blub.png" "fig:" "My figure")
1086−
1087− , "Figure with empty caption" =:
1088− T.unlines [ "#+CAPTION:"
1089− , "[[file:guess.jpg]]"
1090− ] =?>
1091− para (image "guess.jpg" "fig:" "")
1092− ]
1093−
1094− , testGroup "Footnotes"
1095− [ "Footnote" =:
1096− T.unlines [ "A footnote[1]"
1097− , ""
1098− , "[1] First paragraph"
1099− , ""
1100− , "second paragraph"
1101− ] =?>
1102− para (mconcat
1103− [ "A", space, "footnote"
1104− , note $ mconcat [ para ("First" <> space <> "paragraph")
1105− , para ("second" <> space <> "paragraph")
1106− ]
1107− ])
1108−
1109− , "Two footnotes" =:
1110− T.unlines [ "Footnotes[fn:1][fn:2]"
1111− , ""
1112− , "[fn:1] First note."
1113− , ""
1114− , "[fn:2] Second note."
1115− ] =?>
1116− para (mconcat
1117− [ "Footnotes"
1118− , note $ para ("First" <> space <> "note.")
1119− , note $ para ("Second" <> space <> "note.")
1120− ])
1121−
1122− , "Emphasized text before footnote" =:
1123− T.unlines [ "/text/[fn:1]"
1124− , ""
1125− , "[fn:1] unicorn"
1126− ] =?>
1127− para (mconcat
1128− [ emph "text"
1129− , note . para $ "unicorn"
1130− ])
1131−
1132− , "Footnote that starts with emphasized text" =:
1133− T.unlines [ "text[fn:1]"
1134− , ""
1135− , "[fn:1] /emphasized/"
1136− ] =?>
1137− para (mconcat
1138− [ "text"
1139− , note . para $ emph "emphasized"
1140− ])
1141−
1142− , "Footnote followed by header" =:
1143− T.unlines [ "Another note[fn:yay]"
1144− , ""
1145− , "[fn:yay] This is great!"
1146− , ""
1147− , "** Headline"
1148− ] =?>
1149− mconcat
1150− [ para (mconcat
1151− [ "Another", space, "note"
1152− , note $ para ("This" <> space <> "is" <> space <> "great!")
1153− ])
1154− , headerWith ("headline", [], []) 2 "Headline"
1155− ]
1156−
1157− , "Footnote followed by two blank lines" =:
1158− T.unlines [ "footnote[fn:blanklines]"
1159− , ""
1160− , "[fn:blanklines] followed by blank lines"
1161− , ""
1162− , ""
1163− , "next"
1164− ] =?>
1165− mconcat
1166− [ para ("footnote" <> note (para "followed by blank lines"))
1167− , para "next"
1168− ]
1169− ]
1170− ]
1171−
1172− , testGroup "Lists" $
1173− [ "Simple Bullet Lists" =:
1174− ("- Item1\n" <>
1175− "- Item2\n") =?>
1176− bulletList [ plain "Item1"
1177− , plain "Item2"
1178− ]
1179−
1180− , "Indented Bullet Lists" =:
1181− (" - Item1\n" <>
1182− " - Item2\n") =?>
1183− bulletList [ plain "Item1"
1184− , plain "Item2"
1185− ]
1186−
1187− , "Unindented *" =:
1188− ("- Item1\n" <>
1189− "* Item2\n") =?>
1190− bulletList [ plain "Item1"
1191− ] <>
1192− headerWith ("item2", [], []) 1 "Item2"
1193−
1194− , "Multi-line Bullet Lists" =:
1195− ("- *Fat\n" <>
1196− " Tony*\n" <>
1197− "- /Sideshow\n" <>
1198− " Bob/") =?>
1199− bulletList [ plain $ strong ("Fat" <> softbreak <> "Tony")
1200− , plain $ emph ("Sideshow" <> softbreak <> "Bob")
1201− ]
1202−
1203− , "Nested Bullet Lists" =:
1204− ("- Discovery\n" <>
1205− " + One More Time\n" <>
1206− " + Harder, Better, Faster, Stronger\n" <>
1207− "- Homework\n" <>
1208− " + Around the World\n"<>
1209− "- Human After All\n" <>
1210− " + Technologic\n" <>
1211− " + Robot Rock\n") =?>
1212− bulletList [ mconcat
1213− [ plain "Discovery"
1214− , bulletList [ plain ("One" <> space <>
1215− "More" <> space <>
1216− "Time")
1217− , plain ("Harder," <> space <>
1218− "Better," <> space <>
1219− "Faster," <> space <>
1220− "Stronger")
1221− ]
1222− ]
1223− , mconcat
1224− [ plain "Homework"
1225− , bulletList [ plain ("Around" <> space <>
1226− "the" <> space <>
1227− "World")
1228− ]
1229− ]
1230− , mconcat
1231− [ plain ("Human" <> space <> "After" <> space <> "All")
1232− , bulletList [ plain "Technologic"
1233− , plain ("Robot" <> space <> "Rock")
1234− ]
1235− ]
1236− ]
1237−
1238− , "Bullet List with Decreasing Indent" =:
1239− (" - Discovery\n\
1240− \ - Human After All\n") =?>
1241− mconcat [ bulletList [ plain "Discovery" ]
1242− , bulletList [ plain ("Human" <> space <> "After" <> space <> "All")]
1243− ]
1244−
1245− , "Header follows Bullet List" =:
1246− (" - Discovery\n\
1247− \ - Human After All\n\
1248− \* Homework") =?>
1249− mconcat [ bulletList [ plain "Discovery"
1250− , plain ("Human" <> space <> "After" <> space <> "All")
1251− ]
1252− , headerWith ("homework", [], []) 1 "Homework"
1253− ]
1254−
1255− , "Bullet List Unindented with trailing Header" =:
1256− ("- Discovery\n\
1257− \- Homework\n\
1258− \* NotValidListItem") =?>
1259− mconcat [ bulletList [ plain "Discovery"
1260− , plain "Homework"
1261− ]
1262− , headerWith ("notvalidlistitem", [], []) 1 "NotValidListItem"
1263− ]
1264−
1265− , "Empty bullet points" =:
1266− T.unlines [ "-"
1267− , "- "
1268− ] =?>
1269− bulletList [ plain "", plain "" ]
1270−
1271− , "Simple Ordered List" =:
1272− ("1. Item1\n" <>
1273− "2. Item2\n") =?>
1274− let listStyle = (1, DefaultStyle, DefaultDelim)
1275− listStructure = [ plain "Item1"
1276− , plain "Item2"
1277− ]
1278− in orderedListWith listStyle listStructure
1279−
1280− , "Simple Ordered List with Parens" =:
1281− ("1) Item1\n" <>
1282− "2) Item2\n") =?>
1283− let listStyle = (1, DefaultStyle, DefaultDelim)
1284− listStructure = [ plain "Item1"
1285− , plain "Item2"
1286− ]
1287− in orderedListWith listStyle listStructure
1288−
1289− , "Indented Ordered List" =:
1290− (" 1. Item1\n" <>
1291− " 2. Item2\n") =?>
1292− let listStyle = (1, DefaultStyle, DefaultDelim)
1293− listStructure = [ plain "Item1"
1294− , plain "Item2"
1295− ]
1296− in orderedListWith listStyle listStructure
1297−
1298− , "Empty ordered list item" =:
1299− T.unlines [ "1."
1300− , "3. "
1301− ] =?>
1302− orderedList [ plain "", plain "" ]
1303−
1304− , "Nested Ordered Lists" =:
1305− ("1. One\n" <>
1306− " 1. One-One\n" <>
1307− " 2. One-Two\n" <>
1308− "2. Two\n" <>
1309− " 1. Two-One\n"<>
1310− " 2. Two-Two\n") =?>
1311− let listStyle = (1, DefaultStyle, DefaultDelim)
1312− listStructure = [ mconcat
1313− [ plain "One"
1314− , orderedList [ plain "One-One"
1315− , plain "One-Two"
1316− ]
1317− ]
1318− , mconcat
1319− [ plain "Two"
1320− , orderedList [ plain "Two-One"
1321− , plain "Two-Two"
1322− ]
1323− ]
1324− ]
1325− in orderedListWith listStyle listStructure
1326−
1327− , "Ordered List in Bullet List" =:
1328− ("- Emacs\n" <>
1329− " 1. Org\n") =?>
1330− bulletList [ (plain "Emacs") <>
1331− (orderedList [ plain "Org"])
1332− ]
1333−
1334− , "Bullet List in Ordered List" =:
1335− ("1. GNU\n" <>
1336− " - Freedom\n") =?>
1337− orderedList [ (plain "GNU") <> bulletList [ (plain "Freedom") ] ]
1338−
1339− , "Definition List" =:
1340− T.unlines [ "- PLL :: phase-locked loop"
1341− , "- TTL ::"
1342− , " transistor-transistor logic"
1343− , "- PSK :: phase-shift keying"
1344− , ""
1345− , " a digital modulation scheme"
1346− ] =?>
1347− definitionList [ ("PLL", [ plain $ "phase-locked" <> space <> "loop" ])
1348− , ("TTL", [ plain $ "transistor-transistor" <> space <>
1349− "logic" ])
1350− , ("PSK", [ mconcat
1351− [ para $ "phase-shift" <> space <> "keying"
1352− , para $ spcSep [ "a", "digital"
1353− , "modulation", "scheme" ]
1354− ]
1355− ])
1356− ]
1357− , "Definition list with multi-word term" =:
1358− " - Elijah Wood :: He plays Frodo" =?>
1359− definitionList [ ("Elijah" <> space <> "Wood", [plain $ "He" <> space <> "plays" <> space <> "Frodo"])]
1360− , "Compact definition list" =:
1361− T.unlines [ "- ATP :: adenosine 5' triphosphate"
1362− , "- DNA :: deoxyribonucleic acid"
1363− , "- PCR :: polymerase chain reaction"
1364− , ""
1365− ] =?>
1366− definitionList
1367− [ ("ATP", [ plain $ spcSep [ "adenosine", "5'", "triphosphate" ] ])
1368− , ("DNA", [ plain $ spcSep [ "deoxyribonucleic", "acid" ] ])
1369− , ("PCR", [ plain $ spcSep [ "polymerase", "chain", "reaction" ] ])
1370− ]
1371−
1372− , "Definition List With Trailing Header" =:
1373− "- definition :: list\n\
1374− \- cool :: defs\n\
1375− \* header" =?>
1376− mconcat [ definitionList [ ("definition", [plain "list"])
1377− , ("cool", [plain "defs"])
1378− ]
1379− , headerWith ("header", [], []) 1 "header"
1380− ]
1381−
1382− , "Definition lists double-colon markers must be surrounded by whitespace" =:
1383− "- std::cout" =?>
1384− bulletList [ plain "std::cout" ]
1385−
1386− , "Loose bullet list" =:
1387− T.unlines [ "- apple"
1388− , ""
1389− , "- orange"
1390− , ""
1391− , "- peach"
1392− ] =?>
1393− bulletList [ para "apple"
1394− , para "orange"
1395− , para "peach"
1396− ]
1397−
1398− , "Recognize preceding paragraphs in non-list contexts" =:
1399− T.unlines [ "CLOSED: [2015-10-19 Mon 15:03]"
1400− , "- Note taken on [2015-10-19 Mon 13:24]"
1401− ] =?>
1402− mconcat [ para "CLOSED: [2015-10-19 Mon 15:03]"
1403− , bulletList [ plain "Note taken on [2015-10-19 Mon 13:24]" ]
1404− ]
1405− ]
1406−
1407− , testGroup "Tables"
1408− [ "Single cell table" =:
1409− "|Test|" =?>
1410− simpleTable' 1 mempty [[plain "Test"]]
1411−
1412− , "Multi cell table" =:
1413− "| One | Two |" =?>
1414− simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
1415−
1416− , "Multi line table" =:
1417− T.unlines [ "| One |"
1418− , "| Two |"
1419− , "| Three |"
1420− ] =?>
1421− simpleTable' 1 mempty
1422− [ [ plain "One" ]
1423− , [ plain "Two" ]
1424− , [ plain "Three" ]
1425− ]
1426−
1427− , "Empty table" =:
1428− "||" =?>
1429− simpleTable' 1 mempty [[mempty]]
1430−
1431− , "Glider Table" =:
1432− T.unlines [ "| 1 | 0 | 0 |"
1433− , "| 0 | 1 | 1 |"
1434− , "| 1 | 1 | 0 |"
1435− ] =?>
1436− simpleTable' 3 mempty
1437− [ [ plain "1", plain "0", plain "0" ]
1438− , [ plain "0", plain "1", plain "1" ]
1439− , [ plain "1", plain "1", plain "0" ]
1440− ]
1441−
1442− , "Table between Paragraphs" =:
1443− T.unlines [ "Before"
1444− , "| One | Two |"
1445− , "After"
1446− ] =?>
1447− mconcat [ para "Before"
1448− , simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
1449− , para "After"
1450− ]
1451−
1452− , "Table with Header" =:
1453− T.unlines [ "| Species | Status |"
1454− , "|--------------+--------------|"
1455− , "| cervisiae | domesticated |"
1456− , "| paradoxus | wild |"
1457− ] =?>
1458− simpleTable [ plain "Species", plain "Status" ]
1459− [ [ plain "cervisiae", plain "domesticated" ]
1460− , [ plain "paradoxus", plain "wild" ]
1461− ]
1462−
1463− , "Table with final hline" =:
1464− T.unlines [ "| cervisiae | domesticated |"
1465− , "| paradoxus | wild |"
1466− , "|--------------+--------------|"
1467− ] =?>
1468− simpleTable' 2 mempty
1469− [ [ plain "cervisiae", plain "domesticated" ]
1470− , [ plain "paradoxus", plain "wild" ]
1471− ]
1472−
1473− , "Table in a box" =:
1474− T.unlines [ "|---------|---------|"
1475− , "| static | Haskell |"
1476− , "| dynamic | Lisp |"
1477− , "|---------+---------|"
1478− ] =?>
1479− simpleTable' 2 mempty
1480− [ [ plain "static", plain "Haskell" ]
1481− , [ plain "dynamic", plain "Lisp" ]
1482− ]
1483−
1484− , "Table with empty cells" =:
1485− "|||c|" =?>
1486− simpleTable' 3 mempty [[mempty, mempty, plain "c"]]
1487−
1488− , "Table with empty rows" =:
1489− T.unlines [ "| first |"
1490− , "| |"
1491− , "| third |"
1492− ] =?>
1493− simpleTable' 1 mempty [[plain "first"], [mempty], [plain "third"]]
1494−
1495− , "Table with alignment row" =:
1496− T.unlines [ "| Numbers | Text | More |"
1497− , "| <c> | <r> | |"
1498− , "| 1 | One | foo |"
1499− , "| 2 | Two | bar |"
1500− ] =?>
1501− table "" (zip [AlignCenter, AlignRight, AlignDefault] [0, 0, 0])
1502− []
1503− [ [ plain "Numbers", plain "Text", plain "More" ]
1504− , [ plain "1" , plain "One" , plain "foo" ]
1505− , [ plain "2" , plain "Two" , plain "bar" ]
1506− ]
1507−
1508− , "Pipe within text doesn't start a table" =:
1509− "Ceci n'est pas une | pipe " =?>
1510− para (spcSep [ "Ceci", "n'est", "pas", "une", "|", "pipe" ])
1511−
1512− , "Missing pipe at end of row" =:
1513− "|incomplete-but-valid" =?>
1514− simpleTable' 1 mempty [ [ plain "incomplete-but-valid" ] ]
1515−
1516− , "Table with differing row lengths" =:
1517− T.unlines [ "| Numbers | Text "
1518− , "|-"
1519− , "| <c> | <r> |"
1520− , "| 1 | One | foo |"
1521− , "| 2"
1522− ] =?>
1523− table "" (zip [AlignCenter, AlignRight] [0, 0])
1524− [ plain "Numbers", plain "Text" ]
1525− [ [ plain "1" , plain "One" , plain "foo" ]
1526− , [ plain "2" ]
1527− ]
1528−
1529− , "Table with caption" =:
1530− T.unlines [ "#+CAPTION: Hitchhiker's Multiplication Table"
1531− , "| x | 6 |"
1532− , "| 9 | 42 |"
1533− ] =?>
1534− table "Hitchhiker's Multiplication Table"
1535− [(AlignDefault, 0), (AlignDefault, 0)]
1536− []
1537− [ [ plain "x", plain "6" ]
1538− , [ plain "9", plain "42" ]
1539− ]
1540− ]
1541−
1542− , testGroup "Blocks and fragments"
1543− [ "Source block" =:
1544− T.unlines [ " #+BEGIN_SRC haskell"
1545− , " main = putStrLn greeting"
1546− , " where greeting = \"moin\""
1547− , " #+END_SRC" ] =?>
1548− let attr' = ("", ["haskell"], [])
1549− code' = "main = putStrLn greeting\n" <>
1550− " where greeting = \"moin\"\n"
1551− in codeBlockWith attr' code'
1552−
1553− , "Source block with indented code" =:
1554− T.unlines [ " #+BEGIN_SRC haskell"
1555− , " main = putStrLn greeting"
1556− , " where greeting = \"moin\""
1557− , " #+END_SRC" ] =?>
1558− let attr' = ("", ["haskell"], [])
1559− code' = "main = putStrLn greeting\n" <>
1560− " where greeting = \"moin\"\n"
1561− in codeBlockWith attr' code'
1562−
1563− , "Source block with tab-indented code" =:
1564− T.unlines [ "\t#+BEGIN_SRC haskell"
1565− , "\tmain = putStrLn greeting"
1566− , "\t where greeting = \"moin\""
1567− , "\t#+END_SRC" ] =?>
1568− let attr' = ("", ["haskell"], [])
1569− code' = "main = putStrLn greeting\n" <>
1570− " where greeting = \"moin\"\n"
1571− in codeBlockWith attr' code'
1572−
1573− , "Empty source block" =:
1574− T.unlines [ " #+BEGIN_SRC haskell"
1575− , " #+END_SRC" ] =?>
1576− let attr' = ("", ["haskell"], [])
1577− code' = ""
1578− in codeBlockWith attr' code'
1579−
1580− , "Source block between paragraphs" =:
1581− T.unlines [ "Low German greeting"
1582− , " #+BEGIN_SRC haskell"
1583− , " main = putStrLn greeting"
1584− , " where greeting = \"Moin!\""
1585− , " #+END_SRC" ] =?>
1586− let attr' = ("", ["haskell"], [])
1587− code' = "main = putStrLn greeting\n" <>
1588− " where greeting = \"Moin!\"\n"
1589− in mconcat [ para $ spcSep [ "Low", "German", "greeting" ]
1590− , codeBlockWith attr' code'
1591− ]
1592− , "Source block with babel arguments" =:
1593− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
1594− , "(progn (message \"Hello, World!\")"
1595− , " (+ 23 42))"
1596− , "#+END_SRC" ] =?>
1597− let classes = [ "commonlisp" ] -- as kate doesn't know emacs-lisp syntax
1598− params = [ ("org-language", "emacs-lisp")
1599− , ("exports", "both")
1600− ]
1601− code' = unlines [ "(progn (message \"Hello, World!\")"
1602− , " (+ 23 42))" ]
1603− in codeBlockWith ("", classes, params) code'
1604−
1605− , "Source block with results and :exports both" =:
1606− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
1607− , "(progn (message \"Hello, World!\")"
1608− , " (+ 23 42))"
1609− , "#+END_SRC"
1610− , ""
1611− , "#+RESULTS:"
1612− , ": 65"] =?>
1613− let classes = [ "commonlisp" ]
1614− params = [ ("org-language", "emacs-lisp")
1615− , ("exports", "both")
1616− ]
1617− code' = unlines [ "(progn (message \"Hello, World!\")"
1618− , " (+ 23 42))" ]
1619− results' = "65\n"
1620− in codeBlockWith ("", classes, params) code'
1621− <>
1622− codeBlockWith ("", ["example"], []) results'
1623−
1624− , "Source block with results and :exports code" =:
1625− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports code"
1626− , "(progn (message \"Hello, World!\")"
1627− , " (+ 23 42))"
1628− , "#+END_SRC"
1629− , ""
1630− , "#+RESULTS:"
1631− , ": 65" ] =?>
1632− let classes = [ "commonlisp" ]
1633− params = [ ("org-language", "emacs-lisp")
1634− , ("exports", "code")
1635− ]
1636− code' = unlines [ "(progn (message \"Hello, World!\")"
1637− , " (+ 23 42))" ]
1638− in codeBlockWith ("", classes, params) code'
1639−
1640− , "Source block with results and :exports results" =:
1641− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports results"
1642− , "(progn (message \"Hello, World!\")"
1643− , " (+ 23 42))"
1644− , "#+END_SRC"
1645− , ""
1646− , "#+RESULTS:"
1647− , ": 65" ] =?>
1648− let results' = "65\n"
1649− in codeBlockWith ("", ["example"], []) results'
1650−
1651− , "Source block with results and :exports none" =:
1652− T.unlines [ "#+BEGIN_SRC emacs-lisp :exports none"
1653− , "(progn (message \"Hello, World!\")"
1654− , " (+ 23 42))"
1655− , "#+END_SRC"
1656− , ""
1657− , "#+RESULTS:"
1658− , ": 65" ] =?>
1659− (mempty :: Blocks)
1660−
1661− , "Source block with toggling header arguments" =:
1662− T.unlines [ "#+BEGIN_SRC sh :noeval"
1663− , "echo $HOME"
1664− , "#+END_SRC"
1665− ] =?>
1666− let classes = [ "bash" ]
1667− params = [ ("org-language", "sh"), ("noeval", "yes") ]
1668− in codeBlockWith ("", classes, params) "echo $HOME\n"
1669−
1670− , "Source block with line number switch" =:
1671− T.unlines [ "#+BEGIN_SRC sh -n 10"
1672− , ":() { :|:& };:"
1673− , "#+END_SRC"
1674− ] =?>
1675− let classes = [ "bash", "numberLines" ]
1676− params = [ ("org-language", "sh"), ("startFrom", "10") ]
1677− in codeBlockWith ("", classes, params) ":() { :|:& };:\n"
1678−
1679− , "Source block with multi-word parameter values" =:
1680− T.unlines [ "#+BEGIN_SRC dot :cmdline -Kdot -Tpng "
1681− , "digraph { id [label=\"ID\"] }"
1682− , "#+END_SRC"
1683− ] =?>
1684− let classes = [ "dot" ]
1685− params = [ ("cmdline", "-Kdot -Tpng") ]
1686− in codeBlockWith ("", classes, params) "digraph { id [label=\"ID\"] }\n"
1687−
1688− , "Example block" =:
1689− T.unlines [ "#+begin_example"
1690− , "A chosen representation of"
1691− , "a rule."
1692− , "#+eND_exAMPle"
1693− ] =?>
1694− codeBlockWith ("", ["example"], [])
1695− "A chosen representation of\na rule.\n"
1696−
1697− , "HTML block" =:
1698− T.unlines [ "#+BEGIN_HTML"
1699− , "<aside>HTML5 is pretty nice.</aside>"
1700− , "#+END_HTML"
1701− ] =?>
1702− rawBlock "html" "<aside>HTML5 is pretty nice.</aside>\n"
1703−
1704− , "Quote block" =:
1705− T.unlines [ "#+BEGIN_QUOTE"
1706− , "/Niemand/ hat die Absicht, eine Mauer zu errichten!"
1707− , "#+END_QUOTE"
1708− ] =?>
1709− blockQuote (para (spcSep [ emph "Niemand", "hat", "die", "Absicht,"
1710− , "eine", "Mauer", "zu", "errichten!"
1711− ]))
1712−
1713− , "Verse block" =:
1714− T.unlines [ "The first lines of Goethe's /Faust/:"
1715− , "#+begin_verse"
1716− , "Habe nun, ach! Philosophie,"
1717− , "Juristerei und Medizin,"
1718− , "Und leider auch Theologie!"
1719− , "Durchaus studiert, mit heißem Bemühn."
1720− , "#+end_verse"
1721− ] =?>
1722− mconcat
1723− [ para $ spcSep [ "The", "first", "lines", "of"
1724− , "Goethe's", emph "Faust" <> ":"]
1725− , lineBlock
1726− [ "Habe nun, ach! Philosophie,"
1727− , "Juristerei und Medizin,"
1728− , "Und leider auch Theologie!"
1729− , "Durchaus studiert, mit heißem Bemühn."
1730− ]
1731− ]
1732−
1733− , "Verse block with blank lines" =:
1734− T.unlines [ "#+BEGIN_VERSE"
1735− , "foo"
1736− , ""
1737− , "bar"
1738− , "#+END_VERSE"
1739− ] =?>
1740− lineBlock [ "foo", mempty, "bar" ]
1741−
1742− , "Verse block with varying indentation" =:
1743− T.unlines [ "#+BEGIN_VERSE"
1744− , " hello darkness"
1745− , "my old friend"
1746− , "#+END_VERSE"
1747− ] =?>
1748− lineBlock [ "\160\160hello darkness", "my old friend" ]
1749−
1750− , "Raw block LaTeX" =:
1751− T.unlines [ "#+BEGIN_LaTeX"
1752− , "The category $\\cat{Set}$ is adhesive."
1753− , "#+END_LaTeX"
1754− ] =?>
1755− rawBlock "latex" "The category $\\cat{Set}$ is adhesive.\n"
1756−
1757− , "Raw LaTeX line" =:
1758− "#+LATEX: \\let\\foo\\bar" =?>
1759− rawBlock "latex" "\\let\\foo\\bar"
1760−
1761− , "Raw Beamer line" =:
1762− "#+beamer: \\pause" =?>
1763− rawBlock "beamer" "\\pause"
1764−
1765− , "Raw HTML line" =:
1766− "#+HTML: <aside>not important</aside>" =?>
1767− rawBlock "html" "<aside>not important</aside>"
1768−
1769− , "Export block HTML" =:
1770− T.unlines [ "#+BEGIN_export html"
1771− , "<samp>Hello, World!</samp>"
1772− , "#+END_export"
1773− ] =?>
1774− rawBlock "html" "<samp>Hello, World!</samp>\n"
1775−
1776− , "LaTeX fragment" =:
1777− T.unlines [ "\\begin{equation}"
1778− , "X_i = \\begin{cases}"
1779− , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) = \\alpha(i)\\\\"
1780− , " C_{\\alpha(i)} & \\text{otherwise}"
1781− , " \\end{cases}"
1782− , "\\end{equation}"
1783− ] =?>
1784− rawBlock "latex"
1785− (unlines [ "\\begin{equation}"
1786− , "X_i = \\begin{cases}"
1787− , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) =" <>
1788− " \\alpha(i)\\\\"
1789− , " C_{\\alpha(i)} & \\text{otherwise}"
1790− , " \\end{cases}"
1791− , "\\end{equation}"
1792− ])
1793−
1794− , "Code block with caption" =:
1795− T.unlines [ "#+CAPTION: Functor laws in Haskell"
1796− , "#+NAME: functor-laws"
1797− , "#+BEGIN_SRC haskell"
1798− , "fmap id = id"
1799− , "fmap (p . q) = (fmap p) . (fmap q)"
1800− , "#+END_SRC"
1801− ] =?>
1802− divWith
1803− nullAttr
1804− (mappend
1805− (plain $ spanWith ("", ["label"], [])
1806− (spcSep [ "Functor", "laws", "in", "Haskell" ]))
1807− (codeBlockWith ("functor-laws", ["haskell"], [])
1808− (unlines [ "fmap id = id"
1809− , "fmap (p . q) = (fmap p) . (fmap q)"
1810− ])))
1811−
1812− , "Convert blank lines in blocks to single newlines" =:
1813− T.unlines [ "#+begin_html"
1814− , ""
1815− , "<span>boring</span>"
1816− , ""
1817− , "#+end_html"
1818− ] =?>
1819− rawBlock "html" "\n<span>boring</span>\n\n"
1820−
1821− , "Accept `ATTR_HTML` attributes for generic block" =:
1822− T.unlines [ "#+ATTR_HTML: :title hello, world :id test :class fun code"
1823− , "#+BEGIN_TEST"
1824− , "nonsense"
1825− , "#+END_TEST"
1826− ] =?>
1827− let attr = ("test", ["fun", "code", "TEST"], [("title", "hello, world")])
1828− in divWith attr (para "nonsense")
1829−
1830− , "Non-letter chars in source block parameters" =:
1831− T.unlines [ "#+BEGIN_SRC C :tangle xxxx.c :city Zürich"
1832− , "code body"
1833− , "#+END_SRC"
1834− ] =?>
1835− let params = [ ("org-language", "C")
1836− , ("tangle", "xxxx.c")
1837− , ("city", "Zürich")
1838− ]
1839− in codeBlockWith ( "", ["c"], params) "code body\n"
1840− ]
1841−
1842− , testGroup "Smart punctuation"
1843− [ test orgSmart "quote before ellipses"
1844− ("'...hi'"
1845− =?> para (singleQuoted "…hi"))
1846−
1847− , test orgSmart "apostrophe before emph"
1848− ("D'oh! A l'/aide/!"
1849− =?> para ("D’oh! A l’" <> emph "aide" <> "!"))
1850−
1851− , test orgSmart "apostrophe in French"
1852− ("À l'arrivée de la guerre, le thème de l'«impossibilité du socialisme»"
1853− =?> para "À l’arrivée de la guerre, le thème de l’«impossibilité du socialisme»")
1854−
1855− , test orgSmart "Quotes cannot occur at the end of emphasized text"
1856− ("/say \"yes\"/" =?>
1857− para ("/say" <> space <> doubleQuoted "yes" <> "/"))
1858−
1859− , test orgSmart "Dashes are allowed at the borders of emphasis'"
1860− ("/foo---/" =?>
1861− para (emph "foo—"))
1862−
1863− , test orgSmart "Single quotes can be followed by emphasized text"
1864− ("Singles on the '/meat market/'" =?>
1865− para ("Singles on the " <> (singleQuoted $ emph "meat market")))
1866−
1867− , test orgSmart "Double quotes can be followed by emphasized text"
1868− ("Double income, no kids: \"/DINK/\"" =?>
1869− para ("Double income, no kids: " <> (doubleQuoted $ emph "DINK")))
1870− ]
12+ [ testGroup "Inlines" Inline.tests
13+ , testGroup "Basic Blocks" Block.tests
14+ , testGroup "Meta Information" Meta.tests
15+ , testGroup "Directives" Directive.tests
187116 ]
test/Tests/Readers/Org/Block.hsnew file+192
@@ -0,0 +1,192 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block (tests) where
3+
4+import Test.Tasty (TestTree, testGroup)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+import qualified Tests.Readers.Org.Block.CodeBlock as CodeBlock
10+import qualified Tests.Readers.Org.Block.Figure as Figure
11+import qualified Tests.Readers.Org.Block.Header as Header
12+import qualified Tests.Readers.Org.Block.List as List
13+import qualified Tests.Readers.Org.Block.Table as Table
14+
15+tests :: [TestTree]
16+tests =
17+ [ "Paragraph" =:
18+ "Paragraph\n" =?>
19+ para "Paragraph"
20+
21+ , "Paragraph starting with an asterisk" =:
22+ "*five" =?>
23+ para "*five"
24+
25+ , "Paragraph containing asterisk at beginning of line" =:
26+ T.unlines [ "lucky"
27+ , "*star"
28+ ] =?>
29+ para ("lucky" <> softbreak <> "*star")
30+
31+ , "Example block" =:
32+ T.unlines [ ": echo hello"
33+ , ": echo dear tester"
34+ ] =?>
35+ codeBlockWith ("", ["example"], []) "echo hello\necho dear tester\n"
36+
37+ , "Example block surrounded by text" =:
38+ T.unlines [ "Greetings"
39+ , ": echo hello"
40+ , ": echo dear tester"
41+ , "Bye"
42+ ] =?>
43+ mconcat [ para "Greetings"
44+ , codeBlockWith ("", ["example"], [])
45+ "echo hello\necho dear tester\n"
46+ , para "Bye"
47+ ]
48+
49+ , "Horizontal Rule" =:
50+ T.unlines [ "before"
51+ , "-----"
52+ , "after"
53+ ] =?>
54+ mconcat [ para "before"
55+ , horizontalRule
56+ , para "after"
57+ ]
58+
59+ , "Not a Horizontal Rule" =:
60+ "----- em and en dash" =?>
61+ para "\8212\8211 em and en dash"
62+
63+ , "Comment Block" =:
64+ T.unlines [ "#+BEGIN_COMMENT"
65+ , "stuff"
66+ , "bla"
67+ , "#+END_COMMENT"] =?>
68+ (mempty::Blocks)
69+
70+ , testGroup "Blocks and fragments"
71+ [ "HTML block" =:
72+ T.unlines [ "#+BEGIN_HTML"
73+ , "<aside>HTML5 is pretty nice.</aside>"
74+ , "#+END_HTML"
75+ ] =?>
76+ rawBlock "html" "<aside>HTML5 is pretty nice.</aside>\n"
77+
78+ , "Quote block" =:
79+ T.unlines [ "#+BEGIN_QUOTE"
80+ , "/Niemand/ hat die Absicht, eine Mauer zu errichten!"
81+ , "#+END_QUOTE"
82+ ] =?>
83+ blockQuote (para (spcSep [ emph "Niemand", "hat", "die", "Absicht,"
84+ , "eine", "Mauer", "zu", "errichten!"
85+ ]))
86+
87+ , "Verse block" =:
88+ T.unlines [ "The first lines of Goethe's /Faust/:"
89+ , "#+begin_verse"
90+ , "Habe nun, ach! Philosophie,"
91+ , "Juristerei und Medizin,"
92+ , "Und leider auch Theologie!"
93+ , "Durchaus studiert, mit heißem Bemühn."
94+ , "#+end_verse"
95+ ] =?>
96+ mconcat
97+ [ para $ spcSep [ "The", "first", "lines", "of"
98+ , "Goethe's", emph "Faust" <> ":"]
99+ , lineBlock
100+ [ "Habe nun, ach! Philosophie,"
101+ , "Juristerei und Medizin,"
102+ , "Und leider auch Theologie!"
103+ , "Durchaus studiert, mit heißem Bemühn."
104+ ]
105+ ]
106+
107+ , "Verse block with blank lines" =:
108+ T.unlines [ "#+BEGIN_VERSE"
109+ , "foo"
110+ , ""
111+ , "bar"
112+ , "#+END_VERSE"
113+ ] =?>
114+ lineBlock [ "foo", mempty, "bar" ]
115+
116+ , "Verse block with varying indentation" =:
117+ T.unlines [ "#+BEGIN_VERSE"
118+ , " hello darkness"
119+ , "my old friend"
120+ , "#+END_VERSE"
121+ ] =?>
122+ lineBlock [ "\160\160hello darkness", "my old friend" ]
123+
124+ , "Raw block LaTeX" =:
125+ T.unlines [ "#+BEGIN_LaTeX"
126+ , "The category $\\cat{Set}$ is adhesive."
127+ , "#+END_LaTeX"
128+ ] =?>
129+ rawBlock "latex" "The category $\\cat{Set}$ is adhesive.\n"
130+
131+ , "Raw LaTeX line" =:
132+ "#+LATEX: \\let\\foo\\bar" =?>
133+ rawBlock "latex" "\\let\\foo\\bar"
134+
135+ , "Raw Beamer line" =:
136+ "#+beamer: \\pause" =?>
137+ rawBlock "beamer" "\\pause"
138+
139+ , "Raw HTML line" =:
140+ "#+HTML: <aside>not important</aside>" =?>
141+ rawBlock "html" "<aside>not important</aside>"
142+
143+ , "Export block HTML" =:
144+ T.unlines [ "#+BEGIN_export html"
145+ , "<samp>Hello, World!</samp>"
146+ , "#+END_export"
147+ ] =?>
148+ rawBlock "html" "<samp>Hello, World!</samp>\n"
149+
150+ , "LaTeX fragment" =:
151+ T.unlines [ "\\begin{equation}"
152+ , "X_i = \\begin{cases}"
153+ , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) = \\alpha(i)\\\\"
154+ , " C_{\\alpha(i)} & \\text{otherwise}"
155+ , " \\end{cases}"
156+ , "\\end{equation}"
157+ ] =?>
158+ rawBlock "latex"
159+ (unlines [ "\\begin{equation}"
160+ , "X_i = \\begin{cases}"
161+ , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) =" <>
162+ " \\alpha(i)\\\\"
163+ , " C_{\\alpha(i)} & \\text{otherwise}"
164+ , " \\end{cases}"
165+ , "\\end{equation}"
166+ ])
167+
168+ , "Convert blank lines in blocks to single newlines" =:
169+ T.unlines [ "#+begin_html"
170+ , ""
171+ , "<span>boring</span>"
172+ , ""
173+ , "#+end_html"
174+ ] =?>
175+ rawBlock "html" "\n<span>boring</span>\n\n"
176+
177+ , "Accept `ATTR_HTML` attributes for generic block" =:
178+ T.unlines [ "#+ATTR_HTML: :title hello, world :id test :class fun code"
179+ , "#+BEGIN_TEST"
180+ , "nonsense"
181+ , "#+END_TEST"
182+ ] =?>
183+ let attr = ("test", ["fun", "code", "TEST"], [("title", "hello, world")])
184+ in divWith attr (para "nonsense")
185+ ]
186+
187+ , testGroup "Headers" Header.tests
188+ , testGroup "Figures" Figure.tests
189+ , testGroup "Lists" List.tests
190+ , testGroup "CodeBlocks" CodeBlock.tests
191+ , testGroup "Tables" Table.tests
192+ ]
test/Tests/Readers/Org/Block/CodeBlock.hsnew file+194
@@ -0,0 +1,194 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.CodeBlock (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "Source block" =:
13+ T.unlines [ " #+BEGIN_SRC haskell"
14+ , " main = putStrLn greeting"
15+ , " where greeting = \"moin\""
16+ , " #+END_SRC" ] =?>
17+ let attr' = ("", ["haskell"], [])
18+ code' = "main = putStrLn greeting\n" <>
19+ " where greeting = \"moin\"\n"
20+ in codeBlockWith attr' code'
21+
22+ , "Source block with indented code" =:
23+ T.unlines [ " #+BEGIN_SRC haskell"
24+ , " main = putStrLn greeting"
25+ , " where greeting = \"moin\""
26+ , " #+END_SRC" ] =?>
27+ let attr' = ("", ["haskell"], [])
28+ code' = "main = putStrLn greeting\n" <>
29+ " where greeting = \"moin\"\n"
30+ in codeBlockWith attr' code'
31+
32+ , "Source block with tab-indented code" =:
33+ T.unlines [ "\t#+BEGIN_SRC haskell"
34+ , "\tmain = putStrLn greeting"
35+ , "\t where greeting = \"moin\""
36+ , "\t#+END_SRC" ] =?>
37+ let attr' = ("", ["haskell"], [])
38+ code' = "main = putStrLn greeting\n" <>
39+ " where greeting = \"moin\"\n"
40+ in codeBlockWith attr' code'
41+
42+ , "Empty source block" =:
43+ T.unlines [ " #+BEGIN_SRC haskell"
44+ , " #+END_SRC" ] =?>
45+ let attr' = ("", ["haskell"], [])
46+ code' = ""
47+ in codeBlockWith attr' code'
48+
49+ , "Source block between paragraphs" =:
50+ T.unlines [ "Low German greeting"
51+ , " #+BEGIN_SRC haskell"
52+ , " main = putStrLn greeting"
53+ , " where greeting = \"Moin!\""
54+ , " #+END_SRC" ] =?>
55+ let attr' = ("", ["haskell"], [])
56+ code' = "main = putStrLn greeting\n" <>
57+ " where greeting = \"Moin!\"\n"
58+ in mconcat [ para $ spcSep [ "Low", "German", "greeting" ]
59+ , codeBlockWith attr' code'
60+ ]
61+ , "Source block with babel arguments" =:
62+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
63+ , "(progn (message \"Hello, World!\")"
64+ , " (+ 23 42))"
65+ , "#+END_SRC" ] =?>
66+ let classes = [ "commonlisp" ] -- as kate doesn't know emacs-lisp syntax
67+ params = [ ("org-language", "emacs-lisp")
68+ , ("exports", "both")
69+ ]
70+ code' = unlines [ "(progn (message \"Hello, World!\")"
71+ , " (+ 23 42))" ]
72+ in codeBlockWith ("", classes, params) code'
73+
74+ , "Source block with results and :exports both" =:
75+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
76+ , "(progn (message \"Hello, World!\")"
77+ , " (+ 23 42))"
78+ , "#+END_SRC"
79+ , ""
80+ , "#+RESULTS:"
81+ , ": 65"] =?>
82+ let classes = [ "commonlisp" ]
83+ params = [ ("org-language", "emacs-lisp")
84+ , ("exports", "both")
85+ ]
86+ code' = unlines [ "(progn (message \"Hello, World!\")"
87+ , " (+ 23 42))" ]
88+ results' = "65\n"
89+ in codeBlockWith ("", classes, params) code'
90+ <>
91+ codeBlockWith ("", ["example"], []) results'
92+
93+ , "Source block with results and :exports code" =:
94+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports code"
95+ , "(progn (message \"Hello, World!\")"
96+ , " (+ 23 42))"
97+ , "#+END_SRC"
98+ , ""
99+ , "#+RESULTS:"
100+ , ": 65" ] =?>
101+ let classes = [ "commonlisp" ]
102+ params = [ ("org-language", "emacs-lisp")
103+ , ("exports", "code")
104+ ]
105+ code' = unlines [ "(progn (message \"Hello, World!\")"
106+ , " (+ 23 42))" ]
107+ in codeBlockWith ("", classes, params) code'
108+
109+ , "Source block with results and :exports results" =:
110+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports results"
111+ , "(progn (message \"Hello, World!\")"
112+ , " (+ 23 42))"
113+ , "#+END_SRC"
114+ , ""
115+ , "#+RESULTS:"
116+ , ": 65" ] =?>
117+ let results' = "65\n"
118+ in codeBlockWith ("", ["example"], []) results'
119+
120+ , "Source block with results and :exports none" =:
121+ T.unlines [ "#+BEGIN_SRC emacs-lisp :exports none"
122+ , "(progn (message \"Hello, World!\")"
123+ , " (+ 23 42))"
124+ , "#+END_SRC"
125+ , ""
126+ , "#+RESULTS:"
127+ , ": 65" ] =?>
128+ (mempty :: Blocks)
129+
130+ , "Source block with toggling header arguments" =:
131+ T.unlines [ "#+BEGIN_SRC sh :noeval"
132+ , "echo $HOME"
133+ , "#+END_SRC"
134+ ] =?>
135+ let classes = [ "bash" ]
136+ params = [ ("org-language", "sh"), ("noeval", "yes") ]
137+ in codeBlockWith ("", classes, params) "echo $HOME\n"
138+
139+ , "Source block with line number switch" =:
140+ T.unlines [ "#+BEGIN_SRC sh -n 10"
141+ , ":() { :|:& };:"
142+ , "#+END_SRC"
143+ ] =?>
144+ let classes = [ "bash", "numberLines" ]
145+ params = [ ("org-language", "sh"), ("startFrom", "10") ]
146+ in codeBlockWith ("", classes, params) ":() { :|:& };:\n"
147+
148+ , "Source block with multi-word parameter values" =:
149+ T.unlines [ "#+BEGIN_SRC dot :cmdline -Kdot -Tpng "
150+ , "digraph { id [label=\"ID\"] }"
151+ , "#+END_SRC"
152+ ] =?>
153+ let classes = [ "dot" ]
154+ params = [ ("cmdline", "-Kdot -Tpng") ]
155+ in codeBlockWith ("", classes, params) "digraph { id [label=\"ID\"] }\n"
156+
157+ , "Example block" =:
158+ T.unlines [ "#+begin_example"
159+ , "A chosen representation of"
160+ , "a rule."
161+ , "#+eND_exAMPle"
162+ ] =?>
163+ codeBlockWith ("", ["example"], [])
164+ "A chosen representation of\na rule.\n"
165+
166+ , "Code block with caption" =:
167+ T.unlines [ "#+CAPTION: Functor laws in Haskell"
168+ , "#+NAME: functor-laws"
169+ , "#+BEGIN_SRC haskell"
170+ , "fmap id = id"
171+ , "fmap (p . q) = (fmap p) . (fmap q)"
172+ , "#+END_SRC"
173+ ] =?>
174+ divWith
175+ nullAttr
176+ (mappend
177+ (plain $ spanWith ("", ["label"], [])
178+ (spcSep [ "Functor", "laws", "in", "Haskell" ]))
179+ (codeBlockWith ("functor-laws", ["haskell"], [])
180+ (unlines [ "fmap id = id"
181+ , "fmap (p . q) = (fmap p) . (fmap q)"
182+ ])))
183+
184+ , "Non-letter chars in source block parameters" =:
185+ T.unlines [ "#+BEGIN_SRC C :tangle xxxx.c :city Zürich"
186+ , "code body"
187+ , "#+END_SRC"
188+ ] =?>
189+ let params = [ ("org-language", "C")
190+ , ("tangle", "xxxx.c")
191+ , ("city", "Zürich")
192+ ]
193+ in codeBlockWith ( "", ["c"], params) "code body\n"
194+ ]
test/Tests/Readers/Org/Block/Figure.hsnew file+57
@@ -0,0 +1,57 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.Figure (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:))
7+import Text.Pandoc.Builder (image, imageWith, para)
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "Figure" =:
13+ T.unlines [ "#+caption: A very courageous man."
14+ , "#+name: goodguy"
15+ , "[[file:edward.jpg]]"
16+ ] =?>
17+ para (image "edward.jpg" "fig:goodguy" "A very courageous man.")
18+
19+ , "Figure with no name" =:
20+ T.unlines [ "#+caption: I've been through the desert on this"
21+ , "[[file:horse.png]]"
22+ ] =?>
23+ para (image "horse.png" "fig:" "I've been through the desert on this")
24+
25+ , "Figure with `fig:` prefix in name" =:
26+ T.unlines [ "#+caption: Used as a metapher in evolutionary biology."
27+ , "#+name: fig:redqueen"
28+ , "[[./the-red-queen.jpg]]"
29+ ] =?>
30+ para (image "./the-red-queen.jpg" "fig:redqueen"
31+ "Used as a metapher in evolutionary biology.")
32+
33+ , "Figure with HTML attributes" =:
34+ T.unlines [ "#+CAPTION: mah brain just explodid"
35+ , "#+NAME: lambdacat"
36+ , "#+ATTR_HTML: :style color: blue :role button"
37+ , "[[file:lambdacat.jpg]]"
38+ ] =?>
39+ let kv = [("style", "color: blue"), ("role", "button")]
40+ name = "fig:lambdacat"
41+ caption = "mah brain just explodid"
42+ in para (imageWith (mempty, mempty, kv) "lambdacat.jpg" name caption)
43+
44+ , "Labelled figure" =:
45+ T.unlines [ "#+CAPTION: My figure"
46+ , "#+LABEL: fig:myfig"
47+ , "[[file:blub.png]]"
48+ ] =?>
49+ let attr = ("fig:myfig", mempty, mempty)
50+ in para (imageWith attr "blub.png" "fig:" "My figure")
51+
52+ , "Figure with empty caption" =:
53+ T.unlines [ "#+CAPTION:"
54+ , "[[file:guess.jpg]]"
55+ ] =?>
56+ para (image "guess.jpg" "fig:" "")
57+ ]
test/Tests/Readers/Org/Block/Header.hsnew file+182
@@ -0,0 +1,182 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.Header (tests) where
3+
4+import Test.Tasty (TestTree, testGroup)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep, tagSpan)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "First Level Header" =:
13+ "* Headline\n" =?>
14+ headerWith ("headline", [], []) 1 "Headline"
15+
16+ , "Third Level Header" =:
17+ "*** Third Level Headline\n" =?>
18+ headerWith ("third-level-headline", [], [])
19+ 3
20+ ("Third" <> space <> "Level" <> space <> "Headline")
21+
22+ , "Compact Headers with Paragraph" =:
23+ T.unlines [ "* First Level"
24+ , "** Second Level"
25+ , " Text"
26+ ] =?>
27+ mconcat [ headerWith ("first-level", [], [])
28+ 1
29+ ("First" <> space <> "Level")
30+ , headerWith ("second-level", [], [])
31+ 2
32+ ("Second" <> space <> "Level")
33+ , para "Text"
34+ ]
35+
36+ , "Separated Headers with Paragraph" =:
37+ T.unlines [ "* First Level"
38+ , ""
39+ , "** Second Level"
40+ , ""
41+ , " Text"
42+ ] =?>
43+ mconcat [ headerWith ("first-level", [], [])
44+ 1
45+ ("First" <> space <> "Level")
46+ , headerWith ("second-level", [], [])
47+ 2
48+ ("Second" <> space <> "Level")
49+ , para "Text"
50+ ]
51+
52+ , "Headers not preceded by a blank line" =:
53+ T.unlines [ "** eat dinner"
54+ , "Spaghetti and meatballs tonight."
55+ , "** walk dog"
56+ ] =?>
57+ mconcat [ headerWith ("eat-dinner", [], [])
58+ 2
59+ ("eat" <> space <> "dinner")
60+ , para $ spcSep [ "Spaghetti", "and", "meatballs", "tonight." ]
61+ , headerWith ("walk-dog", [], [])
62+ 2
63+ ("walk" <> space <> "dog")
64+ ]
65+
66+ , testGroup "Todo keywords"
67+ [ "Header with known todo keyword" =:
68+ "* TODO header" =?>
69+ let todoSpan = spanWith ("", ["todo", "TODO"], []) "TODO"
70+ in headerWith ("header", [], []) 1 (todoSpan <> space <> "header")
71+
72+ , "Header marked as done" =:
73+ "* DONE header" =?>
74+ let todoSpan = spanWith ("", ["done", "DONE"], []) "DONE"
75+ in headerWith ("header", [], []) 1 (todoSpan <> space <> "header")
76+
77+ , "Header with unknown todo keyword" =:
78+ "* WAITING header" =?>
79+ headerWith ("waiting-header", [], []) 1 "WAITING header"
80+
81+ , "Custom todo keywords" =:
82+ T.unlines [ "#+TODO: WAITING CANCELLED"
83+ , "* WAITING compile"
84+ , "* CANCELLED lunch"
85+ ] =?>
86+ let todoSpan = spanWith ("", ["todo", "WAITING"], []) "WAITING"
87+ doneSpan = spanWith ("", ["done", "CANCELLED"], []) "CANCELLED"
88+ in headerWith ("compile", [], []) 1 (todoSpan <> space <> "compile")
89+ <> headerWith ("lunch", [], []) 1 (doneSpan <> space <> "lunch")
90+
91+ , "Custom todo keywords with multiple done-states" =:
92+ T.unlines [ "#+TODO: WAITING | DONE CANCELLED "
93+ , "* WAITING compile"
94+ , "* CANCELLED lunch"
95+ , "* DONE todo-feature"
96+ ] =?>
97+ let waiting = spanWith ("", ["todo", "WAITING"], []) "WAITING"
98+ cancelled = spanWith ("", ["done", "CANCELLED"], []) "CANCELLED"
99+ done = spanWith ("", ["done", "DONE"], []) "DONE"
100+ in headerWith ("compile", [], []) 1 (waiting <> space <> "compile")
101+ <> headerWith ("lunch", [], []) 1 (cancelled <> space <> "lunch")
102+ <> headerWith ("todo-feature", [], []) 1 (done <> space <> "todo-feature")
103+ ]
104+
105+ , "Tagged headers" =:
106+ T.unlines [ "* Personal :PERSONAL:"
107+ , "** Call Mom :@PHONE:"
108+ , "** Call John :@PHONE:JOHN: "
109+ ] =?>
110+ mconcat [ headerWith ("personal", [], [])
111+ 1
112+ ("Personal " <> tagSpan "PERSONAL")
113+ , headerWith ("call-mom", [], [])
114+ 2
115+ ("Call Mom " <> tagSpan "@PHONE")
116+ , headerWith ("call-john", [], [])
117+ 2
118+ ("Call John " <> tagSpan "@PHONE" <> "\160" <> tagSpan "JOHN")
119+ ]
120+
121+ , "Untagged header containing colons" =:
122+ "* This: is not: tagged" =?>
123+ headerWith ("this-is-not-tagged", [], []) 1 "This: is not: tagged"
124+
125+ , "Header starting with strokeout text" =:
126+ T.unlines [ "foo"
127+ , ""
128+ , "* +thing+ other thing"
129+ ] =?>
130+ mconcat [ para "foo"
131+ , headerWith ("thing-other-thing", [], [])
132+ 1
133+ ((strikeout "thing") <> " other thing")
134+ ]
135+
136+ , "Comment Trees" =:
137+ T.unlines [ "* COMMENT A comment tree"
138+ , " Not much going on here"
139+ , "** This will be dropped"
140+ , "* Comment tree above"
141+ ] =?>
142+ headerWith ("comment-tree-above", [], []) 1 "Comment tree above"
143+
144+ , "Nothing but a COMMENT header" =:
145+ "* COMMENT Test" =?>
146+ (mempty::Blocks)
147+
148+ , "Tree with :noexport:" =:
149+ T.unlines [ "* Should be ignored :archive:noexport:old:"
150+ , "** Old stuff"
151+ , " This is not going to be exported"
152+ ] =?>
153+ (mempty::Blocks)
154+
155+ , "Subtree with :noexport:" =:
156+ T.unlines [ "* Exported"
157+ , "** This isn't exported :noexport:"
158+ , "*** This neither"
159+ , "** But this is"
160+ ] =?>
161+ mconcat [ headerWith ("exported", [], []) 1 "Exported"
162+ , headerWith ("but-this-is", [], []) 2 "But this is"
163+ ]
164+
165+ , "Preferences are treated as header attributes" =:
166+ T.unlines [ "* foo"
167+ , " :PROPERTIES:"
168+ , " :custom_id: fubar"
169+ , " :bar: baz"
170+ , " :END:"
171+ ] =?>
172+ headerWith ("fubar", [], [("bar", "baz")]) 1 "foo"
173+
174+
175+ , "Headers marked with a unnumbered property get a class of the same name" =:
176+ T.unlines [ "* Not numbered"
177+ , " :PROPERTIES:"
178+ , " :UNNUMBERED: t"
179+ , " :END:"
180+ ] =?>
181+ headerWith ("not-numbered", ["unnumbered"], []) 1 "Not numbered"
182+ ]
test/Tests/Readers/Org/Block/List.hsnew file+244
@@ -0,0 +1,244 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.List (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "Simple Bullet Lists" =:
13+ ("- Item1\n" <>
14+ "- Item2\n") =?>
15+ bulletList [ plain "Item1"
16+ , plain "Item2"
17+ ]
18+
19+ , "Indented Bullet Lists" =:
20+ (" - Item1\n" <>
21+ " - Item2\n") =?>
22+ bulletList [ plain "Item1"
23+ , plain "Item2"
24+ ]
25+
26+ , "Unindented *" =:
27+ ("- Item1\n" <>
28+ "* Item2\n") =?>
29+ bulletList [ plain "Item1"
30+ ] <>
31+ headerWith ("item2", [], []) 1 "Item2"
32+
33+ , "Multi-line Bullet Lists" =:
34+ ("- *Fat\n" <>
35+ " Tony*\n" <>
36+ "- /Sideshow\n" <>
37+ " Bob/") =?>
38+ bulletList [ plain $ strong ("Fat" <> softbreak <> "Tony")
39+ , plain $ emph ("Sideshow" <> softbreak <> "Bob")
40+ ]
41+
42+ , "Nested Bullet Lists" =:
43+ ("- Discovery\n" <>
44+ " + One More Time\n" <>
45+ " + Harder, Better, Faster, Stronger\n" <>
46+ "- Homework\n" <>
47+ " + Around the World\n"<>
48+ "- Human After All\n" <>
49+ " + Technologic\n" <>
50+ " + Robot Rock\n") =?>
51+ bulletList [ mconcat
52+ [ plain "Discovery"
53+ , bulletList [ plain ("One" <> space <>
54+ "More" <> space <>
55+ "Time")
56+ , plain ("Harder," <> space <>
57+ "Better," <> space <>
58+ "Faster," <> space <>
59+ "Stronger")
60+ ]
61+ ]
62+ , mconcat
63+ [ plain "Homework"
64+ , bulletList [ plain ("Around" <> space <>
65+ "the" <> space <>
66+ "World")
67+ ]
68+ ]
69+ , mconcat
70+ [ plain ("Human" <> space <> "After" <> space <> "All")
71+ , bulletList [ plain "Technologic"
72+ , plain ("Robot" <> space <> "Rock")
73+ ]
74+ ]
75+ ]
76+
77+ , "Bullet List with Decreasing Indent" =:
78+ (" - Discovery\n\
79+ \ - Human After All\n") =?>
80+ mconcat [ bulletList [ plain "Discovery" ]
81+ , bulletList [ plain ("Human" <> space <> "After" <> space <> "All")]
82+ ]
83+
84+ , "Header follows Bullet List" =:
85+ (" - Discovery\n\
86+ \ - Human After All\n\
87+ \* Homework") =?>
88+ mconcat [ bulletList [ plain "Discovery"
89+ , plain ("Human" <> space <> "After" <> space <> "All")
90+ ]
91+ , headerWith ("homework", [], []) 1 "Homework"
92+ ]
93+
94+ , "Bullet List Unindented with trailing Header" =:
95+ ("- Discovery\n\
96+ \- Homework\n\
97+ \* NotValidListItem") =?>
98+ mconcat [ bulletList [ plain "Discovery"
99+ , plain "Homework"
100+ ]
101+ , headerWith ("notvalidlistitem", [], []) 1 "NotValidListItem"
102+ ]
103+
104+ , "Empty bullet points" =:
105+ T.unlines [ "-"
106+ , "- "
107+ ] =?>
108+ bulletList [ plain "", plain "" ]
109+
110+ , "Simple Ordered List" =:
111+ ("1. Item1\n" <>
112+ "2. Item2\n") =?>
113+ let listStyle = (1, DefaultStyle, DefaultDelim)
114+ listStructure = [ plain "Item1"
115+ , plain "Item2"
116+ ]
117+ in orderedListWith listStyle listStructure
118+
119+ , "Simple Ordered List with Parens" =:
120+ ("1) Item1\n" <>
121+ "2) Item2\n") =?>
122+ let listStyle = (1, DefaultStyle, DefaultDelim)
123+ listStructure = [ plain "Item1"
124+ , plain "Item2"
125+ ]
126+ in orderedListWith listStyle listStructure
127+
128+ , "Indented Ordered List" =:
129+ (" 1. Item1\n" <>
130+ " 2. Item2\n") =?>
131+ let listStyle = (1, DefaultStyle, DefaultDelim)
132+ listStructure = [ plain "Item1"
133+ , plain "Item2"
134+ ]
135+ in orderedListWith listStyle listStructure
136+
137+ , "Empty ordered list item" =:
138+ T.unlines [ "1."
139+ , "3. "
140+ ] =?>
141+ orderedList [ plain "", plain "" ]
142+
143+ , "Nested Ordered Lists" =:
144+ ("1. One\n" <>
145+ " 1. One-One\n" <>
146+ " 2. One-Two\n" <>
147+ "2. Two\n" <>
148+ " 1. Two-One\n"<>
149+ " 2. Two-Two\n") =?>
150+ let listStyle = (1, DefaultStyle, DefaultDelim)
151+ listStructure = [ mconcat
152+ [ plain "One"
153+ , orderedList [ plain "One-One"
154+ , plain "One-Two"
155+ ]
156+ ]
157+ , mconcat
158+ [ plain "Two"
159+ , orderedList [ plain "Two-One"
160+ , plain "Two-Two"
161+ ]
162+ ]
163+ ]
164+ in orderedListWith listStyle listStructure
165+
166+ , "Ordered List in Bullet List" =:
167+ ("- Emacs\n" <>
168+ " 1. Org\n") =?>
169+ bulletList [ (plain "Emacs") <>
170+ (orderedList [ plain "Org"])
171+ ]
172+
173+ , "Bullet List in Ordered List" =:
174+ ("1. GNU\n" <>
175+ " - Freedom\n") =?>
176+ orderedList [ (plain "GNU") <> bulletList [ (plain "Freedom") ] ]
177+
178+ , "Definition List" =:
179+ T.unlines [ "- PLL :: phase-locked loop"
180+ , "- TTL ::"
181+ , " transistor-transistor logic"
182+ , "- PSK :: phase-shift keying"
183+ , ""
184+ , " a digital modulation scheme"
185+ ] =?>
186+ definitionList [ ("PLL", [ plain $ "phase-locked" <> space <> "loop" ])
187+ , ("TTL", [ plain $ "transistor-transistor" <> space <>
188+ "logic" ])
189+ , ("PSK", [ mconcat
190+ [ para $ "phase-shift" <> space <> "keying"
191+ , para $ spcSep [ "a", "digital"
192+ , "modulation", "scheme" ]
193+ ]
194+ ])
195+ ]
196+ , "Definition list with multi-word term" =:
197+ " - Elijah Wood :: He plays Frodo" =?>
198+ definitionList [ ("Elijah" <> space <> "Wood", [plain $ "He" <> space <> "plays" <> space <> "Frodo"])]
199+ , "Compact definition list" =:
200+ T.unlines [ "- ATP :: adenosine 5' triphosphate"
201+ , "- DNA :: deoxyribonucleic acid"
202+ , "- PCR :: polymerase chain reaction"
203+ , ""
204+ ] =?>
205+ definitionList
206+ [ ("ATP", [ plain $ spcSep [ "adenosine", "5'", "triphosphate" ] ])
207+ , ("DNA", [ plain $ spcSep [ "deoxyribonucleic", "acid" ] ])
208+ , ("PCR", [ plain $ spcSep [ "polymerase", "chain", "reaction" ] ])
209+ ]
210+
211+ , "Definition List With Trailing Header" =:
212+ "- definition :: list\n\
213+ \- cool :: defs\n\
214+ \* header" =?>
215+ mconcat [ definitionList [ ("definition", [plain "list"])
216+ , ("cool", [plain "defs"])
217+ ]
218+ , headerWith ("header", [], []) 1 "header"
219+ ]
220+
221+ , "Definition lists double-colon markers must be surrounded by whitespace" =:
222+ "- std::cout" =?>
223+ bulletList [ plain "std::cout" ]
224+
225+ , "Loose bullet list" =:
226+ T.unlines [ "- apple"
227+ , ""
228+ , "- orange"
229+ , ""
230+ , "- peach"
231+ ] =?>
232+ bulletList [ para "apple"
233+ , para "orange"
234+ , para "peach"
235+ ]
236+
237+ , "Recognize preceding paragraphs in non-list contexts" =:
238+ T.unlines [ "CLOSED: [2015-10-19 Mon 15:03]"
239+ , "- Note taken on [2015-10-19 Mon 13:24]"
240+ ] =?>
241+ mconcat [ para "CLOSED: [2015-10-19 Mon 15:03]"
242+ , bulletList [ plain "Note taken on [2015-10-19 Mon 13:24]" ]
243+ ]
244+ ]
test/Tests/Readers/Org/Block/Table.hsnew file+150
@@ -0,0 +1,150 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Block.Table (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+simpleTable' :: Int
11+ -> [Blocks]
12+ -> [[Blocks]]
13+ -> Blocks
14+simpleTable' n = table "" (replicate n (AlignDefault, 0.0))
15+
16+tests :: [TestTree]
17+tests =
18+ [ "Single cell table" =:
19+ "|Test|" =?>
20+ simpleTable' 1 mempty [[plain "Test"]]
21+
22+ , "Multi cell table" =:
23+ "| One | Two |" =?>
24+ simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
25+
26+ , "Multi line table" =:
27+ T.unlines [ "| One |"
28+ , "| Two |"
29+ , "| Three |"
30+ ] =?>
31+ simpleTable' 1 mempty
32+ [ [ plain "One" ]
33+ , [ plain "Two" ]
34+ , [ plain "Three" ]
35+ ]
36+
37+ , "Empty table" =:
38+ "||" =?>
39+ simpleTable' 1 mempty [[mempty]]
40+
41+ , "Glider Table" =:
42+ T.unlines [ "| 1 | 0 | 0 |"
43+ , "| 0 | 1 | 1 |"
44+ , "| 1 | 1 | 0 |"
45+ ] =?>
46+ simpleTable' 3 mempty
47+ [ [ plain "1", plain "0", plain "0" ]
48+ , [ plain "0", plain "1", plain "1" ]
49+ , [ plain "1", plain "1", plain "0" ]
50+ ]
51+
52+ , "Table between Paragraphs" =:
53+ T.unlines [ "Before"
54+ , "| One | Two |"
55+ , "After"
56+ ] =?>
57+ mconcat [ para "Before"
58+ , simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
59+ , para "After"
60+ ]
61+
62+ , "Table with Header" =:
63+ T.unlines [ "| Species | Status |"
64+ , "|--------------+--------------|"
65+ , "| cervisiae | domesticated |"
66+ , "| paradoxus | wild |"
67+ ] =?>
68+ simpleTable [ plain "Species", plain "Status" ]
69+ [ [ plain "cervisiae", plain "domesticated" ]
70+ , [ plain "paradoxus", plain "wild" ]
71+ ]
72+
73+ , "Table with final hline" =:
74+ T.unlines [ "| cervisiae | domesticated |"
75+ , "| paradoxus | wild |"
76+ , "|--------------+--------------|"
77+ ] =?>
78+ simpleTable' 2 mempty
79+ [ [ plain "cervisiae", plain "domesticated" ]
80+ , [ plain "paradoxus", plain "wild" ]
81+ ]
82+
83+ , "Table in a box" =:
84+ T.unlines [ "|---------|---------|"
85+ , "| static | Haskell |"
86+ , "| dynamic | Lisp |"
87+ , "|---------+---------|"
88+ ] =?>
89+ simpleTable' 2 mempty
90+ [ [ plain "static", plain "Haskell" ]
91+ , [ plain "dynamic", plain "Lisp" ]
92+ ]
93+
94+ , "Table with empty cells" =:
95+ "|||c|" =?>
96+ simpleTable' 3 mempty [[mempty, mempty, plain "c"]]
97+
98+ , "Table with empty rows" =:
99+ T.unlines [ "| first |"
100+ , "| |"
101+ , "| third |"
102+ ] =?>
103+ simpleTable' 1 mempty [[plain "first"], [mempty], [plain "third"]]
104+
105+ , "Table with alignment row" =:
106+ T.unlines [ "| Numbers | Text | More |"
107+ , "| <c> | <r> | |"
108+ , "| 1 | One | foo |"
109+ , "| 2 | Two | bar |"
110+ ] =?>
111+ table "" (zip [AlignCenter, AlignRight, AlignDefault] [0, 0, 0])
112+ []
113+ [ [ plain "Numbers", plain "Text", plain "More" ]
114+ , [ plain "1" , plain "One" , plain "foo" ]
115+ , [ plain "2" , plain "Two" , plain "bar" ]
116+ ]
117+
118+ , "Pipe within text doesn't start a table" =:
119+ "Ceci n'est pas une | pipe " =?>
120+ para (spcSep [ "Ceci", "n'est", "pas", "une", "|", "pipe" ])
121+
122+ , "Missing pipe at end of row" =:
123+ "|incomplete-but-valid" =?>
124+ simpleTable' 1 mempty [ [ plain "incomplete-but-valid" ] ]
125+
126+ , "Table with differing row lengths" =:
127+ T.unlines [ "| Numbers | Text "
128+ , "|-"
129+ , "| <c> | <r> |"
130+ , "| 1 | One | foo |"
131+ , "| 2"
132+ ] =?>
133+ table "" (zip [AlignCenter, AlignRight] [0, 0])
134+ [ plain "Numbers", plain "Text" ]
135+ [ [ plain "1" , plain "One" , plain "foo" ]
136+ , [ plain "2" ]
137+ ]
138+
139+ , "Table with caption" =:
140+ T.unlines [ "#+CAPTION: Hitchhiker's Multiplication Table"
141+ , "| x | 6 |"
142+ , "| 9 | 42 |"
143+ ] =?>
144+ table "Hitchhiker's Multiplication Table"
145+ [(AlignDefault, 0), (AlignDefault, 0)]
146+ []
147+ [ [ plain "x", plain "6" ]
148+ , [ plain "9", plain "42" ]
149+ ]
150+ ]
test/Tests/Readers/Org/Directive.hsnew file+128
@@ -0,0 +1,128 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Directive (tests) where
3+
4+import Test.Tasty (TestTree, testGroup)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), tagSpan)
7+import Text.Pandoc
8+import Text.Pandoc.Builder
9+import qualified Data.Text as T
10+
11+tests :: [TestTree]
12+tests =
13+ [ testGroup "export options"
14+ [ "disable simple sub/superscript syntax" =:
15+ T.unlines [ "#+OPTIONS: ^:nil"
16+ , "a^b"
17+ ] =?>
18+ para "a^b"
19+
20+ , "directly select drawers to be exported" =:
21+ T.unlines [ "#+OPTIONS: d:(\"IMPORTANT\")"
22+ , ":IMPORTANT:"
23+ , "23"
24+ , ":END:"
25+ , ":BORING:"
26+ , "very boring"
27+ , ":END:"
28+ ] =?>
29+ divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "23")
30+
31+ , "exclude drawers from being exported" =:
32+ T.unlines [ "#+OPTIONS: d:(not \"BORING\")"
33+ , ":IMPORTANT:"
34+ , "5"
35+ , ":END:"
36+ , ":BORING:"
37+ , "very boring"
38+ , ":END:"
39+ ] =?>
40+ divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "5")
41+
42+ , "don't include archive trees" =:
43+ T.unlines [ "#+OPTIONS: arch:nil"
44+ , "* old :ARCHIVE:"
45+ ] =?>
46+ (mempty ::Blocks)
47+
48+ , "include complete archive trees" =:
49+ T.unlines [ "#+OPTIONS: arch:t"
50+ , "* old :ARCHIVE:"
51+ , " boring"
52+ ] =?>
53+ mconcat [ headerWith ("old", [], mempty) 1
54+ ("old" <> space <> tagSpan "ARCHIVE")
55+ , para "boring"
56+ ]
57+
58+ , "include archive tree header only" =:
59+ T.unlines [ "#+OPTIONS: arch:headline"
60+ , "* old :ARCHIVE:"
61+ , " boring"
62+ ] =?>
63+ headerWith ("old", [], mempty) 1 ("old" <> space <> tagSpan "ARCHIVE")
64+
65+ , "limit headline depth" =:
66+ T.unlines [ "#+OPTIONS: H:2"
67+ , "* top-level section"
68+ , "** subsection"
69+ , "*** list item 1"
70+ , "*** list item 2"
71+ ] =?>
72+ mconcat [ headerWith ("top-level-section", [], []) 1 "top-level section"
73+ , headerWith ("subsection", [], []) 2 "subsection"
74+ , orderedList [ para "list item 1", para "list item 2" ]
75+ ]
76+
77+ , "turn all headlines into lists" =:
78+ T.unlines [ "#+OPTIONS: H:0"
79+ , "first block"
80+ , "* top-level section 1"
81+ , "** subsection"
82+ , "* top-level section 2"
83+ ] =?>
84+ mconcat [ para "first block"
85+ , orderedList
86+ [ (para "top-level section 1" <>
87+ orderedList [ para "subsection" ])
88+ , para "top-level section 2" ]
89+ ]
90+
91+ , "preserve linebreaks as hard breaks" =:
92+ T.unlines [ "#+OPTIONS: \\n:t"
93+ , "first"
94+ , "second"
95+ ] =?>
96+ para ("first" <> linebreak <> "second")
97+
98+ , "disable author export" =:
99+ T.unlines [ "#+OPTIONS: author:nil"
100+ , "#+AUTHOR: ShyGuy"
101+ ] =?>
102+ Pandoc nullMeta mempty
103+
104+ , "disable creator export" =:
105+ T.unlines [ "#+OPTIONS: creator:nil"
106+ , "#+creator: The Architect"
107+ ] =?>
108+ Pandoc nullMeta mempty
109+
110+ , "disable email export" =:
111+ T.unlines [ "#+OPTIONS: email:nil"
112+ , "#+email: no-mail-please@example.com"
113+ ] =?>
114+ Pandoc nullMeta mempty
115+
116+ , "disable inclusion of todo keywords" =:
117+ T.unlines [ "#+OPTIONS: todo:nil"
118+ , "** DONE todo export"
119+ ] =?>
120+ headerWith ("todo-export", [], []) 2 "todo export"
121+
122+ , "remove tags from headlines" =:
123+ T.unlines [ "#+OPTIONS: tags:nil"
124+ , "* Headline :hello:world:"
125+ ] =?>
126+ headerWith ("headline", [], mempty) 1 "Headline"
127+ ]
128+ ]
test/Tests/Readers/Org/Inline.hsnew file+516
@@ -0,0 +1,516 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Inline (tests) where
3+
4+import Data.List (intersperse)
5+import Test.Tasty (TestTree, testGroup)
6+import Tests.Helpers ((=?>))
7+import Tests.Readers.Org.Shared ((=:), spcSep)
8+import Text.Pandoc
9+import Text.Pandoc.Builder
10+import Text.Pandoc.Shared (underlineSpan)
11+import qualified Data.Text as T
12+import qualified Tests.Readers.Org.Inline.Note as Note
13+import qualified Tests.Readers.Org.Inline.Smart as Smart
14+
15+tests :: [TestTree]
16+tests =
17+ [ "Plain String" =:
18+ "Hello, World" =?>
19+ para (spcSep [ "Hello,", "World" ])
20+
21+ , "Emphasis" =:
22+ "/Planet Punk/" =?>
23+ para (emph . spcSep $ ["Planet", "Punk"])
24+
25+ , "Strong" =:
26+ "*Cider*" =?>
27+ para (strong "Cider")
28+
29+ , "Strong Emphasis" =:
30+ "/*strength*/" =?>
31+ para (emph . strong $ "strength")
32+
33+ , "Emphasized Strong preceded by space" =:
34+ " */super/*" =?>
35+ para (strong . emph $ "super")
36+
37+ , "Underline" =:
38+ "_underline_" =?>
39+ para (underlineSpan $ "underline")
40+
41+ , "Strikeout" =:
42+ "+Kill Bill+" =?>
43+ para (strikeout . spcSep $ [ "Kill", "Bill" ])
44+
45+ , "Verbatim" =:
46+ "=Robot.rock()=" =?>
47+ para (code "Robot.rock()")
48+
49+ , "Code" =:
50+ "~word for word~" =?>
51+ para (code "word for word")
52+
53+ , "Math $..$" =:
54+ "$E=mc^2$" =?>
55+ para (math "E=mc^2")
56+
57+ , "Math $$..$$" =:
58+ "$$E=mc^2$$" =?>
59+ para (displayMath "E=mc^2")
60+
61+ , "Math \\[..\\]" =:
62+ "\\[E=ℎν\\]" =?>
63+ para (displayMath "E=ℎν")
64+
65+ , "Math \\(..\\)" =:
66+ "\\(σ_x σ_p ≥ \\frac{ℏ}{2}\\)" =?>
67+ para (math "σ_x σ_p ≥ \\frac{ℏ}{2}")
68+
69+ , "Symbol" =:
70+ "A * symbol" =?>
71+ para (str "A" <> space <> str "*" <> space <> "symbol")
72+
73+ , "Superscript simple expression" =:
74+ "2^-λ" =?>
75+ para (str "2" <> superscript "-λ")
76+
77+ , "Superscript multi char" =:
78+ "2^{n-1}" =?>
79+ para (str "2" <> superscript "n-1")
80+
81+ , "Subscript simple expression" =:
82+ "a_n" =?>
83+ para (str "a" <> subscript "n")
84+
85+ , "Subscript multi char" =:
86+ "a_{n+1}" =?>
87+ para (str "a" <> subscript "n+1")
88+
89+ , "Linebreak" =:
90+ "line \\\\ \nbreak" =?>
91+ para ("line" <> linebreak <> "break")
92+
93+ , "Inline note" =:
94+ "[fn::Schreib mir eine E-Mail]" =?>
95+ para (note $ para "Schreib mir eine E-Mail")
96+
97+ , "Markup-chars not occuring on word break are symbols" =:
98+ T.unlines [ "this+that+ +so+on"
99+ , "seven*eight* nine*"
100+ , "+not+funny+"
101+ ] =?>
102+ para ("this+that+ +so+on" <> softbreak <>
103+ "seven*eight* nine*" <> softbreak <>
104+ strikeout "not+funny")
105+
106+ , "No empty markup" =:
107+ "// ** __ <> == ~~ $$" =?>
108+ para (spcSep [ "//", "**", "__", "<>", "==", "~~", "$$" ])
109+
110+ , "Adherence to Org's rules for markup borders" =:
111+ "/t/& a/ / ./r/ (*l*) /e/! /b/." =?>
112+ para (spcSep [ emph $ "t/&" <> space <> "a"
113+ , "/"
114+ , "./r/"
115+ , "(" <> strong "l" <> ")"
116+ , emph "e" <> "!"
117+ , emph "b" <> "."
118+ ])
119+
120+ , "Quotes are allowed border chars" =:
121+ "/'yep/ *sure\"*" =?>
122+ para (emph "'yep" <> space <> strong "sure\"")
123+
124+ , "Spaces are forbidden border chars" =:
125+ "/nada /" =?>
126+ para "/nada /"
127+
128+ , "Markup should work properly after a blank line" =:
129+ T.unlines ["foo", "", "/bar/"] =?>
130+ (para $ text "foo") <> (para $ emph $ text "bar")
131+
132+ , "Inline math must stay within three lines" =:
133+ T.unlines [ "$a", "b", "c$", "$d", "e", "f", "g$" ] =?>
134+ para ((math "a\nb\nc") <> softbreak <>
135+ "$d" <> softbreak <> "e" <> softbreak <>
136+ "f" <> softbreak <> "g$")
137+
138+ , "Single-character math" =:
139+ "$a$ $b$! $c$?" =?>
140+ para (spcSep [ math "a"
141+ , "$b$!"
142+ , (math "c") <> "?"
143+ ])
144+
145+ , "Markup may not span more than two lines" =:
146+ "/this *is +totally\nnice+ not*\nemph/" =?>
147+ para ("/this" <> space <>
148+ strong ("is" <> space <>
149+ strikeout ("totally" <>
150+ softbreak <> "nice") <>
151+ space <> "not") <>
152+ softbreak <> "emph/")
153+
154+ , "Sub- and superscript expressions" =:
155+ T.unlines [ "a_(a(b)(c)d)"
156+ , "e^(f(g)h)"
157+ , "i_(jk)l)"
158+ , "m^()n"
159+ , "o_{p{q{}r}}"
160+ , "s^{t{u}v}"
161+ , "w_{xy}z}"
162+ , "1^{}2"
163+ , "3_{{}}"
164+ , "4^(a(*b(c*)d))"
165+ ] =?>
166+ para (mconcat $ intersperse softbreak
167+ [ "a" <> subscript "(a(b)(c)d)"
168+ , "e" <> superscript "(f(g)h)"
169+ , "i" <> (subscript "(jk)") <> "l)"
170+ , "m" <> (superscript "()") <> "n"
171+ , "o" <> subscript "p{q{}r}"
172+ , "s" <> superscript "t{u}v"
173+ , "w" <> (subscript "xy") <> "z}"
174+ , "1" <> (superscript "") <> "2"
175+ , "3" <> subscript "{}"
176+ , "4" <> superscript ("(a(" <> strong "b(c" <> ")d))")
177+ ])
178+ , "Verbatim text can contain equal signes (=)" =:
179+ "=is_subst = True=" =?>
180+ para (code "is_subst = True")
181+
182+ , testGroup "Images"
183+ [ "Image" =:
184+ "[[./sunset.jpg]]" =?>
185+ (para $ image "./sunset.jpg" "" "")
186+
187+ , "Image with explicit file: prefix" =:
188+ "[[file:sunrise.jpg]]" =?>
189+ (para $ image "sunrise.jpg" "" "")
190+
191+ , "Multiple images within a paragraph" =:
192+ T.unlines [ "[[file:sunrise.jpg]]"
193+ , "[[file:sunset.jpg]]"
194+ ] =?>
195+ (para $ (image "sunrise.jpg" "" "")
196+ <> softbreak
197+ <> (image "sunset.jpg" "" ""))
198+
199+ , "Image with html attributes" =:
200+ T.unlines [ "#+ATTR_HTML: :width 50%"
201+ , "[[file:guinea-pig.gif]]"
202+ ] =?>
203+ (para $ imageWith ("", [], [("width", "50%")]) "guinea-pig.gif" "" "")
204+ ]
205+
206+ , "Explicit link" =:
207+ "[[http://zeitlens.com/][pseudo-random /nonsense/]]" =?>
208+ (para $ link "http://zeitlens.com/" ""
209+ ("pseudo-random" <> space <> emph "nonsense"))
210+
211+ , "Self-link" =:
212+ "[[http://zeitlens.com/]]" =?>
213+ (para $ link "http://zeitlens.com/" "" "http://zeitlens.com/")
214+
215+ , "Absolute file link" =:
216+ "[[/url][hi]]" =?>
217+ (para $ link "file:///url" "" "hi")
218+
219+ , "Link to file in parent directory" =:
220+ "[[../file.txt][moin]]" =?>
221+ (para $ link "../file.txt" "" "moin")
222+
223+ , "Empty link (for gitit interop)" =:
224+ "[[][New Link]]" =?>
225+ (para $ link "" "" "New Link")
226+
227+ , "Image link" =:
228+ "[[sunset.png][file:dusk.svg]]" =?>
229+ (para $ link "sunset.png" "" (image "dusk.svg" "" ""))
230+
231+ , "Image link with non-image target" =:
232+ "[[http://example.com][./logo.png]]" =?>
233+ (para $ link "http://example.com" "" (image "./logo.png" "" ""))
234+
235+ , "Plain link" =:
236+ "Posts on http://zeitlens.com/ can be funny at times." =?>
237+ (para $ spcSep [ "Posts", "on"
238+ , link "http://zeitlens.com/" "" "http://zeitlens.com/"
239+ , "can", "be", "funny", "at", "times."
240+ ])
241+
242+ , "Angle link" =:
243+ "Look at <http://moltkeplatz.de> for fnords." =?>
244+ (para $ spcSep [ "Look", "at"
245+ , link "http://moltkeplatz.de" "" "http://moltkeplatz.de"
246+ , "for", "fnords."
247+ ])
248+
249+ , "Absolute file link" =:
250+ "[[file:///etc/passwd][passwd]]" =?>
251+ (para $ link "file:///etc/passwd" "" "passwd")
252+
253+ , "File link" =:
254+ "[[file:target][title]]" =?>
255+ (para $ link "target" "" "title")
256+
257+ , "Anchor" =:
258+ "<<anchor>> Link here later." =?>
259+ (para $ spanWith ("anchor", [], []) mempty <>
260+ "Link" <> space <> "here" <> space <> "later.")
261+
262+ , "Inline code block" =:
263+ "src_emacs-lisp{(message \"Hello\")}" =?>
264+ (para $ codeWith ( ""
265+ , [ "commonlisp" ]
266+ , [ ("org-language", "emacs-lisp") ])
267+ "(message \"Hello\")")
268+
269+ , "Inline code block with arguments" =:
270+ "src_sh[:export both :results output]{echo 'Hello, World'}" =?>
271+ (para $ codeWith ( ""
272+ , [ "bash" ]
273+ , [ ("org-language", "sh")
274+ , ("export", "both")
275+ , ("results", "output")
276+ ]
277+ )
278+ "echo 'Hello, World'")
279+
280+ , "Inline code block with toggle" =:
281+ "src_sh[:toggle]{echo $HOME}" =?>
282+ (para $ codeWith ( ""
283+ , [ "bash" ]
284+ , [ ("org-language", "sh")
285+ , ("toggle", "yes")
286+ ]
287+ )
288+ "echo $HOME")
289+
290+ , "Citation" =:
291+ "[@nonexistent]" =?>
292+ let citation = Citation
293+ { citationId = "nonexistent"
294+ , citationPrefix = []
295+ , citationSuffix = []
296+ , citationMode = NormalCitation
297+ , citationNoteNum = 0
298+ , citationHash = 0}
299+ in (para $ cite [citation] "[@nonexistent]")
300+
301+ , "Citation containing text" =:
302+ "[see @item1 p. 34-35]" =?>
303+ let citation = Citation
304+ { citationId = "item1"
305+ , citationPrefix = [Str "see"]
306+ , citationSuffix = [Space ,Str "p.",Space,Str "34-35"]
307+ , citationMode = NormalCitation
308+ , citationNoteNum = 0
309+ , citationHash = 0}
310+ in (para $ cite [citation] "[see @item1 p. 34-35]")
311+
312+ , "Org-ref simple citation" =:
313+ "cite:pandoc" =?>
314+ let citation = Citation
315+ { citationId = "pandoc"
316+ , citationPrefix = mempty
317+ , citationSuffix = mempty
318+ , citationMode = AuthorInText
319+ , citationNoteNum = 0
320+ , citationHash = 0
321+ }
322+ in (para $ cite [citation] "cite:pandoc")
323+
324+ , "Org-ref simple citation with underscores" =:
325+ "cite:pandoc_org_ref" =?>
326+ let citation = Citation
327+ { citationId = "pandoc_org_ref"
328+ , citationPrefix = mempty
329+ , citationSuffix = mempty
330+ , citationMode = AuthorInText
331+ , citationNoteNum = 0
332+ , citationHash = 0
333+ }
334+ in (para $ cite [citation] "cite:pandoc_org_ref")
335+
336+ , "Org-ref simple citation succeeded by comma" =:
337+ "cite:pandoc," =?>
338+ let citation = Citation
339+ { citationId = "pandoc"
340+ , citationPrefix = mempty
341+ , citationSuffix = mempty
342+ , citationMode = AuthorInText
343+ , citationNoteNum = 0
344+ , citationHash = 0
345+ }
346+ in (para $ cite [citation] "cite:pandoc" <> str ",")
347+
348+ , "Org-ref simple citation succeeded by dot" =:
349+ "cite:pandoc." =?>
350+ let citation = Citation
351+ { citationId = "pandoc"
352+ , citationPrefix = mempty
353+ , citationSuffix = mempty
354+ , citationMode = AuthorInText
355+ , citationNoteNum = 0
356+ , citationHash = 0
357+ }
358+ in (para $ cite [citation] "cite:pandoc" <> str ".")
359+
360+ , "Org-ref simple citation succeeded by colon" =:
361+ "cite:pandoc:" =?>
362+ let citation = Citation
363+ { citationId = "pandoc"
364+ , citationPrefix = mempty
365+ , citationSuffix = mempty
366+ , citationMode = AuthorInText
367+ , citationNoteNum = 0
368+ , citationHash = 0
369+ }
370+ in (para $ cite [citation] "cite:pandoc" <> str ":")
371+
372+ , "Org-ref simple citep citation" =:
373+ "citep:pandoc" =?>
374+ let citation = Citation
375+ { citationId = "pandoc"
376+ , citationPrefix = mempty
377+ , citationSuffix = mempty
378+ , citationMode = NormalCitation
379+ , citationNoteNum = 0
380+ , citationHash = 0
381+ }
382+ in (para $ cite [citation] "citep:pandoc")
383+
384+ , "Org-ref extended citation" =:
385+ "[[citep:Dominik201408][See page 20::, for example]]" =?>
386+ let citation = Citation
387+ { citationId = "Dominik201408"
388+ , citationPrefix = toList "See page 20"
389+ , citationSuffix = toList ", for example"
390+ , citationMode = NormalCitation
391+ , citationNoteNum = 0
392+ , citationHash = 0
393+ }
394+ in (para $ cite [citation] "[[citep:Dominik201408][See page 20::, for example]]")
395+
396+ , testGroup "Berkeley-style citations" $
397+ let pandocCite = Citation
398+ { citationId = "Pandoc"
399+ , citationPrefix = mempty
400+ , citationSuffix = mempty
401+ , citationMode = NormalCitation
402+ , citationNoteNum = 0
403+ , citationHash = 0
404+ }
405+ pandocInText = pandocCite { citationMode = AuthorInText }
406+ dominikCite = Citation
407+ { citationId = "Dominik201408"
408+ , citationPrefix = mempty
409+ , citationSuffix = mempty
410+ , citationMode = NormalCitation
411+ , citationNoteNum = 0
412+ , citationHash = 0
413+ }
414+ dominikInText = dominikCite { citationMode = AuthorInText }
415+ in [
416+ "Berkeley-style in-text citation" =:
417+ "See @Dominik201408." =?>
418+ (para $ "See "
419+ <> cite [dominikInText] "@Dominik201408"
420+ <> ".")
421+
422+ , "Berkeley-style parenthetical citation list" =:
423+ "[(cite): see; @Dominik201408;also @Pandoc; and others]" =?>
424+ let pandocCite' = pandocCite {
425+ citationPrefix = toList "also"
426+ , citationSuffix = toList "and others"
427+ }
428+ dominikCite' = dominikCite {
429+ citationPrefix = toList "see"
430+ }
431+ in (para $ cite [dominikCite', pandocCite'] "")
432+
433+ , "Berkeley-style plain citation list" =:
434+ "[cite: See; @Dominik201408; and @Pandoc; and others]" =?>
435+ let pandocCite' = pandocInText {
436+ citationPrefix = toList "and"
437+ }
438+ in (para $ "See "
439+ <> cite [dominikInText] ""
440+ <> "," <> space
441+ <> cite [pandocCite'] ""
442+ <> "," <> space <> "and others")
443+ ]
444+
445+ , "Inline LaTeX symbol" =:
446+ "\\dots" =?>
447+ para "…"
448+
449+ , "Inline LaTeX command" =:
450+ "\\textit{Emphasised}" =?>
451+ para (emph "Emphasised")
452+
453+ , "Inline LaTeX command with spaces" =:
454+ "\\emph{Emphasis mine}" =?>
455+ para (emph "Emphasis mine")
456+
457+ , "Inline LaTeX math symbol" =:
458+ "\\tau" =?>
459+ para (emph "τ")
460+
461+ , "Unknown inline LaTeX command" =:
462+ "\\notacommand{foo}" =?>
463+ para (rawInline "latex" "\\notacommand{foo}")
464+
465+ , "Export snippet" =:
466+ "@@html:<kbd>M-x org-agenda</kbd>@@" =?>
467+ para (rawInline "html" "<kbd>M-x org-agenda</kbd>")
468+
469+ , "MathML symbol in LaTeX-style" =:
470+ "There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: '\\nbsp')." =?>
471+ para ("There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: ' ').")
472+
473+ , "MathML symbol in LaTeX-style, including braces" =:
474+ "\\Aacute{}stor" =?>
475+ para "Ástor"
476+
477+ , "MathML copy sign" =:
478+ "\\copy" =?>
479+ para "©"
480+
481+ , "MathML symbols, space separated" =:
482+ "\\ForAll \\Auml" =?>
483+ para "∀ Ä"
484+
485+ , "LaTeX citation" =:
486+ "\\cite{Coffee}" =?>
487+ let citation = Citation
488+ { citationId = "Coffee"
489+ , citationPrefix = []
490+ , citationSuffix = []
491+ , citationMode = NormalCitation
492+ , citationNoteNum = 0
493+ , citationHash = 0}
494+ in (para . cite [citation] $ rawInline "latex" "\\cite{Coffee}")
495+
496+ , "Macro" =:
497+ T.unlines [ "#+MACRO: HELLO /Hello, $1/"
498+ , "{{{HELLO(World)}}}"
499+ ] =?>
500+ para (emph "Hello, World")
501+
502+ , "Macro repeting its argument" =:
503+ T.unlines [ "#+MACRO: HELLO $1$1"
504+ , "{{{HELLO(moin)}}}"
505+ ] =?>
506+ para "moinmoin"
507+
508+ , "Macro called with too few arguments" =:
509+ T.unlines [ "#+MACRO: HELLO Foo $1 $2 Bar"
510+ , "{{{HELLO()}}}"
511+ ] =?>
512+ para "Foo Bar"
513+
514+ , testGroup "Footnotes" Note.tests
515+ , testGroup "Smart punctuation" Smart.tests
516+ ]
test/Tests/Readers/Org/Inline/Note.hsnew file+87
@@ -0,0 +1,87 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Inline.Note (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:))
7+import Text.Pandoc.Builder
8+import qualified Data.Text as T
9+
10+tests :: [TestTree]
11+tests =
12+ [ "Footnote" =:
13+ T.unlines [ "A footnote[1]"
14+ , ""
15+ , "[1] First paragraph"
16+ , ""
17+ , "second paragraph"
18+ ] =?>
19+ para (mconcat
20+ [ "A", space, "footnote"
21+ , note $ mconcat [ para ("First" <> space <> "paragraph")
22+ , para ("second" <> space <> "paragraph")
23+ ]
24+ ])
25+
26+ , "Two footnotes" =:
27+ T.unlines [ "Footnotes[fn:1][fn:2]"
28+ , ""
29+ , "[fn:1] First note."
30+ , ""
31+ , "[fn:2] Second note."
32+ ] =?>
33+ para (mconcat
34+ [ "Footnotes"
35+ , note $ para ("First" <> space <> "note.")
36+ , note $ para ("Second" <> space <> "note.")
37+ ])
38+
39+ , "Emphasized text before footnote" =:
40+ T.unlines [ "/text/[fn:1]"
41+ , ""
42+ , "[fn:1] unicorn"
43+ ] =?>
44+ para (mconcat
45+ [ emph "text"
46+ , note . para $ "unicorn"
47+ ])
48+
49+ , "Footnote that starts with emphasized text" =:
50+ T.unlines [ "text[fn:1]"
51+ , ""
52+ , "[fn:1] /emphasized/"
53+ ] =?>
54+ para (mconcat
55+ [ "text"
56+ , note . para $ emph "emphasized"
57+ ])
58+
59+ , "Footnote followed by header" =:
60+ T.unlines [ "Another note[fn:yay]"
61+ , ""
62+ , "[fn:yay] This is great!"
63+ , ""
64+ , "** Headline"
65+ ] =?>
66+ mconcat
67+ [ para (mconcat
68+ [ "Another", space, "note"
69+ , note $ para ("This" <> space <> "is" <> space <> "great!")
70+ ])
71+ , headerWith ("headline", [], []) 2 "Headline"
72+ ]
73+
74+ , "Footnote followed by two blank lines" =:
75+ T.unlines [ "footnote[fn:blanklines]"
76+ , ""
77+ , "[fn:blanklines] followed by blank lines"
78+ , ""
79+ , ""
80+ , "next"
81+ ] =?>
82+ mconcat
83+ [ para ("footnote" <> note (para "followed by blank lines"))
84+ , para "next"
85+ ]
86+ ]
87+
test/Tests/Readers/Org/Inline/Smart.hsnew file+46
@@ -0,0 +1,46 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Inline.Smart (tests) where
3+
4+import Data.Text (Text)
5+import Test.Tasty (TestTree)
6+import Tests.Helpers ((=?>), purely, test)
7+import Text.Pandoc (Pandoc, ReaderOptions (readerExtensions),
8+ Extension (Ext_smart), def, enableExtension,
9+ getDefaultExtensions, readOrg)
10+import Text.Pandoc.Builder
11+
12+orgSmart :: Text -> Pandoc
13+orgSmart = purely $
14+ let extensionsSmart = enableExtension Ext_smart (getDefaultExtensions "org")
15+ in readOrg def{ readerExtensions = extensionsSmart }
16+
17+tests :: [TestTree]
18+tests =
19+ [ test orgSmart "quote before ellipses"
20+ ("'...hi'"
21+ =?> para (singleQuoted "…hi"))
22+
23+ , test orgSmart "apostrophe before emph"
24+ ("D'oh! A l'/aide/!"
25+ =?> para ("D’oh! A l’" <> emph "aide" <> "!"))
26+
27+ , test orgSmart "apostrophe in French"
28+ ("À l'arrivée de la guerre, le thème de l'«impossibilité du socialisme»"
29+ =?> para "À l’arrivée de la guerre, le thème de l’«impossibilité du socialisme»")
30+
31+ , test orgSmart "Quotes cannot occur at the end of emphasized text"
32+ ("/say \"yes\"/" =?>
33+ para ("/say" <> space <> doubleQuoted "yes" <> "/"))
34+
35+ , test orgSmart "Dashes are allowed at the borders of emphasis'"
36+ ("/foo---/" =?>
37+ para (emph "foo—"))
38+
39+ , test orgSmart "Single quotes can be followed by emphasized text"
40+ ("Singles on the '/meat market/'" =?>
41+ para ("Singles on the " <> (singleQuoted $ emph "meat market")))
42+
43+ , test orgSmart "Double quotes can be followed by emphasized text"
44+ ("Double income, no kids: \"/DINK/\"" =?>
45+ para ("Double income, no kids: " <> (doubleQuoted $ emph "DINK")))
46+ ]
test/Tests/Readers/Org/Meta.hsnew file+173
@@ -0,0 +1,173 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Tests.Readers.Org.Meta (tests) where
3+
4+import Test.Tasty (TestTree)
5+import Tests.Helpers ((=?>))
6+import Tests.Readers.Org.Shared ((=:), spcSep)
7+import Text.Pandoc
8+import Text.Pandoc.Builder
9+import qualified Data.Text as T
10+
11+tests :: [TestTree]
12+tests =
13+ [ "Comment" =:
14+ "# Nothing to see here" =?>
15+ (mempty::Blocks)
16+
17+ , "Not a comment" =:
18+ "#-tag" =?>
19+ para "#-tag"
20+
21+ , "Comment surrounded by Text" =:
22+ T.unlines [ "Before"
23+ , "# Comment"
24+ , "After"
25+ ] =?>
26+ mconcat [ para "Before"
27+ , para "After"
28+ ]
29+
30+ , "Title" =:
31+ "#+TITLE: Hello, World" =?>
32+ let titleInline = toList $ "Hello," <> space <> "World"
33+ meta = setMeta "title" (MetaInlines titleInline) $ nullMeta
34+ in Pandoc meta mempty
35+
36+ , "Author" =:
37+ "#+author: John /Emacs-Fanboy/ Doe" =?>
38+ let author = toList . spcSep $ [ "John", emph "Emacs-Fanboy", "Doe" ]
39+ meta = setMeta "author" (MetaList [MetaInlines author]) $ nullMeta
40+ in Pandoc meta mempty
41+
42+ , "Multiple authors" =:
43+ "#+author: James Dewey Watson, Francis Harry Compton Crick " =?>
44+ let watson = MetaInlines $ toList "James Dewey Watson"
45+ crick = MetaInlines $ toList "Francis Harry Compton Crick"
46+ meta = setMeta "author" (MetaList [watson, crick]) $ nullMeta
47+ in Pandoc meta mempty
48+
49+ , "Date" =:
50+ "#+Date: Feb. *28*, 2014" =?>
51+ let date = toList . spcSep $ [ "Feb.", (strong "28") <> ",", "2014" ]
52+ meta = setMeta "date" (MetaInlines date) $ nullMeta
53+ in Pandoc meta mempty
54+
55+ , "Description" =:
56+ "#+DESCRIPTION: Explanatory text" =?>
57+ let description = "Explanatory text"
58+ meta = setMeta "description" (MetaString description) $ nullMeta
59+ in Pandoc meta mempty
60+
61+ , "Properties drawer" =:
62+ T.unlines [ " :PROPERTIES:"
63+ , " :setting: foo"
64+ , " :END:"
65+ ] =?>
66+ (mempty::Blocks)
67+
68+ , "LaTeX_headers options are translated to header-includes" =:
69+ "#+LaTeX_header: \\usepackage{tikz}" =?>
70+ let latexInlines = rawInline "latex" "\\usepackage{tikz}"
71+ inclList = MetaList [MetaInlines (toList latexInlines)]
72+ meta = setMeta "header-includes" inclList nullMeta
73+ in Pandoc meta mempty
74+
75+ , "LaTeX_class option is translated to documentclass" =:
76+ "#+LATEX_CLASS: article" =?>
77+ let meta = setMeta "documentclass" (MetaString "article") nullMeta
78+ in Pandoc meta mempty
79+
80+ , "LaTeX_class_options is translated to classoption" =:
81+ "#+LATEX_CLASS_OPTIONS: [a4paper]" =?>
82+ let meta = setMeta "classoption" (MetaString "a4paper") nullMeta
83+ in Pandoc meta mempty
84+
85+ , "LaTeX_class_options is translated to classoption" =:
86+ "#+html_head: <meta/>" =?>
87+ let html = rawInline "html" "<meta/>"
88+ inclList = MetaList [MetaInlines (toList html)]
89+ meta = setMeta "header-includes" inclList nullMeta
90+ in Pandoc meta mempty
91+
92+ , "later meta definitions take precedence" =:
93+ T.unlines [ "#+AUTHOR: this will not be used"
94+ , "#+author: Max"
95+ ] =?>
96+ let author = MetaInlines [Str "Max"]
97+ meta = setMeta "author" (MetaList [author]) $ nullMeta
98+ in Pandoc meta mempty
99+
100+ , "Logbook drawer" =:
101+ T.unlines [ " :LogBook:"
102+ , " - State \"DONE\" from \"TODO\" [2014-03-03 Mon 11:00]"
103+ , " :END:"
104+ ] =?>
105+ (mempty::Blocks)
106+
107+ , "Drawer surrounded by text" =:
108+ T.unlines [ "Before"
109+ , ":PROPERTIES:"
110+ , ":END:"
111+ , "After"
112+ ] =?>
113+ para "Before" <> para "After"
114+
115+ , "Drawer markers must be the only text in the line" =:
116+ T.unlines [ " :LOGBOOK: foo"
117+ , " :END: bar"
118+ ] =?>
119+ para (":LOGBOOK: foo" <> softbreak <> ":END: bar")
120+
121+ , "Drawers can be arbitrary" =:
122+ T.unlines [ ":FOO:"
123+ , "/bar/"
124+ , ":END:"
125+ ] =?>
126+ divWith (mempty, ["FOO", "drawer"], mempty) (para $ emph "bar")
127+
128+ , "Anchor reference" =:
129+ T.unlines [ "<<link-here>> Target."
130+ , ""
131+ , "[[link-here][See here!]]"
132+ ] =?>
133+ (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
134+ para (link "#link-here" "" ("See" <> space <> "here!")))
135+
136+ , "Search links are read as emph" =:
137+ "[[Wally][Where's Wally?]]" =?>
138+ (para (emph $ "Where's" <> space <> "Wally?"))
139+
140+ , "Link to nonexistent anchor" =:
141+ T.unlines [ "<<link-here>> Target."
142+ , ""
143+ , "[[link$here][See here!]]"
144+ ] =?>
145+ (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
146+ para (emph ("See" <> space <> "here!")))
147+
148+ , "Link abbreviation" =:
149+ T.unlines [ "#+LINK: wp https://en.wikipedia.org/wiki/%s"
150+ , "[[wp:Org_mode][Wikipedia on Org-mode]]"
151+ ] =?>
152+ (para (link "https://en.wikipedia.org/wiki/Org_mode" ""
153+ ("Wikipedia" <> space <> "on" <> space <> "Org-mode")))
154+
155+ , "Link abbreviation, defined after first use" =:
156+ T.unlines [ "[[zl:non-sense][Non-sense articles]]"
157+ , "#+LINK: zl http://zeitlens.com/tags/%s.html"
158+ ] =?>
159+ (para (link "http://zeitlens.com/tags/non-sense.html" ""
160+ ("Non-sense" <> space <> "articles")))
161+
162+ , "Link abbreviation, URL encoded arguments" =:
163+ T.unlines [ "#+link: expl http://example.com/%h/foo"
164+ , "[[expl:Hello, World!][Moin!]]"
165+ ] =?>
166+ (para (link "http://example.com/Hello%2C%20World%21/foo" "" "Moin!"))
167+
168+ , "Link abbreviation, append arguments" =:
169+ T.unlines [ "#+link: expl http://example.com/"
170+ , "[[expl:foo][bar]]"
171+ ] =?>
172+ (para (link "http://example.com/foo" "" "bar"))
173+ ]
test/Tests/Readers/Org/Shared.hsnew file+29
@@ -0,0 +1,29 @@
1+module Tests.Readers.Org.Shared
2+ ( (=:)
3+ , org
4+ , spcSep
5+ , tagSpan
6+ ) where
7+
8+import Data.List (intersperse)
9+import Data.Text (Text)
10+import Tests.Helpers (ToString, purely, test)
11+import Test.Tasty (TestTree)
12+import Text.Pandoc (Pandoc, ReaderOptions (readerExtensions),
13+ def, getDefaultExtensions, readOrg)
14+import Text.Pandoc.Builder (Inlines, smallcaps, space, spanWith, str)
15+
16+org :: Text -> Pandoc
17+org = purely $ readOrg def{ readerExtensions = getDefaultExtensions "org" }
18+
19+infix 4 =:
20+(=:) :: ToString c
21+ => String -> (Text, c) -> TestTree
22+(=:) = test org
23+
24+spcSep :: [Inlines] -> Inlines
25+spcSep = mconcat . intersperse space
26+
27+-- | Create a span for the given tag.
28+tagSpan :: String -> Inlines
29+tagSpan t = spanWith ("", ["tag"], [("tag-name", t)]) . smallcaps $ str t