packages feed

git-fmt 0.2.0.2 → 0.2.1.0

raw patch · 8 files changed

+109/−204 lines, 8 files

Files

− CHANGELOG.md
@@ -1,64 +0,0 @@-## Changelog--#### Upcoming--#### 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,79 +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)--(Side note: the formatting component of this project will eventually be split out and named omnifmt.)--`git-fmt` adds a custom command to Git that automatically formats code by using external-    pretty-printers.-The idea was taken from [gofmt](https://golang.org/cmd/gofmt/), just with a bit of expansion to more-    languages.--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).-Alternatively you may download a pre-compiled binary of the-    [latest release](https://github.com/hjwylde/git-fmt/releases/latest).--**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--`git-fmt` itself just provides a way to select files in the git repository and pipe them through a-    pretty-printer.-It also provides a dry-run mode that will show you which files need prettifying.--#### Configuration--Configuration is done through an `.omnifmt.yaml` file in the repository's top-level directory.--To show how, here's an example `.omnifmt.yaml`:-```yaml-haskell:-    extensions: ["hs", "lhs"]-    command:    "stylish-haskell {{input}} > {{output}}"--javascript:-    extensions: ["js"]-    command:    "js-beautify {{input}} > {{output}}"--json:-    extensions: ["json"]-    command:    "json_pp < {{input}} > {{output}}"-```--That's all it takes!-Each command must declare how to read the input file and how to write to the temporary file.-The temporary file is used to compare whether the original input was pretty or ugly before writing-    to the original.--Extensions is pretty self explanatory, but if you use the same extension more than once then-    precedence goes to the first defined pretty-printer.-
app/Main.hs view
@@ -18,14 +18,14 @@  import Control.Monad import Control.Monad.Logger-import Control.Monad.Parallel (MonadParallel(..))+import Control.Monad.Parallel (MonadParallel (..)) -import              Data.List.Extra     (dropEnd, lower)-import              Data.Text           (Text)-import qualified    Data.Text           as T-import qualified    Data.Text.Encoding  as T-import qualified    Data.Text.IO        as T-import              Data.Time           (getZonedTime, formatTime, defaultTimeLocale)+import           Data.List.Extra    (dropEnd, lower)+import           Data.Text          (Text)+import qualified Data.Text          as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.IO       as T+import           Data.Time          (defaultTimeLocale, formatTime, getZonedTime)  import Git.Fmt import Git.Fmt.Options.Applicative.Parser
git-fmt.cabal view
@@ -1,5 +1,5 @@ name:           git-fmt-version:        0.2.0.2+version:        0.2.1.0  author:         Henry J. Wylde maintainer:     public@hjwylde.com@@ -17,8 +17,6 @@ category:       Development build-type:     Simple -extra-source-files: CHANGELOG.md README.md- source-repository head     type:       git     location:   git@github.com:hjwylde/git-fmt@@ -51,6 +49,7 @@         Git.Fmt.Process,         Git.Fmt.Version,         Paths_git_fmt,+        System.Directory.Extra'         System.IO.Extra'      default-language: Haskell2010
src/Git/Fmt.hs view
@@ -29,23 +29,22 @@ import qualified    Control.Monad.Parallel  as Parallel import              Control.Monad.Reader -import              Data.List.Extra     (chunksOf, linesBy, lower, nub, replace)+import              Data.List.Extra     (chunksOf, linesBy, lower, nub) import qualified    Data.Text           as T import              Data.Yaml           (prettyPrintParseException) import              Data.Yaml.Include   (decodeFileEither) -import Git.Fmt.Config   as Config+import Git.Fmt.Config as Config import Git.Fmt.Process-import GHC.IO.Exception (IOErrorType(..))  import Prelude hiding (read) -import System.Directory.Extra hiding (withCurrentDirectory)+import System.Directory.Extra   hiding (withCurrentDirectory)+import System.Directory.Extra' import System.Exit import System.FilePath-import System.IO.Error-import System.IO.Extra' import System.IO.Temp+import System.IO.Extra'  -- | Options. data Options = Options {@@ -66,18 +65,17 @@  -- | Builds the files according to the options. handle :: (MonadIO m, MonadLogger m, MonadMask m, MonadParallel m) => Options -> m ()-handle options = do-    gitDir      <- findGitDirectory-    filePaths   <- fmap (nub . concat) $ paths gitDir >>= mapM+handle options = findTopLevelGitDirectory >>= \dir -> withCurrentDirectory (init dir) $ do+    filePaths <- fmap (nub . concat) $ paths >>= mapM         (\path -> ifM (liftIO $ doesDirectoryExist path)             (liftIO $ listFilesRecursive path)             (return [path])             ) -    unlessM (liftIO . doesFileExist $ gitDir </> Config.fileName) $ panic (gitDir </> Config.fileName ++ ": not found")+    unlessM (liftIO $ doesFileExist Config.defaultFileName) $ panic (Config.defaultFileName ++ ": not found") -    config <- liftIO (decodeFileEither $ gitDir </> Config.fileName) >>= \ethr -> case ethr of-        Left error      -> panic $ gitDir </> Config.fileName ++ ": error\n" ++ prettyPrintParseException error+    config <- liftIO (decodeFileEither Config.defaultFileName) >>= \ethr -> case ethr of+        Left error      -> panic $ Config.defaultFileName ++ ": error\n" ++ prettyPrintParseException error         Right config    -> return config      let supportedFilePaths = filter (supported config . T.pack . drop 1 . lower . takeExtension) filePaths@@ -87,8 +85,8 @@             (fmt options filePath (tmpDir </> filePath))             ($(logWarn) $ T.pack (filePath ++ ": not found"))     where-        paths gitDir-            | null (argPaths options)   = linesBy (== '\0') <$> runProcess_ "git" ["ls-files", "-z", gitDir]+        paths+            | null (argPaths options)   = linesBy (== '\0') <$> runProcess_ "git" ["ls-files", "-z"]             | optNull options           = return $ concatMap (linesBy (== '\0')) (argPaths options)             | otherwise                 = return $ argPaths options         nChunks n xs = chunksOf (maximum [1, length xs `div` n]) xs@@ -120,29 +118,32 @@ normal filePath tmpFilePath = do     $(logInfo) $ T.pack (filePath ++ ": prettified") -    -- Try move the file, but if it's across a filesystem boundary then we may need to copy instead-    liftIO $ renameFile tmpFilePath filePath `catchIOError` \e ->-        if ioeGetErrorType e == UnsupportedOperation-            then copyFile tmpFilePath filePath >> removeFile tmpFilePath-            else ioError e+    liftIO $ renameFile tmpFilePath filePath  dryRun :: (MonadIO m, MonadLogger m) => FilePath -> FilePath -> m () dryRun filePath _ = $(logInfo) $ T.pack (filePath ++ ": ugly") -findGitDirectory :: (MonadIO m, MonadLogger m) => m String-findGitDirectory = do+findTopLevelGitDirectory :: (MonadIO m, MonadLogger m) => m String+findTopLevelGitDirectory = do     (exitCode, stdout, _) <- runProcess "git" ["rev-parse", "--show-toplevel"]      if exitCode == ExitSuccess-        then return $ init stdout+        then return stdout         else panic ".git/: not found"  runProgram :: (MonadIO m, MonadLogger m) => Program -> FilePath -> FilePath -> m (ExitCode, String, String) runProgram program inputFilePath tmpFilePath = do     liftIO $ createDirectoryIfMissing True (takeDirectory tmpFilePath) -    runCommand $ foldr (uncurry replace) (T.unpack $ command program) [-        ("{{input}}", inputFilePath),-        ("{{output}}", tmpFilePath)+    runCommand . T.unpack $ substitute (T.concat [command program, inputSuffix, outputSuffix]) [+        (inputVariableName, T.pack $ '"':inputFilePath ++ "\""),+        (outputVariableName, T.pack $ '"':tmpFilePath ++ "\"")         ]+    where+        inputSuffix+            | usesInputVariable (command program)   = T.empty+            | otherwise                             = T.pack " < " `T.append` inputVariableName+        outputSuffix+            | usesOutputVariable (command program)  = T.empty+            | otherwise                             = T.pack " > " `T.append` outputVariableName 
src/Git/Fmt/Config.hs view
@@ -15,21 +15,22 @@ module Git.Fmt.Config (     -- * Config     Config(..),-    emptyConfig,+    emptyConfig, programFor, unsafeProgramFor, supported,      -- * Program     Program(..),-    emptyProgram, programFor, unsafeProgramFor, supported,+    emptyProgram, substitute, usesInputVariable, usesOutputVariable, inputVariableName,+    outputVariableName,      -- * Helper functions-    fileName,+    defaultFileName, ) where  import Data.Aeson.Types-import Data.HashMap.Lazy    (toList)-import Data.List            (find)-import Data.Maybe           (fromJust, isJust)-import Data.Text            (Text)+import Data.HashMap.Lazy (toList)+import Data.List.Extra   (find)+import Data.Maybe        (fromJust, isJust)+import Data.Text         (Text, isInfixOf, replace)  -- | A list of programs. data Config = Config {@@ -47,12 +48,24 @@ emptyConfig :: Config emptyConfig = Config [] +-- | Attempts to find a program for the given extension.+programFor :: Config -> Text -> Maybe Program+programFor config ext = find (\program -> ext `elem` extensions program) (programs config)++-- | Finds a program for the given extension or errors.+unsafeProgramFor :: Config -> Text -> Program+unsafeProgramFor config = fromJust . programFor config++-- | Checks if the given extension is supported (e.g., there is a program for it).+supported :: Config -> Text -> Bool+supported config = isJust . programFor config+ -- | A program has a semantic name, associated extensions and command. --   The command string may contain variables to be replaced by surrounding them with '{{..}}'. data Program = Program {-    name        :: Text,-    extensions  :: [Text],-    command     :: Text+    name       :: Text,+    extensions :: [Text],+    command    :: Text     }     deriving (Eq, Show) @@ -64,19 +77,27 @@ emptyProgram :: Program emptyProgram = Program "" [] "false" --- | Attempts to find a program for the given extension.-programFor :: Config -> Text -> Maybe Program-programFor config ext = find (\program -> ext `elem` extensions program) (programs config)+-- | Substitutes the mapping throughout the command.+substitute :: Text -> [(Text, Text)] -> Text+substitute = foldr (uncurry replace) --- | Finds a program for the given extension or errors.-unsafeProgramFor :: Config -> Text -> Program-unsafeProgramFor config = fromJust . programFor config+-- | Checks whether the text uses the input variable.+usesInputVariable :: Text -> Bool+usesInputVariable = isInfixOf inputVariableName --- | Checks if the given extension is supported (e.g., there is a program for it).-supported :: Config -> Text -> Bool-supported config = isJust . programFor config+-- | Checks whether the text uses the output variable.+usesOutputVariable :: Text -> Bool+usesOutputVariable = isInfixOf outputVariableName --- | The file name of the default config file.-fileName :: String-fileName = ".omnifmt.yaml"+-- | The input variable name.+inputVariableName :: Text+inputVariableName = "{{input}}"++-- | The output variable name.+outputVariableName :: Text+outputVariableName = "{{output}}"++-- | The file name of the default config.+defaultFileName :: FilePath+defaultFileName = ".omnifmt.yaml" 
src/Git/Fmt/Process.hs view
@@ -22,10 +22,10 @@  import Data.Text hiding (unwords) -import              System.Exit-import              System.IO.Extra'-import              System.Process (CreateProcess, CmdSpec(..))-import qualified    System.Process as System+import           System.Exit+import           System.IO.Extra'+import           System.Process   (CmdSpec (..), CreateProcess)+import qualified System.Process   as System  -- | Runs the given executable with the arguments. --   Returns the exit code, stdout and stderr.
+ src/System/Directory/Extra'.hs view
@@ -0,0 +1,27 @@++{-|+Module      : System.Directory.Extra'+Description : Extra extra directory utilities.++Copyright   : (c) Henry J. Wylde, 2015+License     : BSD3+Maintainer  : public@hjwylde.com++Extra extra directory utilities.+-}++module System.Directory.Extra' (+    -- * Changing directories+    withCurrentDirectory,+) where++import Control.Monad.Catch    (MonadMask, bracket)+import Control.Monad.IO.Class++import System.Directory++-- | @withCurrentDirectory dir action@ performs @action@ with the current directory set to @dir@.+--   The current directory is reset back to what it was afterwards.+withCurrentDirectory :: (MonadIO m, MonadMask m) => FilePath -> m a -> m a+withCurrentDirectory dir action = bracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $ \_ -> liftIO (setCurrentDirectory dir) >> action+