packages feed

pandoc-filter-graphviz-0.1.0.0: src/PandocFilterGraphviz.hs

{-# LANGUAGE OverloadedStrings #-}

module PandocFilterGraphviz where

import Crypto.Hash
import Control.Monad (unless)

import Data.ByteString (ByteString)
import Data.Byteable (toBytes)
import qualified Data.ByteString.Char8 as C8
import qualified Data.ByteString.Base16 as B16

import qualified Data.Map.Strict as M
import Data.Text as T
import Data.Text.Encoding as E

import System.FilePath
import System.Directory
import System.Exit
import System.Process (system)

import Text.Pandoc
import Text.Pandoc.JSON

data Renderer = Dot | Neato | Twopi | Circo | FDP | SFDP | Patchwork
instance Show Renderer where
  show Dot = "dot"
  show Neato = "neato"
  show Twopi = "twopi"
  show Circo = "circo"
  show FDP = "fdp"
  show SFDP = "sfdp"
  show Patchwork = "patchwork"

rendererFromString :: Text -> Maybe Renderer
rendererFromString "dot" = Just Dot
rendererFromString "neato" = Just Neato
rendererFromString "twopi" = Just Twopi
rendererFromString "circo" = Just Circo
rendererFromString "fdp" = Just FDP
rendererFromString "sfdp" = Just SFDP
rendererFromString "patchwork" = Just Patchwork
rendererFromString _ = Nothing

(¤) :: Text -> Text -> Text
(¤) = T.append

hexSha3_512 :: ByteString -> ByteString
hexSha3_512 bs = C8.pack $ show (hash bs :: Digest SHA3_512)

sha :: Text -> Text
sha = E.decodeUtf8 . hexSha3_512 . B16.encode . E.encodeUtf8

fileName4Code :: Text -> Text -> Maybe Text -> FilePath
fileName4Code name source ext =
  filename
  where
    dirname = name ¤ "-images"
    shaN = sha source
    barename = shaN ¤ (case ext of
        Just e -> "." ¤ e
        Nothing -> "")
    filename = T.unpack dirname </> T.unpack barename

getCaption :: M.Map Text Text -> (Text,Text)
getCaption m = case M.lookup "caption" m of
  Just cap -> (cap,"fig:")
  Nothing -> ("","")

getFmt :: Maybe Format -> String
getFmt mfmt = case mfmt of
  Just (Format "latex") -> "pdf"
  Just _ -> "png"
  Nothing -> "png"

renderDot1 :: Maybe Format -> Maybe Renderer -> FilePath -> IO FilePath
renderDot1 mfmt mrndr src = renderDot mfmt rndr src dst >> return dst
  where
    dst = (dropExtension src) <.> (getFmt mfmt)
    rndr = case mrndr of
      Just r -> r
      Nothing -> Dot

renderDot :: Maybe Format -> Renderer -> FilePath -> FilePath -> IO ExitCode
renderDot mfmt rndr src dst =
  system $
    Prelude.unwords [ show rndr
                    , "-T" ++ (getFmt mfmt)
                    , "-o" ++ show dst
                    , show src ]

graphviz :: Maybe Format -> Block -> IO Block
graphviz mfmt cblock@(CodeBlock (id, classes, attrs) content) =
  if "graphviz" `elem` classes then do
    ensureFile dest >> writeFile dest content
    img <- renderDot1 mfmt mrndr dest
    ensureFile img
    return $ Para [Image (id,classes,attrs) [] (img, T.unpack caption)]
  else return cblock
  where
    dest = fileName4Code "graphviz" (T.pack content) (Just "dot")
    ensureFile fp =
      let dir = takeDirectory fp in
      createDirectoryIfMissing True dir >> doesFileExist fp >>=
        \exist ->
          unless exist $ writeFile fp ""
    toTextPairs = Prelude.map (\(f,s) -> (T.pack f,T.pack s))
    m = M.fromList $ toTextPairs $ attrs
    mrndr = case M.lookup "renderer" m of
      Just str -> rendererFromString str
      _ -> Nothing
    (caption, typedef) = getCaption m
graphviz fmt x = return x