git.iko.soy/typeable/pandoc
on 4eff607 help sign in
1619 lines 60 kB history raw
1{-# LANGUAGE OverloadedStrings #-}
2module Tests.Readers.Org (tests) where
4import Text.Pandoc.Definition
5import Test.Framework
6import Tests.Helpers
7import Text.Pandoc.Builder
8import Text.Pandoc
9import Data.List (intersperse)
10import Text.Pandoc.Error
12org :: String -> Pandoc
13org = handleError . readOrg def
15orgSmart :: String -> Pandoc
16orgSmart = handleError . readOrg def { readerSmart = True }
18infix 4 =:
19(=:) :: ToString c
20 => String -> (String, c) -> Test
21(=:) = test org
23spcSep :: [Inlines] -> Inlines
24spcSep = mconcat . intersperse space
26simpleTable' :: Int
27 -> [Blocks]
28 -> [[Blocks]]
29 -> Blocks
30simpleTable' n = table "" (take n $ repeat (AlignDefault, 0.0))
32tests :: [Test]
33tests =
34 [ testGroup "Inlines" $
35 [ "Plain String" =:
36 "Hello, World" =?>
37 para (spcSep [ "Hello,", "World" ])
39 , "Emphasis" =:
40 "/Planet Punk/" =?>
41 para (emph . spcSep $ ["Planet", "Punk"])
43 , "Strong" =:
44 "*Cider*" =?>
45 para (strong "Cider")
47 , "Strong Emphasis" =:
48 "/*strength*/" =?>
49 para (emph . strong $ "strength")
51 , "Strikeout" =:
52 "+Kill Bill+" =?>
53 para (strikeout . spcSep $ [ "Kill", "Bill" ])
55 , "Verbatim" =:
56 "=Robot.rock()=" =?>
57 para (code "Robot.rock()")
59 , "Code" =:
60 "~word for word~" =?>
61 para (code "word for word")
63 , "Math $..$" =:
64 "$E=mc^2$" =?>
65 para (math "E=mc^2")
67 , "Math $$..$$" =:
68 "$$E=mc^2$$" =?>
69 para (displayMath "E=mc^2")
71 , "Math \\[..\\]" =:
72 "\\[E=ℎν\\]" =?>
73 para (displayMath "E=ℎν")
75 , "Math \\(..\\)" =:
76 "\\(σ_x σ_p ≥ \\frac{ℏ}{2}\\)" =?>
77 para (math "σ_x σ_p ≥ \\frac{ℏ}{2}")
79 , "Symbol" =:
80 "A * symbol" =?>
81 para (str "A" <> space <> str "*" <> space <> "symbol")
83 , "Superscript simple expression" =:
84 "2^-λ" =?>
85 para (str "2" <> superscript "-λ")
87 , "Superscript multi char" =:
88 "2^{n-1}" =?>
89 para (str "2" <> superscript "n-1")
91 , "Subscript simple expression" =:
92 "a_n" =?>
93 para (str "a" <> subscript "n")
95 , "Subscript multi char" =:
96 "a_{n+1}" =?>
97 para (str "a" <> subscript "n+1")
99 , "Linebreak" =:
100 "line \\\\ \nbreak" =?>
101 para ("line" <> linebreak <> "break")
103 , "Inline note" =:
104 "[fn::Schreib mir eine E-Mail]" =?>
105 para (note $ para "Schreib mir eine E-Mail")
107 , "Markup-chars not occuring on word break are symbols" =:
108 unlines [ "this+that+ +so+on"
109 , "seven*eight* nine*"
110 , "+not+funny+"
111 ] =?>
112 para ("this+that+ +so+on" <> softbreak <>
113 "seven*eight* nine*" <> softbreak <>
114 strikeout "not+funny")
116 , "No empty markup" =:
117 "// ** __ ++ == ~~ $$" =?>
118 para (spcSep [ "//", "**", "__", "++", "==", "~~", "$$" ])
120 , "Adherence to Org's rules for markup borders" =:
121 "/t/& a/ / ./r/ (*l*) /e/! /b/." =?>
122 para (spcSep [ emph $ "t/&" <> space <> "a"
123 , "/"
124 , "./r/"
125 , "(" <> (strong "l") <> ")"
126 , (emph "e") <> "!"
127 , (emph "b") <> "."
128 ])
130 , "Quotes are forbidden border chars" =:
131 "/'nope/ *nope\"*" =?>
132 para ("/'nope/" <> space <> "*nope\"*")
134 , "Commata are forbidden border chars" =:
135 "/nada,/" =?>
136 para "/nada,/"
138 , "Markup should work properly after a blank line" =:
139 unlines ["foo", "", "/bar/"] =?>
140 (para $ text "foo") <> (para $ emph $ text "bar")
142 , "Inline math must stay within three lines" =:
143 unlines [ "$a", "b", "c$", "$d", "e", "f", "g$" ] =?>
144 para ((math "a\nb\nc") <> softbreak <>
145 "$d" <> softbreak <> "e" <> softbreak <>
146 "f" <> softbreak <> "g$")
148 , "Single-character math" =:
149 "$a$ $b$! $c$?" =?>
150 para (spcSep [ math "a"
151 , "$b$!"
152 , (math "c") <> "?"
153 ])
155 , "Markup may not span more than two lines" =:
156 "/this *is +totally\nnice+ not*\nemph/" =?>
157 para ("/this" <> space <>
158 strong ("is" <> space <>
159 strikeout ("totally" <>
160 softbreak <> "nice") <>
161 space <> "not") <>
162 softbreak <> "emph/")
164 , "Sub- and superscript expressions" =:
165 unlines [ "a_(a(b)(c)d)"
166 , "e^(f(g)h)"
167 , "i_(jk)l)"
168 , "m^()n"
169 , "o_{p{q{}r}}"
170 , "s^{t{u}v}"
171 , "w_{xy}z}"
172 , "1^{}2"
173 , "3_{{}}"
174 , "4^(a(*b(c*)d))"
175 ] =?>
176 para (mconcat $ intersperse softbreak
177 [ "a" <> subscript "(a(b)(c)d)"
178 , "e" <> superscript "(f(g)h)"
179 , "i" <> (subscript "(jk)") <> "l)"
180 , "m" <> (superscript "()") <> "n"
181 , "o" <> subscript "p{q{}r}"
182 , "s" <> superscript "t{u}v"
183 , "w" <> (subscript "xy") <> "z}"
184 , "1" <> (superscript "") <> "2"
185 , "3" <> subscript "{}"
186 , "4" <> superscript ("(a(" <> strong "b(c" <> ")d))")
187 ])
188 , "Verbatim text can contain equal signes (=)" =:
189 "=is_subst = True=" =?>
190 para (code "is_subst = True")
192 , "Image" =:
193 "[[./sunset.jpg]]" =?>
194 (para $ image "./sunset.jpg" "" "")
196 , "Image with explicit file: prefix" =:
197 "[[file:sunrise.jpg]]" =?>
198 (para $ image "sunrise.jpg" "" "")
200 , "Explicit link" =:
201 "[[http://zeitlens.com/][pseudo-random /nonsense/]]" =?>
202 (para $ link "http://zeitlens.com/" ""
203 ("pseudo-random" <> space <> emph "nonsense"))
205 , "Self-link" =:
206 "[[http://zeitlens.com/]]" =?>
207 (para $ link "http://zeitlens.com/" "" "http://zeitlens.com/")
209 , "Absolute file link" =:
210 "[[/url][hi]]" =?>
211 (para $ link "file:///url" "" "hi")
213 , "Link to file in parent directory" =:
214 "[[../file.txt][moin]]" =?>
215 (para $ link "../file.txt" "" "moin")
217 , "Empty link (for gitit interop)" =:
218 "[[][New Link]]" =?>
219 (para $ link "" "" "New Link")
221 , "Image link" =:
222 "[[sunset.png][file:dusk.svg]]" =?>
223 (para $ link "sunset.png" "" (image "dusk.svg" "" ""))
225 , "Image link with non-image target" =:
226 "[[http://example.com][./logo.png]]" =?>
227 (para $ link "http://example.com" "" (image "./logo.png" "" ""))
229 , "Plain link" =:
230 "Posts on http://zeitlens.com/ can be funny at times." =?>
231 (para $ spcSep [ "Posts", "on"
232 , link "http://zeitlens.com/" "" "http://zeitlens.com/"
233 , "can", "be", "funny", "at", "times."
234 ])
236 , "Angle link" =:
237 "Look at <http://moltkeplatz.de> for fnords." =?>
238 (para $ spcSep [ "Look", "at"
239 , link "http://moltkeplatz.de" "" "http://moltkeplatz.de"
240 , "for", "fnords."
241 ])
243 , "Absolute file link" =:
244 "[[file:///etc/passwd][passwd]]" =?>
245 (para $ link "file:///etc/passwd" "" "passwd")
247 , "File link" =:
248 "[[file:target][title]]" =?>
249 (para $ link "target" "" "title")
251 , "Anchor" =:
252 "<<anchor>> Link here later." =?>
253 (para $ spanWith ("anchor", [], []) mempty <>
254 "Link" <> space <> "here" <> space <> "later.")
256 , "Inline code block" =:
257 "src_emacs-lisp{(message \"Hello\")}" =?>
258 (para $ codeWith ( ""
259 , [ "commonlisp", "rundoc-block" ]
260 , [ ("rundoc-language", "emacs-lisp") ])
261 "(message \"Hello\")")
263 , "Inline code block with arguments" =:
264 "src_sh[:export both :results output]{echo 'Hello, World'}" =?>
265 (para $ codeWith ( ""
266 , [ "bash", "rundoc-block" ]
267 , [ ("rundoc-language", "sh")
268 , ("rundoc-export", "both")
269 , ("rundoc-results", "output")
270 ]
271 )
272 "echo 'Hello, World'")
274 , "Inline code block with toggle" =:
275 "src_sh[:toggle]{echo $HOME}" =?>
276 (para $ codeWith ( ""
277 , [ "bash", "rundoc-block" ]
278 , [ ("rundoc-language", "sh")
279 , ("rundoc-toggle", "yes")
280 ]
281 )
282 "echo $HOME")
284 , "Citation" =:
285 "[@nonexistent]" =?>
286 let citation = Citation
287 { citationId = "nonexistent"
288 , citationPrefix = []
289 , citationSuffix = []
290 , citationMode = NormalCitation
291 , citationNoteNum = 0
292 , citationHash = 0}
293 in (para $ cite [citation] "[@nonexistent]")
295 , "Citation containing text" =:
296 "[see @item1 p. 34-35]" =?>
297 let citation = Citation
298 { citationId = "item1"
299 , citationPrefix = [Str "see"]
300 , citationSuffix = [Space ,Str "p.",Space,Str "34-35"]
301 , citationMode = NormalCitation
302 , citationNoteNum = 0
303 , citationHash = 0}
304 in (para $ cite [citation] "[see @item1 p. 34-35]")
306 , "Org-ref simple citation" =:
307 "cite:pandoc" =?>
308 let citation = Citation
309 { citationId = "pandoc"
310 , citationPrefix = mempty
311 , citationSuffix = mempty
312 , citationMode = AuthorInText
313 , citationNoteNum = 0
314 , citationHash = 0
315 }
316 in (para $ cite [citation] "cite:pandoc")
318 , "Org-ref simple citep citation" =:
319 "citep:pandoc" =?>
320 let citation = Citation
321 { citationId = "pandoc"
322 , citationPrefix = mempty
323 , citationSuffix = mempty
324 , citationMode = NormalCitation
325 , citationNoteNum = 0
326 , citationHash = 0
327 }
328 in (para $ cite [citation] "citep:pandoc")
330 , "Org-ref extended citation" =:
331 "[[citep:Dominik201408][See page 20::, for example]]" =?>
332 let citation = Citation
333 { citationId = "Dominik201408"
334 , citationPrefix = toList "See page 20"
335 , citationSuffix = toList ", for example"
336 , citationMode = NormalCitation
337 , citationNoteNum = 0
338 , citationHash = 0
339 }
340 in (para $ cite [citation] "[[citep:Dominik201408][See page 20::, for example]]")
342 , testGroup "Berkeley-style citations" $
343 let pandocCite = Citation
344 { citationId = "Pandoc"
345 , citationPrefix = mempty
346 , citationSuffix = mempty
347 , citationMode = NormalCitation
348 , citationNoteNum = 0
349 , citationHash = 0
350 }
351 pandocInText = pandocCite { citationMode = AuthorInText }
352 dominikCite = Citation
353 { citationId = "Dominik201408"
354 , citationPrefix = mempty
355 , citationSuffix = mempty
356 , citationMode = NormalCitation
357 , citationNoteNum = 0
358 , citationHash = 0
359 }
360 dominikInText = dominikCite { citationMode = AuthorInText }
361 in [
362 "Berkeley-style in-text citation" =:
363 "See @Dominik201408." =?>
364 (para $ "See "
365 <> cite [dominikInText] "@Dominik201408"
366 <> ".")
368 , "Berkeley-style parenthetical citation list" =:
369 "[(cite): see; @Dominik201408;also @Pandoc; and others]" =?>
370 let pandocCite' = pandocCite {
371 citationPrefix = toList "also"
372 , citationSuffix = toList "and others"
373 }
374 dominikCite' = dominikCite {
375 citationPrefix = toList "see"
376 }
377 in (para $ cite [dominikCite', pandocCite'] "")
379 , "Berkeley-style plain citation list" =:
380 "[cite: See; @Dominik201408; and @Pandoc; and others]" =?>
381 let pandocCite' = pandocInText {
382 citationPrefix = toList "and"
383 }
384 in (para $ "See "
385 <> cite [dominikInText] ""
386 <> "," <> space
387 <> cite [pandocCite'] ""
388 <> "," <> space <> "and others")
389 ]
391 , "Inline LaTeX symbol" =:
392 "\\dots" =?>
393 para "…"
395 , "Inline LaTeX command" =:
396 "\\textit{Emphasised}" =?>
397 para (emph "Emphasised")
399 , "Inline LaTeX command with spaces" =:
400 "\\emph{Emphasis mine}" =?>
401 para (emph "Emphasis mine")
403 , "Inline LaTeX math symbol" =:
404 "\\tau" =?>
405 para (emph "τ")
407 , "Unknown inline LaTeX command" =:
408 "\\notacommand{foo}" =?>
409 para (rawInline "latex" "\\notacommand{foo}")
411 , "Export snippet" =:
412 "@@html:<kbd>M-x org-agenda</kbd>@@" =?>
413 para (rawInline "html" "<kbd>M-x org-agenda</kbd>")
415 , "MathML symbol in LaTeX-style" =:
416 "There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: '\\nbsp')." =?>
417 para ("There is a hackerspace in Lübeck, Germany, called nbsp (unicode symbol: ' ').")
419 , "MathML symbol in LaTeX-style, including braces" =:
420 "\\Aacute{}stor" =?>
421 para "Ástor"
423 , "MathML copy sign" =:
424 "\\copy" =?>
425 para "©"
427 , "MathML symbols, space separated" =:
428 "\\ForAll \\Auml" =?>
429 para "∀ Ä"
431 , "LaTeX citation" =:
432 "\\cite{Coffee}" =?>
433 let citation = Citation
434 { citationId = "Coffee"
435 , citationPrefix = []
436 , citationSuffix = []
437 , citationMode = NormalCitation
438 , citationNoteNum = 0
439 , citationHash = 0}
440 in (para . cite [citation] $ rawInline "latex" "\\cite{Coffee}")
441 ]
443 , testGroup "Meta Information" $
444 [ "Comment" =:
445 "# Nothing to see here" =?>
446 (mempty::Blocks)
448 , "Not a comment" =:
449 "#-tag" =?>
450 para "#-tag"
452 , "Comment surrounded by Text" =:
453 unlines [ "Before"
454 , "# Comment"
455 , "After"
456 ] =?>
457 mconcat [ para "Before"
458 , para "After"
459 ]
461 , "Title" =:
462 "#+TITLE: Hello, World" =?>
463 let titleInline = toList $ "Hello," <> space <> "World"
464 meta = setMeta "title" (MetaInlines titleInline) $ nullMeta
465 in Pandoc meta mempty
467 , "Author" =:
468 "#+author: Albert /Emacs-Fanboy/ Krewinkel" =?>
469 let author = toList . spcSep $ [ "Albert", emph "Emacs-Fanboy", "Krewinkel" ]
470 meta = setMeta "author" (MetaList [MetaInlines author]) $ nullMeta
471 in Pandoc meta mempty
473 , "Multiple authors" =:
474 "#+author: James Dewey Watson, Francis Harry Compton Crick " =?>
475 let watson = MetaInlines $ toList "James Dewey Watson"
476 crick = MetaInlines $ toList "Francis Harry Compton Crick"
477 meta = setMeta "author" (MetaList [watson, crick]) $ nullMeta
478 in Pandoc meta mempty
480 , "Date" =:
481 "#+Date: Feb. *28*, 2014" =?>
482 let date = toList . spcSep $ [ "Feb.", (strong "28") <> ",", "2014" ]
483 meta = setMeta "date" (MetaInlines date) $ nullMeta
484 in Pandoc meta mempty
486 , "Description" =:
487 "#+DESCRIPTION: Explanatory text" =?>
488 let description = "Explanatory text"
489 meta = setMeta "description" (MetaString description) $ nullMeta
490 in Pandoc meta mempty
492 , "Properties drawer" =:
493 unlines [ " :PROPERTIES:"
494 , " :setting: foo"
495 , " :END:"
496 ] =?>
497 (mempty::Blocks)
499 , "LaTeX_headers options are translated to header-includes" =:
500 "#+LaTeX_header: \\usepackage{tikz}" =?>
501 let latexInlines = rawInline "latex" "\\usepackage{tikz}"
502 inclList = MetaList [MetaInlines (toList latexInlines)]
503 meta = setMeta "header-includes" inclList nullMeta
504 in Pandoc meta mempty
506 , "LaTeX_class option is translated to documentclass" =:
507 "#+LATEX_CLASS: article" =?>
508 let meta = setMeta "documentclass" (MetaString "article") nullMeta
509 in Pandoc meta mempty
511 , "LaTeX_class_options is translated to classoption" =:
512 "#+LATEX_CLASS_OPTIONS: [a4paper]" =?>
513 let meta = setMeta "classoption" (MetaString "a4paper") nullMeta
514 in Pandoc meta mempty
516 , "LaTeX_class_options is translated to classoption" =:
517 "#+html_head: <meta/>" =?>
518 let html = rawInline "html" "<meta/>"
519 inclList = MetaList [MetaInlines (toList html)]
520 meta = setMeta "header-includes" inclList nullMeta
521 in Pandoc meta mempty
523 , "later meta definitions take precedence" =:
524 unlines [ "#+AUTHOR: this will not be used"
525 , "#+author: Max"
526 ] =?>
527 let author = MetaInlines [Str "Max"]
528 meta = setMeta "author" (MetaList [author]) $ nullMeta
529 in Pandoc meta mempty
531 , "Logbook drawer" =:
532 unlines [ " :LogBook:"
533 , " - State \"DONE\" from \"TODO\" [2014-03-03 Mon 11:00]"
534 , " :END:"
535 ] =?>
536 (mempty::Blocks)
538 , "Drawer surrounded by text" =:
539 unlines [ "Before"
540 , ":PROPERTIES:"
541 , ":END:"
542 , "After"
543 ] =?>
544 para "Before" <> para "After"
546 , "Drawer markers must be the only text in the line" =:
547 unlines [ " :LOGBOOK: foo"
548 , " :END: bar"
549 ] =?>
550 para (":LOGBOOK: foo" <> softbreak <> ":END: bar")
552 , "Drawers can be arbitrary" =:
553 unlines [ ":FOO:"
554 , "/bar/"
555 , ":END:"
556 ] =?>
557 divWith (mempty, ["FOO", "drawer"], mempty) (para $ emph "bar")
559 , "Anchor reference" =:
560 unlines [ "<<link-here>> Target."
561 , ""
562 , "[[link-here][See here!]]"
563 ] =?>
564 (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
565 para (link "#link-here" "" ("See" <> space <> "here!")))
567 , "Search links are read as emph" =:
568 "[[Wally][Where's Wally?]]" =?>
569 (para (emph $ "Where's" <> space <> "Wally?"))
571 , "Link to nonexistent anchor" =:
572 unlines [ "<<link-here>> Target."
573 , ""
574 , "[[link$here][See here!]]"
575 ] =?>
576 (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
577 para (emph ("See" <> space <> "here!")))
579 , "Link abbreviation" =:
580 unlines [ "#+LINK: wp https://en.wikipedia.org/wiki/%s"
581 , "[[wp:Org_mode][Wikipedia on Org-mode]]"
582 ] =?>
583 (para (link "https://en.wikipedia.org/wiki/Org_mode" ""
584 ("Wikipedia" <> space <> "on" <> space <> "Org-mode")))
586 , "Link abbreviation, defined after first use" =:
587 unlines [ "[[zl:non-sense][Non-sense articles]]"
588 , "#+LINK: zl http://zeitlens.com/tags/%s.html"
589 ] =?>
590 (para (link "http://zeitlens.com/tags/non-sense.html" ""
591 ("Non-sense" <> space <> "articles")))
593 , "Link abbreviation, URL encoded arguments" =:
594 unlines [ "#+link: expl http://example.com/%h/foo"
595 , "[[expl:Hello, World!][Moin!]]"
596 ] =?>
597 (para (link "http://example.com/Hello%2C%20World%21/foo" "" "Moin!"))
599 , "Link abbreviation, append arguments" =:
600 unlines [ "#+link: expl http://example.com/"
601 , "[[expl:foo][bar]]"
602 ] =?>
603 (para (link "http://example.com/foo" "" "bar"))
606 , testGroup "export options"
608 [ "disable simple sub/superscript syntax" =:
609 unlines [ "#+OPTIONS: ^:nil"
610 , "a^b"
611 ] =?>
612 para "a^b"
614 , "directly select drawers to be exported" =:
615 unlines [ "#+OPTIONS: d:(\"IMPORTANT\")"
616 , ":IMPORTANT:"
617 , "23"
618 , ":END:"
619 , ":BORING:"
620 , "very boring"
621 , ":END:"
622 ] =?>
623 divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "23")
625 , "exclude drawers from being exported" =:
626 unlines [ "#+OPTIONS: d:(not \"BORING\")"
627 , ":IMPORTANT:"
628 , "5"
629 , ":END:"
630 , ":BORING:"
631 , "very boring"
632 , ":END:"
633 ] =?>
634 divWith (mempty, ["IMPORTANT", "drawer"], mempty) (para "5")
636 , "don't include archive trees" =:
637 unlines [ "#+OPTIONS: arch:nil"
638 , "* old :ARCHIVE:"
639 ] =?>
640 (mempty ::Blocks)
642 , "include complete archive trees" =:
643 unlines [ "#+OPTIONS: arch:t"
644 , "* old :ARCHIVE:"
645 , " boring"
646 ] =?>
647 let tagSpan t = spanWith ("", ["tag"], [("data-tag-name", t)]) mempty
648 in mconcat [ headerWith ("old", [], mempty) 1 ("old" <> tagSpan "ARCHIVE")
649 , para "boring"
650 ]
652 , "include archive tree header only" =:
653 unlines [ "#+OPTIONS: arch:headline"
654 , "* old :ARCHIVE:"
655 , " boring"
656 ] =?>
657 let tagSpan t = spanWith ("", ["tag"], [("data-tag-name", t)]) mempty
658 in headerWith ("old", [], mempty) 1 ("old" <> tagSpan "ARCHIVE")
660 , "limit headline depth" =:
661 unlines [ "#+OPTIONS: H:2"
662 , "* section"
663 , "** subsection"
664 , "*** list item 1"
665 , "*** list item 2"
666 ] =?>
667 mconcat [ headerWith ("section", [], []) 1 "section"
668 , headerWith ("subsection", [], []) 2 "subsection"
669 , orderedList [ para "list item 1", para "list item 2" ]
670 ]
672 , "disable author export" =:
673 unlines [ "#+OPTIONS: author:nil"
674 , "#+AUTHOR: ShyGuy"
675 ] =?>
676 Pandoc nullMeta mempty
678 , "disable creator export" =:
679 unlines [ "#+OPTIONS: creator:nil"
680 , "#+creator: The Architect"
681 ] =?>
682 Pandoc nullMeta mempty
684 , "disable email export" =:
685 unlines [ "#+OPTIONS: email:nil"
686 , "#+email: no-mail-please@example.com"
687 ] =?>
688 Pandoc nullMeta mempty
689 ]
690 ]
692 , testGroup "Basic Blocks" $
693 [ "Paragraph" =:
694 "Paragraph\n" =?>
695 para "Paragraph"
697 , "First Level Header" =:
698 "* Headline\n" =?>
699 headerWith ("headline", [], []) 1 "Headline"
701 , "Third Level Header" =:
702 "*** Third Level Headline\n" =?>
703 headerWith ("third-level-headline", [], [])
704 3
705 ("Third" <> space <> "Level" <> space <> "Headline")
707 , "Compact Headers with Paragraph" =:
708 unlines [ "* First Level"
709 , "** Second Level"
710 , " Text"
711 ] =?>
712 mconcat [ headerWith ("first-level", [], [])
713 1
714 ("First" <> space <> "Level")
715 , headerWith ("second-level", [], [])
716 2
717 ("Second" <> space <> "Level")
718 , para "Text"
719 ]
721 , "Separated Headers with Paragraph" =:
722 unlines [ "* First Level"
723 , ""
724 , "** Second Level"
725 , ""
726 , " Text"
727 ] =?>
728 mconcat [ headerWith ("first-level", [], [])
729 1
730 ("First" <> space <> "Level")
731 , headerWith ("second-level", [], [])
732 2
733 ("Second" <> space <> "Level")
734 , para "Text"
735 ]
737 , "Headers not preceded by a blank line" =:
738 unlines [ "** eat dinner"
739 , "Spaghetti and meatballs tonight."
740 , "** walk dog"
741 ] =?>
742 mconcat [ headerWith ("eat-dinner", [], [])
743 2
744 ("eat" <> space <> "dinner")
745 , para $ spcSep [ "Spaghetti", "and", "meatballs", "tonight." ]
746 , headerWith ("walk-dog", [], [])
747 2
748 ("walk" <> space <> "dog")
749 ]
751 , "Tagged headers" =:
752 unlines [ "* Personal :PERSONAL:"
753 , "** Call Mom :@PHONE:"
754 , "** Call John :@PHONE:JOHN: "
755 ] =?>
756 let tagSpan t = spanWith ("", ["tag"], [("data-tag-name", t)]) mempty
757 in mconcat [ headerWith ("personal", [], [])
758 1
759 ("Personal" <> tagSpan "PERSONAL")
760 , headerWith ("call-mom", [], [])
761 2
762 ("Call Mom" <> tagSpan "@PHONE")
763 , headerWith ("call-john", [], [])
764 2
765 ("Call John" <> tagSpan "@PHONE" <> tagSpan "JOHN")
766 ]
768 , "Untagged header containing colons" =:
769 "* This: is not: tagged" =?>
770 headerWith ("this-is-not-tagged", [], []) 1 "This: is not: tagged"
772 , "Header starting with strokeout text" =:
773 unlines [ "foo"
774 , ""
775 , "* +thing+ other thing"
776 ] =?>
777 mconcat [ para "foo"
778 , headerWith ("thing-other-thing", [], [])
779 1
780 ((strikeout "thing") <> " other thing")
781 ]
783 , "Comment Trees" =:
784 unlines [ "* COMMENT A comment tree"
785 , " Not much going on here"
786 , "** This will be dropped"
787 , "* Comment tree above"
788 ] =?>
789 headerWith ("comment-tree-above", [], []) 1 "Comment tree above"
791 , "Nothing but a COMMENT header" =:
792 "* COMMENT Test" =?>
793 (mempty::Blocks)
795 , "Tree with :noexport:" =:
796 unlines [ "* Should be ignored :archive:noexport:old:"
797 , "** Old stuff"
798 , " This is not going to be exported"
799 ] =?>
800 (mempty::Blocks)
802 , "Subtree with :noexport:" =:
803 unlines [ "* Exported"
804 , "** This isn't exported :noexport:"
805 , "*** This neither"
806 , "** But this is"
807 ] =?>
808 mconcat [ headerWith ("exported", [], []) 1 "Exported"
809 , headerWith ("but-this-is", [], []) 2 "But this is"
810 ]
812 , "Preferences are treated as header attributes" =:
813 unlines [ "* foo"
814 , " :PROPERTIES:"
815 , " :custom_id: fubar"
816 , " :bar: baz"
817 , " :END:"
818 ] =?>
819 headerWith ("fubar", [], [("bar", "baz")]) 1 "foo"
822 , "Headers marked with a unnumbered property get a class of the same name" =:
823 unlines [ "* Not numbered"
824 , " :PROPERTIES:"
825 , " :UNNUMBERED: t"
826 , " :END:"
827 ] =?>
828 headerWith ("not-numbered", ["unnumbered"], []) 1 "Not numbered"
830 , "Paragraph starting with an asterisk" =:
831 "*five" =?>
832 para "*five"
834 , "Paragraph containing asterisk at beginning of line" =:
835 unlines [ "lucky"
836 , "*star"
837 ] =?>
838 para ("lucky" <> softbreak <> "*star")
840 , "Example block" =:
841 unlines [ ": echo hello"
842 , ": echo dear tester"
843 ] =?>
844 codeBlockWith ("", ["example"], []) "echo hello\necho dear tester\n"
846 , "Example block surrounded by text" =:
847 unlines [ "Greetings"
848 , ": echo hello"
849 , ": echo dear tester"
850 , "Bye"
851 ] =?>
852 mconcat [ para "Greetings"
853 , codeBlockWith ("", ["example"], [])
854 "echo hello\necho dear tester\n"
855 , para "Bye"
856 ]
858 , "Horizontal Rule" =:
859 unlines [ "before"
860 , "-----"
861 , "after"
862 ] =?>
863 mconcat [ para "before"
864 , horizontalRule
865 , para "after"
866 ]
868 , "Not a Horizontal Rule" =:
869 "----- five dashes" =?>
870 (para $ spcSep [ "-----", "five", "dashes" ])
872 , "Comment Block" =:
873 unlines [ "#+BEGIN_COMMENT"
874 , "stuff"
875 , "bla"
876 , "#+END_COMMENT"] =?>
877 (mempty::Blocks)
879 , testGroup "Figures" $
880 [ "Figure" =:
881 unlines [ "#+caption: A very courageous man."
882 , "#+name: goodguy"
883 , "[[file:edward.jpg]]"
884 ] =?>
885 para (image "edward.jpg" "fig:goodguy" "A very courageous man.")
887 , "Figure with no name" =:
888 unlines [ "#+caption: I've been through the desert on this"
889 , "[[file:horse.png]]"
890 ] =?>
891 para (image "horse.png" "fig:" "I've been through the desert on this")
893 , "Figure with `fig:` prefix in name" =:
894 unlines [ "#+caption: Used as a metapher in evolutionary biology."
895 , "#+name: fig:redqueen"
896 , "[[./the-red-queen.jpg]]"
897 ] =?>
898 para (image "./the-red-queen.jpg" "fig:redqueen"
899 "Used as a metapher in evolutionary biology.")
901 , "Figure with HTML attributes" =:
902 unlines [ "#+CAPTION: mah brain just explodid"
903 , "#+NAME: lambdacat"
904 , "#+ATTR_HTML: :style color: blue :role button"
905 , "[[file:lambdacat.jpg]]"
906 ] =?>
907 let kv = [("style", "color: blue"), ("role", "button")]
908 name = "fig:lambdacat"
909 caption = "mah brain just explodid"
910 in para (imageWith (mempty, mempty, kv) "lambdacat.jpg" name caption)
912 , "Labelled figure" =:
913 unlines [ "#+CAPTION: My figure"
914 , "#+LABEL: fig:myfig"
915 , "[[file:blub.png]]"
916 ] =?>
917 let attr = ("fig:myfig", mempty, mempty)
918 in para (imageWith attr "blub.png" "fig:" "My figure")
919 ]
921 , "Footnote" =:
922 unlines [ "A footnote[1]"
923 , ""
924 , "[1] First paragraph"
925 , ""
926 , "second paragraph"
927 ] =?>
928 para (mconcat
929 [ "A", space, "footnote"
930 , note $ mconcat [ para ("First" <> space <> "paragraph")
931 , para ("second" <> space <> "paragraph")
932 ]
933 ])
935 , "Two footnotes" =:
936 unlines [ "Footnotes[fn:1][fn:2]"
937 , ""
938 , "[fn:1] First note."
939 , ""
940 , "[fn:2] Second note."
941 ] =?>
942 para (mconcat
943 [ "Footnotes"
944 , note $ para ("First" <> space <> "note.")
945 , note $ para ("Second" <> space <> "note.")
946 ])
948 , "Footnote followed by header" =:
949 unlines [ "Another note[fn:yay]"
950 , ""
951 , "[fn:yay] This is great!"
952 , ""
953 , "** Headline"
954 ] =?>
955 mconcat
956 [ para (mconcat
957 [ "Another", space, "note"
958 , note $ para ("This" <> space <> "is" <> space <> "great!")
959 ])
960 , headerWith ("headline", [], []) 2 "Headline"
961 ]
962 ]
964 , testGroup "Lists" $
965 [ "Simple Bullet Lists" =:
966 ("- Item1\n" ++
967 "- Item2\n") =?>
968 bulletList [ plain "Item1"
969 , plain "Item2"
970 ]
972 , "Indented Bullet Lists" =:
973 (" - Item1\n" ++
974 " - Item2\n") =?>
975 bulletList [ plain "Item1"
976 , plain "Item2"
977 ]
979 , "Unindented *" =:
980 ("- Item1\n" ++
981 "* Item2\n") =?>
982 bulletList [ plain "Item1"
983 ] <>
984 headerWith ("item2", [], []) 1 "Item2"
986 , "Multi-line Bullet Lists" =:
987 ("- *Fat\n" ++
988 " Tony*\n" ++
989 "- /Sideshow\n" ++
990 " Bob/") =?>
991 bulletList [ plain $ strong ("Fat" <> softbreak <> "Tony")
992 , plain $ emph ("Sideshow" <> softbreak <> "Bob")
993 ]
995 , "Nested Bullet Lists" =:
996 ("- Discovery\n" ++
997 " + One More Time\n" ++
998 " + Harder, Better, Faster, Stronger\n" ++
999 "- Homework\n" ++
1000 " + Around the World\n"++
1001 "- Human After All\n" ++
1002 " + Technologic\n" ++
1003 " + Robot Rock\n") =?>
1004 bulletList [ mconcat
1005 [ plain "Discovery"
1006 , bulletList [ plain ("One" <> space <>
1007 "More" <> space <>
1008 "Time")
1009 , plain ("Harder," <> space <>
1010 "Better," <> space <>
1011 "Faster," <> space <>
1012 "Stronger")
1015 , mconcat
1016 [ plain "Homework"
1017 , bulletList [ plain ("Around" <> space <>
1018 "the" <> space <>
1019 "World")
1022 , mconcat
1023 [ plain ("Human" <> space <> "After" <> space <> "All")
1024 , bulletList [ plain "Technologic"
1025 , plain ("Robot" <> space <> "Rock")
1030 , "Bullet List with Decreasing Indent" =:
1031 (" - Discovery\n\
1032 \ - Human After All\n") =?>
1033 mconcat [ bulletList [ plain "Discovery" ]
1034 , bulletList [ plain ("Human" <> space <> "After" <> space <> "All")]
1037 , "Header follows Bullet List" =:
1038 (" - Discovery\n\
1039 \ - Human After All\n\
1040 \* Homework") =?>
1041 mconcat [ bulletList [ plain "Discovery"
1042 , plain ("Human" <> space <> "After" <> space <> "All")
1044 , headerWith ("homework", [], []) 1 "Homework"
1047 , "Bullet List Unindented with trailing Header" =:
1048 ("- Discovery\n\
1049 \- Homework\n\
1050 \* NotValidListItem") =?>
1051 mconcat [ bulletList [ plain "Discovery"
1052 , plain "Homework"
1054 , headerWith ("notvalidlistitem", [], []) 1 "NotValidListItem"
1057 , "Simple Ordered List" =:
1058 ("1. Item1\n" ++
1059 "2. Item2\n") =?>
1060 let listStyle = (1, DefaultStyle, DefaultDelim)
1061 listStructure = [ plain "Item1"
1062 , plain "Item2"
1064 in orderedListWith listStyle listStructure
1066 , "Simple Ordered List with Parens" =:
1067 ("1) Item1\n" ++
1068 "2) Item2\n") =?>
1069 let listStyle = (1, DefaultStyle, DefaultDelim)
1070 listStructure = [ plain "Item1"
1071 , plain "Item2"
1073 in orderedListWith listStyle listStructure
1075 , "Indented Ordered List" =:
1076 (" 1. Item1\n" ++
1077 " 2. Item2\n") =?>
1078 let listStyle = (1, DefaultStyle, DefaultDelim)
1079 listStructure = [ plain "Item1"
1080 , plain "Item2"
1082 in orderedListWith listStyle listStructure
1084 , "Nested Ordered Lists" =:
1085 ("1. One\n" ++
1086 " 1. One-One\n" ++
1087 " 2. One-Two\n" ++
1088 "2. Two\n" ++
1089 " 1. Two-One\n"++
1090 " 2. Two-Two\n") =?>
1091 let listStyle = (1, DefaultStyle, DefaultDelim)
1092 listStructure = [ mconcat
1093 [ plain "One"
1094 , orderedList [ plain "One-One"
1095 , plain "One-Two"
1098 , mconcat
1099 [ plain "Two"
1100 , orderedList [ plain "Two-One"
1101 , plain "Two-Two"
1105 in orderedListWith listStyle listStructure
1107 , "Ordered List in Bullet List" =:
1108 ("- Emacs\n" ++
1109 " 1. Org\n") =?>
1110 bulletList [ (plain "Emacs") <>
1111 (orderedList [ plain "Org"])
1114 , "Bullet List in Ordered List" =:
1115 ("1. GNU\n" ++
1116 " - Freedom\n") =?>
1117 orderedList [ (plain "GNU") <> bulletList [ (plain "Freedom") ] ]
1119 , "Definition List" =:
1120 unlines [ "- PLL :: phase-locked loop"
1121 , "- TTL ::"
1122 , " transistor-transistor logic"
1123 , "- PSK :: phase-shift keying"
1124 , ""
1125 , " a digital modulation scheme"
1126 ] =?>
1127 definitionList [ ("PLL", [ plain $ "phase-locked" <> space <> "loop" ])
1128 , ("TTL", [ plain $ "transistor-transistor" <> space <>
1129 "logic" ])
1130 , ("PSK", [ mconcat
1131 [ para $ "phase-shift" <> space <> "keying"
1132 , para $ spcSep [ "a", "digital"
1133 , "modulation", "scheme" ]
1135 ])
1137 , "Definition list with multi-word term" =:
1138 " - Elijah Wood :: He plays Frodo" =?>
1139 definitionList [ ("Elijah" <> space <> "Wood", [plain $ "He" <> space <> "plays" <> space <> "Frodo"])]
1140 , "Compact definition list" =:
1141 unlines [ "- ATP :: adenosine 5' triphosphate"
1142 , "- DNA :: deoxyribonucleic acid"
1143 , "- PCR :: polymerase chain reaction"
1144 , ""
1145 ] =?>
1146 definitionList
1147 [ ("ATP", [ plain $ spcSep [ "adenosine", "5'", "triphosphate" ] ])
1148 , ("DNA", [ plain $ spcSep [ "deoxyribonucleic", "acid" ] ])
1149 , ("PCR", [ plain $ spcSep [ "polymerase", "chain", "reaction" ] ])
1152 , "Definition List With Trailing Header" =:
1153 "- definition :: list\n\
1154 \- cool :: defs\n\
1155 \* header" =?>
1156 mconcat [ definitionList [ ("definition", [plain "list"])
1157 , ("cool", [plain "defs"])
1159 , headerWith ("header", [], []) 1 "header"
1162 , "Definition lists double-colon markers must be surrounded by whitespace" =:
1163 "- std::cout" =?>
1164 bulletList [ plain "std::cout" ]
1166 , "Loose bullet list" =:
1167 unlines [ "- apple"
1168 , ""
1169 , "- orange"
1170 , ""
1171 , "- peach"
1172 ] =?>
1173 bulletList [ para "apple"
1174 , para "orange"
1175 , para "peach"
1178 , "Recognize preceding paragraphs in non-list contexts" =:
1179 unlines [ "CLOSED: [2015-10-19 Mon 15:03]"
1180 , "- Note taken on [2015-10-19 Mon 13:24]"
1181 ] =?>
1182 mconcat [ para "CLOSED: [2015-10-19 Mon 15:03]"
1183 , bulletList [ plain "Note taken on [2015-10-19 Mon 13:24]" ]
1187 , testGroup "Tables"
1188 [ "Single cell table" =:
1189 "|Test|" =?>
1190 simpleTable' 1 mempty [[plain "Test"]]
1192 , "Multi cell table" =:
1193 "| One | Two |" =?>
1194 simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
1196 , "Multi line table" =:
1197 unlines [ "| One |"
1198 , "| Two |"
1199 , "| Three |"
1200 ] =?>
1201 simpleTable' 1 mempty
1202 [ [ plain "One" ]
1203 , [ plain "Two" ]
1204 , [ plain "Three" ]
1207 , "Empty table" =:
1208 "||" =?>
1209 simpleTable' 1 mempty [[mempty]]
1211 , "Glider Table" =:
1212 unlines [ "| 1 | 0 | 0 |"
1213 , "| 0 | 1 | 1 |"
1214 , "| 1 | 1 | 0 |"
1215 ] =?>
1216 simpleTable' 3 mempty
1217 [ [ plain "1", plain "0", plain "0" ]
1218 , [ plain "0", plain "1", plain "1" ]
1219 , [ plain "1", plain "1", plain "0" ]
1222 , "Table between Paragraphs" =:
1223 unlines [ "Before"
1224 , "| One | Two |"
1225 , "After"
1226 ] =?>
1227 mconcat [ para "Before"
1228 , simpleTable' 2 mempty [ [ plain "One", plain "Two" ] ]
1229 , para "After"
1232 , "Table with Header" =:
1233 unlines [ "| Species | Status |"
1234 , "|--------------+--------------|"
1235 , "| cervisiae | domesticated |"
1236 , "| paradoxus | wild |"
1237 ] =?>
1238 simpleTable [ plain "Species", plain "Status" ]
1239 [ [ plain "cervisiae", plain "domesticated" ]
1240 , [ plain "paradoxus", plain "wild" ]
1243 , "Table with final hline" =:
1244 unlines [ "| cervisiae | domesticated |"
1245 , "| paradoxus | wild |"
1246 , "|--------------+--------------|"
1247 ] =?>
1248 simpleTable' 2 mempty
1249 [ [ plain "cervisiae", plain "domesticated" ]
1250 , [ plain "paradoxus", plain "wild" ]
1253 , "Table in a box" =:
1254 unlines [ "|---------|---------|"
1255 , "| static | Haskell |"
1256 , "| dynamic | Lisp |"
1257 , "|---------+---------|"
1258 ] =?>
1259 simpleTable' 2 mempty
1260 [ [ plain "static", plain "Haskell" ]
1261 , [ plain "dynamic", plain "Lisp" ]
1264 , "Table with empty cells" =:
1265 "|||c|" =?>
1266 simpleTable' 3 mempty [[mempty, mempty, plain "c"]]
1268 , "Table with empty rows" =:
1269 unlines [ "| first |"
1270 , "| |"
1271 , "| third |"
1272 ] =?>
1273 simpleTable' 1 mempty [[plain "first"], [mempty], [plain "third"]]
1275 , "Table with alignment row" =:
1276 unlines [ "| Numbers | Text | More |"
1277 , "| <c> | <r> | |"
1278 , "| 1 | One | foo |"
1279 , "| 2 | Two | bar |"
1280 ] =?>
1281 table "" (zip [AlignCenter, AlignRight, AlignDefault] [0, 0, 0])
1282 []
1283 [ [ plain "Numbers", plain "Text", plain "More" ]
1284 , [ plain "1" , plain "One" , plain "foo" ]
1285 , [ plain "2" , plain "Two" , plain "bar" ]
1288 , "Pipe within text doesn't start a table" =:
1289 "Ceci n'est pas une | pipe " =?>
1290 para (spcSep [ "Ceci", "n'est", "pas", "une", "|", "pipe" ])
1292 , "Missing pipe at end of row" =:
1293 "|incomplete-but-valid" =?>
1294 simpleTable' 1 mempty [ [ plain "incomplete-but-valid" ] ]
1296 , "Table with differing row lengths" =:
1297 unlines [ "| Numbers | Text "
1298 , "|-"
1299 , "| <c> | <r> |"
1300 , "| 1 | One | foo |"
1301 , "| 2"
1302 ] =?>
1303 table "" (zip [AlignCenter, AlignRight] [0, 0])
1304 [ plain "Numbers", plain "Text" ]
1305 [ [ plain "1" , plain "One" , plain "foo" ]
1306 , [ plain "2" ]
1309 , "Table with caption" =:
1310 unlines [ "#+CAPTION: Hitchhiker's Multiplication Table"
1311 , "| x | 6 |"
1312 , "| 9 | 42 |"
1313 ] =?>
1314 table "Hitchhiker's Multiplication Table"
1315 [(AlignDefault, 0), (AlignDefault, 0)]
1316 []
1317 [ [ plain "x", plain "6" ]
1318 , [ plain "9", plain "42" ]
1322 , testGroup "Blocks and fragments"
1323 [ "Source block" =:
1324 unlines [ " #+BEGIN_SRC haskell"
1325 , " main = putStrLn greeting"
1326 , " where greeting = \"moin\""
1327 , " #+END_SRC" ] =?>
1328 let attr' = ("", ["haskell"], [])
1329 code' = "main = putStrLn greeting\n" ++
1330 " where greeting = \"moin\"\n"
1331 in codeBlockWith attr' code'
1333 , "Source block with indented code" =:
1334 unlines [ " #+BEGIN_SRC haskell"
1335 , " main = putStrLn greeting"
1336 , " where greeting = \"moin\""
1337 , " #+END_SRC" ] =?>
1338 let attr' = ("", ["haskell"], [])
1339 code' = "main = putStrLn greeting\n" ++
1340 " where greeting = \"moin\"\n"
1341 in codeBlockWith attr' code'
1343 , "Source block with tab-indented code" =:
1344 unlines [ "\t#+BEGIN_SRC haskell"
1345 , "\tmain = putStrLn greeting"
1346 , "\t where greeting = \"moin\""
1347 , "\t#+END_SRC" ] =?>
1348 let attr' = ("", ["haskell"], [])
1349 code' = "main = putStrLn greeting\n" ++
1350 " where greeting = \"moin\"\n"
1351 in codeBlockWith attr' code'
1353 , "Empty source block" =:
1354 unlines [ " #+BEGIN_SRC haskell"
1355 , " #+END_SRC" ] =?>
1356 let attr' = ("", ["haskell"], [])
1357 code' = ""
1358 in codeBlockWith attr' code'
1360 , "Source block between paragraphs" =:
1361 unlines [ "Low German greeting"
1362 , " #+BEGIN_SRC haskell"
1363 , " main = putStrLn greeting"
1364 , " where greeting = \"Moin!\""
1365 , " #+END_SRC" ] =?>
1366 let attr' = ("", ["haskell"], [])
1367 code' = "main = putStrLn greeting\n" ++
1368 " where greeting = \"Moin!\"\n"
1369 in mconcat [ para $ spcSep [ "Low", "German", "greeting" ]
1370 , codeBlockWith attr' code'
1372 , "Source block with rundoc/babel arguments" =:
1373 unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
1374 , "(progn (message \"Hello, World!\")"
1375 , " (+ 23 42))"
1376 , "#+END_SRC" ] =?>
1377 let classes = [ "commonlisp" -- as kate doesn't know emacs-lisp syntax
1378 , "rundoc-block"
1380 params = [ ("rundoc-language", "emacs-lisp")
1381 , ("rundoc-exports", "both")
1383 code' = unlines [ "(progn (message \"Hello, World!\")"
1384 , " (+ 23 42))" ]
1385 in codeBlockWith ("", classes, params) code'
1387 , "Source block with results and :exports both" =:
1388 unlines [ "#+BEGIN_SRC emacs-lisp :exports both"
1389 , "(progn (message \"Hello, World!\")"
1390 , " (+ 23 42))"
1391 , "#+END_SRC"
1392 , ""
1393 , "#+RESULTS:"
1394 , ": 65"] =?>
1395 let classes = [ "commonlisp" -- as kate doesn't know emacs-lisp syntax
1396 , "rundoc-block"
1398 params = [ ("rundoc-language", "emacs-lisp")
1399 , ("rundoc-exports", "both")
1401 code' = unlines [ "(progn (message \"Hello, World!\")"
1402 , " (+ 23 42))" ]
1403 results' = "65\n"
1404 in codeBlockWith ("", classes, params) code'
1405 <>
1406 codeBlockWith ("", ["example"], []) results'
1408 , "Source block with results and :exports code" =:
1409 unlines [ "#+BEGIN_SRC emacs-lisp :exports code"
1410 , "(progn (message \"Hello, World!\")"
1411 , " (+ 23 42))"
1412 , "#+END_SRC"
1413 , ""
1414 , "#+RESULTS:"
1415 , ": 65" ] =?>
1416 let classes = [ "commonlisp" -- as kate doesn't know emacs-lisp syntax
1417 , "rundoc-block"
1419 params = [ ("rundoc-language", "emacs-lisp")
1420 , ("rundoc-exports", "code")
1422 code' = unlines [ "(progn (message \"Hello, World!\")"
1423 , " (+ 23 42))" ]
1424 in codeBlockWith ("", classes, params) code'
1426 , "Source block with results and :exports results" =:
1427 unlines [ "#+BEGIN_SRC emacs-lisp :exports results"
1428 , "(progn (message \"Hello, World!\")"
1429 , " (+ 23 42))"
1430 , "#+END_SRC"
1431 , ""
1432 , "#+RESULTS:"
1433 , ": 65" ] =?>
1434 let results' = "65\n"
1435 in codeBlockWith ("", ["example"], []) results'
1437 , "Source block with results and :exports none" =:
1438 unlines [ "#+BEGIN_SRC emacs-lisp :exports none"
1439 , "(progn (message \"Hello, World!\")"
1440 , " (+ 23 42))"
1441 , "#+END_SRC"
1442 , ""
1443 , "#+RESULTS:"
1444 , ": 65" ] =?>
1445 rawBlock "html" ""
1447 , "Source block with toggling header arguments" =:
1448 unlines [ "#+BEGIN_SRC sh :noeval"
1449 , "echo $HOME"
1450 , "#+END_SRC"
1451 ] =?>
1452 let classes = [ "bash", "rundoc-block" ]
1453 params = [ ("rundoc-language", "sh"), ("rundoc-noeval", "yes") ]
1454 in codeBlockWith ("", classes, params) "echo $HOME\n"
1456 , "Example block" =:
1457 unlines [ "#+begin_example"
1458 , "A chosen representation of"
1459 , "a rule."
1460 , "#+eND_exAMPle"
1461 ] =?>
1462 codeBlockWith ("", ["example"], [])
1463 "A chosen representation of\na rule.\n"
1465 , "HTML block" =:
1466 unlines [ "#+BEGIN_HTML"
1467 , "<aside>HTML5 is pretty nice.</aside>"
1468 , "#+END_HTML"
1469 ] =?>
1470 rawBlock "html" "<aside>HTML5 is pretty nice.</aside>\n"
1472 , "Quote block" =:
1473 unlines [ "#+BEGIN_QUOTE"
1474 , "/Niemand/ hat die Absicht, eine Mauer zu errichten!"
1475 , "#+END_QUOTE"
1476 ] =?>
1477 blockQuote (para (spcSep [ emph "Niemand", "hat", "die", "Absicht,"
1478 , "eine", "Mauer", "zu", "errichten!"
1479 ]))
1481 , "Verse block" =:
1482 unlines [ "The first lines of Goethe's /Faust/:"
1483 , "#+begin_verse"
1484 , "Habe nun, ach! Philosophie,"
1485 , "Juristerei und Medizin,"
1486 , "Und leider auch Theologie!"
1487 , "Durchaus studiert, mit heißem Bemühn."
1488 , "#+end_verse"
1489 ] =?>
1490 mconcat
1491 [ para $ spcSep [ "The", "first", "lines", "of"
1492 , "Goethe's", emph "Faust" <> ":"]
1493 , lineBlock
1494 [ "Habe nun, ach! Philosophie,"
1495 , "Juristerei und Medizin,"
1496 , "Und leider auch Theologie!"
1497 , "Durchaus studiert, mit heißem Bemühn."
1501 , "Verse block with blank lines" =:
1502 unlines [ "#+BEGIN_VERSE"
1503 , "foo"
1504 , ""
1505 , "bar"
1506 , "#+END_VERSE"
1507 ] =?>
1508 lineBlock [ "foo", mempty, "bar" ]
1510 , "Verse block with varying indentation" =:
1511 unlines [ "#+BEGIN_VERSE"
1512 , " hello darkness"
1513 , "my old friend"
1514 , "#+END_VERSE"
1515 ] =?>
1516 lineBlock [ "\160\160hello darkness", "my old friend" ]
1518 , "Raw block LaTeX" =:
1519 unlines [ "#+BEGIN_LaTeX"
1520 , "The category $\\cat{Set}$ is adhesive."
1521 , "#+END_LaTeX"
1522 ] =?>
1523 rawBlock "latex" "The category $\\cat{Set}$ is adhesive.\n"
1525 , "Export block HTML" =:
1526 unlines [ "#+BEGIN_export html"
1527 , "<samp>Hello, World!</samp>"
1528 , "#+END_export"
1529 ] =?>
1530 rawBlock "html" "<samp>Hello, World!</samp>\n"
1532 , "LaTeX fragment" =:
1533 unlines [ "\\begin{equation}"
1534 , "X_i = \\begin{cases}"
1535 , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) = \\alpha(i)\\\\"
1536 , " C_{\\alpha(i)} & \\text{otherwise}"
1537 , " \\end{cases}"
1538 , "\\end{equation}"
1539 ] =?>
1540 rawBlock "latex"
1541 (unlines [ "\\begin{equation}"
1542 , "X_i = \\begin{cases}"
1543 , " G_{\\alpha(i)} & \\text{if }\\alpha(i-1) =" ++
1544 " \\alpha(i)\\\\"
1545 , " C_{\\alpha(i)} & \\text{otherwise}"
1546 , " \\end{cases}"
1547 , "\\end{equation}"
1548 ])
1550 , "Code block with caption" =:
1551 unlines [ "#+CAPTION: Functor laws in Haskell"
1552 , "#+NAME: functor-laws"
1553 , "#+BEGIN_SRC haskell"
1554 , "fmap id = id"
1555 , "fmap (p . q) = (fmap p) . (fmap q)"
1556 , "#+END_SRC"
1557 ] =?>
1558 divWith
1559 nullAttr
1560 (mappend
1561 (plain $ spanWith ("", ["label"], [])
1562 (spcSep [ "Functor", "laws", "in", "Haskell" ]))
1563 (codeBlockWith ("functor-laws", ["haskell"], [])
1564 (unlines [ "fmap id = id"
1565 , "fmap (p . q) = (fmap p) . (fmap q)"
1566 ])))
1568 , "Convert blank lines in blocks to single newlines" =:
1569 unlines [ "#+begin_html"
1570 , ""
1571 , "<span>boring</span>"
1572 , ""
1573 , "#+end_html"
1574 ] =?>
1575 rawBlock "html" "\n<span>boring</span>\n\n"
1577 , "Non-letter chars in source block parameters" =:
1578 unlines [ "#+BEGIN_SRC C :tangle xxxx.c :city Zürich"
1579 , "code body"
1580 , "#+END_SRC"
1581 ] =?>
1582 let classes = [ "c", "rundoc-block" ]
1583 params = [ ("rundoc-language", "C")
1584 , ("rundoc-tangle", "xxxx.c")
1585 , ("rundoc-city", "Zürich")
1587 in codeBlockWith ( "", classes, params) "code body\n"
1590 , testGroup "Smart punctuation"
1591 [ test orgSmart "quote before ellipses"
1592 ("'...hi'"
1593 =?> para (singleQuoted "…hi"))
1595 , test orgSmart "apostrophe before emph"
1596 ("D'oh! A l'/aide/!"
1597 =?> para ("D’oh! A l’" <> emph "aide" <> "!"))
1599 , test orgSmart "apostrophe in French"
1600 ("À l'arrivée de la guerre, le thème de l'«impossibilité du socialisme»"
1601 =?> para "À l’arrivée de la guerre, le thème de l’«impossibilité du socialisme»")
1603 , test orgSmart "Quotes cannot occur at the end of emphasized text"
1604 ("/say \"yes\"/" =?>
1605 para ("/say" <> space <> doubleQuoted "yes" <> "/"))
1607 , test orgSmart "Dashes are allowed at the borders of emphasis'"
1608 ("/foo---/" =?>
1609 para (emph "foo—"))
1611 , test orgSmart "Single quotes can be followed by emphasized text"
1612 ("Singles on the '/meat market/'" =?>
1613 para ("Singles on the " <> (singleQuoted $ emph "meat market")))
1615 , test orgSmart "Double quotes can be followed by emphasized text"
1616 ("Double income, no kids: \"/DINK/\"" =?>
1617 para ("Double income, no kids: " <> (doubleQuoted $ emph "DINK")))