{-# LANGUAGE CPP                  #-}
{-# LANGUAGE ConstraintKinds      #-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Diagrams.Backend.SVG.CmdLine
-- Copyright   :  (c) 2013 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 SVG backend.
--
-- * 'defaultMain' creates an executable which can render a single
--   diagram at various options.
--
-- * 'multiMain' is like 'defaultMain' but allows for a list of
--   diagrams from which the user can choose one to render.
--
-- * 'mainWith' is a generic form that does all of the above but with
--   a slightly scarier type.  See "Diagrams.Backend.CmdLine".  This
--   form can also take a function type that has a subtable final result
--   (any of arguments to the above types) and 'Parseable' arguments.
--
-- If you want to generate diagrams programmatically---/i.e./ if you
-- want to do anything more complex than what the below functions
-- provide---you have several options.
--
-- * Use a function with 'mainWith'.  This may require making
--   'Parseable' instances for custom argument types.
--
-- * Make a new 'Mainable' instance.  This may require a newtype
--   wrapper on your diagram type to avoid the existing instances.
--   This gives you more control over argument parsing, intervening
--   steps, and diagram creation.
--
-- * Build option records and pass them along with a diagram to 'mainRender'
--   from "Diagrams.Backend.CmdLine".
--
-- * You can use 'Diagrams.Backend.SVG.renderSVG' to render a diagram
--   to a file directly; see "Diagrams.Backend.SVG".
--
-- * A more flexible approach is to directly call 'renderDia'; see
--   "Diagrams.Backend.SVG" for more information.
--
-- For a tutorial on command-line diagram creation see
-- <https://diagrams.github.io/doc/cmdline.html>.
--
-----------------------------------------------------------------------------

module Diagrams.Backend.SVG.CmdLine
       (
         -- * General form of @main@
         -- $mainwith

         mainWith

         -- * Supported forms of @main@

       , defaultMain
       , multiMain

         -- * Backend tokens

       , SVG
       , B
       ) where

import           Diagrams.Backend.CmdLine
import           Diagrams.Backend.SVG
import           Diagrams.Prelude         hiding (height, interval, output,
                                           width)

import           Options.Applicative

import           Data.List.Split

-- $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 SVG V2 Double
-- > f i c = ...
-- >
-- > main = mainWith f
--
-- We can run this program as follows:
--
-- > $ ghc --make MyDiagram
-- >
-- > # output image.svg built by `f 20 red`
-- > $ ./MyDiagram -o image.svg -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
--
-- @
-- ./Program
--
-- Usage: ./Program [-w|--width WIDTH] [-h|--height HEIGHT] [-o|--output OUTPUT] [--loop] [-s|--src ARG] [-i|--interval INTERVAL] [-p|--pretty]
--   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
--   -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.
--   -p,--pretty              Pretty print the SVG output
-- @
--
--   For example, a common scenario is
--
-- @
-- $ ghc --make MyDiagram
--
--   # output image.svg with a width of 400pt (and auto-determined height)
-- $ ./MyDiagram -o image.svg -w 400
-- @

defaultMain :: SVGFloat n => QDiagram SVG V2 n Any -> IO ()
defaultMain :: QDiagram SVG V2 n Any -> IO ()
defaultMain = QDiagram SVG V2 n Any -> IO ()
forall d. (Mainable d, Parseable (MainOpts d)) => d -> IO ()
mainWith

newtype PrettyOpt = PrettyOpt {PrettyOpt -> Bool
isPretty :: Bool}

prettyOpt :: Parser PrettyOpt
prettyOpt :: Parser PrettyOpt
prettyOpt = Bool -> PrettyOpt
PrettyOpt (Bool -> PrettyOpt) -> Parser Bool -> Parser PrettyOpt
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Mod FlagFields Bool -> Parser Bool
switch (String -> Mod FlagFields Bool
forall (f :: * -> *) a. HasName f => String -> Mod f a
long String
"pretty"
                     Mod FlagFields Bool -> Mod FlagFields Bool -> Mod FlagFields Bool
forall a. Semigroup a => a -> a -> a
<> Char -> Mod FlagFields Bool
forall (f :: * -> *) a. HasName f => Char -> Mod f a
short Char
'p'
                     Mod FlagFields Bool -> Mod FlagFields Bool -> Mod FlagFields Bool
forall a. Semigroup a => a -> a -> a
<> String -> Mod FlagFields Bool
forall (f :: * -> *) a. String -> Mod f a
help String
"Pretty print the SVG output")

instance Parseable PrettyOpt where
  parser :: Parser PrettyOpt
parser = Parser PrettyOpt
prettyOpt

instance SVGFloat n => Mainable (QDiagram SVG V2 n Any) where
    type MainOpts (QDiagram SVG V2 n Any) = (DiagramOpts, DiagramLoopOpts, PrettyOpt)
    mainRender :: MainOpts (QDiagram SVG V2 n Any) -> QDiagram SVG V2 n Any -> IO ()
mainRender (opts, loopOpts, pretty) QDiagram SVG V2 n Any
d = do
        DiagramOpts -> PrettyOpt -> QDiagram SVG V2 n Any -> IO ()
forall n.
SVGFloat n =>
DiagramOpts -> PrettyOpt -> QDiagram SVG V2 n Any -> IO ()
chooseRender DiagramOpts
opts PrettyOpt
pretty QDiagram SVG V2 n Any
d
        DiagramLoopOpts -> IO ()
defaultLoopRender DiagramLoopOpts
loopOpts

chooseRender :: SVGFloat n => DiagramOpts -> PrettyOpt -> QDiagram SVG V2 n Any -> IO ()
chooseRender :: DiagramOpts -> PrettyOpt -> QDiagram SVG V2 n Any -> IO ()
chooseRender DiagramOpts
opts PrettyOpt
pretty QDiagram SVG V2 n Any
d =
  case String -> String -> [String]
forall a. Eq a => [a] -> [a] -> [[a]]
splitOn String
"." (DiagramOpts
optsDiagramOpts -> Getting String DiagramOpts String -> String
forall s a. s -> Getting a s a -> a
^.Getting String DiagramOpts String
Lens' DiagramOpts String
output) of
    [String
""] -> String -> IO ()
putStrLn String
"No output file given."
    [String]
ps | [String] -> String
forall a. [a] -> a
last [String]
ps String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"svg"] -> do
           let szSpec :: SizeSpec V2 n
szSpec = Int -> n
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> n) -> SizeSpec V2 Int -> SizeSpec V2 n
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe Int -> Maybe Int -> SizeSpec V2 Int
forall n. Num n => Maybe n -> Maybe n -> SizeSpec V2 n
mkSizeSpec2D (DiagramOpts
optsDiagramOpts
-> Getting (Maybe Int) DiagramOpts (Maybe Int) -> Maybe Int
forall s a. s -> Getting a s a -> a
^.Getting (Maybe Int) DiagramOpts (Maybe Int)
Lens' DiagramOpts (Maybe Int)
width) (DiagramOpts
optsDiagramOpts
-> Getting (Maybe Int) DiagramOpts (Maybe Int) -> Maybe Int
forall s a. s -> Getting a s a -> a
^.Getting (Maybe Int) DiagramOpts (Maybe Int)
Lens' DiagramOpts (Maybe Int)
height)
           if PrettyOpt -> Bool
