git.iko.soy/typeable/pandoc
on master help sign in
Merge pull request #2934 from tarleb/org-properties-drawer

Org properties drawer

commit2e266b6a3a76f78554a67bf53f8fe7ac1d989f31 authorJohn MacFarlane <jgm@berkeley.edu> dateFri 20 May 2016, 10:38 (10 years ago) parent0160e85cd3282b
4 files changed+184−35
src/Text/Pandoc/Readers/Org.hs+56−28 src/Text/Pandoc/Writers/Org.hs+21−2 tests/Tests/Readers/Org.hs+14−5 tests/writer.org+93
src/Text/Pandoc/Readers/Org.hs+56−28
@@ -284,7 +284,7 @@ block = choice [ mempty <$ blanklines
284284 , orgBlock
285285 , figure
286286 , example
287− , drawer
287+ , genericDrawer
288288 , specialLine
289289 , header
290290 , return <$> hline
@@ -582,26 +582,55 @@ exampleCode = B.codeBlockWith ("", ["example"], [])
582582exampleLine :: OrgParser String
583583exampleLine = try $ skipSpaces *> string ": " *> anyLine
584584
585−-- Drawers for properties or a logbook
586−drawer :: OrgParser (F Blocks)
587−drawer = try $ do
585+
586+--
587+-- Drawers
588+--
589+
590+-- | A generic drawer which has no special meaning for org-mode.
591+genericDrawer :: OrgParser (F Blocks)
592+genericDrawer = try $ do
588593 drawerStart
589594 manyTill drawerLine (try drawerEnd)
590595 return mempty
591596
592597drawerStart :: OrgParser String
593598drawerStart = try $
594− skipSpaces *> drawerName <* skipSpaces <* P.newline
595− where drawerName = try $ char ':' *> validDrawerName <* char ':'
596− validDrawerName = stringAnyCase "PROPERTIES"
597− <|> stringAnyCase "LOGBOOK"
599+ skipSpaces *> drawerName <* skipSpaces <* newline
600+ where drawerName = char ':' *> manyTill nonspaceChar (char ':')
598601
599602drawerLine :: OrgParser String
600−drawerLine = try anyLine
603+drawerLine = anyLine
601604
602605drawerEnd :: OrgParser String
603606drawerEnd = try $
604− skipSpaces *> stringAnyCase ":END:" <* skipSpaces <* P.newline
607+ skipSpaces *> stringAnyCase ":END:" <* skipSpaces <* newline
608+
609+-- | Read a :PROPERTIES: drawer and return the key/value pairs contained
610+-- within.
611+propertiesDrawer :: OrgParser [(String, String)]
612+propertiesDrawer = try $ do
613+ drawerType <- drawerStart
614+ guard $ map toUpper drawerType == "PROPERTIES"
615+ manyTill property (try drawerEnd)
616+ where
617+ property :: OrgParser (String, String)
618+ property = try $ (,) <$> key <*> value
619+
620+ key :: OrgParser String
621+ key = try $ skipSpaces *> char ':' *> many1Till nonspaceChar (char ':')
622+
623+ value :: OrgParser String
624+ value = try $ skipSpaces *> manyTill anyChar (try $ skipSpaces *> P.newline)
625+
626+keyValuesToAttr :: [(String, String)] -> Attr
627+keyValuesToAttr kvs =
628+ let
629+ id' = fromMaybe mempty . lookup "id" $ kvs
630+ cls = fromMaybe mempty . lookup "class" $ kvs
631+ kvs' = filter (flip notElem ["id", "class"] . fst) kvs
632+ in
633+ (id', words cls, kvs')
605634
606635
607636--
@@ -700,29 +729,28 @@ parseFormat = try $ do
700729-- | Headers
701730header :: OrgParser (F Blocks)
702731header = try $ do
703− level <- headerStart
704− title <- manyTill inline (lookAhead headerEnd)
705− tags <- headerEnd
706− let inlns = trimInlinesF . mconcat $ title <> map tagToInlineF tags
707− st <- getState
708− let inlines = runF inlns st
709− attr <- registerHeader nullAttr inlines
732+ level <- headerStart
733+ title <- manyTill inline (lookAhead $ optional headerTags <* P.newline)
734+ tags <- option [] headerTags
735+ newline
736+ propAttr <- option nullAttr (keyValuesToAttr <$> propertiesDrawer)
737+ inlines <- runF (tagTitle title tags) <$> getState
738+ attr <- registerHeader propAttr inlines
710739 return $ pure (B.headerWith attr level inlines)
711740 where
741+ tagTitle :: [F Inlines] -> [String] -> F Inlines
742+ tagTitle title tags = trimInlinesF . mconcat $ title <> map tagToInlineF tags
743+
712744 tagToInlineF :: String -> F Inlines
713745 tagToInlineF t = return $ B.spanWith ("", ["tag"], [("data-tag-name", t)]) mempty
714746
715−headerEnd :: OrgParser [String]
716−headerEnd = option [] headerTags <* newline
717−
718−headerTags :: OrgParser [String]
719−headerTags = try $
720− skipSpaces
721− *> char ':'
722− *> many1 tag
723− <* skipSpaces
724− where tag = many1 (alphaNum <|> oneOf "@%#_")
725− <* char ':'
747+ headerTags :: OrgParser [String]
748+ headerTags = try $
749+ let tag = many1 (alphaNum <|> oneOf "@%#_") <* char ':'
750+ in skipSpaces
751+ *> char ':'
752+ *> many1 tag
753+ <* skipSpaces
726754
727755headerStart :: OrgParser Int
728756headerStart = try $
src/Text/Pandoc/Writers/Org.hs+21−2
@@ -137,10 +137,13 @@ blockToOrg (RawBlock f str) | isRawFormat f =
137137 return $ text str
138138blockToOrg (RawBlock _ _) = return empty
139139blockToOrg HorizontalRule = return $ blankline $$ "--------------" $$ blankline
140−blockToOrg (Header level _ inlines) = do
140+blockToOrg (Header level attr inlines) = do
141141 contents <- inlineListToOrg inlines
142142 let headerStr = text $ if level > 999 then " " else replicate level '*'
143− return $ headerStr <> " " <> contents <> blankline
143+ let drawerStr = if attr == nullAttr
144+ then empty
145+ else cr <> nest (level + 1) (propertiesDrawer attr)
146+ return $ headerStr <> " " <> contents <> drawerStr <> blankline
144147blockToOrg (CodeBlock (_,classes,_) str) = do
145148 opts <- stOptions <$> get
146149 let tabstop = writerTabStop opts
@@ -230,6 +233,22 @@ definitionListItemToOrg (label, defs) = do
230233 contents <- liftM vcat $ mapM blockListToOrg defs
231234 return $ hang 3 "- " $ label' <> " :: " <> (contents <> cr)
232235
236+-- | Convert list of key/value pairs to Org :PROPERTIES: drawer.
237+propertiesDrawer :: Attr -> Doc
238+propertiesDrawer (ident, classes, kv) =
239+ let
240+ drawerStart = text ":PROPERTIES:"
241+ drawerEnd = text ":END:"
242+ kv' = if (classes == mempty) then kv else ("class", unwords classes):kv
243+ kv'' = if (ident == mempty) then kv' else ("id", ident):kv'
244+ properties = vcat $ map kvToOrgProperty kv''
245+ in
246+ drawerStart <> cr <> properties <> cr <> drawerEnd
247+ where
248+ kvToOrgProperty :: (String, String) -> Doc
249+ kvToOrgProperty (key, value) =
250+ text ":" <> text key <> text ": " <> text value <> cr
251+
233252-- | Convert list of Pandoc block elements to Org.
234253blockListToOrg :: [Block] -- ^ List of block elements
235254 -> State WriterState Doc
tests/Tests/Readers/Org.hs+14−5
@@ -412,17 +412,17 @@ tests =
412412 ] =?>
413413 para "Before" <> para "After"
414414
415− , "Drawer start is the only text in first line of a drawer" =:
415+ , "Drawer markers must be the only text in the line" =:
416416 unlines [ " :LOGBOOK: foo"
417− , " :END:"
417+ , " :END: bar"
418418 ] =?>
419− para (":LOGBOOK:" <> space <> "foo" <> softbreak <> ":END:")
419+ para (":LOGBOOK: foo" <> softbreak <> ":END: bar")
420420
421− , "Drawers with unknown names are just text" =:
421+ , "Drawers can be arbitrary" =:
422422 unlines [ ":FOO:"
423423 , ":END:"
424424 ] =?>
425− para (":FOO:" <> softbreak <> ":END:")
425+ (mempty::Blocks)
426426
427427 , "Anchor reference" =:
428428 unlines [ "<<link-here>> Target."
@@ -597,6 +597,15 @@ tests =
597597 , headerWith ("but-this-is", [], []) 2 "But this is"
598598 ]
599599
600+ , "Preferences are treated as header attributes" =:
601+ unlines [ "* foo"
602+ , " :PROPERTIES:"
603+ , " :id: fubar"
604+ , " :bar: baz"
605+ , " :END:"
606+ ] =?>
607+ headerWith ("fubar", [], [("bar", "baz")]) 1 "foo"
608+
600609 , "Paragraph starting with an asterisk" =:
601610 "*five" =?>
602611 para "*five"
tests/writer.org+93
@@ -9,30 +9,60 @@ markdown test suite.
99--------------
1010
1111* Headers
12+ :PROPERTIES:
13+ :id: headers
14+ :END:
1215
1316** Level 2 with an [[/url][embedded link]]
17+ :PROPERTIES:
18+ :id: level-2-with-an-embedded-link
19+ :END:
1420
1521*** Level 3 with /emphasis/
22+ :PROPERTIES:
23+ :id: level-3-with-emphasis
24+ :END:
1625
1726**** Level 4
27+ :PROPERTIES:
28+ :id: level-4
29+ :END:
1830
1931***** Level 5
32+ :PROPERTIES:
33+ :id: level-5
34+ :END:
2035
2136* Level 1
37+ :PROPERTIES:
38+ :id: level-1
39+ :END:
2240
2341** Level 2 with /emphasis/
42+ :PROPERTIES:
43+ :id: level-2-with-emphasis
44+ :END:
2445
2546*** Level 3
47+ :PROPERTIES:
48+ :id: level-3
49+ :END:
2650
2751with no blank line
2852
2953** Level 2
54+ :PROPERTIES:
55+ :id: level-2
56+ :END:
3057
3158with no blank line
3259
3360--------------
3461
3562* Paragraphs
63+ :PROPERTIES:
64+ :id: paragraphs
65+ :END:
3666
3767Here's a regular paragraph.
3868
@@ -48,6 +78,9 @@ here.
4878--------------
4979
5080* Block Quotes
81+ :PROPERTIES:
82+ :id: block-quotes
83+ :END:
5184
5285E-mail style:
5386
@@ -87,6 +120,9 @@ And a following paragraph.
87120--------------
88121
89122* Code Blocks
123+ :PROPERTIES:
124+ :id: code-blocks
125+ :END:
90126
91127Code:
92128
@@ -111,8 +147,14 @@ And:
111147--------------
112148
113149* Lists
150+ :PROPERTIES:
151+ :id: lists
152+ :END:
114153
115154** Unordered
155+ :PROPERTIES:
156+ :id: unordered
157+ :END:
116158
117159Asterisks tight:
118160
@@ -157,6 +199,9 @@ Minuses loose:
157199- Minus 3
158200
159201** Ordered
202+ :PROPERTIES:
203+ :id: ordered
204+ :END:
160205
161206Tight:
162207
@@ -197,6 +242,9 @@ Multiple paragraphs:
1972423. Item 3.
198243
199244** Nested
245+ :PROPERTIES:
246+ :id: nested
247+ :END:
200248
201249- Tab
202250
@@ -228,6 +276,9 @@ Same thing but with paragraphs:
2282763. Third
229277
230278** Tabs and spaces
279+ :PROPERTIES:
280+ :id: tabs-and-spaces
281+ :END:
231282
232283- this is a list item indented with tabs
233284
@@ -238,6 +289,9 @@ Same thing but with paragraphs:
238289 - this is an example list item indented with spaces
239290
240291** Fancy list markers
292+ :PROPERTIES:
293+ :id: fancy-list-markers
294+ :END:
241295
2422962) begins with 2
2432973) and now 3
@@ -276,6 +330,9 @@ B. Williams
276330--------------
277331
278332* Definition Lists
333+ :PROPERTIES:
334+ :id: definition-lists
335+ :END:
279336
280337Tight using spaces:
281338
@@ -342,6 +399,9 @@ Blank line after term, indented marker, alternate markers:
342399 2. sublist
343400
344401* HTML Blocks
402+ :PROPERTIES:
403+ :id: html-blocks
404+ :END:
345405
346406Simple block on one line:
347407
@@ -569,6 +629,9 @@ Hr's:
569629--------------
570630
571631* Inline Markup
632+ :PROPERTIES:
633+ :id: inline-markup
634+ :END:
572635
573636This is /emphasized/, and so /is this/.
574637
@@ -598,6 +661,9 @@ spaces: a\^b c\^d, a~b c~d.
598661--------------
599662
600663* Smart quotes, ellipses, dashes
664+ :PROPERTIES:
665+ :id: smart-quotes-ellipses-dashes
666+ :END:
601667
602668"Hello," said the spider. "'Shelob' is my name."
603669
@@ -619,6 +685,9 @@ Ellipses...and...and....
619685--------------
620686
621687* LaTeX
688+ :PROPERTIES:
689+ :id: latex
690+ :END:
622691
623692- \cite[22-23]{smith.1899}
624693- $2+2=4$
@@ -649,6 +718,9 @@ Cat & 1 \\ \hline
649718--------------
650719
651720* Special Characters
721+ :PROPERTIES:
722+ :id: special-characters
723+ :END:
652724
653725Here is some unicode:
654726
@@ -703,8 +775,14 @@ Minus: -
703775--------------
704776
705777* Links
778+ :PROPERTIES:
779+ :id: links
780+ :END:
706781
707782** Explicit
783+ :PROPERTIES:
784+ :id: explicit
785+ :END:
708786
709787Just a [[/url/][URL]].
710788
@@ -725,6 +803,9 @@ Just a [[/url/][URL]].
725803[[][Empty]].
726804
727805** Reference
806+ :PROPERTIES:
807+ :id: reference
808+ :END:
728809
729810Foo [[/url/][bar]].
730811
@@ -753,6 +834,9 @@ Foo [[/url/][bar]].
753834Foo [[/url/][biz]].
754835
755836** With ampersands
837+ :PROPERTIES:
838+ :id: with-ampersands
839+ :END:
756840
757841Here's a [[http://example.com/?foo=1&bar=2][link with an ampersand in the
758842URL]].
@@ -764,6 +848,9 @@ Here's an [[/script?foo=1&bar=2][inline link]].
764848Here's an [[/script?foo=1&bar=2][inline link in pointy braces]].
765849
766850** Autolinks
851+ :PROPERTIES:
852+ :id: autolinks
853+ :END:
767854
768855With an ampersand: [[http://example.com/?foo=1&bar=2]]
769856
@@ -786,6 +873,9 @@ Auto-links should not occur here: =<http://example.com/>=
786873--------------
787874
788875* Images
876+ :PROPERTIES:
877+ :id: images
878+ :END:
789879
790880From "Voyage dans la Lune" by Georges Melies (1902):
791881
@@ -797,6 +887,9 @@ Here is a movie [[movie.jpg]] icon.
797887--------------
798888
799889* Footnotes
890+ :PROPERTIES:
891+ :id: footnotes
892+ :END:
800893
801894Here is a footnote reference, [1] and another. [2] This should /not/ be a
802895footnote reference, because it contains a space.[\^my note] Here is an inline