-- -- Copyright © 2013-2015 Anchor Systems, Pty Ltd and Others -- -- The code in this file, and the program it is a part of, is -- made available to you by its authors as open source software: -- you can redistribute it and/or modify it under the terms of -- the 3-clause BSD licence. -- -- | Description: Check and fix style differences with stylish-haskell module Main where import Control.Monad import Data.Algorithm.Diff import Data.Algorithm.DiffOutput import Data.Foldable import Data.List hiding (and) import Data.Monoid hiding (First) import Data.Traversable import Git.Vogue.PluginCommon import Language.Haskell.Stylish import Prelude hiding (and) import System.Exit import System.IO hiding (hGetContents) import System.IO.Strict (hGetContents) main :: IO () main = do cmd <- getPluginCommand "Check your Haskell project for stylish-haskell-related problems." "git-vogue-stylish - check for stylish-haskell problems" cfg <- getConfig f cfg cmd where hsFiles = filter (isSuffixOf ".hs") f _ CmdName = putStrLn "stylish" f cfg (CmdCheck files _) = do rs <- traverse (stylishCheckFile cfg) (hsFiles files) if and rs then do outputGood $ "Checked " <> show (length rs) <> " file(s)" exitSuccess else exitFailure f cfg (CmdFix files _) = do let files' = hsFiles files -- Fix all of the things first traverse_ (stylishRunFile cfg) files' -- Now double check they are fixed rs <- traverse (stylishCheckFile cfg) files' if and rs then outputGood "Style converged" else do outputBad "Style did not converge, bailing" exitFailure -- | Try various configuration locations as per stylish-haskell binary getConfig :: IO Config getConfig = -- Don't spew every file checked to stdout let v = makeVerbose False in configFilePath v Nothing >>= loadConfig v -- | Checks whether running Stylish over a given file produces any differences. -- Returns TRUE if there's nothing left to change. -- Prints results and returns FALSE if there are things left to change. stylishCheckFile :: Config -- ^ Stylish Haskell config -> FilePath -- ^ File to run through Stylish -> IO Bool stylishCheckFile cfg fp = stylishFile fp cfg (\original stylish -> case getStyleDiffs original stylish of [] -> return True x -> do outputBad $ "\x1b[33m" <> fp <> "\x1b[0m" <> " has differing style:\n" <> ppDiff x return False ) -- | Runs Stylish over a given file. If there are changes, write them. stylishRunFile :: Config -- ^ Stylish Haskell config -> FilePath -- ^ File to run through Stylish -> IO () stylishRunFile cfg fp = stylishFile fp cfg $ \original stylish -> unless (null $ getStyleDiffs original stylish) (writeFile fp stylish) -- | Get diffs and filter out equivalent ones getStyleDiffs :: String -> String -> [Diff [String]] getStyleDiffs original stylish = filter filterDiff $ getGroupedDiff (lines original) (lines stylish) -- | Filters out equal diffs filterDiff :: (Eq a) => Diff a -> Bool filterDiff (Both a b) = a /= b filterDiff _ = True -- | Takes an original file, the Stylish version of the file, and does something -- depending on the outcome of the Stylish transformation. stylishFile :: FilePath -- ^ File to run through Stylish -> Config -- ^ Stylish Haskell config -> (String -> String -> IO a) -> IO a stylishFile fp cfg fn = do original <- readUTF8File fp stylish <- stylishFile' (Just fp) cfg fn original stylish -- | Processes a single file, or stdin if no filepath is given stylishFile' :: Maybe FilePath -- ^ File to run through Stylish -> Config -- ^ Stylish Haskell config -> IO String stylishFile' mfp conf = do contents <- maybe getContents readUTF8File mfp let result = runSteps (configLanguageExtensions conf) mfp (configSteps conf) $ lines contents case result of Left err -> do hPutStrLn stderr err return contents Right ok -> return $ unlines ok -- | Loads a UTF8 file. readUTF8File :: FilePath -- ^ Filepath to read -> IO String -- ^ File data readUTF8File fp = withFile fp ReadMode $ \h -> do hSetEncoding h utf8 hGetContents h