packages feed

cabal-gild-0.2.1.0: source/library/CabalGild/Main.hs

-- |
-- License: BSD-3-Clause
-- Copyright: Oleg Grenrus
module CabalGild.Main (main) where

import CabalGild (cabalGild)
import CabalGild.Error (Error (SomeError), renderError)
import CabalGild.Monad (runCabalGildIO)
import CabalGild.Options
import CabalGild.Prelude
import Control.Applicative (many, (<**>))
import qualified Data.ByteString as BS
import Data.Traversable (for)
import Data.Version (showVersion)
import qualified Options.Applicative as O
import Paths_cabal_gild (version)
import System.Exit (exitFailure)
import System.FilePath (takeDirectory)
import System.IO (hPutStrLn, stderr)

main :: IO ()
main = do
  (opts', filepaths) <- O.execParser optsP'
  let opts = runOptionsMorphism opts' defaultOptions

  notFormatted <-
    catMaybes <$> case filepaths of
      [] -> fmap pure $ BS.getContents >>= main' opts Nothing
      (_ : _) -> for filepaths $ \filepath -> do
        contents <- BS.readFile filepath
        main' opts (Just filepath) contents

  when ((optMode opts == ModeCheck) && not (null notFormatted)) $ do
    for_ notFormatted $ \filepath ->
      hPutStrLn stderr $ "error: Input " <> filepath <> " is not formatted."
    exitFailure
  where
    optsP' =
      O.info (optsP <**> O.helper <**> versionP) $
        mconcat
          [ O.fullDesc,
            O.progDesc "Reformat .cabal files",
            O.header "cabal-gild - .cabal file reformatter"
          ]

    versionP :: O.Parser (a -> a)
    versionP =
      O.infoOption (showVersion version) $
        O.long "version" <> O.help "Show version"

main' :: Options -> Maybe FilePath -> BS.ByteString -> IO (Maybe FilePath)
main' opts mfilepath input = do
  -- name of the input
  let filepath = fromMaybe "<stdin>" mfilepath

  mroot <-
    fmap takeDirectory <$> case (mfilepath, optStdinInputFile opts) of
      (Just _, Just _) -> do
        renderError $ SomeError "cannot pass both --stdin-input-file and FILE"
        exitFailure
      (Just f, Nothing) -> pure $ Just f
      (Nothing, Just f) -> pure $ Just f
      (Nothing, Nothing) -> pure Nothing

  -- process
  res <- runCabalGildIO mroot opts (cabalGild filepath input)

  case res of
    Right output -> do
      let outputBS = toUTF8BS output
          formatted = outputBS == input

      case optMode opts of
        ModeStdout -> BS.putStr outputBS
        ModeInplace -> case mfilepath of
          Nothing -> BS.putStr outputBS
          Just _ -> unless formatted $ BS.writeFile filepath outputBS
        _ -> return ()

      return $ if formatted then Nothing else Just filepath
    Left err -> do
      renderError err
      exitFailure

-------------------------------------------------------------------------------
-- Options parser
-------------------------------------------------------------------------------

optsP :: O.Parser (OptionsMorphism, [FilePath])
optsP =
  (,)
    <$> optsP'
    <*> many (O.strArgument (O.metavar "FILE..." <> O.help "input files"))
  where
    optsP' =
      fmap mconcat $
        many $
          asum
            [ werrorP,
              noWerrorP,
              indentP,
              tabularP,
              noTabularP,
              cabalFileP,
              noCabalFileP,
              stdoutP,
              inplaceP,
              checkP,
              rootP
            ]

    werrorP =
      O.flag' (mkOptionsMorphism $ \opts -> opts {optError = True}) $
        O.long "Werror" <> O.help "Treat warnings as errors"

    noWerrorP =
      O.flag' (mkOptionsMorphism $ \opts -> opts {optError = False}) $
        O.long "Wno-error"

    indentP =
      O.option (fmap (\n -> mkOptionsMorphism $ \opts -> opts {optIndent = n}) O.auto) $
        O.long "indent" <> O.help "Indentation" <> O.metavar "N"

    tabularP =
      O.flag' (mkOptionsMorphism $ \opts -> opts {optTabular = True}) $
        O.long "tabular" <> O.help "Tabular formatting"

    noTabularP =
      O.flag' (mkOptionsMorphism $ \opts -> opts {optTabular = False}) $
        O.long "no-tabular"

    cabalFileP =
      O.flag' (mkOptionsMorphism $ \opts -> opts {optCabalFile = True}) $
        O.long "cabal-file"

    noCabalFileP =
      O.flag' (mkOptionsMorphism $ \opts -> opts {optCabalFile = False}) $
        O.short 'n' <> O.long "no-cabal-file" <> O.help "Don't parse as .cabal file"

    stdoutP =
      O.flag' (mkOptionsMorphism $ \opts -> opts {optMode = ModeStdout}) $
        O.long "stdout" <> O.help "Write output to stdout (default)"

    inplaceP =
      O.flag' (mkOptionsMorphism $ \opts -> opts {optMode = ModeInplace}) $
        O.short 'i' <> O.long "inplace" <> O.help "Process files in-place"

    checkP =
      O.flag' (mkOptionsMorphism $ \opts -> opts {optMode = ModeCheck}) $
        O.short 'c' <> O.long "check" <> O.help "Fail with non-zero exit code if input is not formatted"

    rootP =
      O.option (fmap (\f -> mkOptionsMorphism $ \opts -> opts {optStdinInputFile = Just f}) O.str) $
        O.long "stdin-input-file" <> O.help "When reading from STDIN, use this file path to resolve relative references" <> O.metavar "FILE"