pdf-slave-1.0.0.0: src/Text/PDF/Slave/Template.hs
-- | Defines document template
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Text.PDF.Slave.Template(
TemplateName
, TemplateInput
, TemplateBody
, TemplateBibtex
, DependencyBody
, BibTexBody
, TemplateDependency(..)
, Template(..)
, TemplateDependencyFile(..)
, TemplateFile(..)
-- * Helpers
, loadTemplateInMemory
, storeTemplateInFiles
) where
import Control.Monad (mzero)
import Data.ByteString (ByteString)
import Data.Monoid
import Data.Text as T
import Data.Yaml
import Filesystem.Path.CurrentOS (directory)
import GHC.Generics
import Prelude hiding (FilePath)
import Shelly as Sh
import qualified Data.Aeson as A
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Lazy as BZ
import qualified Data.Map.Strict as M
import qualified Data.Text.Encoding as T
-- | Template unique name
type TemplateName = Text
-- | A template takes simple YAML document as input
type TemplateInput = Value
-- | Template body is text with .htex content
type TemplateBody = Text
-- | Template can define additional bibtex database
type TemplateBibtex = Text
-- | Dependency can be a binary file
type DependencyBody = ByteString
-- | Content of bibtex file
type BibTexBody = Text
-- | Template has different types of dependencies, each type of the dependecy
-- has own affect on rendering pipe.
data TemplateDependency =
-- | Bibtex file for references to other documents. Need call to bibtex
BibtexDep BibTexBody
-- | HTex file that need to be compiled to .tex file
| TemplateDep Template
-- | HTex file that need to be compiled to .pdf file
| TemplatePdfDep Template
-- | Any other file that doesn't need a compilation (listings, images, etc)
| OtherDep DependencyBody
deriving (Generic, Show)
instance FromJSON TemplateDependency where
parseJSON val@(Object o) = do
depType <- o .: "type"
case T.toLower . T.strip $ depType of
"bibtex" -> BibtexDep <$> o .: "body"
"template" -> TemplateDep <$> parseJSON val
"template_pdf" -> TemplatePdfDep <$> parseJSON val
"other" -> do
(t :: Text) <- o .: "body"
either (\e -> fail $ "Cannot decode dependency body (base64): " <> e) (return . OtherDep) $
B64.decode . T.encodeUtf8 $ t
_ -> fail $ "Unknown template type " <> unpack depType
parseJSON _ = mzero
instance ToJSON TemplateDependency where
toJSON d = case d of
BibtexDep body -> object [
"type" .= ("bibtex" :: Text)
, "body" .= body
]
TemplateDep body -> let
Object o1 = object [ "type" .= ("template" :: Text) ]
Object o2 = toJSON body
in Object (o1 <> o2)
TemplatePdfDep body -> let
Object o1 = object [ "type" .= ("template_pdf" :: Text) ]
Object o2 = toJSON body
in Object (o1 <> o2)
OtherDep bs -> object [
"type" .= ("other" :: Text)
, "body" .= (T.decodeUtf8 . B64.encode $ bs)
]
-- | Description of document template
data Template = Template {
-- | Template has human readable name
templateName :: TemplateName
-- | Template expects input in YAML format
, templateInput :: Maybe TemplateInput
-- | Template contents
, templateBody :: TemplateBody
-- | Template dependencies (bibtex, listings, other htex files)
, templateDeps :: M.Map TemplateName TemplateDependency
-- | Additional flags for `haskintex`
, templateHaskintexOpts :: [Text]
} deriving (Generic, Show)
instance FromJSON Template where
parseJSON (Object o) = Template
<$> o .: "name"
<*> o .:? "input"
<*> o .: "body"
<*> o .:? "dependencies" .!= mempty
<*> o .:? "haskintex-opts" .!= mempty
parseJSON _ = mzero
instance ToJSON Template where
toJSON Template{..} = object [
"name" .= templateName
, "input" .= templateInput
, "body" .= templateBody
, "dependencies" .= templateDeps
, "haskintex-opts" .= templateHaskintexOpts
]
-- | Same as 'TemplateDependency' but keeps contents in separate files
data TemplateDependencyFile =
-- | Bibtex file for references to other documents. Need call to bibtex.
-- Name of dependency is a filename with contents.
BibtexDepFile
-- | HTex file that need to be compiled to .tex file
-- Name of dependency defines a subfolder for the template.
| TemplateDepFile TemplateFile
-- | HTex file that need to be compiled to .pdf file
-- Name of dependency deinfes a subfolder for the template.
| TemplatePdfDepFile TemplateFile
-- | Any other file that doesn't need a compilation (listings, images, etc)
-- Name of dependency is a filename with contents.
| OtherDepFile
deriving (Generic, Show)
instance FromJSON FilePath where
parseJSON (String s) = return $ Sh.fromText s
parseJSON _ = mzero
instance ToJSON FilePath where
toJSON = String . Sh.toTextIgnore
instance FromJSON TemplateDependencyFile where
parseJSON val@(Object o) = do
depType <- o .: "type"
case T.toLower . T.strip $ depType of
"bibtex" -> pure BibtexDepFile
"template" -> TemplateDepFile <$> parseJSON val
"template_pdf" -> TemplatePdfDepFile <$> parseJSON val
"other" -> pure OtherDepFile
_ -> fail $ "Unknown template type " <> unpack depType
parseJSON _ = mzero
instance ToJSON TemplateDependencyFile where
toJSON d = case d of
BibtexDepFile -> object [
"type" .= ("bibtex" :: Text)
]
TemplateDepFile body -> let
Object o1 = object [ "type" .= ("template" :: Text) ]
Object o2 = toJSON body
in Object (o1 <> o2)
TemplatePdfDepFile body -> let
Object o1 = object [ "type" .= ("template_pdf" :: Text) ]
Object o2 = toJSON body
in Object (o1 <> o2)
OtherDepFile -> object [
"type" .= ("other" :: Text)
]
-- | Same as 'Template', but holds info about template content and dependencies
-- in other files.
data TemplateFile = TemplateFile {
-- | Template has human readable name
templateFileName :: TemplateName
-- | Template expects input in JSON format. The field contains filename of the YAML file.
, templateFileInput :: Maybe FilePath
-- | Template contents filename.
, templateFileBody :: FilePath
-- | Template dependencies (bibtex, listings, other htex files)
, templateFileDeps :: M.Map TemplateName TemplateDependencyFile
-- | Additional flags for `haskintex`
, templateFileHaskintexOpts :: [Text]
} deriving (Generic, Show)
instance FromJSON TemplateFile where
parseJSON (Object o) = TemplateFile
<$> o .: "name"
<*> o .:? "input"
<*> o .: "body"
<*> o .:? "dependencies" .!= mempty
<*> o .:? "haskintex-opts" .!= mempty
parseJSON _ = mzero
instance ToJSON TemplateFile where
toJSON TemplateFile{..} = object [
"name" .= templateFileName
, "input" .= templateFileInput
, "body" .= templateFileBody
, "dependencies" .= templateFileDeps
, "haskintex-opts" .= templateFileHaskintexOpts
]
-- | Load all external references of template into memory
loadTemplateInMemory :: TemplateFile -> Sh (Either String Template)
loadTemplateInMemory TemplateFile{..} = do
inputCnt <- case templateFileInput of
Nothing -> return $ Right Nothing
Just fname -> do
cnt <- readBinary fname
return $ fmap Just . A.eitherDecode' . BZ.fromStrict $ cnt
body <- readfile templateFileBody
deps <- M.traverseWithKey loadDep templateFileDeps
return $ Template
<$> pure templateFileName
<*> inputCnt
<*> pure body
<*> sequence deps
<*> pure templateFileHaskintexOpts
where
loadDep name d = let
filename = fromText name
in case d of
BibtexDepFile -> do
cnt <- readfile filename
return . pure $ BibtexDep cnt
TemplateDepFile body -> do
tmpl <- chdir filename $ loadTemplateInMemory body
return $ TemplateDep <$> tmpl
TemplatePdfDepFile body -> do
tmpl <- chdir filename $ loadTemplateInMemory body
return $ TemplatePdfDep <$> tmpl
OtherDepFile -> do
cnt <- readBinary filename
return . pure $ OtherDep cnt
-- | Extract all external references of template into file system
storeTemplateInFiles :: Template -> FilePath -> Sh TemplateFile
storeTemplateInFiles Template{..} folder = do
mkdir_p folder
relInputName <- case templateInput of
Nothing -> return Nothing
Just input -> do
let inputName = folder </> (templateName <> "_input") <.> "json"
writeBinary inputName $ BZ.toStrict $ A.encode input
fmap Just $ relativeTo folder inputName
let bodyName = folder </> templateName <.> "htex"
mkdir_p $ directory bodyName
writefile bodyName templateBody
relBodyName <- relativeTo folder bodyName
deps <- M.traverseWithKey storeDep templateDeps
return $ TemplateFile {
templateFileName = templateName
, templateFileInput = relInputName
, templateFileBody = relBodyName
, templateFileDeps = deps
, templateFileHaskintexOpts = templateHaskintexOpts
}
where
storeDep name d = case d of
BibtexDep body -> do
let bodyName = folder </> name
mkdir_p $ directory bodyName
writefile bodyName body
return BibtexDepFile
TemplateDep template -> do
let subfolderName = folder </> Sh.fromText name
mkdir_p subfolderName
dep <- storeTemplateInFiles template subfolderName
return $ TemplateDepFile dep
TemplatePdfDep template -> do
let subfolderName = folder </> Sh.fromText name
mkdir_p subfolderName
dep <- storeTemplateInFiles template subfolderName
return $ TemplatePdfDepFile dep
OtherDep body -> do
let bodyName = folder </> name
mkdir_p $ directory bodyName
writeBinary bodyName body
return OtherDepFile