{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeSynonymInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} ----------------------------------------------------------------------------- -- | -- Module : Diagrams.Backend.PGF.CmdLine -- Copyright : (c) 2015 Diagrams team (see LICENSE) -- License : BSD-style (see LICENSE) -- Maintainer : diagrams-discuss@googlegroups.com -- -- Convenient creation of command-line-driven executables for -- rendering diagrams using the PGF backend. -- ----------------------------------------------------------------------------- module Diagrams.Backend.PGF.CmdLine ( -- * General form of @main@ -- $mainwith mainWith -- * Supported forms of @main@ , defaultMain , mainWithSurf , onlineMain , onlineMainWithSurf , multiMain , module Diagrams.Backend.PGF ) where import Data.ByteString.Builder import Options.Applicative as OP import System.IO (stdout) import Diagrams.Backend.CmdLine import Diagrams.Prelude hiding (height, interval, output, width, (<>)) import Diagrams.Backend.PGF import Diagrams.Backend.PGF.Surface -- pgf specific stuff data PGFCmdLineOpts = PGFCmdLineOpts { _cmdStandalone :: Bool , _cmdReadable :: Bool } makeLenses ''PGFCmdLineOpts instance Parseable PGFCmdLineOpts where parser = PGFCmdLineOpts <$> switch ( long "standalone" <> short 'a' <> help "Produce standalone .tex output" ) <*> switch ( long "readable" <> short 'r' <> help "Indent lines" ) -- not sure if this is of any use instance ToResult d => ToResult (OnlineTex d) where type Args (OnlineTex d) = (Surface, Args d) type ResultOf (OnlineTex d) = IO (ResultOf d) toResult d (surf, args) = flip toResult args <$> surfOnlineTexIO surf d -- $mainwith -- The 'mainWith' method unifies all of the other forms of @main@ and is -- now the recommended way to build a command-line diagrams program. It -- works as a direct replacement for 'defaultMain' or 'multiMain' as well -- as allowing more general arguments. For example, given a function that -- produces a diagram when given an @Int@ and a @'Colour' Double@, 'mainWith' -- will produce a program that looks for additional number and color arguments. -- -- > ... definitions ... -- > f :: Int -> Colour Double -> Diagram PGF -- > f i c = ... -- > -- > main = mainWith f -- -- We can run this program as follows: -- -- > $ ghc --make mydiagram -- > -- > # output image.tex built by `f 20 red` -- > $ ./MyDiagram -o image.tex -w 200 20 red -- | This is the simplest way to render diagrams, and is intended to -- be used like so: -- -- > ... definitions ... -- > -- > main = defaultMain myDiagram -- -- Compiling this file will result in an executable which takes -- various command-line options for setting the size, output file, -- and so on, and renders @myDiagram@ with the specified options. -- -- Pass @--help@ to the generated executable to see all available -- options. Currently it looks something like -- -- @ -- mydiagram -- -- Usage: mydiagram [-?|--help] [-w|--width WIDTH] [-h|--height HEIGHT] -- [-o|--output OUTPUT] [-f|--format FORMAT] [-a|--standalone] -- [-r|--readable] [-l|--loop] [-s|--src ARG] -- [-i|--interval INTERVAL] -- Command-line diagram generation. -- -- Available options: -- -?,--help Show this help text -- -w,--width WIDTH Desired WIDTH of the output image -- -h,--height HEIGHT Desired HEIGHT of the output image -- -o,--output OUTPUT OUTPUT file -- -f,--format FORMAT l for LaTeX, c for ConTeXt, p for plain -- TeX (default: LaTeX) -- -a,--standalone Produce standalone .tex output -- -r,--readable Indent lines -- -l,--loop Run in a self-recompiling loop -- -s,--src ARG Source file to watch -- -i,--interval INTERVAL When running in a loop, check for changes every -- INTERVAL seconds. -- @ -- -- For example, a common scenario is -- -- @ -- $ ghc --make mydiagram -- -- # output image.tex with a width of 400bp (and auto-determined height) -- # (bp = big point = 1px at 72dpi) -- $ ./mydiagram -o image.tex -w 400 -- @ defaultMain :: Diagram PGF -> IO () defaultMain = mainWith -- | Allows you to pick a surface the diagram will be rendered with. -- (This mainWithSurf :: Surface -> Diagram PGF -> IO () mainWithSurf = curry mainWith -- For online diagrams. -- | Same as @defaultMain@ but takes an online pgf diagram. onlineMain :: OnlineTex (Diagram PGF) -> IO () onlineMain = mainWith -- | Same as @mainWithSurf@ but takes an online pgf diagram. onlineMainWithSurf :: Surface -> OnlineTex (Diagram PGF) -> IO () onlineMainWithSurf = curry mainWith -- Mainable instances instance TypeableFloat n => Mainable (QDiagram PGF V2 n Any) where type MainOpts (QDiagram PGF V2 n Any) = (DiagramOpts, DiagramLoopOpts, PGFCmdLineOpts, TexFormat) mainRender (diaOpts, loopOpts, pgfOpts, format) d = do chooseRender diaOpts pgfOpts (formatToSurf format) d defaultLoopRender loopOpts instance TypeableFloat n => Mainable (Surface, QDiagram PGF V2 n Any) where type MainOpts (Surface, QDiagram PGF V2 n Any) = (DiagramOpts, DiagramLoopOpts, PGFCmdLineOpts) mainRender (diaOpts, loopOpts, pgfOpts) (surf,d) = do chooseRender diaOpts pgfOpts surf d defaultLoopRender loopOpts -- Online diagrams instance TypeableFloat n => Mainable (OnlineTex (QDiagram PGF V2 n Any)) where type MainOpts (OnlineTex (QDiagram PGF V2 n Any)) = (DiagramOpts, DiagramLoopOpts, PGFCmdLineOpts, TexFormat) mainRender (diaOpts, loopOpts, pgfOpts, format) d = do chooseOnlineRender diaOpts pgfOpts (formatToSurf format) d defaultLoopRender loopOpts instance TypeableFloat n => Mainable (Surface, OnlineTex (QDiagram PGF V2 n Any)) where type MainOpts (Surface, OnlineTex (QDiagram PGF V2 n Any)) = (DiagramOpts, DiagramLoopOpts, PGFCmdLineOpts) mainRender (diaOpts, loopOpts, pgfOpts) (surf, d) = do chooseOnlineRender diaOpts pgfOpts surf d defaultLoopRender loopOpts formatToSurf :: TexFormat -> Surface formatToSurf format = case format of LaTeX -> latexSurface ConTeXt -> contextSurface PlainTeX -> plaintexSurface cmdLineOpts :: TypeableFloat n => DiagramOpts -> Surface -> PGFCmdLineOpts -> Options PGF V2 n cmdLineOpts opts surf pgf = def & surface .~ surf & sizeSpec .~ sz & readable .~ pgf^.cmdReadable & standalone .~ pgf^.cmdStandalone where sz = fromIntegral <$> mkSizeSpec2D (opts^.width) (opts^.height) chooseRender :: TypeableFloat n => DiagramOpts -> PGFCmdLineOpts -> Surface -> QDiagram PGF V2 n Any -> IO () chooseRender diaOpts pgfOpts surf d = case diaOpts^.output of "" -> hPutBuilder stdout $ renderDia PGF opts d out -> renderPGF' out opts d where opts = cmdLineOpts diaOpts surf pgfOpts chooseOnlineRender :: TypeableFloat n => DiagramOpts -> PGFCmdLineOpts -> Surface -> OnlineTex (QDiagram PGF V2 n Any) -> IO () chooseOnlineRender diaOpts pgfOpts surf d = case diaOpts^.output of "" -> surfOnlineTexIO surf d >>= hPutBuilder stdout . renderDia PGF opts out -> renderOnlinePGF' out opts d where opts = cmdLineOpts diaOpts surf pgfOpts -- | @multiMain@ is like 'defaultMain', except instead of a single -- diagram it takes a list of diagrams paired with names as input. -- The generated executable then takes a @--selection@ option -- specifying the name of the diagram that should be rendered. The -- list of available diagrams may also be printed by passing the -- option @--list@. -- -- Example usage: -- -- @ -- $ ghc --make MultiTest -- [1 of 1] Compiling Main ( MultiTest.hs, MultiTest.o ) -- Linking MultiTest ... -- $ ./MultiTest --list -- Available diagrams: -- foo bar -- $ ./MultiTest --selection bar -o Bar.tex -w 200 -- @ multiMain :: [(String, Diagram PGF)] -> IO () multiMain = mainWith instance TypeableFloat n => Mainable [(String,QDiagram PGF V2 n Any)] where type MainOpts [(String,QDiagram PGF V2 n Any)] = (MainOpts (QDiagram PGF V2 n Any), DiagramMultiOpts) mainRender = defaultMultiMainRender instance Parseable TexFormat where parser = OP.option (eitherReader parseFormat) $ short 'f' <> long "format" <> help "l for LaTeX, c for ConTeXt, p for plain TeX" <> metavar "FORMAT" <> OP.value LaTeX <> showDefault parseFormat :: String -> Either String TexFormat parseFormat ('l':_) = Right LaTeX parseFormat ('c':_) = Right ConTeXt parseFormat ('p':_) = Right PlainTeX parseFormat ('t':_) = Right PlainTeX parseFormat x = Left $ "Unknown format" ++ x