From b9e00595f4ea7d37bbedd30053c0d67be270128b Mon Sep 17 00:00:00 2001 From: Mark Wales Date: Wed, 6 Mar 2019 13:10:37 +0000 Subject: [PATCH 1/6] chore (stack.yaml): added cabal support --- README.md | 21 ++++++++++++++++++++- stack.yaml | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bbeeb7..b391c0e 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,31 @@ Write consistent git commit messages -## Installation +## Install + +[Binaries for Mac and Linux are available](https://github.com/smallhadroncollider/cmt/releases). Add the binary to a directory in your path (such as `/usr/local/bin`). + +### Cabal + +**Requirements**: [Cabal](https://www.haskell.org/cabal/) + +```bash +cabal install cmt +``` + +Make sure you run `cabal update` if you haven't run it recently. + +### Building + +**Requirements**: [Stack](https://docs.haskellstack.org/en/stable/README/) + +The following command will build cmt and then install it in `~/.local/bin`: ```bash stack build && stack install ``` + ## Usage Add a `.cmt` file to your project directory. diff --git a/stack.yaml b/stack.yaml index 281c377..e415d04 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,3 +1,4 @@ resolver: lts-13.8 +pvp-bounds: both packages: - . From 9189f432d30e338169294f52fe064a9a9c253135 Mon Sep 17 00:00:00 2001 From: Mark Wales Date: Wed, 6 Mar 2019 14:21:01 +0000 Subject: [PATCH 2/6] fix (Cmt.Parser.Config): smooshed Literals Updated parser to combine consecutive `Literal`s --- src/Cmt/Parser/Config.hs | 16 ++++++++++++++-- src/Cmt/Types/Config.hs | 4 +--- test/Cmt/Parser/ConfigTest.hs | 13 ++++--------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/Cmt/Parser/Config.hs b/src/Cmt/Parser/Config.hs index 4a79c92..d7c66f1 100644 --- a/src/Cmt/Parser/Config.hs +++ b/src/Cmt/Parser/Config.hs @@ -37,14 +37,26 @@ valid :: [Name] -> Parser Text valid names = choice $ "*" : (string <$> names) -- format parts +merge :: [FormatPart] -> FormatPart -> [FormatPart] +merge ps (Literal str) = maybe [Literal str] merge' (fromNullable ps) + where + merge' ps' = + case last ps' of + Literal prev -> init ps' <> [Literal (prev <> str)] + _ -> ps <> [Literal str] +merge ps p = ps <> [p] + +smoosh :: [FormatPart] -> [FormatPart] +smoosh = foldl' merge [] + formatNamedP :: [Name] -> Parser FormatPart formatNamedP names = Named <$> (string "${" *> valid names <* char '}') formatLiteralP :: Parser FormatPart formatLiteralP = Literal <$> (singleton <$> anyChar) -formatP :: [Name] -> Parser Format -formatP names = stripComments $ many1 (formatNamedP names <|> formatLiteralP) +formatP :: [Name] -> Parser [FormatPart] +formatP names = smoosh <$> stripComments (many1 (formatNamedP names <|> formatLiteralP)) -- input parts lineP :: Parser PartType diff --git a/src/Cmt/Types/Config.hs b/src/Cmt/Types/Config.hs index 091ab4d..ffc78bd 100644 --- a/src/Cmt/Types/Config.hs +++ b/src/Cmt/Types/Config.hs @@ -13,8 +13,6 @@ data FormatPart | Literal Text deriving (Show, Eq) -type Format = [FormatPart] - data PartType = Options [Text] | Line @@ -28,7 +26,7 @@ data Part = data Config = Config [Part] - Format + [FormatPart] deriving (Show, Eq) partName :: Part -> Name diff --git a/test/Cmt/Parser/ConfigTest.hs b/test/Cmt/Parser/ConfigTest.hs index 6bf82dc..7f21dd2 100644 --- a/test/Cmt/Parser/ConfigTest.hs +++ b/test/Cmt/Parser/ConfigTest.hs @@ -18,8 +18,7 @@ basic :: Text basic = decodeUtf8 $(embedFile "test/data/.cmt") basicConfig :: Config -basicConfig = - Config [Part "Week" Line] [Named "Week", Literal ":", Literal " ", Named "*", Literal "\n"] +basicConfig = Config [Part "Week" Line] [Named "Week", Literal ": ", Named "*", Literal "\n"] angular :: Text angular = decodeUtf8 $(embedFile "test/data/.cmt-angular") @@ -36,15 +35,11 @@ angularConfig = , Part "Body" Lines ] [ Named "Type" - , Literal " " - , Literal "(" + , Literal " (" , Named "Scope" - , Literal ")" - , Literal ":" - , Literal " " + , Literal "): " , Named "Short Message" - , Literal "\n" - , Literal "\n" + , Literal "\n\n" , Named "Body" , Literal "\n" ] From 2b33b0112b1811b66a649910886ca395f1ddf133 Mon Sep 17 00:00:00 2001 From: Mark Wales Date: Wed, 6 Mar 2019 12:24:01 +0000 Subject: [PATCH 3/6] fix (Cmt.IO.Git): added error output --- roadmap.md | 7 +++---- src/Cmt/IO/Git.hs | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/roadmap.md b/roadmap.md index 94da554..ede6481 100644 --- a/roadmap.md +++ b/roadmap.md @@ -1,8 +1,5 @@ ## Bugs -- pre-commit errors don't get displayed at all - > Probably need to show stderr in some cases? -- Extra new-line at the end of the output ## Features @@ -14,7 +11,7 @@ - Make parts optional > @? and !@? operators? - Option to show files that have changed - > Useful for things like ${Scope} - autocomplete maybe? + > Useful for things like ${Scope} - autocomplete maybe? `git diff --name-only` ## Doing @@ -33,3 +30,5 @@ - Should search up directories to find .cmt - Should support ~/.cmt for global option - Add comments to .cmt +- pre-commit errors don't get displayed at all + > Probably need to show stderr in some cases? diff --git a/src/Cmt/IO/Git.hs b/src/Cmt/IO/Git.hs index 5539a66..de75c5b 100644 --- a/src/Cmt/IO/Git.hs +++ b/src/Cmt/IO/Git.hs @@ -12,5 +12,5 @@ import System.Process (readCreateProcessWithExitCode, shell) commit :: Text -> IO Text commit message = do let msg = "git commit -m '" <> unpack message <> "'" - (_, out, _) <- readCreateProcessWithExitCode (shell msg) "" - pure $ pack out + (_, out, err) <- readCreateProcessWithExitCode (shell msg) "" + pure $ unlines (pack <$> filter (not . null) [out, err]) From 1005aa93415775b871d85325b171b321fb493b00 Mon Sep 17 00:00:00 2001 From: Mark Wales Date: Wed, 6 Mar 2019 12:24:01 +0000 Subject: [PATCH 4/6] style (Cmt.IO.Input): shorts lists of options appear on one line --- package.yaml | 1 + roadmap.md | 2 +- src/Cmt/IO/Input.hs | 36 +++++++++++++++++++++++++++--------- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/package.yaml b/package.yaml index 43ec889..8a39f54 100644 --- a/package.yaml +++ b/package.yaml @@ -29,6 +29,7 @@ library: - directory - filepath - process + - terminal-size executables: cmt: diff --git a/roadmap.md b/roadmap.md index ede6481..87efbce 100644 --- a/roadmap.md +++ b/roadmap.md @@ -3,7 +3,6 @@ ## Features -- Show options in flat list if short? - Store previous commit info if it fails > Store if commit fails. `cmt --prev` option? - List option? @@ -32,3 +31,4 @@ - Add comments to .cmt - pre-commit errors don't get displayed at all > Probably need to show stderr in some cases? +- Show options in flat list if short? diff --git a/src/Cmt/IO/Input.hs b/src/Cmt/IO/Input.hs index 89044b3..3cdc20e 100644 --- a/src/Cmt/IO/Input.hs +++ b/src/Cmt/IO/Input.hs @@ -7,35 +7,53 @@ module Cmt.IO.Input import ClassyPrelude +import System.Console.Terminal.Size (size, width) + import Cmt.Types.Config -listItem :: (Int, Text) -> IO () -listItem (n, o) = putStrLn $ tshow n <> ") " <> o +prompt :: Text -> IO Text +prompt s = do + putStr $ s <> " " + hFlush stdout + getLine + +getWidth :: IO Int +getWidth = maybe 0 width <$> size + +putName :: Text -> IO () +putName name = putStrLn $ "\n" <> name <> ":" + +listItem :: (Int, Text) -> Text +listItem (n, o) = tshow n <> ") " <> o multiLine :: [Text] -> IO [Text] multiLine input = do - value <- getLine + value <- prompt ">" if null value then pure input else multiLine $ input ++ [value] output :: Part -> IO (Name, Text) output (Part name Line) = do - putStrLn $ name <> ":" - val <- getLine + putName name + val <- prompt ">" if null val then output (Part name Line) else pure (name, val) output (Part name (Options opts)) = do + putName name let opts' = zip [1 ..] opts - putStrLn $ name <> ":" - sequence_ $ listItem <$> opts' - chosen <- getLine + let long = intercalate " " $ listItem <$> opts' + maxLength <- getWidth + if length long < maxLength + then putStrLn long + else sequence_ $ putStrLn . listItem <$> opts' + chosen <- prompt ">" case find ((== chosen) . tshow . fst) opts' of Nothing -> output (Part name (Options opts)) Just (_, o) -> pure (name, o) output (Part name Lines) = do - putStrLn $ name <> ":" + putName name val <- unlines <$> multiLine [] pure (name, val) From ec0adae16ac548237da751e81a04548f692f3425 Mon Sep 17 00:00:00 2001 From: Mark Wales Date: Thu, 7 Mar 2019 15:48:46 +0000 Subject: [PATCH 5/6] feat (src/Cmt/Parser/Options.hs, src/Cmt/IO/Input.hs, src/Cmt/IO/Git.hs): added staged files input option --- .cmt | 2 +- README.md | 9 +++--- roadmap.md | 5 +-- src/Cmt/IO/Git.hs | 7 +++++ src/Cmt/IO/Input.hs | 57 ++++++++++++++++++++-------------- src/Cmt/Parser/Attoparsec.hs | 12 +++++++ src/Cmt/Parser/Config.hs | 9 +++--- src/Cmt/Parser/Options.hs | 24 ++++++++++++++ src/Cmt/Types/Config.hs | 1 + test/Cmt/Parser/ConfigTest.hs | 2 +- test/Cmt/Parser/OptionsTest.hs | 34 ++++++++++++++++++++ test/data/.cmt-angular | 2 +- test/data/.cmt-comments | 2 +- 13 files changed, 129 insertions(+), 37 deletions(-) create mode 100644 src/Cmt/Parser/Attoparsec.hs create mode 100644 src/Cmt/Parser/Options.hs create mode 100644 test/Cmt/Parser/OptionsTest.hs diff --git a/.cmt b/.cmt index b125dcd..d7e0cd2 100644 --- a/.cmt +++ b/.cmt @@ -8,7 +8,7 @@ "test", "chore" ] - "Scope" = @ + "Scope" = % "Body" = !@ } diff --git a/README.md b/README.md index b391c0e..ec87c4d 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,9 @@ For example, the [AngularJS Commit Message Guidelines](https://gist.github.com/s "test", "chore" ] - "Scope" = @ # Allows a single line of input - "Subject" = @ - "Body" = !@ # Allows multi-line input + "Scope" = % # Select from a list of staged files + "Subject" = @ # Single line input + "Body" = !@ # Multi-line input "Footer" = !@ } @@ -83,6 +83,7 @@ These are at the top of the `.cmt` file and surrounded by opening and closing cu - `@`: single line input - `!@`: multi line input +- `%`: select from a list of staged files - `["option 1", "option 2"]`: list of options #### Output Format @@ -97,7 +98,7 @@ For example: # Input parts # * input not needed, as comes from command-line { - "Scope" = @ + "Scope" = % } # Scope from input and * from command-line diff --git a/roadmap.md b/roadmap.md index 87efbce..785505b 100644 --- a/roadmap.md +++ b/roadmap.md @@ -1,5 +1,6 @@ ## Bugs +- Multi-line is always optional ## Features @@ -9,8 +10,6 @@ > Automatically adds a hyphen to each entry? - Make parts optional > @? and !@? operators? -- Option to show files that have changed - > Useful for things like ${Scope} - autocomplete maybe? `git diff --name-only` ## Doing @@ -32,3 +31,5 @@ - pre-commit errors don't get displayed at all > Probably need to show stderr in some cases? - Show options in flat list if short? +- Option to show files that have changed + > Useful for things like ${Scope} - autocomplete maybe? `git diff --name-only` diff --git a/src/Cmt/IO/Git.hs b/src/Cmt/IO/Git.hs index de75c5b..fb38b48 100644 --- a/src/Cmt/IO/Git.hs +++ b/src/Cmt/IO/Git.hs @@ -3,6 +3,7 @@ module Cmt.IO.Git ( commit + , changed ) where import ClassyPrelude @@ -14,3 +15,9 @@ commit message = do let msg = "git commit -m '" <> unpack message <> "'" (_, out, err) <- readCreateProcessWithExitCode (shell msg) "" pure $ unlines (pack <$> filter (not . null) [out, err]) + +changed :: IO [Text] +changed = do + let msg = "git diff --name-only --cached" + (_, out, _) <- readCreateProcessWithExitCode (shell msg) "" + pure . lines $ pack out diff --git a/src/Cmt/IO/Input.hs b/src/Cmt/IO/Input.hs index 3cdc20e..0d81ae4 100644 --- a/src/Cmt/IO/Input.hs +++ b/src/Cmt/IO/Input.hs @@ -9,6 +9,8 @@ import ClassyPrelude import System.Console.Terminal.Size (size, width) +import Cmt.IO.Git (changed) +import Cmt.Parser.Options (parse) import Cmt.Types.Config prompt :: Text -> IO Text @@ -26,36 +28,45 @@ putName name = putStrLn $ "\n" <> name <> ":" listItem :: (Int, Text) -> Text listItem (n, o) = tshow n <> ") " <> o +displayOptions :: [(Int, Text)] -> IO () +displayOptions opts = do + let long = intercalate " " $ listItem <$> opts + maxLength <- getWidth + if length long < maxLength + then putStrLn long + else sequence_ $ putStrLn . listItem <$> opts + +choice :: [(Int, Text)] -> Int -> Maybe Text +choice opts chosen = snd <$> find ((== chosen) . fst) opts + +options :: [Text] -> IO Text +options opts = do + let opts' = zip [1 ..] opts + displayOptions opts' + chosen <- parse <$> prompt ">" + case chosen of + Nothing -> options opts + Just chosen' -> pure . intercalate ", " . catMaybes $ choice opts' <$> chosen' + multiLine :: [Text] -> IO [Text] multiLine input = do value <- prompt ">" - if null value + if null value && not (null input) then pure input else multiLine $ input ++ [value] +line :: IO Text +line = do + value <- prompt ">" + if null value + then line + else pure value + output :: Part -> IO (Name, Text) -output (Part name Line) = do - putName name - val <- prompt ">" - if null val - then output (Part name Line) - else pure (name, val) -output (Part name (Options opts)) = do - putName name - let opts' = zip [1 ..] opts - let long = intercalate " " $ listItem <$> opts' - maxLength <- getWidth - if length long < maxLength - then putStrLn long - else sequence_ $ putStrLn . listItem <$> opts' - chosen <- prompt ">" - case find ((== chosen) . tshow . fst) opts' of - Nothing -> output (Part name (Options opts)) - Just (_, o) -> pure (name, o) -output (Part name Lines) = do - putName name - val <- unlines <$> multiLine [] - pure (name, val) +output (Part name Line) = putName name >> (,) name <$> line +output (Part name (Options opts)) = putName name >> (,) name <$> options opts +output (Part name Lines) = putName name >> (,) name <$> (unlines <$> multiLine []) +output (Part name Changed) = putName name >> (,) name <$> (options =<< changed) loop :: Config -> IO [(Name, Text)] loop (Config parts _) = sequence $ output <$> parts diff --git a/src/Cmt/Parser/Attoparsec.hs b/src/Cmt/Parser/Attoparsec.hs new file mode 100644 index 0000000..d59ef33 --- /dev/null +++ b/src/Cmt/Parser/Attoparsec.hs @@ -0,0 +1,12 @@ +{-# LANGUAGE NoImplicitPrelude #-} + +module Cmt.Parser.Attoparsec + ( lexeme + ) where + +import ClassyPrelude + +import Data.Attoparsec.Text + +lexeme :: Parser a -> Parser a +lexeme p = skipSpace *> p <* skipSpace diff --git a/src/Cmt/Parser/Config.hs b/src/Cmt/Parser/Config.hs index d7c66f1..61d7c50 100644 --- a/src/Cmt/Parser/Config.hs +++ b/src/Cmt/Parser/Config.hs @@ -9,12 +9,10 @@ import ClassyPrelude import Data.Attoparsec.Text +import Cmt.Parser.Attoparsec import Cmt.Types.Config -- useful bits -lexeme :: Parser a -> Parser a -lexeme p = skipSpace *> p <* skipSpace - tchar :: Char -> Parser Text tchar ch = singleton <$> char ch @@ -59,6 +57,9 @@ formatP :: [Name] -> Parser [FormatPart] formatP names = smoosh <$> stripComments (many1 (formatNamedP names <|> formatLiteralP)) -- input parts +changedP :: Parser PartType +changedP = char '%' $> Changed + lineP :: Parser PartType lineP = char '@' $> Line @@ -77,7 +78,7 @@ nameP = char '"' *> word <* char '"' <* lexeme (char '=') -- part partP :: Parser Part -partP = stripComments $ Part <$> nameP <*> (listP <|> lineP <|> linesP) +partP = stripComments $ Part <$> nameP <*> (listP <|> lineP <|> linesP <|> changedP) partsP :: Parser [Part] partsP = stripComments $ stripComments (char '{') *> many' partP <* stripComments (char '}') diff --git a/src/Cmt/Parser/Options.hs b/src/Cmt/Parser/Options.hs new file mode 100644 index 0000000..482c2cf --- /dev/null +++ b/src/Cmt/Parser/Options.hs @@ -0,0 +1,24 @@ +{-# LANGUAGE NoImplicitPrelude #-} + +module Cmt.Parser.Options + ( parse + ) where + +import ClassyPrelude + +import Data.Attoparsec.Text (Parser, char, decimal, many', many1, parseOnly) + +import Cmt.Parser.Attoparsec (lexeme) + +commaP :: Parser () +commaP = void $ many' (lexeme $ char ',') + +optionsP :: Parser [Int] +optionsP = lexeme . many1 $ commaP *> decimal <* commaP + +-- run parser +parse :: Text -> Maybe [Int] +parse options = + case parseOnly optionsP options of + Right c -> Just c + Left _ -> Nothing diff --git a/src/Cmt/Types/Config.hs b/src/Cmt/Types/Config.hs index ffc78bd..af01cc2 100644 --- a/src/Cmt/Types/Config.hs +++ b/src/Cmt/Types/Config.hs @@ -17,6 +17,7 @@ data PartType = Options [Text] | Line | Lines + | Changed deriving (Show, Eq) data Part = diff --git a/test/Cmt/Parser/ConfigTest.hs b/test/Cmt/Parser/ConfigTest.hs index 7f21dd2..f852f1a 100644 --- a/test/Cmt/Parser/ConfigTest.hs +++ b/test/Cmt/Parser/ConfigTest.hs @@ -30,7 +30,7 @@ angularConfig :: Config angularConfig = Config [ Part "Type" (Options ["feat", "fix", "docs", "style", "refactor", "test", "chore"]) - , Part "Scope" Line + , Part "Scope" Changed , Part "Short Message" Line , Part "Body" Lines ] diff --git a/test/Cmt/Parser/OptionsTest.hs b/test/Cmt/Parser/OptionsTest.hs new file mode 100644 index 0000000..76a236d --- /dev/null +++ b/test/Cmt/Parser/OptionsTest.hs @@ -0,0 +1,34 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} + +module Cmt.Parser.OptionsTest where + +import ClassyPrelude + +import Test.Tasty +import Test.Tasty.HUnit + +import Cmt.Parser.Options (parse) + +-- import Test.Tasty.HUnit +test_config :: TestTree +test_config = + testGroup + "Cmt.Parser.Options" + [ testCase + "basic" + (assertEqual "Gives back correct format" (Just [1, 2, 3]) (parse "1, 2, 3")) + , testCase "no spaces" (assertEqual "Gives back correct format" (Just [1, 3]) (parse "1,3")) + , testCase + "missing number" + (assertEqual "Gives back correct format" (Just [12, 3]) (parse "12, , 3")) + , testCase + "starting comma" + (assertEqual "Gives back correct format" (Just [12, 3]) (parse ",12, 3")) + , testCase + "mess" + (assertEqual + "Gives back correct format" + (Just [12, 4, 3]) + (parse ",, , 12,,4 , 3, , ")) + ] diff --git a/test/data/.cmt-angular b/test/data/.cmt-angular index 321a1f6..0644a71 100644 --- a/test/data/.cmt-angular +++ b/test/data/.cmt-angular @@ -8,7 +8,7 @@ "test", "chore" ] - "Scope" = @ + "Scope" = % "Short Message" = @ "Body" = !@ } diff --git a/test/data/.cmt-comments b/test/data/.cmt-comments index ba5e40f..e843e7b 100644 --- a/test/data/.cmt-comments +++ b/test/data/.cmt-comments @@ -16,7 +16,7 @@ "chore" ] # A comment # The scope - "Scope" = @ # Another comment + "Scope" = % # Another comment "Short Message" = @ # All the comments all the time "Body" = !@ # So many comments! } # Another comment From d1031278519ad6e9811ff1c7d7676356a92e3b70 Mon Sep 17 00:00:00 2001 From: Mark Wales Date: Thu, 7 Mar 2019 15:49:43 +0000 Subject: [PATCH 6/6] chore (package.yaml): version bump --- package.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.yaml b/package.yaml index 8a39f54..852bc53 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: cmt -version: 0.2.0.0 +version: 0.2.1.0 github: "smallhadroncollider/cmt" license: BSD3 author: "Small Hadron Collider / Mark Wales"