1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
{-# LANGUAGE CPP #-}
{- |
Module : Text.Pandoc.Readers.Haddock
Copyright : Copyright (C) 2013 David Lazar
License : GNU GPL, version 2 or above
Maintainer : David Lazar <lazar6@illinois.edu>,
John MacFarlane <jgm@berkeley.edu>
Stability : alpha
Conversion of Haddock markup to 'Pandoc' document.
-}
module Text.Pandoc.Readers.Haddock
( readHaddock
) where
import Text.Pandoc.Builder (Blocks, Inlines)
import qualified Text.Pandoc.Builder as B
import Text.Pandoc.Shared (trim, splitBy)
import Data.Monoid
import Data.List (intersperse, stripPrefix)
import Data.Maybe (fromMaybe)
import Text.Pandoc.Definition
import Text.Pandoc.Options
import Documentation.Haddock.Parser
import Documentation.Haddock.Types
import Debug.Trace (trace)
-- | Parse Haddock markup and return a 'Pandoc' document.
readHaddock :: ReaderOptions -- ^ Reader options
-> String -- ^ String to parse
-> Pandoc
readHaddock opts =
#if MIN_VERSION_haddock_library(1,2,0)
B.doc . docHToBlocks . trace' . _doc . parseParas
#else
B.doc . docHToBlocks . trace' . parseParas
#endif
where trace' x = if readerTrace opts
then trace (show x) x
else x
docHToBlocks :: DocH String Identifier -> Blocks
docHToBlocks d' =
case d' of
DocEmpty -> mempty
DocAppend (DocParagraph (DocHeader h)) (DocParagraph (DocAName ident)) ->
B.headerWith (ident,[],[]) (headerLevel h)
(docHToInlines False $ headerTitle h)
DocAppend d1 d2 -> mappend (docHToBlocks d1) (docHToBlocks d2)
DocString _ -> inlineFallback
DocParagraph (DocAName h) -> B.plain $ docHToInlines False $ DocAName h
DocParagraph x -> B.para $ docHToInlines False x
DocIdentifier _ -> inlineFallback
DocIdentifierUnchecked _ -> inlineFallback
DocModule s -> B.plain $ docHToInlines False $ DocModule s
DocWarning _ -> mempty -- TODO
DocEmphasis _ -> inlineFallback
DocMonospaced _ -> inlineFallback
DocBold _ -> inlineFallback
DocHeader h -> B.header (headerLevel h)
(docHToInlines False $ headerTitle h)
DocUnorderedList items -> B.bulletList (map docHToBlocks items)
DocOrderedList items -> B.orderedList (map docHToBlocks items)
DocDefList items -> B.definitionList (map (\(d,t) ->
(docHToInlines False d,
[consolidatePlains $ docHToBlocks t])) items)
DocCodeBlock (DocString s) -> B.codeBlockWith ("",[],[]) s
DocCodeBlock d -> B.para $ docHToInlines True d
DocHyperlink _ -> inlineFallback
DocPic _ -> inlineFallback
DocAName _ -> inlineFallback
DocProperty s -> B.codeBlockWith ("",["property","haskell"],[]) (trim s)
DocExamples es -> mconcat $ map (\e ->
makeExample ">>>" (exampleExpression e) (exampleResult e)) es
where inlineFallback = B.plain $ docHToInlines False d'
consolidatePlains = B.fromList . consolidatePlains' . B.toList
consolidatePlains' zs@(Plain _ : _) =
let (xs, ys) = span isPlain zs in
Para (concatMap extractContents xs) : consolidatePlains' ys
consolidatePlains' (x : xs) = x : consolidatePlains' xs
consolidatePlains' [] = []
isPlain (Plain _) = True
isPlain _ = False
extractContents (Plain xs) = xs
extractContents _ = []
docHToInlines :: Bool -> DocH String Identifier -> Inlines
docHToInlines isCode d' =
case d' of
DocEmpty -> mempty
DocAppend d1 d2 -> mappend (docHToInlines isCode d1)
(docHToInlines isCode d2)
DocString s
| isCode -> mconcat $ intersperse B.linebreak
$ map B.code $ splitBy (=='\n') s
| otherwise -> B.text s
DocParagraph _ -> mempty
DocIdentifier (_,s,_) -> B.codeWith ("",["haskell","identifier"],[]) s
DocIdentifierUnchecked s -> B.codeWith ("",["haskell","identifier"],[]) s
DocModule s -> B.codeWith ("",["haskell","module"],[]) s
DocWarning _ -> mempty -- TODO
DocEmphasis d -> B.emph (docHToInlines isCode d)
DocMonospaced (DocString s) -> B.code s
DocMonospaced d -> docHToInlines True d
DocBold d -> B.strong (docHToInlines isCode d)
DocHeader _ -> mempty
DocUnorderedList _ -> mempty
DocOrderedList _ -> mempty
DocDefList _ -> mempty
DocCodeBlock _ -> mempty
DocHyperlink h -> B.link (hyperlinkUrl h) (hyperlinkUrl h)
(maybe (B.text $ hyperlinkUrl h) B.text $ hyperlinkLabel h)
DocPic p -> B.image (pictureUri p) (fromMaybe (pictureUri p) $ pictureTitle p)
(maybe mempty B.text $ pictureTitle p)
DocAName s -> B.spanWith (s,["anchor"],[]) mempty
DocProperty _ -> mempty
DocExamples _ -> mempty
-- | Create an 'Example', stripping superfluous characters as appropriate
makeExample :: String -> String -> [String] -> Blocks
makeExample prompt expression result =
B.para $ B.codeWith ("",["prompt"],[]) prompt
<> B.space
<> B.codeWith ([], ["haskell","expr"], []) (trim expression)
<> B.linebreak
<> (mconcat $ intersperse B.linebreak $ map coder result')
where
-- 1. drop trailing whitespace from the prompt, remember the prefix
prefix = takeWhile (`elem` " \t") prompt
-- 2. drop, if possible, the exact same sequence of whitespace
-- characters from each result line
--
-- 3. interpret lines that only contain the string "<BLANKLINE>" as an
-- empty line
result' = map (substituteBlankLine . tryStripPrefix prefix) result
where
tryStripPrefix xs ys = fromMaybe ys $ stripPrefix xs ys
substituteBlankLine "<BLANKLINE>" = ""
substituteBlankLine line = line
coder = B.codeWith ([], ["result"], [])
|