-- findExecutable

module Development.Shake.Linters ( tomlcheck
                                 , yamllint
                                 , hlint
                                 , shellcheck
                                 , ghc
                                 , dhall
                                 , madlang
                                 -- * Formatters
                                 , clangFormat
                                 , atsfmt
                                 , stylishHaskell
                                 , cFormat
                                 -- * File detection
                                 , module Development.Shake.FileDetect
                                 ) where

import           Control.Monad                (when)
import           Data.Char                    (isSpace)
import           Data.Foldable                (traverse_)
import           Development.Shake
import           Development.Shake.FileDetect

-- | Check all @.dhall@ files.
dhall :: Action ()
dhall = traverse_ checkDhall =<< getDhall

checkDhall :: FilePath -> Action ()
checkDhall dh = do
    contents <- liftIO $ readFile dh
    command [Stdin contents] "dhall" []

trim :: String -> String
trim = f . f
   where f = reverse . dropWhile isSpace

-- | Check a given formatter is idempotent.
checkIdempotent :: String -> FilePath -> Action ()
checkIdempotent s p = do
    contents <- liftIO $ readFile p
    (Stdout out) <- cmd (s ++ " " ++ p)
    when (trim contents /= trim out) (error $ "formatter " ++ s ++ " is not fully applied to file " ++ p ++ "!")

-- | Check that given files are formatted according to @stylish-haskell@
stylishHaskell :: [FilePath] -> Action ()
stylishHaskell = traverse_ (checkIdempotent "stylish-haskell")

-- | Check that given files are formatted according to @atsfmt@
atsfmt :: [FilePath] -> Action ()
atsfmt = traverse_ (checkIdempotent "atsfmt")

-- | Check that given files are formatted according to @clang-format@
clangFormat :: [FilePath] -> Action ()
clangFormat = traverse_ (checkIdempotent "clang-format")

-- | Check all @.c@ files using @clang-format@.
cFormat :: Action ()
cFormat = clangFormat =<< getC

checkFiles :: String -> [FilePath] -> Action ()
checkFiles str = traverse_ (cmd_ . ((str ++ " ") ++))

-- | Check all @.mad@ files.
madlang :: [FilePath] -> Action ()
madlang = checkFiles "madlang check"

-- | Lint @.sh@ files using
-- [shellcheck](http://hackage.haskell.org/package/ShellCheck).
shellcheck :: [FilePath] -> Action ()
shellcheck = checkFiles "shellcheck"

-- | Check Haskell files using @ghc@.
ghc :: [FilePath] -> Action ()
ghc dirs = checkFiles "ghc -Wall -Werror -Wincomplete-uni-patterns -Wincomplete-record-updates -fno-code" =<< getHs dirs

-- | Check all @.toml@ files using
-- [tomlcheck](http://hackage.haskell.org/package/tomlcheck).
tomlcheck :: Action ()
tomlcheck = checkFiles "tomlcheck --file" =<< getToml

-- | Lint all @.hs@, @.hsig@, and @.hs-boot@ files using
-- [hlint](http://hackage.haskell.org/package/hlint).
hlint :: [FilePath] -> Action ()
hlint dirs = checkFiles "hlint" =<< getHs dirs

-- | Lint all files ending in @yaml@ or @yml@ using
-- [yamllint](https://pypi.python.org/pypi/yamllint).
yamllint :: Action ()
yamllint = checkFiles "yamllint -s" =<< getYml