packages feed

ats-format-0.2.0.32: src/Main.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell   #-}

module Main where

import           Control.Exception            (displayException)
import           Control.Monad                (unless, (<=<))
import           Data.Bifunctor               (first)
import           Data.FileEmbed               (embedStringFile)
import           Data.List                    (lookup)
import           Data.Maybe                   (fromMaybe)
import           Data.Monoid                  ((<>))
import qualified Data.Text.IO                 as TIO
import           Data.Version                 (showVersion)
import           Language.ATS
import           Options.Applicative
import           Paths_ats_format
import           System.Directory             (doesFileExist)
import           System.Exit                  (exitFailure)
import           System.IO                    (hPutStr, stderr)
import           System.Process               (readCreateProcess, shell)
import           Text.PrettyPrint.ANSI.Leijen (pretty)
import           TOML

data Program = Program { _path          :: Maybe FilePath
                       , _inplace       :: Bool
                       , _noConfig      :: Bool
                       , _defaultConfig :: Bool
                       }

takeBlock :: String -> (String, String)
takeBlock ('%':'}':ys) = ("", ('%':) . ('}':) $ ys)
takeBlock (y:ys)       = first (y:) $ takeBlock ys
takeBlock []           = ([], [])

rest :: String -> IO String
rest xs = (<> (snd $ takeBlock xs)) <$> printClang (fst $ takeBlock xs)

printClang :: String -> IO String
printClang = readCreateProcess (shell "clang-format")

processClang :: String -> IO String
processClang ('%':'{':'^':xs) = ('%':) . ('{':) . ('^':) <$> rest xs
processClang ('%':'{':'#':xs) = ('%':) . ('{':) . ('#':) <$> rest xs
processClang ('%':'{':'$':xs) = ('%':) . ('{':) . ('$':) <$> rest xs
processClang ('%':'{':xs)     = ('%':) . ('{':) <$> rest xs
processClang (x:xs)           = (x:) <$> processClang xs
processClang []               = pure []

file :: Parser Program
file = Program
    <$> optional (argument str
        (metavar "FILEPATH"
        <> completer (bashCompleter "file -X '!*.*ats' -o plusdirs")
        <> help "File path to ATS source."))
    <*> switch
        (short 'i'
        <> help "Modify file in-place")
    <*> switch
        (long "no-config"
        <> short 'o'
        <> help "Ignore configuration file")
    <*> switch
        (long "default-config"
        <> help "Generate default configuration file in the current directory")

versionInfo :: Parser (a -> a)
versionInfo = infoOption ("atsfmt version: " ++ showVersion version ++ "\nlanguage-ats version: " ++ showVersion languageATSVersion) (short 'V' <> long "version" <> help "Show version")

wrapper :: ParserInfo Program
wrapper = info (helper <*> versionInfo <*> file)
    (fullDesc
    <> progDesc "ATS source code formater. For more detailed help, see 'man atsfmt'"
    <> header "ats-format - a source code formatter written in Haskell")

main :: IO ()
main = execParser wrapper >>= pick

printFail :: String -> IO a
printFail = pure exitFailure <=< hPutStr stderr

defaultConfig :: FilePath -> IO ()
defaultConfig = flip writeFile $(embedStringFile ".atsfmt.toml")

asFloat :: Value -> Maybe Float
asFloat (Double d) = Just (realToFrac d)
asFloat _          = Nothing

asInt :: Value -> Maybe Int
asInt (Integer i) = Just (fromIntegral i)
asInt _           = Nothing

asBool :: Value -> Maybe Bool
asBool (Bool x) = Just x
asBool _        = Nothing

defaults :: (Float, Int, Bool)
defaults = (0.6, 120, False)

parseToml :: FilePath -> IO (Float, Int, Bool)
parseToml fp = do
    f <- TIO.readFile fp
    case parseTOML f of
        Right x -> pure $ fromMaybe defaults $ do
            r <- asFloat =<< lookup "ribbon" x
            w <- asInt =<< lookup "width" x
            cf <- asBool =<< lookup "clang-format" x
            pure (r, w, cf)
        Left err -> printFail $ displayException err

printCustom :: Eq a => ATS a -> IO String
printCustom ats = do
    let p = ".atsfmt.toml"
    config <- doesFileExist p
    if config then do
        (r, w, cf) <- parseToml p
        let t = printATSCustom r w ats
        if cf then
            processClang t
        else
            pure t
    else
        pure $ printATS ats

genErr :: Eq a => Bool -> Either ATSError (ATS a) -> IO ()
genErr b = either (printFail . show . pretty) (putStrLn <=< go)
    where go = if not b then printCustom else pure . printATS

inplace :: FilePath -> (String -> IO String) -> IO ()
inplace p f = do
    contents <- readFile p
    newContents <- f contents
    unless (null newContents) $
        writeFile p newContents

fancyError :: Either ATSError (ATS a) -> IO (ATS a)
fancyError = either (printFail . show . pretty) pure

pick :: Program -> IO ()
pick (Program (Just p) False nc _)  = (genErr nc . parse) =<< readFile p
pick (Program Nothing _ nc False)   = (genErr nc . parse) =<< getContents
pick (Program Nothing _ _ True)     = defaultConfig ".atsfmt.toml"
pick (Program (Just p) True True _) = inplace p (fmap ((<> "\n") . printATS) . fancyError . parse)
pick (Program (Just p) True _ _)    = inplace p (fmap (<> "\n") . printCustom <=< fancyError . parse)