module Text.Pandoc.Writers.Haddock (writeHaddock) where
import Text.Pandoc.Definition
import Text.Pandoc.Templates (renderTemplate')
import Text.Pandoc.Shared
import Text.Pandoc.Writers.Shared
import Text.Pandoc.Options
import Data.List ( intersperse, transpose )
import Text.Pandoc.Pretty
import Control.Monad.State
import Text.Pandoc.Readers.TeXMath (texMathToInlines)
import Network.URI (isURI)
import Data.Default
type Notes = [[Block]]
data WriterState = WriterState { stNotes :: Notes }
instance Default WriterState
where def = WriterState{ stNotes = [] }
writeHaddock :: WriterOptions -> Pandoc -> String
writeHaddock opts document =
evalState (pandocToHaddock opts{
writerWrapText = writerWrapText opts } document) def
pandocToHaddock :: WriterOptions -> Pandoc -> State WriterState String
pandocToHaddock opts (Pandoc meta blocks) = do
let colwidth = if writerWrapText opts == WrapAuto
then Just $ writerColumns opts
else Nothing
body <- blockListToHaddock opts blocks
st <- get
notes' <- notesToHaddock opts (reverse $ stNotes st)
let render' :: Doc -> String
render' = render colwidth
let main = render' $ body <>
(if isEmpty notes' then empty else blankline <> notes')
metadata <- metaToJSON opts
(fmap (render colwidth) . blockListToHaddock opts)
(fmap (render colwidth) . inlineListToHaddock opts)
meta
let context = defField "body" main
$ metadata
if writerStandalone opts
then return $ renderTemplate' (writerTemplate opts) context
else return main
notesToHaddock :: WriterOptions -> [[Block]] -> State WriterState Doc
notesToHaddock opts notes =
if null notes
then return empty
else do
contents <- blockToHaddock opts $ OrderedList (1,DefaultStyle,DefaultDelim) notes
return $ text "#notes#" <> blankline <> contents
escapeString :: String -> String
escapeString = escapeStringUsing haddockEscapes
where haddockEscapes = backslashEscapes "\\/'`\"@<"
blockToHaddock :: WriterOptions
-> Block
-> State WriterState Doc
blockToHaddock _ Null = return empty
blockToHaddock opts (Div _ ils) = do
contents <- blockListToHaddock opts ils
return $ contents <> blankline
blockToHaddock opts (Plain inlines) = do
contents <- inlineListToHaddock opts inlines
return $ contents <> cr
blockToHaddock opts (Para [Image attr alt (src,'f':'i':'g':':':tit)]) =
blockToHaddock opts (Para [Image attr alt (src,tit)])
blockToHaddock opts (Para inlines) =
(<> blankline) `fmap` blockToHaddock opts (Plain inlines)
blockToHaddock _ (RawBlock f str)
| f == "haddock" = do
return $ text str <> text "\n"
| otherwise = return empty
blockToHaddock opts HorizontalRule =
return $ blankline <> text (replicate (writerColumns opts) '_') <> blankline
blockToHaddock opts (Header level (ident,_,_) inlines) = do
contents <- inlineListToHaddock opts inlines
let attr' = if null ident
then empty
else cr <> text "#" <> text ident <> text "#"
return $ nowrap (text (replicate level '=') <> space <> contents)
<> attr' <> blankline
blockToHaddock _ (CodeBlock (_,_,_) str) =
return $ prefixed "> " (text str) <> blankline
blockToHaddock opts (BlockQuote blocks) =
blockListToHaddock opts blocks
blockToHaddock opts (Table caption aligns widths headers rows) = do
caption' <- inlineListToHaddock opts caption
let caption'' = if null caption
then empty
else blankline <> caption' <> blankline
rawHeaders <- mapM (blockListToHaddock opts) headers
rawRows <- mapM (mapM (blockListToHaddock opts)) rows
let isSimple = all (==0) widths
let isPlainBlock (Plain _) = True
isPlainBlock _ = False
let hasBlocks = not (all isPlainBlock $ concat . concat $ headers:rows)
(nst,tbl) <- case True of
_ | isSimple -> fmap (nest 2,) $
pandocTable opts (all null headers) aligns widths
rawHeaders rawRows
| not hasBlocks -> fmap (nest 2,) $
pandocTable opts (all null headers) aligns widths
rawHeaders rawRows
| otherwise -> fmap (id,) $
gridTable opts (all null headers) aligns widths
rawHeaders rawRows
return $ (prefixed "> " $ nst $ tbl $$ blankline $$ caption'') $$ blankline
blockToHaddock opts (BulletList items) = do
contents <- mapM (bulletListItemToHaddock opts) items
return $ cat contents <> blankline
blockToHaddock opts (OrderedList (start,_,delim) items) = do
let attribs = (start, Decimal, delim)
let markers = orderedListMarkers attribs
let markers' = map (\m -> if length m < 3
then m ++ replicate (3 length m) ' '
else m) markers
contents <- mapM (\(item, num) -> orderedListItemToHaddock opts item num) $
zip markers' items
return $ cat contents <> blankline
blockToHaddock opts (DefinitionList items) = do
contents <- mapM (definitionListItemToHaddock opts) items
return $ cat contents <> blankline
pandocTable :: WriterOptions -> Bool -> [Alignment] -> [Double]
-> [Doc] -> [[Doc]] -> State WriterState Doc
pandocTable opts headless aligns widths rawHeaders rawRows = do
let isSimple = all (==0) widths
let alignHeader alignment = case alignment of
AlignLeft -> lblock
AlignCenter -> cblock
AlignRight -> rblock
AlignDefault -> lblock
let numChars = maximum . map offset
let widthsInChars = if isSimple
then map ((+2) . numChars)
$ transpose (rawHeaders : rawRows)
else map
(floor . (fromIntegral (writerColumns opts) *))
widths
let makeRow = hcat . intersperse (lblock 1 (text " ")) .
(zipWith3 alignHeader aligns widthsInChars)
let rows' = map makeRow rawRows
let head' = makeRow rawHeaders
let maxRowHeight = maximum $ map height (head':rows')
let underline = cat $ intersperse (text " ") $
map (\width -> text (replicate width '-')) widthsInChars
let border = if maxRowHeight > 1
then text (replicate (sum widthsInChars +
length widthsInChars 1) '-')
else if headless
then underline
else empty
let head'' = if headless
then empty
else border <> cr <> head'
let body = if maxRowHeight > 1
then vsep rows'
else vcat rows'
let bottom = if headless
then underline
else border
return $ head'' $$ underline $$ body $$ bottom
gridTable :: WriterOptions -> Bool -> [Alignment] -> [Double]
-> [Doc] -> [[Doc]] -> State WriterState Doc
gridTable opts headless _aligns widths headers' rawRows = do
let numcols = length headers'
let widths' = if all (==0) widths
then replicate numcols (1.0 / fromIntegral numcols)
else widths
let widthsInChars = map (floor . (fromIntegral (writerColumns opts) *)) widths'
let hpipeBlocks blocks = hcat [beg, middle, end]
where h = maximum (map height blocks)
sep' = lblock 3 $ vcat (map text $ replicate h " | ")
beg = lblock 2 $ vcat (map text $ replicate h "| ")
end = lblock 2 $ vcat (map text $ replicate h " |")
middle = chomp $ hcat $ intersperse sep' blocks
let makeRow = hpipeBlocks . zipWith lblock widthsInChars
let head' = makeRow headers'
let rows' = map (makeRow . map chomp) rawRows
let border ch = char '+' <> char ch <>
(hcat $ intersperse (char ch <> char '+' <> char ch) $
map (\l -> text $ replicate l ch) widthsInChars) <>
char ch <> char '+'
let body = vcat $ intersperse (border '-') rows'
let head'' = if headless
then empty
else head' $$ border '='
return $ border '-' $$ head'' $$ body $$ border '-'
bulletListItemToHaddock :: WriterOptions -> [Block] -> State WriterState Doc
bulletListItemToHaddock opts items = do
contents <- blockListToHaddock opts items
let sps = replicate (writerTabStop opts 2) ' '
let start = text ('-' : ' ' : sps)
let contents' = case reverse items of
(BulletList xs:_) | isTightList xs ->
chomp contents <> cr
(OrderedList _ xs:_) | isTightList xs ->
chomp contents <> cr
_ -> contents
return $ hang (writerTabStop opts) start $ contents' <> cr
orderedListItemToHaddock :: WriterOptions
-> String
-> [Block]
-> State WriterState Doc
orderedListItemToHaddock opts marker items = do
contents <- blockListToHaddock opts items
let sps = case length marker writerTabStop opts of
n | n > 0 -> text $ replicate n ' '
_ -> text " "
let start = text marker <> sps
return $ hang (writerTabStop opts) start $ contents <> cr
definitionListItemToHaddock :: WriterOptions
-> ([Inline],[[Block]])
-> State WriterState Doc
definitionListItemToHaddock opts (label, defs) = do
labelText <- inlineListToHaddock opts label
defs' <- mapM (mapM (blockToHaddock opts)) defs
let contents = vcat $ map (\d -> hang 4 empty $ vcat d <> cr) defs'
return $ nowrap (brackets labelText) <> cr <> contents <> cr
blockListToHaddock :: WriterOptions
-> [Block]
-> State WriterState Doc
blockListToHaddock opts blocks =
mapM (blockToHaddock opts) blocks >>= return . cat
inlineListToHaddock :: WriterOptions -> [Inline] -> State WriterState Doc
inlineListToHaddock opts lst =
mapM (inlineToHaddock opts) lst >>= return . cat
inlineToHaddock :: WriterOptions -> Inline -> State WriterState Doc
inlineToHaddock opts (Span (ident,_,_) ils) = do
contents <- inlineListToHaddock opts ils
if not (null ident) && null ils
then return $ "#" <> text ident <> "#"
else return contents
inlineToHaddock opts (Emph lst) = do
contents <- inlineListToHaddock opts lst
return $ "/" <> contents <> "/"
inlineToHaddock opts (Strong lst) = do
contents <- inlineListToHaddock opts lst
return $ "__" <> contents <> "__"
inlineToHaddock opts (Strikeout lst) = do
contents <- inlineListToHaddock opts lst
return $ "~~" <> contents <> "~~"
inlineToHaddock opts (Superscript lst) = inlineListToHaddock opts lst
inlineToHaddock opts (Subscript lst) = inlineListToHaddock opts lst
inlineToHaddock opts (SmallCaps lst) = inlineListToHaddock opts lst
inlineToHaddock opts (Quoted SingleQuote lst) = do
contents <- inlineListToHaddock opts lst
return $ "‘" <> contents <> "’"
inlineToHaddock opts (Quoted DoubleQuote lst) = do
contents <- inlineListToHaddock opts lst
return $ "“" <> contents <> "”"
inlineToHaddock _ (Code _ str) =
return $ "@" <> text (escapeString str) <> "@"
inlineToHaddock _ (Str str) = do
return $ text $ escapeString str
inlineToHaddock opts (Math mt str) = do
let adjust x = case mt of
DisplayMath -> cr <> x <> cr
InlineMath -> x
adjust `fmap` (inlineListToHaddock opts $ texMathToInlines mt str)
inlineToHaddock _ (RawInline f str)
| f == "haddock" = return $ text str
| otherwise = return empty
inlineToHaddock _ (LineBreak) = return cr
inlineToHaddock opts SoftBreak =
case writerWrapText opts of
WrapAuto -> return space
WrapNone -> return space
WrapPreserve -> return cr
inlineToHaddock _ Space = return space
inlineToHaddock opts (Cite _ lst) = inlineListToHaddock opts lst
inlineToHaddock _ (Link _ txt (src, _)) = do
let linktext = text $ escapeString $ stringify txt
let useAuto = isURI src &&
case txt of
[Str s] | escapeURI s == src -> True
_ -> False
return $ nowrap $ "<" <> text src <>
(if useAuto then empty else space <> linktext) <> ">"
inlineToHaddock opts (Image attr alternate (source, tit)) = do
linkhaddock <- inlineToHaddock opts (Link attr alternate (source, tit))
return $ "<" <> linkhaddock <> ">"
inlineToHaddock opts (Note contents) = do
modify (\st -> st{ stNotes = contents : stNotes st })
st <- get
let ref = text $ writerIdentifierPrefix opts ++ show (length $ stNotes st)
return $ "<#notes [" <> ref <> "]>"