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