packages feed

asciidiagram-1.3: exec-src/asciidiagram.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE CPP #-}

#if !MIN_VERSION_base(4,8,0)
import Control.Applicative( (<$>), (<*>), pure )
#endif

import Control.Applicative( (<|>) )
import Control.Monad( when )
import Data.Monoid( (<>) )

import qualified Data.ByteString.Lazy as LB
import qualified Data.Text.IO as STIO
import System.Directory( getTemporaryDirectory )
import System.FilePath( (</>)
                      , replaceExtension
                      , takeExtension )

import Graphics.Svg( Document, loadSvgFile, saveXmlFile )
import Graphics.Rasterific.Svg( loadCreateFontCache )
import Graphics.Text.TrueType( FontCache )
import Codec.Picture( writePng )
import Options.Applicative( Parser
                          , ParserInfo
                          , argument
                          , execParser
                          , flag
                          , fullDesc
                          , header
                          , help
                          , helper
                          , info
                          , long
                          , metavar
                          , optional
                          , progDesc
                          , short
                          , str
                          , strOption
                          , switch
                          )
import Graphics.Rasterific.Svg( renderSvgDocument
                              , pdfOfSvgDocument )
import Text.AsciiDiagram

data Mode
  = Convert !(FilePath, FilePath)
  | DumpLibrary !FilePath

data Options = Options
  { _workingMode :: !Mode
  , _verbose     :: !Bool
  , _withLibrary :: !(Maybe FilePath)
  , _format      :: !(Maybe Format)
  }

data Format = FormatSvg | FormatPng | FormatPdf

ioParser :: Parser (String, String)
ioParser = (,)
    <$> argument str
          (metavar "INPUTFILE"
          <> help "Text file of the Ascii diagram to parse.")
    <*> (argument str
            (metavar "OUTPUTFILE"
            <> help ("Output file name, same as input with"
                    <> " different extension if unspecified."))
        <|> pure "")

modeParser :: Parser Mode
modeParser = (Convert <$> ioParser) <|> (DumpLibrary <$> dumpParser)
  where 
    dumpParser = strOption 
      ( long "dump-library"
      <> short 'd'
      <> metavar "FILENAME"
      <> help "Dump the default shape library & styles in an SVG document") 

argParser :: Parser Options
argParser = Options
  <$> modeParser
  <*> ( switch (long "verbose" <> help "Display more information") )
  <*> ( optional $ strOption
            ( long "with-library"
            <> short 'l'
            <> metavar "LIBRARY_FILENAME"
            <> help "Use a custom shape & style library instead of the default one") )
  <*> ( flag Nothing (Just FormatSvg)
            (  long "svg"
            <> help "Force the use of the SVG format (deduced from extension otherwise)")
     <|> flag Nothing (Just FormatPng)
            ( long "png"
            <> help "Force the use of the PNG format (deduced from extension otherwise) (by default)")
     <|> flag Nothing (Just FormatPdf)
            ( long "pdf"
            <> help  "Force the use of the PDF format (deduced from extension otherwise)")
      )

progOptions :: ParserInfo Options
progOptions = info (helper <*> argParser)
      ( fullDesc
     <> progDesc "Convert INPUTFILE into a svg or png OUTPUTFILE"
     <> header "asciidiagram - A pretty printer for ASCII art diagram to SVG." )

formatOfOuputFilename :: FilePath -> Format
formatOfOuputFilename f = case takeExtension f of
    ".png" -> FormatPng
    ".svg" -> FormatSvg
    ".pdf" -> FormatPdf
    _ -> FormatPng

getFontCache :: Bool -> IO FontCache
getFontCache verbose = do
  when verbose $ putStrLn "Loading/Building font cache (can be long)"
  tempDir <- getTemporaryDirectory 
  loadCreateFontCache $ tempDir </> "asciidiagram-fonty-fontcache"


loadLibrary :: Options -> IO Document
loadLibrary opt = case _withLibrary opt of
   Nothing -> return defaultLib
   Just p -> loadLib p
  where
    defaultLib = defaultLibrary defaultGridSize
    loadLib p = do
      f <- loadSvgFile p
      case f of
        Just doc -> return doc
        Nothing -> do
          putStrLn "Invalid library file, using default lib"
          return defaultLib 

runConversion :: Options -> FilePath -> FilePath -> IO ()
runConversion opt inputFile outputFile = do
  verbose . putStrLn $ "Loading file " ++ inputFile
  inputData <- STIO.readFile inputFile
  lib <- loadLibrary opt
  let diag = parseAsciiDiagram inputData
      svg = svgOfDiagramAtSize defaultGridSize lib diag
      format = _format opt <|> (pure $ formatOfOuputFilename outputFile)
  case format of
    Nothing -> saveDoc svg
    Just FormatSvg -> saveDoc svg
    Just FormatPng -> savePng svg
    Just FormatPdf -> savePdf svg
  where
    verbose = when $ _verbose opt
    saveDoc svg = do
      verbose . putStrLn $ "Writing SVG file " ++ outputFile
      saveXmlFile (savingPath "svg") svg

    savingPath ext = case outputFile of
      "" -> replaceExtension inputFile ext
      p -> p

    savePdf svg = do
      cache <- getFontCache  $ _verbose opt
      verbose . putStrLn $ "Writing PDF file " ++ outputFile
      (pdf, _) <- pdfOfSvgDocument cache Nothing 96 svg
      LB.writeFile (savingPath "pdf") pdf

    savePng svg = do
      cache <- getFontCache  $ _verbose opt
      verbose . putStrLn $ "Writing PNG file " ++ outputFile
      (img, _) <- renderSvgDocument cache Nothing 96 svg
      writePng (savingPath "png") img

dumpLibrary :: FilePath -> IO ()
dumpLibrary path =
  saveXmlFile path $ defaultLibrary defaultGridSize

main :: IO ()
main = execParser progOptions >>= \opts ->
  case _workingMode opts of
    Convert (i, o) -> runConversion opts i o
    DumpLibrary p -> dumpLibrary p