lima-0.1.0.0: app/Main.hs
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main (main) where
import Control.Exception (SomeException (SomeException), bracketOnError, catch, throwIO)
import Control.Exception.Base (try)
import Control.Monad (when, zipWithM_, (>=>))
import Converter (Config (..), ConfigHsMd (..), hsToMd, lhsToMd, mdToHs, mdToLhs)
import Data.Aeson.Types (prependFailure)
import Data.Char (toLower)
import Data.Default (def)
import Data.Either (isLeft)
import Data.Foldable (Foldable (..))
import Data.List (intersperse)
import Data.Maybe (fromJust, fromMaybe, isNothing)
import Data.String (IsString)
import Data.Traversable (forM)
import Data.Yaml (FromJSON (..), ParseException, Value (..), withObject, (.:), (.:?))
import Data.Yaml.Aeson (decodeFileEither, withArray)
import Data.Yaml.Parser (typeMismatch)
import GHC.Generics (Generic)
import Options.Applicative
import Options.Applicative.Help (Doc, bold, colon, comma, dot, fill, indent, lparen, nest, rparen, softline, text, (<+>))
import Options.Applicative.Help.Pretty (hardline)
import System.Environment (getArgs)
import System.Exit (ExitCode (..), exitWith)
data CommandType = Hs2Md | Md2Hs | Lhs2Md | Md2Lhs deriving (Show)
data Options = Options
{ commandType :: CommandType
, config :: Maybe FilePath
, files :: [FilePath]
}
deriving (Show)
-- TODO make config and --file arguments mutually exclusive
parseConfig :: Parser ([FilePath], Maybe FilePath)
parseConfig = do
file <- some $ strOption (long "file" <> short 'f' <> metavar "FILE" <> help "Path to a file to convert")
config <- optional (strOption (long "config" <> short 'c' <> metavar "FILE" <> help "Path to a config"))
return (file, config)
mkConfigParser :: String -> CommandType -> Doc -> Mod CommandFields Options
mkConfigParser command_ commandType progDesc_ =
command
command_
( info
((\(files, config) -> Options{..}) <$> parseConfig)
(progDescDoc $ Just progDesc_)
)
uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d
uncurry3 f (a, b, c) = f a b c
lima :: Parser Options
lima =
subparser $
foldMap
(uncurry3 mkConfigParser)
[ ("hs2md", Hs2Md, "Convert" <-> bold "Haskell" <-> "to" <-> bold "Markdown")
, ("md2hs", Md2Hs, "Convert" <-> bold "Markdown" <-> "to" <-> bold "Haskell")
, ("md2lhs", Md2Lhs, "Convert" <-> bold "Markdown" <-> "to" <-> bold "Literate Haskell")
, ("lhs2md", Lhs2Md, "Convert" <-> bold "Literate Haskell" <-> "to" <-> bold "Markdown")
]
maybe' :: Maybe a -> b -> (a -> b) -> b
maybe' x f g = maybe f g x
either' :: Either a b -> (a -> c) -> (b -> c) -> c
either' x f g = either f g x
descriptionBlock :: [Doc] -> Doc
descriptionBlock desc = fold (intersperse softline desc) <> hardline
(<->) :: Doc -> Doc -> Doc
x <-> y = x <> softline <> y
fill' :: Doc
fill' = fill 100 $ text ""
header_ :: Doc
header_ =
descriptionBlock
[ bold "lima" <-> "converts between" <-> bold "Haskell" <> lparen <> bold ".hs" <> rparen
, "and" <-> bold "Markdown" <> lparen <> bold ".md" <> rparen
, "and between" <-> bold "Literate Haskell" <> lparen <> bold ".lhs" <> rparen
, "and" <-> bold "Markdown" <> lparen <> bold ".md" <> rparen <> dot
, fill'
, "Learn more about a command by running:" <-> bold "lima COMMAND"
, fill'
, "Example usage:" <> fill'
, fill'
, bold "lima hs2md -f file.hs -c config.yaml" <> fill'
, indent 2 $ "Convert" <+> bold "HS" <+> "to" <+> bold "Markdown" <+> "using the config" <+> bold "config.yaml" <> colon <> fill'
, indent 2 "file1.hs -> file1.hs.md"
, fill'
, bold "lima lhs2md -f file1.lhs -f file2.lhs" <> fill'
, indent 2 $ "Convert" <+> bold "LHS" <+> "to" <+> bold "Markdown" <> colon <> fill'
, indent 2 $ "file1.lhs -> file1.lhs.md" <> comma <-> "file2.lhs -> file2.lhs.md"
]
runLima :: IO Options
runLima =
customExecParser
(prefs (showHelpOnError <> disambiguate))
( info
(helper <*> lima)
( fullDesc <> headerDoc (Just header_)
)
)
main :: IO ()
main = do
Options{..} <- runLima
Config{..} <-
maybe'
config
def
( \c ->
decodeFileEither c
>>= ( \(x :: Either ParseException Config) ->
either' x (const $ error $ "Could not parse the config file at " <> c) pure
)
)
contents_ <- forM files (\file -> readFile file `catch` (\(x :: SomeException) -> error $ "Could not read file at " <> file))
let
convert :: String -> FilePath -> IO ()
convert contents out =
(\(f, ext) -> writeFile (out <> "." <> ext) (f contents)) $
case commandType of
Md2Hs -> (mdToHs (fromMaybe def configHsMd), "md")
Hs2Md -> (hsToMd (fromMaybe def configHsMd), "md")
Md2Lhs -> (mdToLhs, "lhs")
Lhs2Md -> (lhsToMd, "md")
zipWithM_ convert contents_ files
putStrLn "Converted!"
where
opts = info (lima <**> helper) mempty
p = prefs (disambiguate <> showHelpOnError)