packages feed

hindent-6.0.0: app/Main.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Unsafe #-}

-- | Main entry point to hindent.
--
-- hindent
module Main
  ( main
  ) where

import Control.Applicative
import Control.Exception
import Control.Monad
import qualified Data.ByteString as S
import qualified Data.ByteString.Builder as S
import qualified Data.ByteString.Lazy.Char8 as L8
import Data.Maybe
import qualified Data.Text as T
import Data.Version (showVersion)
import Foreign.C.Error
import GHC.IO.Exception
import HIndent
import HIndent.CabalFile
import HIndent.Config
import HIndent.LanguageExtension
import HIndent.LanguageExtension.Types
import Options.Applicative hiding (action, style)
import Paths_hindent (version)
import qualified System.Directory as IO
import System.Exit (exitWith)
import qualified System.IO as IO

data Action
  = Validate
  | Reformat

data RunMode
  = ShowVersion
  | Run Config [Extension] Action [FilePath]

-- | Main entry point.
main :: IO ()
main = do
  config <- getConfig
  runMode <-
    execParser
      (info
         (options config <**> helper)
         (header "hindent - Reformat Haskell source code"))
  case runMode of
    ShowVersion -> putStrLn ("hindent " ++ showVersion version)
    Run style exts action paths ->
      if null paths
        then L8.interact
               (either error S.toLazyByteString .
                reformat style (Just exts) Nothing . L8.toStrict)
        else forM_ paths $ \filepath -> do
               cabalexts <- getCabalExtensionsForSourcePath filepath
               text <- S.readFile filepath
               case reformat
                      style
                      (Just $ cabalexts ++ exts)
                      (Just filepath)
                      text of
                 Left e -> error e
                 Right out ->
                   unless (L8.fromStrict text == S.toLazyByteString out) $
                   case action of
                     Validate -> do
                       IO.putStrLn $ filepath ++ " is not formatted"
                       exitWith (ExitFailure 1)
                     Reformat -> do
                       tmpDir <- IO.getTemporaryDirectory
                       (fp, h) <- IO.openTempFile tmpDir "hindent.hs"
                       L8.hPutStr h (S.toLazyByteString out)
                       IO.hFlush h
                       IO.hClose h
                       let exdev e =
                             if ioe_errno e == Just ((\(Errno a) -> a) eXDEV)
                               then IO.copyFile fp filepath >> IO.removeFile fp
                               else throw e
                       IO.copyPermissions filepath fp
                       IO.renameFile fp filepath `catch` exdev

-- | Program options.
options :: Config -> Parser RunMode
options config =
  flag' ShowVersion (long "version" <> help "Print the version") <|>
  (Run <$> style <*> exts <*> action <*> files)
  where
    style =
      (makeStyle config <$> lineLen <*> indentSpaces <*> trailingNewline <*>
       sortImports) <*
      optional
        (strOption
           (long "style" <>
            help "Style to print with (historical, now ignored)" <>
            metavar "STYLE") :: Parser String)
    exts =
      fmap
        getExtensions
        (many
           (T.pack <$>
            strOption
              (short 'X' <> help "Language extension" <> metavar "GHCEXT")))
    indentSpaces =
      option
        auto
        (long "indent-size" <>
         help "Indentation size in spaces" <>
         value (configIndentSpaces config) <> showDefault) <|>
      option
        auto
        (long "tab-size" <> help "Same as --indent-size, for compatibility")
    lineLen =
      option
        auto
        (long "line-length" <>
         help "Desired length of lines" <>
         value (configMaxColumns config) <> showDefault)
    trailingNewline =
      not <$>
      flag
        (not (configTrailingNewline config))
        (configTrailingNewline config)
        (long "no-force-newline" <>
         help "Don't force a trailing newline" <> showDefault)
    sortImports =
      flag
        Nothing
        (Just True)
        (long "sort-imports" <> help "Sort imports in groups" <> showDefault) <|>
      flag
        Nothing
        (Just False)
        (long "no-sort-imports" <> help "Don't sort imports")
    action =
      flag
        Reformat
        Validate
        (long "validate" <>
         help "Check if files are formatted without changing them")
    makeStyle s mlen tabs trailing imports =
      s
        { configMaxColumns = mlen
        , configIndentSpaces = tabs
        , configTrailingNewline = trailing
        , configSortImports = fromMaybe (configSortImports s) imports
        }
    files = many (strArgument (metavar "FILENAMES"))