packages feed

literatex-0.1.0.0: app/Main.hs

------------------------------------------------------------------------------
-- |
-- Module      : Main
-- Description : literatex command-line utility
-- Copyright   : Copyright (c) 2021 Travis Cardwell
-- License     : MIT
------------------------------------------------------------------------------

{-# LANGUAGE RecordWildCards #-}

module Main (main) where

-- https://hackage.haskell.org/package/ansi-wl-pprint
import Text.PrettyPrint.ANSI.Leijen (Doc)

-- https://hackage.haskell.org/package/base
import Control.Applicative (optional)
import System.Exit (ExitCode(ExitFailure), exitWith)
import System.IO (hPutStrLn, stderr, stdin, stdout)

-- https://hackage.haskell.org/package/optparse-applicative
import qualified Options.Applicative as OA

-- https://hackage.haskell.org/package/ttc
import qualified Data.TTC as TTC

-- (literatex)
import qualified LiterateX
import qualified LiterateX.Renderer as Renderer
import qualified LiterateX.SourceDefaults as SourceDefaults
import LiterateX.Types (CodeLanguage, SourceFormat, TargetFormat)
import qualified LiterateX.Types.SourceFormat as SourceFormat
import qualified LiterateX.Types.TargetFormat as TargetFormat

-- (literatex:executable)
import qualified LibOA

------------------------------------------------------------------------------
-- $Defaults

defaultTargetFormat :: TargetFormat
defaultTargetFormat = TargetFormat.PandocMarkdown

------------------------------------------------------------------------------
-- $Options

data Options
  = Options
    { formatOpt      :: !(Maybe SourceFormat)
    , rendererOpts   :: !Renderer.Options
    , noHighlightOpt :: !Bool
    , inputOpt       :: !(Maybe FilePath)
    , outputOpt      :: !(Maybe FilePath)
    }

sourceFormatOption :: OA.Parser SourceFormat
sourceFormatOption = OA.option (OA.eitherReader TTC.parse) $ mconcat
    [ OA.long "source"
    , OA.short 's'
    , OA.metavar "SOURCE"
    , OA.help "source format"
    ]

targetFormatOption :: OA.Parser TargetFormat
targetFormatOption = OA.option (OA.eitherReader TTC.parse) $ mconcat
    [ OA.long "target"
    , OA.short 't'
    , OA.metavar "TARGET"
    , OA.value defaultTargetFormat
    , OA.showDefaultWith TTC.render
    , OA.help "target format"
    ]

languageOption :: OA.Parser CodeLanguage
languageOption = OA.option (OA.eitherReader TTC.parse) $ mconcat
    [ OA.long "language"
    , OA.short 'l'
    , OA.metavar "LANGUAGE"
    , OA.help "code language"
    ]

ignoreShebangOption :: OA.Parser Bool
ignoreShebangOption = OA.switch $ mconcat
    [ OA.long "ignore-shebang"
    , OA.help "ignore shebangs"
    ]

noCodeOption :: OA.Parser Bool
noCodeOption = OA.switch $ mconcat
    [ OA.long "no-code"
    , OA.help "do not display code"
    ]

noNumbersOption :: OA.Parser Bool
noNumbersOption = OA.switch $ mconcat
    [ OA.long "no-numbers"
    , OA.help "do not number code lines"
    ]

renderOptions :: OA.Parser Renderer.Options
renderOptions = Renderer.Options
    <$> targetFormatOption
    <*> optional languageOption
    <*> ignoreShebangOption
    <*> fmap not noCodeOption
    <*> fmap not noNumbersOption

noHighlightOption :: OA.Parser Bool
noHighlightOption = OA.switch $ mconcat
    [ OA.long "no-highlight"
    , OA.help "do not highlight code"
    ]

inputOption :: OA.Parser FilePath
inputOption = OA.strOption $ mconcat
    [ OA.long "input"
    , OA.short 'i'
    , OA.metavar "FILE"
    , OA.help "input file (default: STDIN)"
    ]

outputOption :: OA.Parser FilePath
outputOption = OA.strOption $ mconcat
    [ OA.long "output"
    , OA.short 'o'
    , OA.metavar "FILE"
    , OA.help "output file (default: STDOUT)"
    ]

options :: OA.Parser Options
options = Options
    <$> optional sourceFormatOption
    <*> renderOptions
    <*> noHighlightOption
    <*> optional inputOption
    <*> optional outputOption

------------------------------------------------------------------------------
-- $Library

errorExit :: String -> IO a
errorExit message = do
    hPutStrLn stderr $ "error: " ++ message
    exitWith $ ExitFailure 1

------------------------------------------------------------------------------
-- $Main

main :: IO ()
main = do
    Options{..} <- OA.execParser pinfo
    let defaultOpts = SourceDefaults.defaultsFor =<< inputOpt
    format <- case (formatOpt, defaultOpts, inputOpt) of
      (Just format, _, _) -> pure format
      (Nothing, Just (format, _), _) -> pure format
      (Nothing, Nothing, Just{}) ->
        errorExit "source format not specified and unknown input filename"
      (Nothing, Nothing, Nothing) ->
        errorExit "source format not specified and no input filename"
    let codeLanguage = Renderer.codeLanguage rendererOpts
        opts = rendererOpts
          { Renderer.codeLanguage =
              case (noHighlightOpt, codeLanguage, defaultOpts) of
                (True, _, _) -> Nothing
                (False, opt@Just{}, _) -> opt
                (False, Nothing, Just (_, lang)) -> Just lang
                (False, Nothing, Nothing) -> Nothing
          }
    case (inputOpt, outputOpt) of
      (Just input, Just output) ->
        LiterateX.transformFileToFile format opts input output
      (Just input, Nothing) ->
        LiterateX.transformFileToHandle format opts input stdout
      (Nothing, Just output) ->
        LiterateX.transformHandleToFile format opts stdin output
      (Nothing, Nothing) ->
        LiterateX.transformHandleToHandle format opts stdin stdout
  where
    pinfo :: OA.ParserInfo Options
    pinfo
      = OA.info
          (LibOA.helper <*> LibOA.versioner LiterateX.version <*> options)
      $ mconcat
          [ OA.fullDesc
          , OA.progDesc "transform literate source code to Markdown"
          , OA.failureCode 2
          , OA.footerDoc . Just $ LibOA.vspace
              [ sourceFormatHelp
              , targetFormatHelp
              , defaultsHelp
              ]
          ]

    sourceFormatHelp :: Doc
    sourceFormatHelp = LibOA.section "SOURCE options:" $ LibOA.table
      [ (TTC.render format, SourceFormat.describe format)
      | format <- SourceFormat.list
      ]

    targetFormatHelp :: Doc
    targetFormatHelp = LibOA.section "TARGET options:" $ LibOA.table
      [ (TTC.render format, TargetFormat.describe format)
      | format <- TargetFormat.list
      ]

    defaultsHelp :: Doc
    defaultsHelp = LibOA.section "Default options:" $ LibOA.table
      [ (ext, TTC.render lang ++ " (" ++ TTC.render format ++ ")")
      | (ext, (format, lang)) <- SourceDefaults.extensionDefaults
      ]