call-plantuml-0.0.1: src/Language/PlantUML/Call.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-|
Module : Language.PlantUML.Call
Description : A simple library to call PlantUML given a diagram specification
Copyright : (c) Marcellus Siegburg, 2022
License : MIT
Maintainer : marcellus.siegburg@uni-due.de
This module provides the basic functionality to call PlantUML.
-}
module Language.PlantUML.Call (
DiagramType (..),
drawPlantUMLDiagram,
) where
import Paths_call_plantuml (getDataDir)
import qualified Data.ByteString.Char8 as BS (
dropWhile,
head,
null,
putStrLn,
tail,
)
import Control.Concurrent (
forkIO, killThread, newEmptyMVar, putMVar, takeMVar,
)
import Control.Monad (unless, when)
import Data.ByteString (ByteString, hGetContents, hPutStr)
import Data.ByteString.Char8 (unpack)
import System.Exit (ExitCode (..))
import System.FilePath
((</>), (<.>))
import System.IO (
hClose,
hFlush,
#ifndef mingw32_HOST_OS
BufferMode (NoBuffering),
hSetBuffering,
#endif
)
import System.Process (
CreateProcess (..), StdStream (..),
createProcess, proc, waitForProcess,
)
{-|
An output format for PlantUML.
-}
data DiagramType =
ASCIIArt |
ASCIIArtUnicode |
EPS |
LaTeX |
LaTeXFull |
PNG |
SVG |
VDX
deriving (Bounded, Enum, Read, Show)
typeShortName :: DiagramType -> String
typeShortName x = case x of
ASCIIArt -> "txt"
ASCIIArtUnicode -> "utxt"
EPS -> "eps"
LaTeX -> "latex"
LaTeXFull -> "latex:nopreamble"
PNG -> "png"
SVG -> "svg"
VDX -> "vdx"
{-|
This function may be used to draw a PlantUML diagram given a valid
specification and a return type.
It calls PlantUML via Java.
-}
drawPlantUMLDiagram
:: DiagramType
-- ^ The return type of diagram to return
-> ByteString
-- ^ The PlantUML diagram specification which should be loaded
-> IO ByteString
drawPlantUMLDiagram what content = do
dataDir <- getDataDir
let callPlantUML = proc "java" [
"-jar", dataDir </> "plantuml" <.> "jar",
"-p", "-t" ++ typeShortName what, "-nometadata", "-noerror"
]
(Just hin, Just hout, Just herr, ph) <-
createProcess callPlantUML {
std_out = CreatePipe,
std_in = CreatePipe,
std_err = CreatePipe
}
pout <- listenForOutput hout
perr <- listenForOutput herr
#ifndef mingw32_HOST_OS
hSetBuffering hin NoBuffering
#endif
hPutStr hin content
hFlush hin
hClose hin
out <- getOutput pout
err <- getOutput perr
printContentOnError ph out
unless (BS.null err) $ fail $ unpack err
return out
where
printContentOnError ph out = do
code <- waitForProcess ph
when (code == ExitFailure 1 || isError out)
$ BS.putStrLn $ "Error on calling PlantUML with:\n" <> content
listenForOutput h = do
mvar <- newEmptyMVar
pid <- forkIO $ hGetContents h >>= putMVar mvar
return (pid, mvar)
getOutput (pid, mvar) = do
output <- takeMVar mvar
killThread pid
return output
isError :: ByteString -> Bool
isError xs =
let ys = BS.dropWhile (== ' ') xs
zs = BS.dropWhile (== ' ') $ BS.tail ys
in not (BS.null ys)
&& BS.head ys == '\n'
&& not (BS.null zs)
&& BS.head zs == '\n'