pdf-slave 1.2.3.0 → 1.3.0.0
raw patch · 5 files changed
+139/−65 lines, 5 filesdep +directoryPVP ok
version bump matches the API change (PVP)
Dependencies added: directory
API changes (from Hackage documentation)
- Text.PDF.Slave: renderBundleOrTemplateFromFile :: FilePath -> Maybe Value -> Sh PDFContent
+ Text.PDF.Slave: renderBundleOrTemplateFromFile :: FilePath -> Maybe Value -> Bool -> Sh PDFContent
- Text.PDF.Slave: renderBundleToPDF :: Template -> Sh PDFContent
+ Text.PDF.Slave: renderBundleToPDF :: Template -> Bool -> Sh PDFContent
- Text.PDF.Slave: renderFromFileBundleToPDF :: FilePath -> Maybe Value -> Sh PDFContent
+ Text.PDF.Slave: renderFromFileBundleToPDF :: FilePath -> Maybe Value -> Bool -> Sh PDFContent
- Text.PDF.Slave: renderFromFileToPDF :: FilePath -> Sh PDFContent
+ Text.PDF.Slave: renderFromFileToPDF :: FilePath -> Bool -> Sh PDFContent
- Text.PDF.Slave: renderTemplateToPDF :: TemplateFile -> FilePath -> Sh PDFContent
+ Text.PDF.Slave: renderTemplateToPDF :: TemplateFile -> FilePath -> Bool -> Sh PDFContent
- Text.PDF.Slave.Render: renderBundleOrTemplateFromFile :: FilePath -> Maybe Value -> Sh PDFContent
+ Text.PDF.Slave.Render: renderBundleOrTemplateFromFile :: FilePath -> Maybe Value -> Bool -> Sh PDFContent
- Text.PDF.Slave.Render: renderBundleToPDF :: Template -> Sh PDFContent
+ Text.PDF.Slave.Render: renderBundleToPDF :: Template -> Bool -> Sh PDFContent
- Text.PDF.Slave.Render: renderFromFileBundleToPDF :: FilePath -> Maybe Value -> Sh PDFContent
+ Text.PDF.Slave.Render: renderFromFileBundleToPDF :: FilePath -> Maybe Value -> Bool -> Sh PDFContent
- Text.PDF.Slave.Render: renderFromFileToPDF :: FilePath -> Sh PDFContent
+ Text.PDF.Slave.Render: renderFromFileToPDF :: FilePath -> Bool -> Sh PDFContent
- Text.PDF.Slave.Render: renderTemplateToPDF :: TemplateFile -> FilePath -> Sh PDFContent
+ Text.PDF.Slave.Render: renderTemplateToPDF :: TemplateFile -> FilePath -> Bool -> Sh PDFContent
Files
- CHANGELOG.md +9/−0
- README.md +75/−47
- app/Main.hs +13/−5
- pdf-slave.cabal +3/−1
- src/Text/PDF/Slave/Render.hs +39/−12
CHANGELOG.md view
@@ -1,3 +1,12 @@+1.3.0.0+=======++* Default input file name is now `input.json`, not `<template name>_input.json`.++* Changed recommended way of reading input via `Helper.hs`++* Added flag `preserve-temp` to not nuke temporary files after execution.+ 1.2.3.0 =======
README.md view
@@ -46,68 +46,88 @@ * `template_input.json` - Input data for template in JSON format. -``` JSON-{- "line-width": 2,- "spiral-precision": 0.01,- "spiral-interval": [0,4],- "spiral-a": 0.1,- "spiral-b": 4-}-```+ ``` JSON+ {+ "line-width": 2,+ "spiral-precision": 0.01,+ "spiral-interval": [0,4],+ "spiral-a": 0.1,+ "spiral-b": 4+ }+ ``` * `template.htex` - TeX/LaTeX with embedded Haskell that reads input data from file `template.json`. You have to provide code at the beginning of file that reads the inputs, like that: -``` Haskell-\begin{document}+ Make a `Helper.hs` file in directory with template:+ ``` haskell+ module Helper where -\begin{haskellpragmas}-{-# LANGUAGE OverloadedStrings #-}-\end{haskellpragmas}-\begin{writehaskell}-import Data.Aeson-import System.IO.Unsafe (unsafePerformIO)-import qualified Data.ByteString.Lazy as BS+ import qualified Data.ByteString.Lazy as BS+ import Data.Aeson+ import System.IO.Unsafe -data Input = Input {- lineWidth :: Double-, spiralPrecision :: Double-, spiralInterval :: (Double, Double)-, spiralA :: Double-, spiralB :: Double-}+ defaultInput :: FromJSON a => a+ {-# NOINLINE defaultInput #-}+ defaultInput = case unsafePerformIO $ fmap eitherDecode' $ BS.readFile "input.json" of+ Left e -> error (show e)+ Right a -> a+ ``` -instance FromJSON Input where- parseJSON (Object o) = Input- <$> o .: "line-width"- <*> o .: "spiral-precision"- <*> o .: "spiral-interval"- <*> o .: "spiral-a"- <*> o .: "spiral-b"+ Next in your `.htex` file:+ ``` Haskell+ \begin{document} -inpt :: Input-inpt = case unsafePerformIO $ fmap eitherDecode' $ BS.readFile "template_input.json" of- Left e -> error (show e)- Right a -> a+ \begin{haskellpragmas}+ {-# LANGUAGE OverloadedStrings #-}+ \end{haskellpragmas}+ \begin{writehaskell}+ import Data.Aeson+ import Helper -\end{writehaskell}-```+ data Input = Input {+ lineWidth :: Double+ , spiralPrecision :: Double+ , spiralInterval :: (Double, Double)+ , spiralA :: Double+ , spiralB :: Double+ } -Note: The tool copies JSON with inputs to build folder with .htex twice, once with-name specified at `input` key in template description and secondly at fixed `<template_name>_input.json`.+ instance FromJSON Input where+ parseJSON (Object o) = Input+ <$> o .: "line-width"+ <*> o .: "spiral-precision"+ <*> o .: "spiral-interval"+ <*> o .: "spiral-a"+ <*> o .: "spiral-b" + inpt :: Input+ inpt = defaultInput+ \end{writehaskell}+ ```++ Note: The tool copies JSON with inputs to build folder with .htex twice, once with+ name specified at `input` key in template description and secondly at fixed `input.json`.+ * `template.yaml` - description of template and its dependencies. -``` YAML-name: template01 # name of template-input: template01_input.json # name of input file-body: template01.htex # name of .htex file-dependencies: {} # dependency tree (see below)-haskintex-opts: [] # additional flags to haskintex-```+ ``` YAML+ name: template01 # name of template+ input: template01_input.json # name of input file+ body: template01.htex # name of .htex file+ dependencies: {} # dependency tree (see below)+ haskintex-opts: [] # additional flags to haskintex+ ``` + For input `Helper.hs` you should make a record to inform `pdf-slave` that the module should be copied to build directory:++ ``` YAML+ dependencies:+ Helper.hs:+ type: other+ ```+ ## Dependencies There are 4 different types of dependencies:@@ -310,3 +330,11 @@ ``` 3. Or download [precompiled container](https://hub.docker.com/r/ncrashed/pdf-slave/) from Docker Hub.+++Credits+=======++* Daniel Díaz - author of `haskintex` tool that is core of the package.++* Alexander Vershilov aka qnikst - help with debugging and new `Helper.hs` module.
app/Main.hs view
@@ -33,13 +33,15 @@ -- | CLI options data Options = Options { -- | Path to template file, if missing, stdin is used- templatePath :: Maybe FilePath+ templatePath :: Maybe FilePath -- | Path to output pdf file, if missing, stdout is used-, pdfOutputPath :: Maybe FilePath+, pdfOutputPath :: Maybe FilePath -- | Path to JSON input file for overwritting input data of bundle , inputOverwritePath :: Maybe FilePath+ -- | If the flag is set, temporary dirs won't be nuked after execution+, preserveTempDirs :: Bool -- | CLI action-, cliCommand :: Command+, cliCommand :: Command } -- | Same as 'strOption' but parses Text@@ -65,6 +67,10 @@ <> " for bundle or template input file" ) <> metavar "INPUT_JSON_PATH" )+ <*> switch (+ long "preserve-temp"+ <> help "If the flag is set, temporary dirs won't be nuked after execution"+ ) <*> commandParser -- | Parser of CLI commands@@ -110,12 +116,12 @@ bs <- case res of Left bundle -> do let bundle' = fromMaybe bundle $ (\i -> bundle { templateInput = i }) <$> optInput- renderBundleToPDF bundle'+ renderBundleToPDF bundle' nuke Right template -> do inputOverwrite <- sequence $ fmap toTextWarn inputOverwritePath let template' = fromMaybe template $ (const $ template { templateFileInput = inputOverwrite }) <$> optInput- renderTemplateToPDF template' baseDir+ renderTemplateToPDF template' baseDir nuke -- output results case pdfOutputPath of Nothing -> liftIO $ BS.putStr bs@@ -150,6 +156,8 @@ outputFolder' <- toTextWarn outputFolder echo $ "Bundle is unpacked to " <> outputFolder' _ -> return ()++ nuke = not preserveTempDirs main :: IO () main = execParser opts >>= pdfSlave
pdf-slave.cabal view
@@ -1,5 +1,5 @@ name: pdf-slave-version: 1.2.3.0+version: 1.3.0.0 synopsis: Tool to generate PDF from haskintex templates and YAML input description: Please see README.md homepage: https://github.com/NCrashed/pdf-slave#readme@@ -31,6 +31,7 @@ , base64-bytestring >= 1.0 && < 1.1 , bytestring >= 0.10 && < 0.11 , containers >= 0.5 && < 0.6+ , directory >= 1.2 && < 1.3 , exceptions >= 0.8 && < 0.9 , haskintex >= 0.6 && < 0.8 , HaTeX >= 3.16 && < 3.18@@ -39,6 +40,7 @@ , system-filepath >= 0.4 && < 0.5 , unordered-containers >= 0.2 && < 0.3 , yaml >= 0.8 && < 0.9+ default-extensions: DeriveGeneric LambdaCase
src/Text/PDF/Slave/Render.hs view
@@ -20,19 +20,25 @@ , parseBundleOrTemplateFromFile ) where +import Control.Concurrent import Control.Monad (join, forM_) import Control.Monad.Catch import Data.Aeson (Value(..)) import Data.ByteString (ByteString)+import Data.Char import Data.Maybe (fromMaybe) import Data.Monoid import Data.Set (Set) import Data.Yaml (ParseException, decodeEither') import Filesystem.Path (dropExtension, directory)+import Filesystem.Path.CurrentOS (decodeString) import GHC.Generics import Prelude hiding (FilePath) import Shelly+import System.IO (hClose, openTempFile) +import System.Directory (getTemporaryDirectory)+ import qualified Data.Aeson as A import qualified Data.ByteString.Lazy as BZ import qualified Data.Foldable as F@@ -73,15 +79,16 @@ renderBundleOrTemplateFromFile :: FilePath -- ^ Path to either bundle 'Template' or template 'TemplateFile' -> Maybe Value -- ^ Overwrite of input JSON for bundle+ -> Bool -- ^ Nuke temp folder? -> Sh PDFContent-renderBundleOrTemplateFromFile filename bundleInput = do+renderBundleOrTemplateFromFile filename bundleInput nuke = do res <- parseBundleOrTemplateFromFile filename let baseDir = directory filename case res of Left bundle -> do let bundle' = fromMaybe bundle $ fmap (\i -> bundle { templateInput = Just i }) bundleInput- renderBundleToPDF bundle'- Right template -> renderTemplateToPDF template baseDir+ renderBundleToPDF bundle' nuke+ Right template -> renderTemplateToPDF template baseDir nuke -- | Try to parse either a bundle or template file parseBundleOrTemplateFromFile :: FilePath -- ^ Path to either 'Template' or 'TemplateFile'@@ -102,36 +109,40 @@ -- | Helper to render from all-in bundle template renderFromFileBundleToPDF :: FilePath -- ^ Path to 'Template' all-in bundle -> Maybe Value -- ^ Overwrite of input JSON for bundle+ -> Bool -- ^ Nuke temp folder? -> Sh PDFContent-renderFromFileBundleToPDF filename bundleInput = do+renderFromFileBundleToPDF filename bundleInput nuke = do cnt <- readBinary filename case decodeEither' cnt of Left e -> throwM $ BundleFormatError filename e Right bundle -> do let bundle' = fromMaybe bundle $ fmap (\i -> bundle { templateInput = Just i }) bundleInput- renderBundleToPDF bundle'+ renderBundleToPDF bundle' nuke -- | Helper to render from template file renderFromFileToPDF :: FilePath -- ^ Path to 'TemplateFile'+ -> Bool -- ^ Nuke temp folder? -> Sh PDFContent-renderFromFileToPDF filename = do+renderFromFileToPDF filename nuke = do cnt <- readBinary filename case decodeEither' cnt of Left e -> throwM $ TemplateFormatError filename e- Right template -> renderTemplateToPDF template (directory filename)+ Right template -> renderTemplateToPDF template (directory filename) nuke -- | Unpack bundle, render the template, cleanup and return PDF renderBundleToPDF :: Template -- ^ Input all-in template+ -> Bool -- ^ Nuke temp folder? -> Sh PDFContent-renderBundleToPDF bundle = withTmpDir $ \unpackDir -> do+renderBundleToPDF bundle nuke = withTmpDir' nuke $ \unpackDir -> do template <- storeTemplateInFiles bundle unpackDir- renderTemplateToPDF template unpackDir+ renderTemplateToPDF template unpackDir nuke -- | Render template and return content of resulted PDF file renderTemplateToPDF :: TemplateFile -- ^ Input template -> FilePath -- ^ Base directory+ -> Bool -- ^ Nuke temp folder? -> Sh PDFContent -- ^ Output PDF file-renderTemplateToPDF t@TemplateFile{..} baseDir = withTmpDir $ \outputFolder -> do+renderTemplateToPDF t@TemplateFile{..} baseDir nuke = withTmpDir' nuke $ \outputFolder -> do -- Parse global input file and pass it as inherited input minput <- case templateFileInput of Nothing -> return Nothing@@ -185,7 +196,7 @@ , toTextArg $ baseDir </> bodyName ] ++ templateFileHaskintexOpts -- input file might be missing, if missing we can inject input from parent- let outputFixedInputName = outputFolder </> (templateFileName <> "_input") <.> "json"+ let outputFixedInputName = outputFolder </> (("input" :: FilePath) <.> "json") case templateFileInput of Nothing -> whenJust minput $ \input -> do writeBinary outputFixedInputName $ BZ.toStrict . A.encode $ input@@ -287,7 +298,7 @@ relInputName <- case templateInput of Nothing -> return Nothing Just input -> do- let inputName = folder </> (templateName <> "_input") <.> "json"+ let inputName = folder </> ("input" :: FilePath) <.> "json" writeBinary inputName $ BZ.toStrict $ A.encode input fmap Just $ relativeTo folder inputName let bodyName = folder </> templateName <.> "htex"@@ -324,3 +335,19 @@ mkdir_p $ directory bodyName writeBinary bodyName body return OtherDepFile++-- | Create a temporary directory and pass it as a parameter to a Sh+-- computation. The directory is nuked afterwards if first parameter is 'True'.+withTmpDir' :: Bool -> (FilePath -> Sh a) -> Sh a+withTmpDir' nuke act = do+ trace "withTmpDir"+ dir <- liftIO getTemporaryDirectory+ tid <- liftIO myThreadId+ (pS, fhandle) <- liftIO $ openTempFile dir ("tmp" ++ filter isAlphaNum (show tid))+ let p = decodeString pS+ liftIO $ hClose fhandle -- required on windows+ rm_f p+ mkdir p+ if nuke+ then act p `finally_sh` rm_rf p+ else act p