@@ -43,7 +43,7 @@ import Text.Pandoc.Shared (compactify', compactify'DL)
44import Control.Applicative ( Applicative, pure
44import Control.Applicative ( Applicative, pure
45 , (<$>), (<$), (<*>), (<*), (*>), (<**>) )
45 , (<$>), (<$), (<*>), (<*), (*>), (<**>) )
46−import Control.Monad (foldM, guard, liftM, liftM2, when)
46+import Control.Monad (foldM, guard, liftM, liftM2, mzero, when)
47import Control.Monad.Reader (Reader, runReader, ask, asks)
47import Control.Monad.Reader (Reader, runReader, ask, asks)
48import Data.Char (isAlphaNum, toLower)
48import Data.Char (isAlphaNum, toLower)
49import Data.Default
49import Data.Default
@@ -51,6 +51,7 @@ import Data.List (intersperse, isPrefixOf, isSuffixOf)
51import qualified Data.Map as M
51import qualified Data.Map as M
52import Data.Maybe (listToMaybe, fromMaybe, isJust)
52import Data.Maybe (listToMaybe, fromMaybe, isJust)
53import Data.Monoid (Monoid, mconcat, mempty, mappend)
53import Data.Monoid (Monoid, mconcat, mempty, mappend)
54+import Network.HTTP (urlEncode)
55-- | Parse org-mode string and return a Pandoc document.
56-- | Parse org-mode string and return a Pandoc document.
56readOrg :: ReaderOptions -- ^ Reader options
57readOrg :: ReaderOptions -- ^ Reader options
@@ -76,6 +77,8 @@ type OrgNoteTable = [OrgNoteRecord]
77type OrgBlockAttributes = M.Map String String
78type OrgBlockAttributes = M.Map String String
80+type OrgLinkFormatters = M.Map String (String -> String)
79-- | Org-mode parser state
82-- | Org-mode parser state
80data OrgParserState = OrgParserState
83data OrgParserState = OrgParserState
81 { orgStateOptions :: ReaderOptions
84 { orgStateOptions :: ReaderOptions
@@ -86,6 +89,7 @@ data OrgParserState = OrgParserState
86 , orgStateLastForbiddenCharPos :: Maybe SourcePos
89 , orgStateLastForbiddenCharPos :: Maybe SourcePos
87 , orgStateLastPreCharPos :: Maybe SourcePos
90 , orgStateLastPreCharPos :: Maybe SourcePos
88 , orgStateLastStrPos :: Maybe SourcePos
91 , orgStateLastStrPos :: Maybe SourcePos
92+ , orgStateLinkFormatters :: OrgLinkFormatters
89 , orgStateMeta :: Meta
93 , orgStateMeta :: Meta
90 , orgStateMeta' :: F Meta
94 , orgStateMeta' :: F Meta
91 , orgStateNotes' :: OrgNoteTable
95 , orgStateNotes' :: OrgNoteTable
@@ -113,6 +117,7 @@ defaultOrgParserState = OrgParserState
113 , orgStateLastForbiddenCharPos = Nothing
117 , orgStateLastForbiddenCharPos = Nothing
114 , orgStateLastPreCharPos = Nothing
118 , orgStateLastPreCharPos = Nothing
115 , orgStateLastStrPos = Nothing
119 , orgStateLastStrPos = Nothing
120+ , orgStateLinkFormatters = M.empty
116 , orgStateMeta = nullMeta
121 , orgStateMeta = nullMeta
117 , orgStateMeta' = return nullMeta
122 , orgStateMeta' = return nullMeta
118 , orgStateNotes' = []
123 , orgStateNotes' = []
@@ -175,6 +180,13 @@ resetEmphasisNewlines :: OrgParser ()
175resetEmphasisNewlines = updateState $ \s ->
180resetEmphasisNewlines = updateState $ \s ->
176 s{ orgStateEmphasisNewlines = Nothing }
181 s{ orgStateEmphasisNewlines = Nothing }
183+addLinkFormat :: String
184+ -> (String -> String)
186+addLinkFormat key formatter = updateState $ \s ->
187+ let fs = orgStateLinkFormatters s
188+ in s{ orgStateLinkFormatters = M.insert key formatter fs }
178addToNotesTable :: OrgNoteRecord -> OrgParser ()
190addToNotesTable :: OrgNoteRecord -> OrgParser ()
179addToNotesTable note = do
191addToNotesTable note = do
180 oldnotes <- orgStateNotes' <$> getState
192 oldnotes <- orgStateNotes' <$> getState
@@ -423,7 +435,8 @@ specialLine :: OrgParser (F Blocks)
423specialLine = fmap return . try $ metaLine <|> commentLine
435specialLine = fmap return . try $ metaLine <|> commentLine
425metaLine :: OrgParser Blocks
437metaLine :: OrgParser Blocks
438+metaLine = try $ mempty
426−metaLine = try $ metaLineStart *> declarationLine
439+ <$ (metaLineStart *> (optionLine <|> declarationLine))
428commentLine :: OrgParser Blocks
441commentLine :: OrgParser Blocks
429commentLine = try $ commentLineStart *> anyLine *> pure mempty
442commentLine = try $ commentLineStart *> anyLine *> pure mempty
@@ -436,14 +449,14 @@ metaLineStart = try $ mappend <$> many spaceChar <*> string "#+"
436commentLineStart :: OrgParser String
449commentLineStart :: OrgParser String
437commentLineStart = try $ mappend <$> many spaceChar <*> string "# "
450commentLineStart = try $ mappend <$> many spaceChar <*> string "# "
439−declarationLine :: OrgParser Blocks
452+declarationLine :: OrgParser ()
440declarationLine = try $ do
453declarationLine = try $ do
441 key <- metaKey
454 key <- metaKey
442 inlinesF <- metaInlines
455 inlinesF <- metaInlines
443 updateState $ \st ->
456 updateState $ \st ->
444 let meta' = B.setMeta <$> pure key <*> inlinesF <*> pure nullMeta
457 let meta' = B.setMeta <$> pure key <*> inlinesF <*> pure nullMeta
445 in st { orgStateMeta' = orgStateMeta' st <> meta' }
458 in st { orgStateMeta' = orgStateMeta' st <> meta' }
446− return mempty
459+ return ()
448metaInlines :: OrgParser (F MetaValue)
461metaInlines :: OrgParser (F MetaValue)
449metaInlines = fmap (MetaInlines . B.toList) <$> inlinesTillNewline
462metaInlines = fmap (MetaInlines . B.toList) <$> inlinesTillNewline
@@ -453,6 +466,35 @@ metaKey = map toLower <$> many1 (noneOf ": \n\r")
453 <* char ':'
466 <* char ':'
454 <* skipSpaces
467 <* skipSpaces
469+optionLine :: OrgParser ()
470+optionLine = try $ do
473+ "link" -> parseLinkFormat >>= uncurry addLinkFormat
476+parseLinkFormat :: OrgParser ((String, String -> String))
477+parseLinkFormat = try $ do
478+ linkType <- (:) <$> letter <*> many (alphaNum <|> oneOf "-_") <* skipSpaces
479+ linkSubst <- parseFormat
480+ return (linkType, linkSubst)
482+-- | An ad-hoc, single-argument-only implementation of a printf-style format
484+parseFormat :: OrgParser (String -> String)
485+parseFormat = try $ do
486+ replacePlain <|> replaceUrl <|> justAppend
488+ -- inefficient, but who cares
489+ replacePlain = try $ (\x -> concat . flip intersperse x)
490+ <$> sequence [tillSpecifier 's', rest]
491+ replaceUrl = try $ (\x -> concat . flip intersperse x . urlEncode)
492+ <$> sequence [tillSpecifier 'h', rest]
493+ justAppend = try $ (++) <$> rest
495+ rest = manyTill anyChar (eof <|> () <$ oneOf "\n\r")
496+ tillSpecifier c = manyTill (noneOf "\n\r") (try $ string ('%':c:""))
457-- Headers
499-- Headers
@@ -850,13 +892,15 @@ linkOrImage = explicitOrImageLink
850explicitOrImageLink :: OrgParser (F Inlines)
892explicitOrImageLink :: OrgParser (F Inlines)
851explicitOrImageLink = try $ do
893explicitOrImageLink = try $ do
853− src <- linkTarget
895+ srcF <- applyCustomLinkFormat =<< linkTarget
854 title <- enclosedRaw (char '[') (char ']')
896 title <- enclosedRaw (char '[') (char ']')
855 title' <- parseFromString (mconcat <$> many inline) title
897 title' <- parseFromString (mconcat <$> many inline) title
857− return $ if isImageFilename src && isImageFilename title
901+ if isImageFilename src && isImageFilename title
858− then pure $ B.link src "" $ B.image title mempty mempty
902+ then pure $ B.link src "" $ B.image title mempty mempty
859− else linkToInlinesF src =<< title'
903+ else linkToInlinesF src =<< title'
861selflinkOrImage :: OrgParser (F Inlines)
905selflinkOrImage :: OrgParser (F Inlines)
862selflinkOrImage = try $ do
906selflinkOrImage = try $ do
@@ -881,6 +925,14 @@ selfTarget = try $ char '[' *> linkTarget <* char ']'
881linkTarget :: OrgParser String
925linkTarget :: OrgParser String
882linkTarget = enclosed (char '[') (char ']') (noneOf "\n\r[]")
926linkTarget = enclosed (char '[') (char ']') (noneOf "\n\r[]")
928+applyCustomLinkFormat :: String -> OrgParser (F String)
929+applyCustomLinkFormat link = do
930+ let (linkType, rest) = break (== ':') link
932+ formatter <- M.lookup linkType <$> asksF orgStateLinkFormatters
933+ return $ maybe link ($ drop 1 rest) formatter
884linkToInlinesF :: String -> Inlines -> F Inlines
936linkToInlinesF :: String -> Inlines -> F Inlines
885linkToInlinesF s@('#':_) = pure . B.link s ""
937linkToInlinesF s@('#':_) = pure . B.link s ""
886linkToInlinesF s
938linkToInlinesF s