packages feed

git-fmt 0.3.0.5 → 0.3.1.0

raw patch · 8 files changed

+67/−314 lines, 8 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Omnifmt.Config: instance Data.Aeson.Types.FromJSON.FromJSON Omnifmt.Config.Config
- Omnifmt.Config: instance Data.Aeson.Types.FromJSON.FromJSON Omnifmt.Config.Program
- Omnifmt.Pipes: statusPrinter :: MonadLogger m => Consumer (Status, FilePath, FilePath) m ()
+ Omnifmt.Config: instance Data.Aeson.Types.Class.FromJSON Omnifmt.Config.Config
+ Omnifmt.Config: instance Data.Aeson.Types.Class.FromJSON Omnifmt.Config.Program
+ Omnifmt.Pipes: Timeout :: Status
+ Omnifmt.Pipes: diff :: (MonadIO m, MonadLogger m) => Pipe (Status, FilePath, FilePath) (Status, FilePath, FilePath) m ()
+ Omnifmt.Pipes: printFileStatus :: MonadLogger m => (Status -> Text -> m ()) -> Pipe (Status, FilePath, FilePath) (Status, FilePath, FilePath) m ()
+ Omnifmt.Process: runTimedCommand :: (MonadIO m, MonadLogger m) => Int -> String -> m (ExitCode, String, String)
+ Omnifmt.Process: runTimedProcess :: (MonadIO m, MonadLogger m) => Int -> FilePath -> [String] -> m (ExitCode, String, String)

Files

