pandoc-plot-0.5.0.0: executable/Main.hs
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RecordWildCards #-}
module Main where
import Control.Applicative ((<|>))
import Control.Monad (join, forM_)
import Data.List (intersperse, (\\))
import Data.Monoid ((<>))
import Data.Text (unpack)
import GitHash as Git
import Options.Applicative
import qualified Options.Applicative.Help.Pretty as P
import System.Directory (doesFileExist)
import System.IO.Temp (writeSystemTempFile)
import Text.Pandoc.Filter.Plot (availableToolkits,
plotTransform,
defaultConfiguration,
configuration,
Configuration(..))
import Text.Pandoc.Filter.Plot.Internal (cls, supportedSaveFormats,
toolkits, readDoc,
cleanOutputDirs, )
import Text.Pandoc (pandocVersion)
import Text.Pandoc.Definition (pandocTypesVersion)
import Text.Pandoc.JSON (toJSONFilter)
import Web.Browser (openBrowser)
import qualified Data.Version as V
import Paths_pandoc_plot (version)
import ManPage (embedManualHtml)
import ExampleConfig (embedExampleConfig)
-- It is understood that Opts Nothing Nothing should be used for filtering
data Opts = Opts
{ optCommand :: Maybe Command
, optFlag :: Maybe Flag
}
-- The difference between commands and flags is that commands perform actions,
-- while flags only display information.
data Command = Clean FilePath
| WriteConfig
data Flag = Version
| FullVersion
| Manual
| Toolkits
deriving (Eq)
main :: IO ()
main = join $ execParser opts
where
opts = info (optparse <**> helper)
(fullDesc
<> progDesc "This pandoc filter generates plots from code blocks using a multitude of possible renderers. \
\This allows to keep documentation and figures in perfect synchronicity."
<> header "pandoc-plot - generate figures directly in documents using your plotting toolkit of choice."
<> footerDoc (Just footer')
)
optparse = do
flag_ <- flagParser
command_ <- commandParser
-- The extra optional input below only serves to show
-- to the user that the last argument is the AST from pandoc
-- The parsed input is never used
input <- optional $ strArgument (metavar "AST")
return $ go flag_ command_ input
go :: Maybe Flag -> Maybe Command -> Maybe String -> IO ()
go (Just Version) _ _ = putStrLn (V.showVersion version)
go (Just FullVersion) _ _ = showFullVersion
go (Just Manual) _ _ = showManPage
go (Just Toolkits) _ _ = showAvailableToolkits
go _ (Just (Clean fp)) _ = clean fp
go _ (Just WriteConfig) _ = writeFile ".example-pandoc-plot.yml" $(embedExampleConfig)
go Nothing Nothing _ = toJSONFilterWithConfig
flagParser :: Parser (Maybe Flag)
flagParser = versionP <|> fullVersionP <|> manualP <|> toolkitsP
where
versionP = flag Nothing (Just Version) (mconcat
[ long "version"
, short 'v'
, help "Show version number and exit."
])
fullVersionP = flag Nothing (Just FullVersion) (mconcat
[ long "full-version"
, help "Show full version information and exit."
])
manualP = flag Nothing (Just Manual) (mconcat
[ long "manual"
, short 'm'
, help "Open the manual page in the default web browser and exit."
])
toolkitsP = flag Nothing (Just Toolkits) (mconcat
[ long "toolkits"
, short 't'
, help "Show information on toolkits and exit. Executables from the configuration \
\file will be used, if a '.pandoc-plot.yml' file is in the current directory."
])
commandParser :: Parser (Maybe Command)
commandParser = optional $ subparser (
command "clean" (
info (cleanP <**> helper) (
progDesc "Clean output directories where figures from FILE might be stored.\
\ WARNING: All files in those directories will be deleted."
)
)
<> command "write-example-config" (
info (writeConfigP <**> helper) (progDesc "Write example configuration to a file.")
)
)
where
cleanP = Clean <$> strArgument (metavar "FILE")
writeConfigP = pure WriteConfig
toJSONFilterWithConfig :: IO ()
toJSONFilterWithConfig = do
c <- config
toJSONFilter (plotTransform c)
config :: IO Configuration
config = do
configExists <- doesFileExist ".pandoc-plot.yml"
if configExists
then configuration ".pandoc-plot.yml"
else return defaultConfiguration
showFullVersion :: IO ()
showFullVersion = do
putStrLn $ "pandoc-plot " <> (V.showVersion version)
putStrLn $ "Git revision " <> gitrev
putStrLn $ mconcat [ "Compiled with pandoc "
, (unpack pandocVersion)
, " and pandoc-types "
, V.showVersion pandocTypesVersion
]
where
-- In certain environments (e.g. Hackage when building documentation),
-- there is no git information.
gitrev = either (const "unknown") Git.giHash ($$tGitInfoCwdTry)
showAvailableToolkits :: IO ()
showAvailableToolkits = do
c <- config
putStrLn "\nAVAILABLE TOOLKITS\n"
available <- availableToolkits c
return available >>= mapM_ toolkitInfo
putStrLn "\nUNAVAILABLE TOOLKITS\n"
-- We don't use unavailableToolkits because this would force
-- more IO actions
let unavailable = toolkits \\ available
return unavailable >>= mapM_ toolkitInfo
where
toolkitInfo tk = do
putStrLn $ "Toolkit: " <> show tk
putStrLn $ " Code block trigger: " <> (unpack . cls $ tk)
putStrLn $ " Supported save formats: " <> (mconcat . intersperse ", " . fmap show $ supportedSaveFormats tk)
putStrLn mempty
clean :: FilePath -> IO ()
clean fp = do
conf <- config
putStrLn $ "Cleaning output directories for " <> fp
cleanedDirs <- readDoc fp >>= cleanOutputDirs conf
forM_ cleanedDirs $ \d -> putStrLn $ "Removed directory " <> d
showManPage :: IO ()
showManPage =
writeSystemTempFile "pandoc-plot-manual.html" $(embedManualHtml)
>>= \fp -> openBrowser ("file:///" <> fp)
>> return ()
-- | Use Doc type directly because of newline formatting
footer' :: P.Doc
footer' = mconcat
[ P.text "More information can be found via the manual (pandoc-plot --manual) or the repository README, located at"
, P.line
, P.indent 4 $ P.text "https://github.com/LaurentRDC/pandoc-plot"
, P.line
]