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 +0/−150
- README.md +0/−110
- app/Git/Fmt/Options.hs +3/−2
- app/Main.hs +20/−14
- git-fmt.cabal +1/−3
- src/Omnifmt/Config.hs +4/−2
- src/Omnifmt/Pipes.hs +25/−32
- src/Omnifmt/Process.hs +14/−1
− 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--[](http://www.repostatus.org/#wip)-[](https://travis-ci.org/hjwylde/git-fmt)-[](https://github.com/hjwylde/git-fmt/releases/latest)-[](http://stackage.org/lts/package/git-fmt)-[](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.