− CHANGELOG.md
@@ -1,150 +0,0 @@-## Changelog--#### Upcoming--#### v0.3.0.5--*Revisions*--* Fixed a bug causing prettifying to fail across filesystem boundaries. ([#72](https://github.com/hjwylde/git-fmt/issues/72))--#### v0.3.0.4--*Revisions*--* Fixed a bug causing the program to hang when not in the root directory. ([#66](https://github.com/hjwylde/git-fmt/issues/66))-* Fixed a bug that omitted searching the drive for a config file. ([#66](https://github.com/hjwylde/git-fmt/issues/66))-* Fixed a bug where output files could be created outside of the temp directory. ([#68](https://github.com/hjwylde/git-fmt/issues/68))--#### v0.3.0.3--*Revisions*--* Restricted use of `--operate-on-tracked` and `--operate-on REF` at the same time. ([#65](https://github.com/hjwylde/git-fmt/issues/65))-* Removed long option for help text (as git overrides it for man pages). ([#65](https://github.com/hjwylde/git-fmt/issues/65))--#### v0.3.0.2--*Revisions*--* Fixed a bug where passing arguments didn't properly narrow down the operation files. ([#64](https://github.com/hjwylde/git-fmt/issues/64))--#### v0.3.0.1--*Revisions*--* Relaxed version constraints. ([#63](https://github.com/hjwylde/git-fmt/issues/63))--#### v0.3.0.0--*Major*--* Restricted arguments to being inside the repository. ([#34](https://github.com/hjwylde/git-fmt/issues/34))-* Refactored library to use pipes. ([#32](https://github.com/hjwylde/git-fmt/issues/32))-* Made `Options` and `Version` modules private. ([#62](https://github.com/hjwylde/git-fmt/issues/62))-* Renamed library modules to `Omnifmt`. ([#62](https://github.com/hjwylde/git-fmt/issues/62))-* Refactored pipeline to feed and consume triples. ([#61](https://github.com/hjwylde/git-fmt/issues/61))-* Set default `--operate-on` to `head`. ([#28](https://github.com/hjwylde/git-fmt/issues/28))--*Minor*--* Changed "not found" status to print as debug message. ([#61](https://github.com/hjwylde/git-fmt/issues/61))-* Added "unsupported" status as debug message. ([#61](https://github.com/hjwylde/git-fmt/issues/61))-* Added `--operate-on-tracked` and `--operate-on REF` options. ([#28](https://github.com/hjwylde/git-fmt/issues/28))--#### v0.2.2.1--*Revisions*--* Fixed a bug causing prettifying to fail across filesystem boundaries. ([#72](https://github.com/hjwylde/git-fmt/issues/72))--#### v0.2.2.0--*Minor*--* Added `--threads` option to change the number of threads for parallelisation. ([#54](https://github.com/hjwylde/git-fmt/issues/54))--*Revisions*--* Added quoting to the command variables during substitution. ([#59](https://github.com/hjwylde/git-fmt/issues/59))-* Changed parallelisation to use the number of capabilities (and processors) for the number of-  threads by default. ([#54](https://github.com/hjwylde/git-fmt/issues/54))--#### v0.2.1.2--*Revisions*--* Fixed a bug causing prettifying to fail across filesystem boundaries. ([#72](https://github.com/hjwylde/git-fmt/issues/72))--#### v0.2.1.1--*Revisions*--* Fixed a bug where passing arguments would only work when running in the git directory. ([#57](https://github.com/hjwylde/git-fmt/issues/57))--#### v0.2.1.0--*Minor*--* Added default use of stdin and stdout when variables not specified in a program command. ([#49](https://github.com/hjwylde/git-fmt/issues/49))--#### v0.2.0.2--*Revisions*--* Fixed a bug causing prettifying to fail across filesystem boundaries. ([#72](https://github.com/hjwylde/git-fmt/issues/72))--#### v0.2.0.1--*Revisions*--* Fixed a bug where passing arguments would only work when running in the git directory. ([#57](https://github.com/hjwylde/git-fmt/issues/57))--#### v0.2.0.0--*Major*--* Removed `--list-ugly` and `--dry-run` options. ([#29](https://github.com/hjwylde/git-fmt/issues/29))-* Restricted use of `--quiet` and `--verbose` at the same time. ([#35](https://github.com/hjwylde/git-fmt/issues/35))-* Updated project structure to delegate pretty printing to other binaries. ([#38](https://github.com/hjwylde/git-fmt/issues/38))-* Added a `.omniyaml.yaml` config file. ([#38](https://github.com/hjwylde/git-fmt/issues/38))--*Minor*--* Added `--mode` option (either `normal` or `dry-run`). ([#29](https://github.com/hjwylde/git-fmt/issues/29))-* Added `--null` option (use the null terminator as the delimiter for inputs). ([#27](https://github.com/hjwylde/git-fmt/issues/27))-* Added support for directories as arguments (directories include all files within recursively). ([#30](https://github.com/hjwylde/git-fmt/issues/30))-* Added parallelisation. ([#48](https://github.com/hjwylde/git-fmt/issues/48))--*Revisions*--* Added a warning for when files aren't found. ([#29](https://github.com/hjwylde/git-fmt/issues/29))-* Updated internal use of `git ls-files` to use the null terminator option. ([#27](https://github.com/hjwylde/git-fmt/issues/27))-* Fixed debug log messages to have timestamp and log level on all lines. ([#33](https://github.com/hjwylde/git-fmt/issues/33))-* Tidied up error messages from git. ([#40](https://github.com/hjwylde/git-fmt/issues/40))-* Tidied up error messages from parsing the config. ([#43](https://github.com/hjwylde/git-fmt/issues/43))--#### v0.1.0.3--*Revisions*--* Fixed a bug where passing arguments would only work when running in the git directory. ([#57](https://github.com/hjwylde/git-fmt/issues/57))--#### v0.1.0.2--*Revisions*--* Fixed a bug where UTF-8 characters in strings weren't printed properly. ([#26](https://github.com/hjwylde/git-fmt/issues/26))--#### v0.1.0.1--*Revisions*--* Fixed a bug where integers were printed as rationals. ([#25](https://github.com/hjwylde/git-fmt/issues/25))--#### v0.1.0.0--This is first release of the `git-fmt` binary!-It provides a basic syntax for formatting files in a git repository.-Currently only JSON is supported.-
− README.md
@@ -1,110 +0,0 @@-# git-fmt--[![Project Status: Wip - Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](http://www.repostatus.org/badges/1.0.0/wip.svg)](http://www.repostatus.org/#wip)-[![Build Status](https://travis-ci.org/hjwylde/git-fmt.svg?branch=master)](https://travis-ci.org/hjwylde/git-fmt)-[![Release](https://img.shields.io/github/release/hjwylde/git-fmt.svg)](https://github.com/hjwylde/git-fmt/releases/latest)-[![git-fmt on Stackage LTS](http://stackage.org/package/git-fmt/badge/lts)](http://stackage.org/lts/package/git-fmt)-[![git-fmt on Stackage Nightly](http://stackage.org/package/git-fmt/badge/nightly)](http://stackage.org/nightly/package/git-fmt)--(Side note: the formatting component of this project will eventually be split out and named omnifmt.)--`git-fmt` was created to make prettifying code easy.-It adds a custom (easy to use) command to Git that formats code through external pretty-printers.--Formatted code is:--* Easier to write: never worry about minor formatting concerns while hacking away.-* Easier to read: when all code looks the same you need not mentally convert others' formatting-  style into something you can understand.-* Easier to maintain: mechanical changes to the source don't cause unrelated changes to the file's-  formatting; diffs show only the real changes.-* Uncontroversial: never have a debate about spacing or brace position ever again.--(Bullet points taken from https://blog.golang.org/go-fmt-your-code.)--### Installing--Installing `git-fmt` is easiest done using either-    [stack](https://github.com/commercialhaskell/stack) (recommended) or-    [Cabal](https://github.com/haskell/cabal).--**Using stack:**--```bash-stack install git-fmt-export PATH=$PATH:~/.local/bin-```--**Using Cabal:**--```bash-cabal-install git-fmt-export PATH=$PATH:~/.cabal/bin-```--### Usage--The `git-fmt` binary wraps external pretty-printers and provides an interface for selecting files-    to prettify and performing dry-runs.--**The basics:**--`git-fmt` operates only on tracked git files (thus it implicitly respects the .gitignore).-By default it operates on files in the index (i.e., `--operate-on head`).-It is possible to operate on all tracked files (`--operate-on-tracked`) or on a specific reference-    (`--operate-on REF`).-The `REF` argument is passed directly into `git diff REF --name-only`, so you can even play with-    ranges such as `master...`.--Passing arguments to `git-fmt` will narrow down the operation files.-For example, `git-fmt --operate-on-tracked src/` will format all tracked files under `src/` and-    `git-fmt --operate-on head src/` will format all files in the index under `src/`.--`git-fmt` can run in two different modes, *normal* and *dry-run*.-Normal mode (`--mode normal`) writes to (prettifies) all ugly files.-Dry-run mode (`--mode dry-run`) outputs the ugly file paths to `stdout`.--**NB:** a third mode, *patch*, is in the making, see-    [#23](https://github.com/hjwylde/git-fmt/issues/23) for details.--For the more uncommon usage options, have a read of `git fmt -h`.--#### Configuration--Configuration is done via an `.omnifmt.yaml` file in the git repository.-The file contains a list of *programs* that link *extensions* to a prettifying *command*, e.g.,-```yaml-haskell:-    extensions: ["hs", "lhs"]-    command:    "stylish-haskell {{input}} > {{output}}"--javascript:-    extensions: ["js"]-    command:    "js-beautify -f {{input}}"--json:-    extensions: ["json"]-    command:    "json_pp"--ruby:-    extensions: ["rb"]-    command:    "ruby-beautify"-```--Each command declares how to read the *input file* and how to write to the *output file*.-If the input variable is omitted, the file contents are fed to the command through `stdin`.-Likewise if the output variable is omitted, the pretty contents are read from `stdout`.-The output file is used to compare whether the original was pretty or ugly before writing to it.--The extensions field is pretty self explanatory, but if you use the same extension more than once-    then precedence goes to the program defined first.--#### Examples--See the [docs/example-configs/](https://github.com/hjwylde/git-fmt/tree/master/docs/example-configs/)-    directory for some common pretty-printers and their corresponding omnifmt config (pull requests-    are welcome for adding more).-Just don't forget to actually call the config file `.omnifmt.yaml`!--**NB:** I haven't tested them fully, be careful in case one is buggy.-
app/Git/Fmt/Options.hs view
@@ -42,7 +42,7 @@     deriving (Eq, Show)  -- | Run mode.-data Mode = Normal | DryRun+data Mode = Normal | DryRun | Diff     deriving (Eq, Show)  -- | Operation files.@@ -92,7 +92,7 @@     <*> modeOption (mconcat [         long "mode", short 'm', metavar "MODE",         value Normal, showDefaultWith $ const "normal",-        help "Specify the mode as either `normal' or `dry-run'"+        help "Specify the mode as either `normal', `dry-run' or `diff'"         ])     <*> (             flag' Tracked (mconcat [@@ -120,5 +120,6 @@         modeOption  = option $ readerAsk >>= \opt -> case opt of             "normal"    -> return Normal             "dry-run"   -> return DryRun+            "diff"      -> return Diff             _           -> readerError $ "unrecognised mode `" ++ opt ++ "'" 
app/Main.hs view
@@ -29,6 +29,7 @@  import           Data.List.Extra    (dropEnd, intersect, linesBy, lower) import           Data.Maybe         (fromJust, fromMaybe, isNothing)+import           Data.Text          (Text) import qualified Data.Text          as T import qualified Data.Text.Encoding as T import qualified Data.Text.IO       as T@@ -44,9 +45,10 @@  import Options.Applicative -import Pipes-import Pipes.Concurrent-import Prelude          hiding (filter, log)+import           Pipes+import           Pipes.Concurrent+import qualified Pipes.Prelude    as Pipes+import           Prelude          hiding (filter, log)  import System.Directory.Extra import System.Exit@@ -63,9 +65,9 @@ main :: IO () main = do     options     <- customExecParser gitFmtPrefs gitFmtInfo-    let chatty  = optChatty options+    let chatty  = if optMode options == Diff then Quiet else optChatty options -    flip runLoggingT (log chatty) . filter chatty $ do+    flip runLoggingT (log $ optChatty options) . filter chatty $ do         checkGitRepository          mFilePath <- Config.nearestConfigFile "."@@ -84,12 +86,9 @@  handle :: (MonadIO m, MonadLogger m, MonadMask m, MonadParallel m, MonadReader Config m) => Options -> m () handle options = do-    rootDir         <- liftIO getCurrentDirectory-    filePaths       <- runPanic $ if null (argPaths options)+    filePaths <- runPanic $ if null (argPaths options)         then initialFilePaths options         else liftM2 intersect (initialFilePaths options) (providedFilePaths options)-    absFilePaths    <- forM filePaths $ \filePath -> ifM (liftIO $ doesFileExist filePath)-        (liftIO $ canonicalizePath filePath) (return filePath)      numThreads <- liftIO getNumCapabilities >>= \numCapabilities ->         return $ fromMaybe numCapabilities (optThreads options)@@ -97,12 +96,12 @@     (output, input) <- liftIO $ spawn unbounded      withSystemTempDirectory "git-fmt" $ \tmpDir -> do-        runEffect $ each (map (\filePath -> omnifmt (makeRelative rootDir filePath) (tmpDir </> dropDrive filePath)) absFilePaths) >-> toOutput output+        runEffect $ each (map (\filePath -> omnifmt filePath (tmpDir </> filePath)) filePaths) >-> toOutput output          liftIO performGC          Parallel.forM_ [1..numThreads] $ \_ ->-            runEffect (fromInput input >-> pipeline >-> runner (optMode options) >-> statusPrinter) >>+            runEffect (fromInput input >-> pipeline >-> runner (optMode options)) >>             liftIO performGC  providedFilePaths :: MonadIO m => Options -> m [FilePath]@@ -124,9 +123,16 @@              return item -runner :: (MonadIO m, MonadLogger m) => Mode -> Pipe (Status, FilePath, FilePath) (Status, FilePath, FilePath) m ()-runner Normal = commit-runner DryRun = cat+runner :: (MonadIO m, MonadLogger m) => Mode -> Consumer (Status, FilePath, FilePath) m ()+runner Normal   = commit    >-> printFileStatus logFunction >-> Pipes.drain+runner DryRun   = cat       >-> printFileStatus logFunction >-> Pipes.drain+runner Diff     = diff      >-> Pipes.drain++logFunction :: MonadLogger m => Status -> Text -> m ()+logFunction status+    | status `elem` [Unknown, Error, Timeout]       = logWarnN+    | status `elem` [Unsupported, NotFound, Pretty] = logDebugN+    | otherwise                                     = logInfoN  filter :: Chatty -> LoggingT m a -> LoggingT m a filter Quiet    = filterLogger (\_ level -> level >= LevelError)
git-fmt.cabal view
@@ -1,5 +1,5 @@ name:           git-fmt-version:        0.3.0.5+version:        0.3.1.0  author:         Henry J. Wylde maintainer:     public@hjwylde.com@@ -16,8 +16,6 @@ cabal-version:  >= 1.10 category:       Development build-type:     Simple--extra-source-files: CHANGELOG.md README.md  source-repository head     type:       git
src/Omnifmt/Config.hs view
@@ -66,10 +66,12 @@     Right config    -> return $ Just config  -- | Finds the nearest config file by searching from the given directory upwards.+--+--   TODO (hjw): fix the bug where it won't search the root directory. nearestConfigFile :: MonadIO m => FilePath -> m (Maybe FilePath)-nearestConfigFile dir = liftIO (canonicalizePath dir) >>= \dir' -> findM (liftIO . doesFileExist) $ map (</> defaultFileName) (parents dir')+nearestConfigFile dir = findM (liftIO . doesFileExist) $ map (</> defaultFileName) parents     where-        parents = reverse . scanl1 combine . splitDirectories+        parents = takeWhile (\dir -> dir /= takeDrive dir) (iterate takeDirectory dir)  -- | The file name of the default config, '.omnifmt.yaml'. defaultFileName :: FilePath
src/Omnifmt/Pipes.hs view
@@ -24,10 +24,8 @@     omnifmt,      -- * Transformers-    select, checkFileSupported, checkFileExists, runProgram, checkFilePretty, commit,--    -- * Consumers-    statusPrinter,+    select, checkFileSupported, checkFileExists, runProgram, checkFilePretty, commit, diff,+    printFileStatus, ) where  import Control.Monad.Except@@ -36,11 +34,10 @@ import Control.Monad.Reader  import           Data.List.Extra  (lower)+import           Data.Text        (Text) import qualified Data.Text        as T import           Data.Tuple.Extra (fst3) -import GHC.IO.Exception (IOErrorType (..))- import Omnifmt.Config import Omnifmt.Process @@ -50,13 +47,13 @@ import System.Directory.Extra import System.Exit import System.FilePath-import System.IO.Error  -- | A status for a file going through the omnifmt pipeline. data Status = Unknown       -- ^ The file has not been processed.             | Error         -- ^ An error occurred somewhere.             | Unsupported   -- ^ The file type is unsupported (i.e., no applicable 'Program').             | NotFound      -- ^ The file could not be found.+            | Timeout       -- ^ A command timed out.             | Pretty        -- ^ The file is pretty.             | Ugly          -- ^ The file is ugly.             | Prettified    -- ^ The file is now pretty.@@ -98,15 +95,17 @@     config <- ask     let program = unsafeProgramFor config (T.pack . drop 1 $ takeExtension uglyFilePath) -    (exitCode, _, stderr) <- runCommand . T.unpack $ substitute (T.concat [command program, inputSuffix program, outputSuffix program]) [+    (exitCode, _, stderr) <- runTimedCommand 5 . T.unpack $ substitute (T.concat [command program, inputSuffix program, outputSuffix program]) [         (inputVariableName, T.pack uglyFilePath),         (outputVariableName, T.pack prettyFilePath)         ] -    if exitCode == ExitSuccess-        then return item-        else logDebugN (T.pack stderr) >>-             return (Error, uglyFilePath, prettyFilePath)+    case exitCode of+        ExitSuccess     -> return item+        ExitFailure 124 -> return (Timeout, uglyFilePath, prettyFilePath)+        ExitFailure 137 -> return (Timeout, uglyFilePath, prettyFilePath)+        _               -> logDebugN (T.pack stderr) >>+                           return (Error, uglyFilePath, prettyFilePath)     where         inputSuffix program             | usesInputVariable (command program)   = T.empty@@ -136,30 +135,24 @@ --   This function updates the status to 'Prettified'. commit :: MonadIO m => Pipe (Status, FilePath, FilePath) (Status, FilePath, FilePath) m () commit = select [Ugly] $ \(_, uglyFilePath, prettyFilePath) -> do-    -- Try move the file, but if it's across a filesystem boundary then we may need to copy instead-    liftIO $ renameFile prettyFilePath uglyFilePath `catchIOError` \e ->-        if ioeGetErrorType e == UnsupportedOperation-            then copyFile prettyFilePath uglyFilePath >> removeFile prettyFilePath-            else ioError e+    liftIO $ renameFile prettyFilePath uglyFilePath      return (Prettified, uglyFilePath, prettyFilePath) --- | Logs the status of each file.---   'Unsupported', 'NotFound' and 'Pretty' are logged using 'logDebugN'.---   'Ugly' and 'Prettified' are logged using 'logInfoN'.---   'Unknown' and 'Error' are logged using 'logErrorN'.-statusPrinter :: MonadLogger m => Consumer (Status, FilePath, FilePath) m ()-statusPrinter = Pipes.mapM_ $ \(status, uglyFilePath, _) ->-    logFunction status (T.pack $ uglyFilePath ++ ": " ++ showStatus status)-    where-        logFunction Unknown     = logWarnN-        logFunction Unsupported = logDebugN-        logFunction NotFound    = logDebugN-        logFunction Error       = logWarnN-        logFunction Pretty      = logDebugN-        logFunction Ugly        = logInfoN-        logFunction Prettified  = logInfoN+-- | Prints out the diff of all ugly files to standard output.+diff :: (MonadIO m, MonadLogger m) => Pipe (Status, FilePath, FilePath) (Status, FilePath, FilePath) m ()+diff = select [Ugly] $ \item@(_, uglyFilePath, prettyFilePath) -> do+    (_, stdout, _) <- runProcess "git" ["diff", "--no-index", "--", uglyFilePath, prettyFilePath] +    liftIO $ putStr stdout++    return item++-- | Logs the status of each file using the given function.+printFileStatus :: MonadLogger m => (Status -> Text -> m ()) -> Pipe (Status, FilePath, FilePath) (Status, FilePath, FilePath) m ()+printFileStatus f = Pipes.mapM_ $ \(status, uglyFilePath, _) ->+    f status (T.pack $ uglyFilePath ++ ": " ++ showStatus status)+    where         showStatus NotFound = "not found"         showStatus status   = lower $ show status 
src/Omnifmt/Process.hs view
@@ -15,7 +15,8 @@  module Omnifmt.Process (     -- * Run-    runProcess, runProcess_, runCommand, runCommand_, runCreateProcess, runCreateProcess_,+    runProcess, runProcess_, runTimedProcess, runCommand, runCommand_, runTimedCommand,+    runCreateProcess, runCreateProcess_, ) where  import Control.Monad.Except@@ -39,6 +40,12 @@ runProcess_ :: (MonadError ExitCode m, MonadIO m, MonadLogger m) => FilePath -> [String] -> m String runProcess_ cmd args = runCreateProcess_ (System.proc cmd args) "" +-- | Runs the given executable with the arguments.+--   Returns the exit code, stdout and stderr.+--   The command is wrapped in a `timeout -k N*2 N` call.+runTimedProcess :: (MonadIO m, MonadLogger m) => Int -> FilePath -> [String] -> m (ExitCode, String, String)+runTimedProcess n cmd args = runCreateProcess (System.proc "timeout" $ "-k":show (n * 2):show n:cmd:args) ""+ -- | Runs the given command. --   Returns the exit code, stdout and stderr. runCommand :: (MonadIO m, MonadLogger m) => String -> m (ExitCode, String, String)@@ -48,6 +55,12 @@ --   Depending on the exit code, either logs the stderr and exits fast (128) or returns the stdout. runCommand_ :: (MonadError ExitCode m, MonadIO m, MonadLogger m) => String -> m String runCommand_ cmd = runCreateProcess_ (System.shell cmd) ""++-- | Runs the given command.+--   Returns the exit code, stdout and stderr.+--   The command is wrapped in a `timeout -k N*2 N` call.+runTimedCommand :: (MonadIO m, MonadLogger m) => Int -> String -> m (ExitCode, String, String)+runTimedCommand n cmd = runCreateProcess (System.shell $ unwords ["timeout -k", show $ n * 2, show n, cmd]) ""  -- | Runs the given 'CreateProcess'. --   Returns the exit code, stdout and stderr.