isPretty PrettyOpt
pretty
             then String -> SizeSpec V2 n -> QDiagram SVG V2 n Any -> IO ()
forall n.
SVGFloat n =>
String -> SizeSpec V2 n -> QDiagram SVG V2 n Any -> IO ()
renderPretty (DiagramOpts
optsDiagramOpts -> Getting String DiagramOpts String -> String
forall s a. s -> Getting a s a -> a
^.Getting String DiagramOpts String
Lens' DiagramOpts String
output) SizeSpec V2 n
szSpec QDiagram SVG V2 n Any
d
             else String -> SizeSpec V2 n -> QDiagram SVG V2 n Any -> IO ()
forall n.
SVGFloat n =>
String -> SizeSpec V2 n -> QDiagram SVG V2 n Any -> IO ()
renderSVG (DiagramOpts
optsDiagramOpts -> Getting String DiagramOpts String -> String
forall s a. s -> Getting a s a -> a
^.Getting String DiagramOpts String
Lens' DiagramOpts String
output) SizeSpec V2 n
szSpec QDiagram SVG V2 n Any
d
       | Bool
otherwise -> String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"Unknown file type: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [String] -> String
forall a. [a] -> a
last [String]
ps

-- | @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.eps -w 200
-- @

multiMain :: SVGFloat n => [(String, QDiagram SVG V2 n Any)] -> IO ()
multiMain :: [(String, QDiagram SVG V2 n Any)] -> IO ()
multiMain = [(String, QDiagram SVG V2 n Any)] -> IO ()
forall d. (Mainable d, Parseable (MainOpts d)) => d -> IO ()
mainWith

instance SVGFloat n => Mainable [(String,QDiagram SVG V2 n Any)] where
    type MainOpts [(String,QDiagram SVG V2 n Any)]
        = (MainOpts (QDiagram SVG V2 n Any), DiagramMultiOpts)

    mainRender :: MainOpts [(String, QDiagram SVG V2 n Any)]
-> [(String, QDiagram SVG V2 n Any)] -> IO ()
mainRender = MainOpts [(String, QDiagram SVG V2 n Any)]
-> [(String, QDiagram SVG V2 n Any)] -> IO ()
forall d.
Mainable d =>
(MainOpts d, DiagramMultiOpts) -> [(String, d)] -> IO ()
defaultMultiMainRender