git.iko.soy/typeable/pandoc
on master help sign in
Org reader: support macros
da8c153Albert Krewinkel
src/Text/Pandoc/Readers/Org/Inlines.hs+21
@@ -120,6 +120,7 @@ inline =
120 , superscript
120 , superscript
121 , inlineLaTeX
121 , inlineLaTeX
122 , exportSnippet
122 , exportSnippet
123+ , macro
123 , smart
124 , smart
124 , symbol
125 , symbol
125 ] <* (guard =<< newlinesCountWithinLimits)
126 ] <* (guard =<< newlinesCountWithinLimits)
@@ -839,6 +840,26 @@ exportSnippet = try $ do
839 snippet <- manyTill anyChar (try $ string "@@")
840 snippet <- manyTill anyChar (try $ string "@@")
840 returnF $ B.rawInline format snippet
841 returnF $ B.rawInline format snippet
841
842
843+macro :: PandocMonad m => OrgParser m (F Inlines)
844+macro = try $ do
845+ recursionDepth <- orgStateMacroDepth <$> getState
846+ guard $ recursionDepth < 15
847+ string "{{{"
848+ name <- many alphaNum
849+ args <- ([] <$ string "}}}")
850+ <|> char '(' *> argument `sepBy` char ',' <* eoa
851+ expander <- lookupMacro name <$> getState
852+ case expander of
853+ Nothing -> mzero
854+ Just fn -> do
855+ updateState $ \s -> s { orgStateMacroDepth = recursionDepth + 1 }
856+ res <- parseFromString (mconcat <$> many inline) $ fn args
857+ updateState $ \s -> s { orgStateMacroDepth = recursionDepth }
858+ return res
859+ where
860+ argument = many $ notFollowedBy eoa *> noneOf ","
861+ eoa = string ")}}}"
862+
842smart :: PandocMonad m => OrgParser m (F Inlines)
863smart :: PandocMonad m => OrgParser m (F Inlines)
843smart = do
864smart = do
844 guardEnabled Ext_smart
865 guardEnabled Ext_smart
src/Text/Pandoc/Readers/Org/Meta.hs+26−1
@@ -46,7 +46,7 @@ import Text.Pandoc.Definition
46
46
47import Control.Monad (mzero, void)
47import Control.Monad (mzero, void)
48import Data.Char (toLower)
48import Data.Char (toLower)
49−import Data.List (intersperse)
49+import Data.List (intersperse, sort)
50import qualified Data.Map as M
50import qualified Data.Map as M
51import Network.HTTP (urlEncode)
51import Network.HTTP (urlEncode)
52
52
@@ -151,6 +151,7 @@ optionLine = try $ do
151 "todo" -> todoSequence >>= updateState . registerTodoSequence
151 "todo" -> todoSequence >>= updateState . registerTodoSequence
152 "seq_todo" -> todoSequence >>= updateState . registerTodoSequence
152 "seq_todo" -> todoSequence >>= updateState . registerTodoSequence
153 "typ_todo" -> todoSequence >>= updateState . registerTodoSequence
153 "typ_todo" -> todoSequence >>= updateState . registerTodoSequence
154+ "macro" -> macroDefinition >>= updateState . registerMacro
154 _ -> mzero
155 _ -> mzero
155
156
156addLinkFormat :: Monad m => String
157addLinkFormat :: Monad m => String
@@ -218,3 +219,27 @@ todoSequence = try $ do
218 let todoMarkers = map (TodoMarker Todo) todo
219 let todoMarkers = map (TodoMarker Todo) todo
219 doneMarkers = map (TodoMarker Done) done
220 doneMarkers = map (TodoMarker Done) done
220 in todoMarkers ++ doneMarkers
221 in todoMarkers ++ doneMarkers
222+
223+macroDefinition :: Monad m => OrgParser m (String, [String] -> String)
224+macroDefinition = try $ do
225+ macroName <- many1 nonspaceChar <* skipSpaces
226+ firstPart <- expansionPart
227+ (elemOrder, parts) <- unzip <$> many ((,) <$> placeholder <*> expansionPart)
228+ let expander = mconcat . alternate (firstPart:parts) . reorder elemOrder
229+ return (macroName, expander)
230+ where
231+ placeholder :: Monad m => OrgParser m Int
232+ placeholder = try . fmap read $ char '$' *> many1 digit
233+
234+ expansionPart :: Monad m => OrgParser m String
235+ expansionPart = try $ many (notFollowedBy placeholder *> noneOf "\n\r")
236+
237+ alternate :: [a] -> [a] -> [a]
238+ alternate [] ys = ys
239+ alternate xs [] = xs
240+ alternate (x:xs) (y:ys) = x : y : alternate xs ys
241+
242+ reorder :: [Int] -> [String] -> [String]
243+ reorder perm xs =
244+ let element n = take 1 $ drop (n - 1) xs
245+ in concatMap element perm
src/Text/Pandoc/Readers/Org/ParserState.hs+18
@@ -39,6 +39,9 @@ module Text.Pandoc.Readers.Org.ParserState
39 , TodoState (..)
39 , TodoState (..)
40 , activeTodoMarkers
40 , activeTodoMarkers
41 , registerTodoSequence
41 , registerTodoSequence
42+ , MacroExpander
43+ , lookupMacro
44+ , registerMacro
42 , F
45 , F
43 , askF
46 , askF
44 , asksF
47 , asksF
@@ -78,6 +81,8 @@ type OrgNoteTable = [OrgNoteRecord]
78-- | Map of functions for link transformations. The map key is refers to the
81-- | Map of functions for link transformations. The map key is refers to the
79-- link-type, the corresponding function transforms the given link string.
82-- link-type, the corresponding function transforms the given link string.
80type OrgLinkFormatters = M.Map String (String -> String)
83type OrgLinkFormatters = M.Map String (String -> String)
84+-- | Macro expander function
85+type MacroExpander = [String] -> String
81
86
82-- | The states in which a todo item can be
87-- | The states in which a todo item can be
83data TodoState = Todo | Done
88data TodoState = Todo | Done
@@ -105,6 +110,8 @@ data OrgParserState = OrgParserState
105 , orgStateLastPreCharPos :: Maybe SourcePos
110 , orgStateLastPreCharPos :: Maybe SourcePos
106 , orgStateLastStrPos :: Maybe SourcePos
111 , orgStateLastStrPos :: Maybe SourcePos
107 , orgStateLinkFormatters :: OrgLinkFormatters
112 , orgStateLinkFormatters :: OrgLinkFormatters
113+ , orgStateMacros :: M.Map String MacroExpander
114+ , orgStateMacroDepth :: Int
108 , orgStateMeta :: F Meta
115 , orgStateMeta :: F Meta
109 , orgStateNotes' :: OrgNoteTable
116 , orgStateNotes' :: OrgNoteTable
110 , orgStateOptions :: ReaderOptions
117 , orgStateOptions :: ReaderOptions
@@ -156,6 +163,8 @@ defaultOrgParserState = OrgParserState
156 , orgStateLastPreCharPos = Nothing
163 , orgStateLastPreCharPos = Nothing
157 , orgStateLastStrPos = Nothing
164 , orgStateLastStrPos = Nothing
158 , orgStateLinkFormatters = M.empty
165 , orgStateLinkFormatters = M.empty
166+ , orgStateMacros = M.empty
167+ , orgStateMacroDepth = 0
159 , orgStateMeta = return nullMeta
168 , orgStateMeta = return nullMeta
160 , orgStateNotes' = []
169 , orgStateNotes' = []
161 , orgStateOptions = def
170 , orgStateOptions = def
@@ -185,6 +194,15 @@ activeTodoSequences st =
185activeTodoMarkers :: OrgParserState -> TodoSequence
194activeTodoMarkers :: OrgParserState -> TodoSequence
186activeTodoMarkers = concat . activeTodoSequences
195activeTodoMarkers = concat . activeTodoSequences
187
196
197+lookupMacro :: String -> OrgParserState -> Maybe MacroExpander
198+lookupMacro macroName = M.lookup macroName . orgStateMacros
199+
200+registerMacro :: (String, MacroExpander) -> OrgParserState -> OrgParserState
201+registerMacro (name, expander) st =
202+ let curMacros = orgStateMacros st
203+ in st{ orgStateMacros = M.insert name expander curMacros }
204+
205+
188
206
189--
207--
190-- Export Settings
208-- Export Settings
test/Tests/Readers/Org.hs+18
@@ -469,6 +469,24 @@ tests =
469 , citationNoteNum = 0
469 , citationNoteNum = 0
470 , citationHash = 0}
470 , citationHash = 0}
471 in (para . cite [citation] $ rawInline "latex" "\\cite{Coffee}")
471 in (para . cite [citation] $ rawInline "latex" "\\cite{Coffee}")
472+
473+ , "Macro" =:
474+ unlines [ "#+MACRO: HELLO /Hello, $1/"
475+ , "{{{HELLO(World)}}}"
476+ ] =?>
477+ para (emph "Hello, World")
478+
479+ , "Macro repeting its argument" =:
480+ unlines [ "#+MACRO: HELLO $1$1"
481+ , "{{{HELLO(moin)}}}"
482+ ] =?>
483+ para "moinmoin"
484+
485+ , "Macro called with too few arguments" =:
486+ unlines [ "#+MACRO: HELLO Foo $1 $2 Bar"
487+ , "{{{HELLO()}}}"
488+ ] =?>
489+ para "Foo Bar"
472 ]
490 ]
473
491
474 , testGroup "Meta Information" $
492 , testGroup "Meta Information" $
test/command/3401.mdnew file+19
@@ -0,0 +1,19 @@
1+See #3401 and <http://orgmode.org/manual/Macro-replacement.html>
2+
3+```
4+% pandoc -f org -t native
5+#+MACRO: HELLO /Hello, $1/
6+{{{HELLO(World)}}}
7+^D
8+[Para [Emph [Str "Hello,",Space,Str "World"]]]
9+```
10+
11+Inverted argument order
12+
13+```
14+% pandoc -f org -t native
15+#+MACRO: A $2,$1
16+{{{A(1,2)}}}
17+^D
18+[Para [Str "2,1"]]
19+```