Skip to content

Commit 1c36857

Browse files
tarlebjgm
authored andcommitted
Add jira writer (#5548)
This adds support for Atlassian's jira markup. Closes #2497
1 parent 62f8422 commit 1c36857

File tree

7 files changed

+1015
-0
lines changed

7 files changed

+1015
-0
lines changed

data/templates/default.jira

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
$for(include-before)$
2+
$include-before$
3+
4+
$endfor$
5+
$body$
6+
$for(include-after)$
7+
8+
$include-after$
9+
$endfor$

pandoc.cabal

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ data-files:
5353
data/templates/default.latex
5454
data/templates/default.context
5555
data/templates/default.texinfo
56+
data/templates/default.jira
5657
data/templates/default.man
5758
data/templates/default.ms
5859
data/templates/default.markdown
@@ -242,6 +243,7 @@ extra-source-files:
242243
test/tables.docbook4
243244
test/tables.docbook5
244245
test/tables.jats
246+
test/tables.jira
245247
test/tables.dokuwiki
246248
test/tables.zimwiki
247249
test/tables.icml
@@ -273,6 +275,7 @@ extra-source-files:
273275
test/writer.docbook4
274276
test/writer.docbook5
275277
test/writer.jats
278+
test/writer.jira
276279
test/writer.html4
277280
test/writer.html5
278281
test/writer.man
@@ -500,6 +503,7 @@ library
500503
Text.Pandoc.Writers.HTML,
501504
Text.Pandoc.Writers.Ipynb,
502505
Text.Pandoc.Writers.ICML,
506+
Text.Pandoc.Writers.Jira,
503507
Text.Pandoc.Writers.LaTeX,
504508
Text.Pandoc.Writers.ConTeXt,
505509
Text.Pandoc.Writers.OpenDocument,

src/Text/Pandoc/Writers.hs

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module Text.Pandoc.Writers
4141
, writeICML
4242
, writeJATS
4343
, writeJSON
44+
, writeJira
4445
, writeLaTeX
4546
, writeMan
4647
, writeMarkdown
@@ -91,6 +92,7 @@ import Text.Pandoc.Writers.Haddock
9192
import Text.Pandoc.Writers.HTML
9293
import Text.Pandoc.Writers.ICML
9394
import Text.Pandoc.Writers.JATS
95+
import Text.Pandoc.Writers.Jira
9496
import Text.Pandoc.Writers.LaTeX
9597
import Text.Pandoc.Writers.Man
9698
import Text.Pandoc.Writers.Markdown
@@ -141,6 +143,7 @@ writers = [
141143
,("docbook4" , TextWriter writeDocbook4)
142144
,("docbook5" , TextWriter writeDocbook5)
143145
,("jats" , TextWriter writeJATS)
146+
,("jira" , TextWriter writeJira)
144147
,("opml" , TextWriter writeOPML)
145148
,("opendocument" , TextWriter writeOpenDocument)
146149
,("latex" , TextWriter writeLaTeX)

src/Text/Pandoc/Writers/Jira.hs

+322
Original file line numberDiff line numberDiff line change
@@ -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+
'&' -> "&amp;"
70+
'<' -> "&lt;"
71+
'>' -> "&gt;"
72+
'"' -> "&quot;"
73+
'*' -> "&ast;"
74+
'_' -> "&lowbar;"
75+
'@' -> "&commat;"
76+
'+' -> "&plus;"
77+
'-' -> "&hyphen;"
78+
'|' -> "&vert;"
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+
]

test/Tests/Old.hs

+3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ tests pandocPath =
9696
, test' "reader" ["-r", "jats", "-w", "native", "-s"]
9797
"jats-reader.xml" "jats-reader.native"
9898
]
99+
, testGroup "jira"
100+
[ testGroup "writer" $ writerTests' "jira"
101+
]
99102
, testGroup "native"
100103
[ testGroup "writer" $ writerTests' "native"
101104
, test' "reader" ["-r", "native", "-w", "native", "-s"]

0 commit comments

Comments
 (0)