/vendor/heist-0.7.0.1/src/Text/Templating/Heist/Splices/Markdown.hs

https://github.com/roman/noomii-crawler · Haskell · 173 lines · 117 code · 42 blank · 14 comment · 2 complexity · a35a9e4e56cd0be16222eea2375e9ea4 MD5 · raw file

  1. {-# LANGUAGE DeriveDataTypeable #-}
  2. module Text.Templating.Heist.Splices.Markdown where
  3. ------------------------------------------------------------------------------
  4. import Data.ByteString (ByteString)
  5. import qualified Data.ByteString as B
  6. import qualified Data.ByteString.Char8 as BC
  7. import Data.Text (Text)
  8. import qualified Data.Text as T
  9. import qualified Data.Text.Encoding as T
  10. import Data.Maybe
  11. import Control.Concurrent
  12. import Control.Exception (throwIO)
  13. import Control.Monad
  14. import Control.Monad.CatchIO
  15. import Control.Monad.Trans
  16. import Data.Typeable
  17. import Prelude hiding (catch)
  18. import System.Directory
  19. import System.Exit
  20. import System.FilePath.Posix
  21. import System.IO
  22. import System.Process
  23. import Text.XmlHtml
  24. ------------------------------------------------------------------------------
  25. import Text.Templating.Heist.Internal
  26. import Text.Templating.Heist.Types
  27. data PandocMissingException = PandocMissingException
  28. deriving (Typeable)
  29. instance Show PandocMissingException where
  30. show PandocMissingException =
  31. "Cannot find the \"pandoc\" executable; is it on your $PATH?"
  32. instance Exception PandocMissingException
  33. data MarkdownException = MarkdownException ByteString
  34. deriving (Typeable)
  35. instance Show MarkdownException where
  36. show (MarkdownException e) =
  37. "Markdown error: pandoc replied:\n\n" ++ BC.unpack e
  38. instance Exception MarkdownException
  39. data NoMarkdownFileException = NoMarkdownFileException
  40. deriving (Typeable)
  41. instance Show NoMarkdownFileException where
  42. show NoMarkdownFileException =
  43. "Markdown error: no file or template in context" ++
  44. " during processing of markdown tag"
  45. instance Exception NoMarkdownFileException where
  46. ------------------------------------------------------------------------------
  47. -- | Default name for the markdown splice.
  48. markdownTag :: Text
  49. markdownTag = "markdown"
  50. ------------------------------------------------------------------------------
  51. -- | Implementation of the markdown splice.
  52. markdownSplice :: MonadIO m => Splice m
  53. markdownSplice = do
  54. templateDir <- liftM (fmap takeDirectory) getTemplateFilePath
  55. pdMD <- liftIO $ findExecutable "pandoc"
  56. when (isNothing pdMD) $ liftIO $ throwIO PandocMissingException
  57. tree <- getParamNode
  58. (source,markup) <- liftIO $
  59. case getAttribute "file" tree of
  60. Just f -> do
  61. m <- maybe (liftIO $ throwIO NoMarkdownFileException )
  62. (\tp -> pandoc (fromJust pdMD) tp $ T.unpack f)
  63. templateDir
  64. return (T.unpack f,m)
  65. Nothing -> do
  66. m <- pandocBS (fromJust pdMD) $ T.encodeUtf8 $ nodeText tree
  67. return ("inline_splice",m)
  68. let ee = parseHTML source markup
  69. case ee of
  70. Left e -> throw $ MarkdownException
  71. $ BC.pack ("Error parsing markdown output: " ++ e)
  72. Right d -> return (docContent d)
  73. pandoc :: FilePath -> FilePath -> FilePath -> IO ByteString
  74. pandoc pandocPath templateDir inputFile = do
  75. (ex, sout, serr) <- readProcessWithExitCode' pandocPath args ""
  76. when (isFail ex) $ throw $ MarkdownException serr
  77. return $ BC.concat [ "<div class=\"markdown\">\n"
  78. , sout
  79. , "\n</div>" ]
  80. where
  81. isFail ExitSuccess = False
  82. isFail _ = True
  83. args = [ "-S", "--no-wrap", templateDir </> inputFile ]
  84. pandocBS :: FilePath -> ByteString -> IO ByteString
  85. pandocBS pandocPath s = do
  86. -- using the crummy string functions for convenience here
  87. (ex, sout, serr) <- readProcessWithExitCode' pandocPath args s
  88. when (isFail ex) $ throw $ MarkdownException serr
  89. return $ BC.concat [ "<div class=\"markdown\">\n"
  90. , sout
  91. , "\n</div>" ]
  92. where
  93. isFail ExitSuccess = False
  94. isFail _ = True
  95. args = [ "-S", "--no-wrap" ]
  96. -- a version of readProcessWithExitCode that does I/O properly
  97. readProcessWithExitCode'
  98. :: FilePath -- ^ command to run
  99. -> [String] -- ^ any arguments
  100. -> ByteString -- ^ standard input
  101. -> IO (ExitCode,ByteString,ByteString) -- ^ exitcode, stdout, stderr
  102. readProcessWithExitCode' cmd args input = do
  103. (Just inh, Just outh, Just errh, pid) <-
  104. createProcess (proc cmd args){ std_in = CreatePipe,
  105. std_out = CreatePipe,
  106. std_err = CreatePipe }
  107. outMVar <- newEmptyMVar
  108. outM <- newEmptyMVar
  109. errM <- newEmptyMVar
  110. -- fork off a thread to start consuming stdout
  111. forkIO $ do
  112. out <- B.hGetContents outh
  113. putMVar outM out
  114. putMVar outMVar ()
  115. -- fork off a thread to start consuming stderr
  116. forkIO $ do
  117. err <- B.hGetContents errh
  118. putMVar errM err
  119. putMVar outMVar ()
  120. -- now write and flush any input
  121. when (not (B.null input)) $ do B.hPutStr inh input; hFlush inh
  122. hClose inh -- done with stdin
  123. -- wait on the output
  124. takeMVar outMVar
  125. takeMVar outMVar
  126. hClose outh
  127. -- wait on the process
  128. ex <- waitForProcess pid
  129. out <- readMVar outM
  130. err <- readMVar errM
  131. return (ex, out, err)