1
1
mirror of https://github.com/tweag/ormolu.git synced 2024-09-11 13:16:13 +03:00
ormolu/app/Main.hs

235 lines
6.9 KiB
Haskell
Raw Normal View History

{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
2019-02-24 23:38:10 +03:00
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
2019-02-24 23:38:10 +03:00
2020-04-24 20:43:45 +03:00
module Main (main) where
2018-11-25 16:34:28 +03:00
2019-02-24 23:38:10 +03:00
import Control.Monad
import Data.Bool (bool)
import Data.List (intercalate, sort)
import Data.Maybe (mapMaybe)
import qualified Data.Text.IO as TIO
2019-02-24 23:38:10 +03:00
import Data.Version (showVersion)
import Development.GitRev
import Options.Applicative
import Ormolu
import Ormolu.Diff.Text (diffText, printTextDiff)
import Ormolu.Parser (manualExts)
import Ormolu.Terminal
import Ormolu.Utils (showOutputable)
import Ormolu.Utils.IO
2019-02-24 23:38:10 +03:00
import Paths_ormolu (version)
import System.Exit (ExitCode (..), exitWith)
import qualified System.FilePath as FP
2019-07-04 03:52:35 +03:00
import System.IO (hPutStrLn, stderr)
2019-02-24 23:38:10 +03:00
-- | Entry point of the program.
2018-11-25 16:34:28 +03:00
main :: IO ()
main = do
2019-02-24 23:38:10 +03:00
Opts {..} <- execParser optsParserInfo
let formatOne' = formatOne optMode optConfig
exitCode <- case optInputFiles of
[] -> formatOne' Nothing
["-"] -> formatOne' Nothing
[x] -> formatOne' (Just x)
xs -> do
let selectFailure = \case
ExitSuccess -> Nothing
ExitFailure n -> Just n
errorCodes <-
mapMaybe selectFailure <$> mapM (formatOne' . Just) (sort xs)
return $
if null errorCodes
then ExitSuccess
else
ExitFailure $
if all (== 100) errorCodes
then 100
else 102
exitWith exitCode
-- | Format a single input.
formatOne ::
-- | Mode of operation
Mode ->
-- | Configuration
2020-04-23 19:41:32 +03:00
Config RegionIndices ->
-- | File to format or stdin as 'Nothing'
Maybe FilePath ->
IO ExitCode
formatOne mode config mpath = withPrettyOrmoluExceptions (cfgColorMode config) $
case FP.normalise <$> mpath of
Nothing -> do
r <- ormoluStdin config
case mode of
Stdout -> do
TIO.putStr r
return ExitSuccess
_ -> do
hPutStrLn
stderr
"This feature is not supported when input comes from stdin."
-- 101 is different from all the other exit codes we already use.
return (ExitFailure 101)
Just inputFile -> do
originalInput <- readFileUtf8 inputFile
formattedInput <- ormoluFile config inputFile
case mode of
Stdout -> do
TIO.putStr formattedInput
return ExitSuccess
InPlace -> do
-- Only write when the contents have changed, in order to avoid
-- updating the modified timestamp if the file was already correctly
-- formatted.
when (formattedInput /= originalInput) $
writeFileUtf8 inputFile formattedInput
return ExitSuccess
Check ->
case diffText originalInput formattedInput inputFile of
Nothing -> return ExitSuccess
Just diff -> do
runTerm (printTextDiff diff) (cfgColorMode config) stderr
-- 100 is different to all the other exit code that are emitted
-- either from an 'OrmoluException' or from 'error' and
-- 'notImplemented'.
return (ExitFailure 100)
2019-02-24 23:38:10 +03:00
----------------------------------------------------------------------------
-- Command line options parsing
2019-02-24 23:38:10 +03:00
data Opts = Opts
{ -- | Mode of operation
optMode :: !Mode,
-- | Ormolu 'Config'
2020-04-23 19:41:32 +03:00
optConfig :: !(Config RegionIndices),
-- | Haskell source files to format or stdin (when the list is empty)
optInputFiles :: ![FilePath]
}
2019-02-24 23:38:10 +03:00
-- | Mode of operation.
data Mode
= -- | Output formatted source code to stdout
Stdout
| -- | Overwrite original file
InPlace
| -- | Exit with non-zero status code if
-- source is not already formatted
Check
2019-02-24 23:38:10 +03:00
deriving (Eq, Show)
optsParserInfo :: ParserInfo Opts
optsParserInfo =
info (helper <*> ver <*> exts <*> optsParser) . mconcat $
[fullDesc]
2019-02-24 23:38:10 +03:00
where
ver :: Parser (a -> a)
ver =
infoOption verStr . mconcat $
[ long "version",
short 'v',
help "Print version of the program"
]
verStr =
intercalate
"\n"
[ unwords
[ "ormolu",
showVersion version,
$gitBranch,
$gitHash
],
"using ghc-lib-parser " ++ VERSION_ghc_lib_parser
2019-02-24 23:38:10 +03:00
]
exts :: Parser (a -> a)
exts =
infoOption displayExts . mconcat $
[ long "manual-exts",
help "Display extensions that need to be enabled manually"
]
displayExts = unlines $ sort (showOutputable <$> manualExts)
2019-02-24 23:38:10 +03:00
optsParser :: Parser Opts
optsParser =
Opts
<$> ( (fmap (bool Stdout InPlace) . switch . mconcat)
[ short 'i',
help "A shortcut for --mode inplace"
]
<|> (option parseMode . mconcat)
[ long "mode",
short 'm',
metavar "MODE",
value Stdout,
help "Mode of operation: 'stdout' (the default), 'inplace', or 'check'"
]
)
<*> configParser
<*> (many . strArgument . mconcat)
[ metavar "FILE",
help "Haskell source files to format or stdin (the default)"
]
2020-04-23 19:41:32 +03:00
configParser :: Parser (Config RegionIndices)
configParser =
Config
<$> (fmap (fmap DynOption) . many . strOption . mconcat)
[ long "ghc-opt",
short 'o',
metavar "OPT",
help "GHC options to enable (e.g. language extensions)"
]
<*> (switch . mconcat)
[ long "unsafe",
short 'u',
help "Do formatting faster but without automatic detection of defects"
]
<*> (switch . mconcat)
[ long "debug",
short 'd',
help "Output information useful for debugging"
]
<*> (switch . mconcat)
[ long "check-idempotence",
short 'c',
help "Fail if formatting is not idempotent"
]
<*> (option parseColorMode . mconcat)
[ long "color",
metavar "WHEN",
value Auto,
help "Colorize the output; WHEN can be 'never', 'always', or 'auto' (the default)"
]
2020-04-23 19:41:32 +03:00
<*> ( RegionIndices
<$> (optional . option auto . mconcat)
[ long "start-line",
metavar "START",
help "Start line of the region to format (starts from 1)"
2020-04-23 19:41:32 +03:00
]
<*> (optional . option auto . mconcat)
[ long "end-line",
metavar "END",
2020-04-23 19:41:32 +03:00
help "End line of the region to format (inclusive)"
]
)
2019-02-24 23:38:10 +03:00
----------------------------------------------------------------------------
-- Helpers
-- | Parse 'Mode'.
parseMode :: ReadM Mode
parseMode = eitherReader $ \case
"stdout" -> Right Stdout
"inplace" -> Right InPlace
"check" -> Right Check
s -> Left $ "unknown mode: " ++ s
parseColorMode :: ReadM ColorMode
parseColorMode = eitherReader $ \case
"never" -> Right Never
"always" -> Right Always
"auto" -> Right Auto
s -> Left $ "unknown color mode: " ++ s