packages feed

git-vogue (empty) → 0.1.0.0

raw patch · 15 files changed

+1488/−0 lines, 15 filesdep +Cabaldep +Diffdep +MissingHbuild-type:Customsetup-changed

Dependencies added: Cabal, Diff, MissingH, base, bifunctors, cpphs, directory, filepath, formatting, ghc-mod, git-vogue, haskell-src-exts, hlint, hscolour, hspec, mtl, optparse-applicative, process, split, strict, stylish-haskell, text, transformers, transformers-base, unix

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Anchor Engineering <engineering@anchor.com.au>++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Anchor Engineering <engineering@anchor.com.au> nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,141 @@+git-vogue - A framework for pre-commit checks +=========================================================++[![Travis Status](http://travis-ci.org/anchor/git-vogue.png)](https://travis-ci.org/anchor/git-vogue)++Intended to be used as a git pre-commit hook, *git-vogue* encourages developers+to keep their Haskell code ["en vogue"][1] by providing a framework for+checking code quality and some supporting plugins.++Currently, *git-vogue* ships with the following plugins:++* [cabal][6]+* [hlint][2]+* [stylish-haskell][4] with automatic fixing+* [ghc-mod][5]++[1]: https://www.youtube.com/watch?v=GuJQSAiODqI+[2]: http://hackage.haskell.org/package/hlint+[4]: https://hackage.haskell.org/package/stylish-haskell+[5]: https://hackage.haskell.org/package/ghc-mod+[6]: https://hackage.haskell.org/package/Cabal++Quickstart+----------++```bash+cabal install git-vogue+```++If you wish to set up pre-commit hooks (recommended):++```bash+git vogue init+```++With pre-commit hooks set up, `git vogue check` will be run before every+commit. If you wish to check the whole repository, run `git vogue check --all`.++You can attempt to automatically rectify any problems discovered via `git vogue+fix` and `git vogue fix --all`. The only plugin that currently supports this+auto-fixing is stylish-haskell.++# Plugin discovery/disabling++Running `git-vogue plugins` will show you the libexec directory in which+git-vogue will discover plugins.++Should one or more plugins annoy you, you may disable it by setting it+non-executable:++```bash+chmod -x .cabal/libexec/git-vogue/git-vogue-stylish+```++A more sophisticated interface to plugin manipulation is planned.++# Plugins++## cabal++Checks your .cabal file for packaging problems. Can not fix problems+automatically.++## hlint++Checks .hs files for linting issues, respects `HLint.hs` in the top level of+the repository. Can not fix problems automatically.++## ghc-mod++Checks .hs files (excluding `HLint.hs` and `Setup.hs`) as per ghc-mod check.+ghc-mod can be temperamental, so if this fails to run the plugin will allow the+commit to pass. Can not fix problems automatically.++## stylish-haskell++Checks if .hs files would have been modified by stylish-haskell. Respects+`.stylish-haskell.yaml`. Can fix problems automatically.++Uninstalling +------------++To remove a `git-vogue init` configured pre-commit hook, run:++```bash+rm .git/hooks/pre-commit+```++[Here are instructions](https://www.youtube.com/watch?v=4qXD5l-ZlfA) for+uninstalling a cabal package.++Philosophy+---------++At Anchor Engineering, we're pretty un-dogmatic about using one editor or even+one OS for development. As such, we've found ourselves in need of a common+benchmark for linting, formatting and code quality checks.++We wanted a tool that would:++* Install in one command+* Require nothing to be learned for a new developer+* Tell that developer what they need to fix in the code that they modify and+  that code only.++git-vogue aims to satisfy these needs, whilst saving developers from the+drudgery of installing, configuring and running each of these tools+independently.++Plugin specification+-------------------++**The interface** for an executable (to be called by git-vogue) is a single+command line argument, one of:++* check+* fix+* name++The plugin can assume that the CWD will be set to the top-level directory of+the package.++The plugin will receive a list of all files in the current repository that may+be looked at via STDIN when running in "check" or "fix" mode. These file paths+will be absolute and newline separated. The plugin is expected to filter them+appropriate to its needs.++## Invariants for well-behaved plugin commands++* `name` will return a human-readable name one line+* `check` will not modify any files+* `check` will exit with a return code of:+    * No errors - 0+    * Errors need fixing - 1+    * Catastrophic failure to check - 2+* `fix` is idempotent+* `fix` will exit with a return code of:+    * The code is now good (changes may or may not have been made) - 0+    * Some errors remain - 1+    * Catastrophic failure to check - 2+* If `fix` returns "success" (return code 0), `check` must no longer fail
+ Setup.hs view
@@ -0,0 +1,105 @@+module Main where++import           Control.Monad+import           Data.List+import           Distribution.PackageDescription    (PackageDescription (..))+import           Distribution.Simple+import           Distribution.Simple.Install+import           Distribution.Simple.LocalBuildInfo+import           Distribution.Simple.Setup+import           Distribution.Simple.Utils+import           System.FilePath++-- | Run cabal with custom copyHook which puts our extra executables in the+-- libexec directory.+main :: IO ()+main = defaultMainWithHooks $ simpleUserHooks+    { copyHook = copyThings+    }++-- | Copy "extra" executables to $PREFIX/libexec/git-vogue/ instead of+-- $PREFIX/bin.+copyThings+    :: PackageDescription+    -> LocalBuildInfo+    -> UserHooks+    -> CopyFlags+    -> IO ()+copyThings pkg lbi _ flags = do+    -- First install only the "main" components with the default settings.+    let (main_pkg, main_lbi) = tweakMainInstall pkg lbi+    install main_pkg main_lbi flags++    -- Then install the "secondary" executables and plugin scripts with the+    -- special settings.+    let (sub_pkg, sub_lbi) = tweakSubcommandInstall pkg lbi+    install sub_pkg sub_lbi flags++    -- Install shell scripts from plugins/.+    --+    -- This was stolen from "Distribution.Simple.Install" and hacked. Why is+    -- cabal so terrible compared to make and friends?+    let scripts = fmap stripPluginPrefix . filter isPlugin $ dataFiles pkg+        verbosity = fromFlag (copyVerbosity flags)+        src_data_dir = dataDir sub_pkg+        dest_data_dir = datadir . absoluteInstallDirs sub_pkg sub_lbi $+                        fromFlag (copyDest flags)+    forM_ scripts $ \ file -> do+        let dir = takeDirectory file+        files <- matchDirFileGlob src_data_dir file+        createDirectoryIfMissingVerbose verbosity True (dest_data_dir </> dir)+        forM_ files $ \file' ->+            installExecutableFile verbosity (src_data_dir </> file')+                                            (dest_data_dir </> file')++-- | Directory our plugin scripts are kept in.+pluginPrefix :: FilePath+pluginPrefix = "plugins/"++-- | Strip the 'pluginPrefix' off the front of a 'FilePath'.+stripPluginPrefix :: FilePath -> FilePath+stripPluginPrefix = dropWhile (== '/') . dropWhile (/= '/')++-- | A data file is in the plugins directory.+isPlugin :: FilePath -> Bool+isPlugin = (pluginPrefix `isPrefixOf`)++-- | Tweak parameters for install of main components (i.e. library and first+-- executable) only.+tweakMainInstall+    :: PackageDescription+    -> LocalBuildInfo+    -> (PackageDescription, LocalBuildInfo)+tweakMainInstall pkg lbi =+    let pkg' = pkg { executables = take 1 $ executables pkg+                   , dataFiles = filter (not . isPlugin) $ dataFiles pkg+                   }+    in (pkg', lbi)++-- | Tweak parameters for install of secondary components (i.e. executables+-- other than the first.+tweakSubcommandInstall+    :: PackageDescription+    -> LocalBuildInfo+    -> (PackageDescription, LocalBuildInfo)+tweakSubcommandInstall pkg lbi =+    let dest = suffixIt . libexecdir $ installDirTemplates lbi+        pkg' = pkg { executables = tail $ executables pkg+                   , dataFiles = []+                   , dataDir = dataDir pkg </> pluginPrefix+                   , library = Nothing+                   , testSuites = []+                   , benchmarks = []+                   , extraSrcFiles = []+                   , extraTmpFiles = []+                   , extraDocFiles = []+                   }+        lbi' = lbi { installDirTemplates = (installDirTemplates lbi)+                        { bindir = dest+                        , datadir = dest+                        , datasubdir = toPathTemplate ""+                        }+                   }+    in (pkg', lbi')+  where+    suffixIt = toPathTemplate . (</> "git-vogue") . fromPathTemplate
+ git-vogue.cabal view
@@ -0,0 +1,139 @@+name:                git-vogue+version:             0.1.0.0+synopsis:            A framework for pre-commit checks.+description:         Make your Haskell git repositories fashionable.+homepage:            https://github.com/anchor/git-vogue+license:             BSD3+license-file:        LICENSE+author:              Anchor Engineering <engineering@anchor.com.au>+maintainer:          Anchor Engineering <engineering@anchor.com.au>+copyright:           (c) 2015 Anchor Systems, Pty Ltd and Others+category:            Development+build-type:          Custom+extra-source-files:  README.md+cabal-version:       >=1.10+data-files:          templates/pre-commit++source-repository HEAD+  type: git+  location: https://github.com/anchor/git-vogue++flag gpl+    default: True+    description: Use GPL libraries, specifically hscolour++library+  default-language:    Haskell2010+  hs-source-dirs:      lib+  exposed-modules:     Git.Vogue+                       Git.Vogue.Types+                       Git.Vogue.Plugins+  other-modules:       Paths_git_vogue+  build-depends:       base >=4.7 && <4.8+                     , MissingH+                     , directory+                     , filepath+                     , process+                     , formatting+                     , split+                     , text+                     , transformers+                     , mtl+                     , unix+                       -- To fix depedencies+                     , hlint           >= 1.9    && < 1.10+                     , stylish-haskell >= 0.5.11 && < 0.5.12+                     , ghc-mod         >= 5.2  && < 5.3++executable git-vogue+  default-language:    Haskell2010+  hs-source-dirs:      src+  main-is:             git-vogue.hs+  build-depends:       base >=4.7 && <4.8+                     , filepath+                     , directory+                     , git-vogue+                     , optparse-applicative >= 0.11+                     , split++executable git-vogue-cabal+  default-language:    Haskell2010+  hs-source-dirs:      src+  main-is:             git-vogue-cabal.hs+  build-depends:       base >=4.7 && <4.8+                     , Cabal+                     , optparse-applicative >= 0.11+                     , process++executable git-vogue-hlint+  default-language:    Haskell2010+  hs-source-dirs:      src+  main-is:             git-vogue-hlint.hs+  build-depends:       base >=4.7 && <4.8+                     , directory+                     , filepath+                     , hlint+                     , cpphs+                     , bifunctors+                     , haskell-src-exts+                     , optparse-applicative >= 0.11+                     , hscolour+                     , process+  if !flag(gpl)+        cpp-options: -DGPL_SCARES_ME++executable git-vogue-stylish+  default-language:    Haskell2010+  hs-source-dirs:      src+  main-is:             git-vogue-stylish.hs+  build-depends:       base >=4.7 && <4.8+                     , Diff+                     , directory+                     , filepath+                     , optparse-applicative >= 0.11+                     , process+                     , strict+                     , stylish-haskell++executable git-vogue-ghc-mod+  default-language:    Haskell2010+  hs-source-dirs:      src+  main-is:             git-vogue-ghc-mod.hs+  build-depends:       base >=4.7 && <4.8+                     , Diff+                     , directory+                     , filepath+                     , optparse-applicative >= 0.11+                     , process+                     , strict+                     , ghc-mod++test-suite test-git-setup+  type:                exitcode-stdio-1.0+  default-language:    Haskell2010+  hs-source-dirs:      tests+  main-is:             git-setup.hs+  build-depends:       base >=4.7 && <4.8+                     , directory+                     , filepath+                     , git-vogue+                     , hspec+                     , process+                     , transformers+                     , unix++test-suite test-plugins+  type:                exitcode-stdio-1.0+  default-language:    Haskell2010+  hs-source-dirs:      tests+  main-is:             plugins.hs+  build-depends:       base >=4.7 && <4.8+                     , filepath+                     , git-vogue+                     , hspec+                     , process+                     , transformers+                     , transformers-base+                     , unix++-- vim: set tabstop=21 expandtab:
+ lib/Git/Vogue.hs view
@@ -0,0 +1,192 @@+--+-- 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.+--++{-# LANGUAGE FlexibleContexts           #-}+{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses      #-}+{-# LANGUAGE RecordWildCards            #-}+{-# LANGUAGE TupleSections              #-}+{-# LANGUAGE TypeFamilies               #-}++module Git.Vogue where++import           Control.Applicative+import           Control.Monad+import           Control.Monad.IO.Class+import           Control.Monad.Reader+import           Data.List+import           Data.Monoid+import           Data.String.Utils+import           System.Directory+import           System.Exit+import           System.FilePath+import           System.IO+import           System.Posix.Files+import           System.Process++import           Git.Vogue.Plugins+import           Git.Vogue.Types+import           Paths_git_vogue++-- | Options parsed from the command-line.+data VogueOptions = Options+    { optSearch  :: SearchMode+    , optCommand :: VogueCommand+    }+  deriving (Eq, Show)++-- | Commands, with parameters, to be executed.+data VogueCommand+    -- | Add git-vogue support to a git repository.+    = CmdInit { templatePath :: Maybe FilePath }+    -- | Verify that support is installed and plugins happen.+    | CmdVerify+    -- | List the plugins that git-vogue knows about.+    | CmdPlugins+    -- | Run check plugins on files in a git repository.+    | CmdRunCheck+    -- | Run fix plugins on files in a git repository.+    | CmdRunFix+  deriving (Eq, Show)++-- | Plugins that git-vogue knows about.+--   FIXME: this will become the fix/check modules++newtype Vogue m x = Vogue { vogue :: ReaderT [Plugin] m x }+  deriving ( Functor, Applicative, Monad+           , MonadTrans, MonadIO, MonadReader [Plugin] )++-- | Execute a Vogue program+runVogue+    :: [Plugin]+    -> Vogue m a+    -> m a+runVogue ps (Vogue act) = runReaderT act ps++-- | Execute a git-vogue command.+runCommand+    :: (MonadIO m, Functor m)+    => VogueCommand+    -> SearchMode+    -> Vogue m ()+runCommand CmdInit{..} _ = runWithRepoPath (gitAddHook templatePath)+runCommand CmdVerify   _ = runWithRepoPath (gitCheckHook runsVogue)+runCommand CmdPlugins  _ = listPlugins+runCommand CmdRunCheck search = runCheck search+runCommand CmdRunFix   search = runFix search++-- | Try to fix the broken things. We first do one pass to check what's broken,+-- then only run fix on those.+runFix :: (MonadIO m, Functor m) => SearchMode -> Vogue m ()+runFix sm = do+    -- See which plugins failed first+    rs <- ask >>= mapM (\x -> (x,) <$> executeCheck ioPluginExecutorImpl sm x)+    -- Now fix the failed ones only+    getWorst (executeFix ioPluginExecutorImpl sm) [ x | (x, Failure{}) <- rs ]+    >>= outputStatusAndExit++-- | Check for broken things.+runCheck :: MonadIO m => SearchMode -> Vogue m ()+runCheck sm =+    ask+    >>= getWorst (executeCheck ioPluginExecutorImpl sm)+    >>= outputStatusAndExit++-- | Find the git repository path and pass it to an action.+--+-- Throws an error if the PWD is not in a git repo.+runWithRepoPath+    :: MonadIO m+    => (FilePath -> m a)+    -> m a+runWithRepoPath action =+    -- Get the path to the git repo top-level directory.+    liftIO (readProcess "git" ["rev-parse", "--show-toplevel"] "")+    >>= action . strip++--- | Command string to insert into pre-commit hooks.+preCommitCommand :: String+preCommitCommand = "git-vogue check"++-- | Add the git pre-commit hook.+gitAddHook+    :: MonadIO m+    => Maybe FilePath -- ^ Template path+    -> FilePath       -- ^ Hook path+    -> Vogue m ()+gitAddHook template path = liftIO $ do+    let hook = path </> ".git" </> "hooks" </> "pre-commit"+    exists <- fileExist hook+    if exists+        then updateHook hook+        else createHook hook+  where+    createHook = copyHookTemplateTo template+    updateHook hook = do+        content <- readFile hook+        unless (preCommitCommand `isInfixOf` content) $ do+            putStrLn $ "A pre-commit hook already exists at \n\t"+                <> hook+                <> "\nbut it does not contain the command\n\t"+                <> preCommitCommand+                <> "\nPlease edit the hook and add this command yourself!"+            exitFailure+        putStrLn "Your commit hook is already in place."++-- | Copy the template pre-commit hook to a git repo.+copyHookTemplateTo+    :: Maybe FilePath+    -> FilePath+    -> IO ()+copyHookTemplateTo maybe_t hook = do+    template <- maybe (getDataFileName "templates/pre-commit") return maybe_t+    copyFile template hook+    perm <- getPermissions hook+    setPermissions hook $ perm { executable = True }++-- | Use a predicate to check a git commit hook.+gitCheckHook+    :: MonadIO m+    => (FilePath -> IO Bool)+    -> FilePath+    -> Vogue m ()+gitCheckHook p path = do+    let hook = path </> ".git" </> "hooks" </> "pre-commit"+    -- Check it exists (so openFile doesn't explode).+    exists <- liftIO . fileExist $ hook+    if exists+        then checkPredicate hook+        else failWith $ "Missing file " <> hook+    liftIO exitSuccess+  where+    checkPredicate hook = liftIO $ do+        pass <- p hook+        unless pass $ failWith "Invalid configuration."+    failWith msg = liftIO $ do+        hPutStrLn stderr msg+        exitFailure++-- | Check that a script seems to run git vogue.+runsVogue+    :: FilePath+    -> IO Bool+runsVogue path = do+    c <- readFile path+    return $ preCommitCommand `isInfixOf` c++-- | Print a list of all plugins.+listPlugins :: MonadIO m => Vogue m ()+listPlugins = do+    dir <- liftIO ((</> "git-vogue") <$> getLibexecDir)+    liftIO . putStrLn $ "git-vogue looks for plugins in:\n\n\t"  <> dir <> "\n"+    plugins <- ask+    liftIO .  putStr+         $  "git-vogue knows about the following plugins:\n\n"+         <> unlines (fmap (('\t':) . unPlugin) plugins)
+ lib/Git/Vogue/Plugins.hs view
@@ -0,0 +1,111 @@+--+-- 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.+--++{-# LANGUAGE OverloadedStrings #-}++module Git.Vogue.Plugins where++import           Git.Vogue.Types++import           Control.Applicative+import           Control.Monad+import           Control.Monad.IO.Class+import           Data.Foldable+import           Data.Monoid+import           Data.String+import           Data.String.Utils+import           Data.Text.Lazy         (Text)+import qualified Data.Text.Lazy         as T+import qualified Data.Text.Lazy.IO      as T+import           Formatting+import           Prelude                hiding (maximum)+import           System.Directory+import           System.Exit+import           System.Process++-- | Execute a plugin in IO+ioPluginExecutorImpl :: MonadIO m => PluginExecutorImpl m+ioPluginExecutorImpl =+    PluginExecutorImpl (f "fix") (f "check")+  where+    -- | Given the command sub-type, and the path to the plugin, execute it+    -- appropriately.+    --+    -- This involves the interface described in README under "Plugin design".+    f :: MonadIO m => String -> SearchMode -> Plugin -> m (Status a)+    f arg sm (Plugin path) = liftIO $ do+        name <- getName path+        fs <- unlines <$> (lines <$> paths sm >>= filterM doesFileExist)+        (status, out, err) <- readProcessWithExitCode path [arg] fs+        let glommed = fromString $ out <> err++        return $ case status of+            ExitSuccess   -> Success name glommed+            ExitFailure 1 -> Failure name glommed+            ExitFailure n -> Catastrophe n name glommed++    paths FindChanged = git ["diff", "--cached", "--name-only"]+    paths FindAll     = git ["ls-files"]++    git args = readProcess "git" args ""++    getName path = do+        (status, name, _) <- readProcessWithExitCode path ["name"] mempty+        return . PluginName . fromString . strip $ case status of+            ExitSuccess -> if null name then path else name+            ExitFailure _ -> path++colorize :: Status a -> Text+colorize (Success     (PluginName x) y) =+    format ("\x1b[32m" % text % " succeeded\x1b[0m with:\n" % text) x y+colorize (Failure     (PluginName x) y) =+    format ("\x1b[33m" % text % " failed\x1b[0m with:\n" % text) x y+colorize (Catastrophe n (PluginName x) y) =+    format ("\x1b[31m"+           % text+           % " exploded \x1b[0m with exit code "+           % int+           %":\n"+           % text) x n y++-- | Output the result of a Plugin and exit with an appropriate return code+outputStatusAndExit+    :: MonadIO m+    => Status a+    -> m ()+outputStatusAndExit status = liftIO $+    case status of+        Success _ output -> do+            T.putStrLn output+            exitSuccess+        Failure _ output -> do+            T.putStrLn output+            exitWith $ ExitFailure 1+        Catastrophe _ _ output -> do+            T.putStrLn output+            exitWith $ ExitFailure 2++-- | Run a bunch of plugin actions, mush the statuses together and stick them+-- all under the header of the worst.+getWorst+    :: Monad m+    => (Plugin -> m (Status a))+    -> [Plugin]+    -> m (Status a)+getWorst f ps = do+    rs <- mapM f ps+    return $ insertMax rs (T.unlines $ fmap colorize rs)++insertMax :: [Status a] -> Text -> Status a+insertMax [] _   = Success mempty "No plugins to run, vacuous success."+insertMax rs txt =+    case maximum rs of+        Success{} -> Success mempty txt+        Failure{} -> Failure mempty txt+        Catastrophe{} -> Catastrophe 0 mempty txt
+ lib/Git/Vogue/Types.hs view
@@ -0,0 +1,50 @@+--+-- 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.+--++{-# LANGUAGE EmptyDataDecls             #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Git.Vogue.Types where++import           Data.Monoid+import           Data.String+import           Data.Text.Lazy (Text)++-- | Phantom type for Statuses related to checking+data Check+-- | Phantom type for Statuses related to fixing+data Fix++-- | Result of running a Plugin+data Status a+    = Success PluginName Text+    | Failure PluginName Text+    | Catastrophe Int PluginName Text+  deriving (Show, Ord, Eq)++-- | Absolute path to an executable+newtype Plugin = Plugin {+    unPlugin :: FilePath+} deriving (Show, Ord, Eq, IsString)++-- | Nice, human readable name of a plugin+newtype PluginName = PluginName {+    unPluginName :: Text+} deriving (Show, Ord, Eq, IsString, Monoid)++-- | We want the flexibility of just checking changed files, or maybe checking+-- all of them.+data SearchMode = FindAll | FindChanged+  deriving (Eq, Show)++-- | An implementation of a "runner" of plugins. Mostly for easy testing.+data PluginExecutorImpl m = PluginExecutorImpl{+    executeFix   :: SearchMode -> Plugin -> m (Status Fix),+    executeCheck :: SearchMode -> Plugin -> m (Status Check)+}
+ src/git-vogue-cabal.hs view
@@ -0,0 +1,72 @@+--+-- 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 with "cabal check".+module Main where++import           Common+import           Control.Monad                                 (unless, when)+import           Data.Monoid+import           Distribution.PackageDescription.Check+import           Distribution.PackageDescription.Configuration (flattenPackageDescription)+import           Distribution.PackageDescription.Parse         (readPackageDescription)+import           Distribution.Simple.Utils                     (defaultPackageDesc,+                                                                toUTF8,+                                                                wrapText)+import           Distribution.Verbosity                        (Verbosity,+                                                                silent)+import           System.Exit++main :: IO ()+main = f =<< getPluginCommand+                "Check your Haskell project for cabal-related problems."+                "git-vogue-cabal - check for cabal problems"+  where+    f CmdName  = putStrLn "cabal"+    f CmdCheck = do+        ok <- check silent+        unless ok exitFailure+    f CmdFix     = exitFailure++-- | Runs the same thing as cabal check.+-- See also "Distribution.Client.Check" in cabal-install.+check :: Verbosity -> IO Bool+check verbosity = do+    pdfile <- defaultPackageDesc verbosity+    ppd <- readPackageDescription verbosity pdfile+    let pkg_desc = flattenPackageDescription ppd+    ioChecks <- checkPackageFiles pkg_desc "."+    let packageChecks = ioChecks <> checkPackage ppd (Just pkg_desc)+        buildImpossible = [ x | x@PackageBuildImpossible {} <- packageChecks ]+        buildWarning    = [ x | x@PackageBuildWarning {}    <- packageChecks ]+        distSuspicious  = [ x | x@PackageDistSuspicious {}  <- packageChecks ]+        distInexusable  = [ x | x@PackageDistInexcusable {} <- packageChecks ]+    unless (null buildImpossible) $ do+        putStrLn "The package will not build sanely due to these errors:"+        printCheckMessages buildImpossible+    unless (null buildWarning) $ do+        putStrLn "The following warnings are likely affect your build negatively:"+        printCheckMessages buildWarning+    unless (null distSuspicious) $ do+        putStrLn "These warnings may cause trouble when distributing the package:"+        printCheckMessages distSuspicious+    unless (null distInexusable) $ do+        putStrLn "The following errors will cause portability problems on other environments:"+        printCheckMessages distInexusable+    let isDistError (PackageDistSuspicious {}) = False+        isDistError _                          = True+        errors = filter isDistError packageChecks+    unless (null errors) $+        putStrLn "Hackage would reject this package."+    when (null packageChecks) $+        putStrLn "Checked cabal file"+    return (null packageChecks)+  where+    printCheckMessages = mapM_ (putStrLn . format . explanation)+    format = toUTF8 . wrapText . ("* "++)
+ src/git-vogue-ghc-mod.hs view
@@ -0,0 +1,93 @@+--+-- 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 with "cabal check".+module Main where++import           Common+import           Control.Applicative+import           Data.Char+import           Data.Foldable+import           Data.List               hiding (elem)+import           Data.Maybe+import           Data.Monoid+import           Data.Traversable+import           Language.Haskell.GhcMod+import           Prelude                 hiding (elem)+import           System.Exit++main :: IO ()+main =+    f =<< getPluginCommand+            "Check your Haskell project for ghc-mod problems."+            "git-vogue-ghc-mod - check for ghc-mod problems"+  where+    f CmdName  = putStrLn "ghc-mod check"+    f CmdCheck = ghcModCheck+    f CmdFix   = do+        putStrLn "you need to fix ghc-mod check failures"+        exitFailure++-- | Try to help the user out with some munging of error messages+explain :: String -> String+explain s+    -- A terrible heuristic, but it may help some people+    | "test" `isInfixOf` fmap toLower s && "hidden package" `isInfixOf` s =+        s <> "\n\tSuggestion: cabal configure --enable-tests\n"+    | "bench" `isInfixOf` fmap toLower s && "hidden package" `isInfixOf` s =+        s <> "\n\tSuggestion: cabal configure --enable-benchmarks\n"+    | otherwise = s++-- | ghc-mod check all of the .hs files from stdin+ghcModCheck ::  IO ()+ghcModCheck = do+    -- HLint.hs is weird and more of a config file than a source file, so+    -- ghc-mod doesn't like it.+    --+    -- Setup.hs can import cabal things magically, without requiring it to be+    -- mentioned in cabal (which ghc-mod hates)+    --+    -- This is ugly, and should be replaced in favor of a git-vogue ignoring+    -- mechanism.+    files <- filter (not . (`elem` ["HLint.hs", "Setup.hs"])) <$> hsFiles++    -- We can't actually check all at once, or ghc-mod gets confused, so we+    -- traverse+    (r,_) <- runGhcModT defaultOptions (traverse (check . return) files)++    -- Seriously guys? Eithers within eithers?+    warn_errs <- case r of+            -- This is some kind of outer-error, we don't fail on it.+            Left e -> do+                print e+                return []+            -- And these are the warnings and errors.+            Right rs ->+                return rs++    -- Traverse the errors, picking the warnings out. We don't fail on errors+    -- but do warn about them.+    maybe_ws <- for warn_errs $ \warn_err ->+        case warn_err of+            -- Errors in files+            Left e -> do+                putStrLn (explain e)+                return Nothing+            -- Warnings, sometimes empty strings+            Right warn ->+                return $ if null warn then Nothing else Just warn++    let warns = catMaybes maybe_ws+    if null warns+        then do+            putStrLn $ "Checked " <> show (length files)  <> " file(s)"+            exitSuccess+        else do+            traverse_ putStrLn warns+            exitFailure
+ src/git-vogue-hlint.hs view
@@ -0,0 +1,117 @@+--+-- 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.+--++{-# LANGUAGE CPP             #-}+{-# LANGUAGE RecordWildCards #-}++-- | Description: Check with "cabal check".+module Main where++import           Common+import           Control.Applicative+import           Data.Bifunctor+import           Data.List+import           Data.Monoid+import           Data.Traversable+import           Language.Haskell.Exts.SrcLoc+import           Language.Haskell.HLint3+import           Language.Preprocessor.Cpphs+import           System.Directory+import           System.Exit++#ifndef GPL_SCARES_ME+import           Language.Haskell.HsColour.Colourise+import           Language.Haskell.HsColour.TTY+#endif++main :: IO ()+main =+    f =<< getPluginCommand+            "Check your Haskell project for hlint-related problems."+            "git-vogue-hlint - check for hlint problems"+  where+    f CmdName  = putStrLn "hlint"+    f CmdCheck = lint+    f CmdFix   = putStrLn "you need to fix hlint failures" >> exitFailure++-- | Lint all of the .hs files from stdin+lint ::  IO ()+lint = do+    files <- hsFiles+    (flags, classify, hint) <- autoSettings'++    -- Cpphs is off by default+    let flags' = flags { cppFlags = Cpphs defaultCpphsOptions }++    -- Traverse the files, parsing and processing as we go for efficiency+    parsed <- for files $ \f ->+                process classify hint <$> parseModuleEx flags' f Nothing+++    let ideas = concat [ x | Right x <- parsed]+    let errors = [  x | Left x <- parsed ]+    let out = unlines errors <> "\n" <> ideas++    if null ideas && null errors+      then do+        putStrLn ("Checked " <> show (length files) <> " file(s)")+        exitSuccess+      else putStrLn out >> exitFailure+  where+    process classify hint =+        bimap f g+      where+        f x = parseErrorMessage x <> show (parseErrorLocation x)+        g x = showANSI $ applyHints classify hint [x]++-- | The default autoSettings form HLint3 does not handle custom HLint.hs files+-- in the current directory. So we define our own.+autoSettings' :: IO (ParseFlags, [Classify], Hint)+autoSettings' = do+    local_hlint <- doesFileExist "HLint.hs"+    let start_at = if local_hlint then Just "HLint" else Nothing+    (fixities, classify, hints) <- findSettings (readSettingsFile Nothing)+                                                start_at+    return (parseFlagsAddFixities fixities defaultParseFlags, classify, resolveHints hints)++#ifdef GPL_SCARES_ME+format :: String -> String+format s = "\x1b[36m" <> s <> "\x1b[0m"+#else+format :: String -> String+format = hscolour defaultColourPrefs+#endif++-- All of the code below is more or less salvaged from hlint internals.++-- | Pretty print and Idea with colouring+showANSI :: [Idea] -> String+showANSI =+    (>>= \i -> showEx format i <> "\n")++-- | Show an idea with a function that highlights.+showEx :: (String -> String) -> Idea -> String+showEx tt Idea{..} = unlines $+    ["\x1b[33m" <> showSrcLoc (getPointLoc ideaSpan) <> "\x1b[0m " <> (if ideaHint == "" then "" else show ideaSeverity <> ": " <> ideaHint)] <>+    f "Found" (Just ideaFrom) <> f "Why not" ideaTo <>+    ["Note: " <> n | let n = showNotes ideaNote, n /= ""]+    where+        f _ Nothing = []+        f msg (Just x) | null xs = [msg <> " remove it."]+                       | otherwise = (msg <> ":") : fmap ("  "<>) xs+            where xs = lines $ tt x++showSrcLoc :: SrcLoc -> String+showSrcLoc (SrcLoc file line col) = file <> ":" <> show line <> ":" <> show col <> ":"++showNotes :: [Note] -> String+showNotes = intercalate ", " . fmap show . filter use+    where use ValidInstance{} = False -- Not important enough to tell an end user+          use _ = True+
+ src/git-vogue-stylish.hs view
@@ -0,0 +1,140 @@+--+-- 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           Common+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           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+    files <- hsFiles+    f files cfg cmd+  where+    f _ _ CmdName  = putStrLn "stylish"+    f files cfg CmdCheck = do+        rs <- traverse (stylishCheckFile cfg) files+        if and rs+            then do+                putStrLn $ "Checked " <> show (length rs) <> " file(s)"+                exitSuccess+            else+                exitFailure++    f files cfg CmdFix = do+        -- 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+                putStrLn "Style converged"+            else do+                putStrLn "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+            putStrLn $ "\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
+ src/git-vogue.hs view
@@ -0,0 +1,101 @@+--+-- 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.+--++{-# LANGUAGE OverloadedStrings #-}++module Main where++import           Control.Monad+import           Data.List.Split+import           Data.Maybe+import           Data.String+import           Data.Traversable+import           Options.Applicative+import           Options.Applicative.Types+import           System.Directory+import           System.Environment+import           System.FilePath++import           Git.Vogue+import           Git.Vogue.Types++import           Common+import qualified Paths_git_vogue           as Paths++-- | Parse command-line options.+optionsParser :: Parser VogueOptions+optionsParser = Options+    <$> flag FindChanged FindAll+        (  long "all"+        <> short 'A'+        <> help "Apply to all files, not just changed files."+        )+    <*> commandParser++commandParser :: Parser VogueCommand+commandParser = subparser+    ( command "init" (info pInit+        (progDesc "Initialise git-vogue support in a git repo"))+    <> pCommand "verify"+                CmdVerify+                "Check git-vogue support is all legit"+    <> pCommand "plugins"+                CmdPlugins+                "List installed plugins."+    <> pCommand "check"+                CmdRunCheck+                "Run check plugins on files in a git repo"+    <> pCommand "fix"+                CmdRunFix+                "Run fix plugins on files a git repo"+    )+  where+    pInit = CmdInit <$> option (Just <$> readerAsk)+        (  long "template"+        <> value Nothing+        )++-- | Discover all available plugins.+--+-- This function inspects the $PREFIX/libexec/git-vogue directory and the+-- directories listed in the $GIT_VOGUE_PATH environmental variable (if+-- defined) and builds a 'Plugin' for the executables found.+discoverPlugins :: IO [Plugin]+discoverPlugins = do+    -- Use the environmental variable and $libexec/git-vogue/ directories as+    -- the search path.+    path <- fromMaybe "" <$> lookupEnv "GIT_VOGUE_PATH"+    libexec <- (</> "git-vogue") <$> Paths.getLibexecDir+    let directories = splitOn ":" path <> [libexec]++    -- Find all executables in the directories in path.+    (fmap . fmap) fromString+                  (traverse ls directories >>= filterM isExecutable . concat)+  where+    ls :: FilePath -> IO [FilePath]+    ls p = do+        exists <- doesDirectoryExist p+        if exists+            then fmap (p </>) <$> getDirectoryContents p+            else return []++    isExecutable :: FilePath -> IO Bool+    isExecutable = fmap executable . getPermissions++-- | Parse the command line and run the command.+main :: IO ()+main = do+  opt <- execParser opts+  plugins <- discoverPlugins+  runVogue plugins (runCommand (optCommand opt) (optSearch opt))+  where+    opts = info (helper <*> optionsParser)+      ( fullDesc+     <> progDesc "Make your Haskell git repository fashionable"+     <> header "git-vogue - git integration for fashionable Haskell" )
+ templates/pre-commit view
@@ -0,0 +1,3 @@+#!/bin/sh++git-vogue check
+ tests/git-setup.hs view
@@ -0,0 +1,106 @@+--+-- 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.+--++{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE MultiParamTypeClasses #-}+-- | Description: Test git repository setup.+module Main where++import           Control.Exception+import           Control.Monad+import           Data.List+import           Data.Monoid+import           System.Directory+import           System.Exit+import           System.FilePath+import           System.Posix.Files+import           System.Posix.Temp+import           System.Process+import           Test.Hspec++import           Git.Vogue++main :: IO ()+main = hspec . describe "Git repository setup" $ do+    it "should install a new pre-commit hook" . withGitRepo $ \path -> do+            let hook = path </> ".git" </> "hooks" </> "pre-commit"++            shouldInstallWorking path hook++    it "should skip a correct pre-commit hook" . withGitRepo $ \path -> do+            let hook = path </> ".git" </> "hooks" </> "pre-commit"++            -- Create an existing hook to update.+            copyHookTemplateTo (Just "templates/pre-commit") hook++            shouldInstallWorking path hook++    it "should report a conflict pre-commit hook" . withGitRepo $ \path -> do+        let hook = path </> ".git" </> "hooks" </> "pre-commit"++        -- Create an existing hook to update.+        writeFile hook "echo YAY\n"+        setPermissions hook $ emptyPermissions+            { readable = True+            , executable = True+            }++        -- Run the setup program.+        code <- runInRepo path+        code `shouldBe` ExitFailure 1++-- | Run "git-vogue init" and check we wind up with a working pre-commit hook.+shouldInstallWorking :: FilePath -> FilePath -> IO ()+shouldInstallWorking path hook = do+    code <- runInRepo path+    code `shouldBe` ExitSuccess+    checkPreCommitHook hook++-- | Execute the setup command in a git repository.+runInRepo+    :: FilePath+    -> IO ExitCode+runInRepo path = do+    pwd <- getCurrentDirectory+    let exe = pwd </> "dist/build/git-vogue/git-vogue"+    let tpl = pwd </> "templates/pre-commit"+    ps <- spawnCommand $+        "cd " <> path <> " && " <> exe <> " init --template=" <> tpl+    waitForProcess ps++-- | Check that a pre-commit hook script is "correct".+checkPreCommitHook+    :: FilePath+    -> IO ()+checkPreCommitHook hook = do+    -- Check the hook exists.+    exists <- fileExist hook+    unless exists $ error "Commit hook missing"++    -- Check the hook is executable.+    perm <- getPermissions hook+    unless (executable perm) $ error "Commit hook is not executable"++    -- Check it has our command in it.+    content <- readFile hook+    unless ("git-vogue check" `isInfixOf` content) $+        error "Commit hook does not contain command"++-- | Create a git repository and run an action with it.+withGitRepo+    :: (FilePath -> IO ())+    -> IO ()+withGitRepo = bracket createRepo deleteRepo+  where+    createRepo = do+        path <- mkdtemp "/tmp/git-setup-test."+        callProcess "git" ["init", path]+        return path+    deleteRepo path =+        callProcess "rm" ["-rf", path]
+ tests/plugins.hs view
@@ -0,0 +1,88 @@+--+-- 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.+--++{-# LANGUAGE FlexibleContexts  #-}+{-# LANGUAGE OverloadedStrings #-}++-- | Description: Test git repository setup.+module Main where++import           Control.Monad.IO.Class ()+import           Data.Monoid+import           System.FilePath+import           Test.Hspec++import           Git.Vogue.Plugins+import           Git.Vogue.Types++failingExecutor, succeedingExecutor :: b -> IO (Status a)+failingExecutor    = const . return $ Failure mempty mempty+succeedingExecutor = const . return $ Success mempty mempty++main :: IO ()+main = hspec $ do+    describe "module collation" $ do+        it "maximum of statuses is failure" $+            maximum [Success mempty mempty, Failure mempty mempty]+                `shouldBe` Failure mempty mempty++        it "fails if one module fails" $ do+            getWorst failingExecutor ["a", "b"]+              >>= (`shouldBe` Failure mempty "\ESC[33m failed\ESC[0m with:\n\n\ESC[33m failed\ESC[0m with:\n\n")+            getWorst failingExecutor  ["monkey"]+              >>= (`shouldBe` Failure mempty "\ESC[33m failed\ESC[0m with:\n\n")++        it "succeeds if all modules succeed" $ do+            getWorst succeedingExecutor ["a", "b"]+              >>= (`shouldBe` Success mempty "\ESC[32m succeeded\ESC[0m with:\n\n\ESC[32m succeeded\ESC[0m with:\n\n")+            getWorst succeedingExecutor ["monkey"]+              >>= (`shouldBe` Success mempty "\ESC[32m succeeded\ESC[0m with:\n\n")++    describe "IO module executor" $ do+        describe "check" $ do+            it "fails on failing module" $+                runCheckExecutor FindAll "failing"+                                (Failure "failing" "ohnoes\n")++            it "fails on succeeding module" $+                runCheckExecutor FindAll "succeeding"+                                (Success "succeeding" "yay\n")++            it "fails on exploding module" $+                runCheckExecutor FindAll "exploding"+                                (Catastrophe 3 "exploding" "something broke\n")++        describe "fix" $ do+            it "fails on failing module" $+                runFixExecutor FindAll "failing"+                                (Failure "failing" "ohnoes\n")++            it "fails on succeeding module" $+                runFixExecutor FindAll "succeeding"+                                (Success "succeeding" "yay\n")++            it "fails on exploding module" $+                runFixExecutor FindAll "exploding"+                                (Catastrophe 3 "exploding" "something broke\n")++runCheckExecutor :: SearchMode -> FilePath -> Status Check -> Expectation+runCheckExecutor = runTestExecutor executeCheck++runFixExecutor :: SearchMode -> FilePath -> Status Fix -> Expectation+runFixExecutor = runTestExecutor executeFix++runTestExecutor+    :: (PluginExecutorImpl IO -> SearchMode -> Plugin -> IO (Status a))+    -> SearchMode+    -> FilePath+    -> Status a+    -> Expectation+runTestExecutor act search_mode file expected =+    act ioPluginExecutorImpl search_mode (Plugin ("fixtures" </> file))+      >>= (`shouldBe` expected)