yesod-markdown-0.2.1: Yesod/Markdown/Macros.hs
{-# LANGUAGE ViewPatterns, QuasiQuotes, FlexibleContexts, CPP #-}
-- | Macros allow users to access more advanced functionality from within Markdown syntax. There are two types
-- of macros, block and inline, which allow substitution of 'Block' and 'Inline' data, respectively. Macros
-- are called in a very similar fashion to shell programs: the argument string is split on whitespace. The
-- first word is the name of the macro, and the remaining words are the arguments. Option-parsing libraries
-- may be useful for interpreting the arguments.
--
-- Note: using 'blockMacros' and 'inlineMacros' at the same time with the same magic string and can lead to behavior
-- that depends on the order in which they are called, if any of the macro names are the same for block and inline
-- macros. However, if none of the macro names are the same, unrecognized macro names will be ignored by the pass
-- that doesn't recognize them, leaving them available to be recognized by the other pass.
module Yesod.Markdown.Macros
where
import Text.Pandoc
import Safe
import Yesod
import Control.Applicative
import Data.Map ( Map )
import qualified Data.Map as Map
import qualified Data.ByteString.Lazy.UTF8 as U
-- | Convert block-level macros. Block-level macros are signalled by a first-level header containing a piece of
-- inline code starting with a client-specified magic string. For example, if the magic string is @??@, a macro
-- can be called by
--
-- > #`??MACRO_NAME MACRO_ARGS`
--
-- where @MACRO_NAME@ is the identifying name of the macro and @MACRO_ARGS@ is a space-separated list of arguments.
blockMacros
:: Yesod master
=> String -- ^ Magic string to introduce the macro
-> Map String ([String] -> GHandler sub master Block) -- ^ Lookup table from macro names to macro functions
-> Pandoc
-> GHandler sub master Pandoc
blockMacros magic table p = processWithM blockMacros' p where
blockMacros' (Header 1 [Code (splitAt (length magic) -> (magic',words -> ((flip Map.lookup table -> Just f):xs)))])
| magic == magic' = f xs
blockMacros' b = return b
-- | Convert block-level macros. Inline-level macros are signalled by a piece of inline code starting with a
-- client-specified magic string. For example, if the magic string is @??@, a macro can be called by
--
-- > `??MACRO_NAME MACRO_ARGS`
--
-- where @MACRO_NAME@ is the identifying name of the macro and @MACRO_ARGS@ is a space-separated list of arguments.
inlineMacros
:: Yesod master
=> String -- ^ Magic string to introduce the macro
-> Map String ([String] -> GHandler sub master [Inline]) -- ^ Lookup table from macro names to macro functions
-> Pandoc
-> GHandler sub master Pandoc
inlineMacros magic table p = processWithM (fmap concat . mapM inlineMacros') p where
inlineMacros' (Code (splitAt (length magic) -> (magic',words -> ((flip Map.lookup table -> Just f):xs))))
| magic == magic' = f xs
inlineMacros' b = return [b]
-- | Convert a 'Hamlet' value to a 'Block'.
hamletToBlock :: Hamlet (Route master) -> GHandler sub master Block
hamletToBlock x = RawHtml . U.toString . renderHtml . x <$> getUrlRenderParams
-- | Convert a 'Hamlet' value to an 'Inline'.
hamletToInline :: Hamlet (Route master) -> GHandler sub master Inline
hamletToInline x = HtmlInline . U.toString . renderHtml . x <$> getUrlRenderParams
-- | Read in
localRoute :: Read (Route master) => [String] -> GHandler sub master Inline
localRoute = maybe (return (Str "")) f . readMay . unwords where
f = hamletToInline . (\x ->
#if GHC7
[hamlet|
#else
[$hamlet|
#endif
@x@|])