@@ -0,0 +1,322 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE NoImplicitPrelude #-}
3+{- |
4+ Module : Text.Pandoc.Writers.Jira
5+ Copyright : © 2010-2019 Albert Krewinkel, John MacFarlane
6+ License : GNU GPL, version 2 or above
7+
8+ Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
9+ Stability : alpha
10+ Portability : portable
11+
12+Conversion of 'Pandoc' documents to Jira markup.
13+
14+JIRA:
15+<https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=all>
16+-}
17+module Text.Pandoc.Writers.Jira ( writeJira ) where
18+import Prelude
19+import Control.Monad.State.Strict
20+import Data.Char (toLower)
21+import Data.Foldable (find)
22+import Data.Text (Text, pack)
23+import Text.Pandoc.Class (PandocMonad, report)
24+import Text.Pandoc.Definition
25+import Text.Pandoc.Logging (LogMessage (BlockNotRendered, InlineNotRendered))
26+import Text.Pandoc.Options (WriterOptions (writerTemplate))
27+import Text.Pandoc.Shared (blocksToInlines, linesToPara)
28+import Text.Pandoc.Templates (renderTemplate')
29+import Text.Pandoc.Writers.Math (texMathToInlines)
30+import Text.Pandoc.Writers.Shared (metaToJSON, defField)
31+import qualified Data.Text as T
32+
33+data WriterState = WriterState
34+ { stNotes :: [Text] -- Footnotes
35+ , stListLevel :: Text -- String at beginning of list items, e.g. "**"
36+ }
37+
38+-- | Initial writer state
39+startState :: WriterState
40+startState = WriterState
41+ { stNotes = []
42+ , stListLevel = ""
43+ }
44+
45+type JiraWriter = StateT WriterState
46+
47+-- | Convert Pandoc to Jira.
48+writeJira :: PandocMonad m => WriterOptions -> Pandoc -> m Text
49+writeJira opts document =
50+ evalStateT (pandocToJira opts document) startState
51+
52+-- | Return Jira representation of document.
53+pandocToJira :: PandocMonad m
54+ => WriterOptions -> Pandoc -> JiraWriter m Text
55+pandocToJira opts (Pandoc meta blocks) = do
56+ metadata <- metaToJSON opts (blockListToJira opts)
57+ (inlineListToJira opts) meta
58+ body <- blockListToJira opts blocks
59+ notes <- gets $ T.intercalate "\n" . reverse . stNotes
60+ let main = body <> if T.null notes then "" else "\n\n" <> notes
61+ let context = defField "body" main metadata
62+ case writerTemplate opts of
63+ Nothing -> return main
64+ Just tpl -> renderTemplate' tpl context
65+
66+-- | Escape one character as needed for Jira.
67+escapeCharForJira :: Char -> Text
68+escapeCharForJira c = case c of
69+ '&' -> "&"
70+ '<' -> "<"
71+ '>' -> ">"
72+ '"' -> """
73+ '*' -> "*"
74+ '_' -> "_"
75+ '@' -> "@"
76+ '+' -> "+"
77+ '-' -> "‐"
78+ '|' -> "|"
79+ '{' -> "\\{"
80+ '\x2014' -> " -- "
81+ '\x2013' -> " - "
82+ '\x2019' -> "'"
83+ '\x2026' -> "..."
84+ _ -> T.singleton c
85+
86+-- | Escape string as needed for Jira.
87+escapeStringForJira :: Text -> Text
88+escapeStringForJira = T.concatMap escapeCharForJira
89+
90+-- | Create an anchor macro from the given element attributes.
91+anchor :: Attr -> Text
92+anchor (ident,_,_) =
93+ if ident == ""
94+ then ""
95+ else "{anchor:" <> pack ident <> "}"
96+
97+-- | Append a newline character unless we are in a list.
98+appendNewlineUnlessInList :: PandocMonad m
99+ => Text
100+ -> JiraWriter m Text
101+appendNewlineUnlessInList t = do
102+ listLevel <- gets stListLevel
103+ return (if T.null listLevel then t <> "\n" else t)
104+
105+-- | Convert Pandoc block element to Jira.
106+blockToJira :: PandocMonad m
107+ => WriterOptions -- ^ Options
108+ -> Block -- ^ Block element
109+ -> JiraWriter m Text
110+
111+blockToJira _ Null = return ""
112+
113+blockToJira opts (Div attr bs) =
114+ (anchor attr <>) <$> blockListToJira opts bs
115+
116+blockToJira opts (Plain inlines) =
117+ inlineListToJira opts inlines
118+
119+blockToJira opts (Para inlines) = do
120+ contents <- inlineListToJira opts inlines
121+ appendNewlineUnlessInList contents
122+
123+blockToJira opts (LineBlock lns) =
124+ blockToJira opts $ linesToPara lns
125+
126+blockToJira _ b@(RawBlock f str) =
127+ if f == Format "jira"
128+ then return (pack str)
129+ else "" <$ report (BlockNotRendered b)
130+
131+blockToJira _ HorizontalRule = return "----\n"
132+
133+blockToJira opts (Header level attr inlines) = do
134+ contents <- inlineListToJira opts inlines
135+ let prefix = "h" <> pack (show level) <> ". "
136+ return $ prefix <> anchor attr <> contents <> "\n"
137+
138+blockToJira _ (CodeBlock attr@(_,classes,_) str) = do
139+ let lang = find (\c -> map toLower c `elem` knownLanguages) classes
140+ let start = case lang of
141+ Nothing -> "{code}"
142+ Just l -> "{code:" <> pack l <> "}"
143+ let anchorMacro = anchor attr
144+ appendNewlineUnlessInList . T.intercalate "\n" $
145+ (if anchorMacro == "" then id else (anchorMacro :))
146+ [start, escapeStringForJira (pack str), "{code}"]
147+
148+blockToJira opts (BlockQuote [p@(Para _)]) = do
149+ contents <- blockToJira opts p
150+ appendNewlineUnlessInList ("bq. " <> contents)
151+
152+blockToJira opts (BlockQuote blocks) = do
153+ contents <- blockListToJira opts blocks
154+ appendNewlineUnlessInList . T.intercalate "\n" $
155+ [ "{quote}", contents, "{quote}"]
156+
157+blockToJira opts (Table _caption _aligns _widths headers rows) = do
158+ headerCells <- mapM blocksToCell headers
159+ bodyRows <- mapM (mapM blocksToCell) rows
160+ let tblHead = headerCellsToRow headerCells
161+ let tblBody = map cellsToRow bodyRows
162+ return $ if all null headers
163+ then T.unlines tblBody
164+ else T.unlines (tblHead : tblBody)
165+ where
166+ blocksToCell :: PandocMonad m => [Block] -> JiraWriter m Text
167+ blocksToCell = inlineListToJira opts . blocksToInlines
168+
169+ cellsToRow :: [Text] -> Text
170+ cellsToRow cells = "|" <> T.intercalate "|" cells <> "|"
171+
172+ headerCellsToRow :: [Text] -> Text
173+ headerCellsToRow cells = "||" <> T.intercalate "||" cells <> "||"
174+
175+blockToJira opts (BulletList items) =
176+ listWithMarker opts items '*'
177+
178+blockToJira opts (OrderedList _listAttr items) =
179+ listWithMarker opts items '#'
180+
181+blockToJira opts (DefinitionList items) =
182+ blockToJira opts (BulletList (map defToBulletItem items))
183+ where
184+ defToBulletItem :: ([Inline], [[Block]]) -> [Block]
185+ defToBulletItem (inlns, defs) =
186+ let term = Plain [Strong inlns]
187+ blks = mconcat defs
188+ in term : blks
189+
190+-- Auxiliary functions for lists:
191+
192+-- | Create a list using the given character as bullet item marker.
193+listWithMarker :: PandocMonad m
194+ => WriterOptions
195+ -> [[Block]]
196+ -> Char
197+ -> JiraWriter m Text
198+listWithMarker opts items marker = do
199+ modify $ \s -> s { stListLevel = stListLevel s `T.snoc` marker }
200+ contents <- mapM (listItemToJira opts) items
201+ modify $ \s -> s { stListLevel = T.init (stListLevel s) }
202+ appendNewlineUnlessInList $ T.intercalate "\n" contents
203+
204+-- | Convert bullet or ordered list item (list of blocks) to Jira.
205+listItemToJira :: PandocMonad m
206+ => WriterOptions
207+ -> [Block]
208+ -> JiraWriter m Text
209+listItemToJira opts items = do
210+ contents <- blockListToJira opts items
211+ marker <- gets stListLevel
212+ return $ marker <> " " <> contents
213+
214+-- | Convert list of Pandoc block elements to Jira.
215+blockListToJira :: PandocMonad m
216+ => WriterOptions -- ^ Options
217+ -> [Block] -- ^ List of block elements
218+ -> JiraWriter m Text
219+blockListToJira opts blocks =
220+ T.intercalate "\n" <$> mapM (blockToJira opts) blocks
221+
222+-- | Convert list of Pandoc inline elements to Jira.
223+inlineListToJira :: PandocMonad m
224+ => WriterOptions
225+ -> [Inline]
226+ -> JiraWriter m Text
227+inlineListToJira opts lst =
228+ T.concat <$> mapM (inlineToJira opts) lst
229+
230+-- | Convert Pandoc inline element to Jira.
231+inlineToJira :: PandocMonad m
232+ => WriterOptions
233+ -> Inline
234+ -> JiraWriter m Text
235+
236+inlineToJira opts (Span attr lst) =
237+ (anchor attr <>) <$> inlineListToJira opts lst
238+
239+inlineToJira opts (Emph lst) = do
240+ contents <- inlineListToJira opts lst
241+ return $ "_" <> contents <> "_"
242+
243+inlineToJira opts (Strong lst) = do
244+ contents <- inlineListToJira opts lst
245+ return $ "*" <> contents <> "*"
246+
247+inlineToJira opts (Strikeout lst) = do
248+ contents <- inlineListToJira opts lst
249+ return $ "-" <> contents <> "-"
250+
251+inlineToJira opts (Superscript lst) = do
252+ contents <- inlineListToJira opts lst
253+ return $ "{^" <> contents <> "^}"
254+
255+inlineToJira opts (Subscript lst) = do
256+ contents <- inlineListToJira opts lst
257+ return $ "{~" <> contents <> "~}"
258+
259+inlineToJira opts (SmallCaps lst) = inlineListToJira opts lst
260+
261+inlineToJira opts (Quoted SingleQuote lst) = do
262+ contents <- inlineListToJira opts lst
263+ return $ "'" <> contents <> "'"
264+
265+inlineToJira opts (Quoted DoubleQuote lst) = do
266+ contents <- inlineListToJira opts lst
267+ return $ "\"" <> contents <> "\""
268+
269+inlineToJira opts (Cite _ lst) = inlineListToJira opts lst
270+
271+inlineToJira _ (Code attr str) =
272+ return (anchor attr <> "{{" <> escapeStringForJira (pack str) <> "}}")
273+
274+inlineToJira _ (Str str) = return $ escapeStringForJira (pack str)
275+
276+inlineToJira opts (Math InlineMath str) =
277+ lift (texMathToInlines InlineMath str) >>= inlineListToJira opts
278+
279+inlineToJira opts (Math DisplayMath str) = do
280+ mathInlines <- lift (texMathToInlines DisplayMath str)
281+ contents <- inlineListToJira opts mathInlines
282+ return $ "\\\\" <> contents <> "\\\\"
283+
284+inlineToJira _opts il@(RawInline f str) =
285+ if f == Format "jira"
286+ then return (pack str)
287+ else "" <$ report (InlineNotRendered il)
288+
289+inlineToJira _ LineBreak = return "\n"
290+
291+inlineToJira _ SoftBreak = return " "
292+
293+inlineToJira _ Space = return " "
294+
295+inlineToJira opts (Link _attr txt (src, _title)) = do
296+ linkText <- inlineListToJira opts txt
297+ return $ T.concat
298+ [ "["
299+ , if null txt then "" else linkText <> "|"
300+ , pack src
301+ , "]"
302+ ]
303+
304+inlineToJira _opts (Image attr _alt (src, _title)) =
305+ return . T.concat $ [anchor attr, "!", pack src, "!"]
306+
307+inlineToJira opts (Note contents) = do
308+ curNotes <- gets stNotes
309+ let newnum = length curNotes + 1
310+ contents' <- blockListToJira opts contents
311+ let thisnote = "[" <> pack (show newnum) <> "] " <> contents' <> "\n"
312+ modify $ \s -> s { stNotes = thisnote : curNotes }
313+ return $ "[" <> pack (show newnum) <> "]"
314+
315+-- | Language codes recognized by jira
316+knownLanguages :: [String]
317+knownLanguages =
318+ [ "actionscript", "ada", "applescript", "bash", "c", "c#", "c++"
319+ , "css", "erlang", "go", "groovy", "haskell", "html", "javascript"
320+ , "json", "lua", "nyan", "objc", "perl", "php", "python", "r", "ruby"
321+ , "scala", "sql", "swift", "visualbasic", "xml", "yaml"
322+ ]