diff --git a/shift.cabal b/shift.cabal index 523d573042d51cde5a7a3d25d73b7c248195f670..95eab874be02e74a75112a89e3e844ef08d5f516 100644 --- a/shift.cabal +++ b/shift.cabal @@ -47,6 +47,7 @@ library , vector , exceptions , semigroups + , cmark-gfm default-language: Haskell2010 executable shift diff --git a/src/Shift/Git.hs b/src/Shift/Git.hs index 1863b6c957da35e7216c32a28c5850148c80d4ac..929a5e76eb893d496069a9e30e40871a58e36114 100644 --- a/src/Shift/Git.hs +++ b/src/Shift/Git.hs @@ -14,8 +14,15 @@ import qualified Data.ByteString.Char8 as BS import Data.Default (def) import Data.Either (rights) import Data.Git + ( tagList, + getObject, + withRepo, + RefName(refNameRaw), + Ref, + Git, + Commit(commitMessage) ) import Data.Git.Ref (HashAlgorithm, fromHex, isHex) -import Data.Git.Storage.Object +import Data.Git.Storage.Object ( Object(ObjCommit) ) import Data.List (sortBy) import Data.Maybe (catMaybes) import Data.Set as S (toList) @@ -30,13 +37,14 @@ import GitHub.Auth (Auth (OAuth)) import Network.HTTP.Client (newManager) import Network.HTTP.Client.TLS (tlsManagerSettings) import Shift.CLI -import Shift.Processing -import Shift.Rendering +import Shift.Processing ( generateReport, parseCommit ) +import Shift.Rendering ( printReport ) import Shift.Types import Shift.Utilities (orError, pairs) -import System.Process +import System.Process ( readCreateProcess, shell ) import Text.Megaparsec (ParseError) import Text.Megaparsec.Error (ParseErrorBundle) +import CMarkGFM (NodeType(DOCUMENT), NodeType(DOCUMENT), nodeToCommonmark, nodeToCommonmark, Node(Node)) parseTag :: RefName -> Either (ParseErrorBundle Text Void) TagRef parseTag ref = case versioning . cs . refNameRaw $ ref of @@ -44,23 +52,31 @@ parseTag ref = case versioning . cs . refNameRaw $ ref of Right v -> Right (TagRef ref v) tempMain :: ShiftOptions -> IO () -tempMain opts = withRepo ".git" $ \repo -> do +tempMain opts = do + node <- renderToNode opts + + liftIO $ TIO.putStr $ nodeToCommonmark [] Nothing node + +renderToNode :: ShiftOptions -> IO Node +renderToNode opts = withRepo ".git" $ \repo -> do tags <- tagList repo let sortedVersions = sortBy (flip compare) . rights $ parseTag <$> toList tags pairedTags = swap <$> pairs sortedVersions - case opts ^. soHostingType of + nodes <- case opts ^. soHostingType of GitHubType -> do state <- initGitHubState - runReaderT - (void $ runStateT (mapM_ (renderDiff repo) pairedTags) state) + fst <$> runReaderT + (runStateT (mapM (renderDiff repo) pairedTags) state) opts GitType -> - runReaderT - (void $ runStateT (mapM_ (renderDiff repo) pairedTags) GitClientState) + fst <$> runReaderT + (runStateT (mapM (renderDiff repo) pairedTags) GitClientState) opts + + pure (Node Nothing DOCUMENT (concat nodes)) where initGitHubState = do manager <- newManager tlsManagerSettings @@ -80,15 +96,13 @@ tempMain opts = withRepo ".git" $ \repo -> do _gcsRepository = cs repositoryName } -renderDiff :: (ClientState s, HashAlgorithm hash) => Git hash -> (TagRef, TagRef) -> GitM s () +renderDiff :: (ClientState s, HashAlgorithm hash) => Git hash -> (TagRef, TagRef) -> GitM s [Node] renderDiff repo (tx, ty) = do - liftIO . TIO.putStrLn . headerOne $ renderRange tx ty - diff <- lookupCommitsDiff repo tx ty case diff of [] -> throwM SEUnableToComputeDiff - diff_ -> printReport (generateReport . rights $ parseCommit <$> diff_) + diff_ -> printReport (tx, ty) (generateReport . rights $ parseCommit <$> diff_) lookupCommitsDiff :: (HashAlgorithm hash) => Git hash -> TagRef -> TagRef -> GitM s [(Ref hash, Commit hash)] lookupCommitsDiff repo x y = do diff --git a/src/Shift/Rendering.hs b/src/Shift/Rendering.hs index 1242ad215ecea749fe8465b42d64ad3c2d2ab5dd..ea1944d852f72ee818e5f50e19d8d17544d8faa9 100644 --- a/src/Shift/Rendering.hs +++ b/src/Shift/Rendering.hs @@ -1,117 +1,157 @@ -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} module Shift.Rendering where +import CMarkGFM as M +import Control.Lens ((^.)) +import Control.Monad.Catch (MonadThrow) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Control.Monad.State (MonadState) +import Control.Monad.Trans.Writer (WriterT (runWriterT), execWriterT, runWriter) +import Control.Monad.Trans.Writer.Lazy (tell) +import Data.Git (Commit, Ref, commitAuthor) import Data.List (sortOn) - -import Control.Lens ((^.)) -import Control.Monad.Catch (MonadThrow) -import Control.Monad.IO.Class (MonadIO, liftIO) -import Control.Monad.State (MonadState) -import Data.Git (Commit, Ref, commitAuthor) -import Data.Monoid ((<>)) -import Data.String.Conversions (cs) -import Data.Text (Text) -import qualified Data.Text as T (take) -import qualified Data.Text.IO as TIO (putStrLn) -import Data.Versions (prettyV) - +import Data.Maybe (catMaybes) +import Data.Monoid ((<>)) +import Data.String.Conversions (cs) +import Data.Text (Text) +import qualified Data.Text as T (take) +import qualified Data.Text.IO as TIO (putStr, putStrLn) +import Data.Versions (prettyV) import Shift.Types -renderRef - :: (MonadIO m, MonadState s m, ClientState s, MonadThrow m) - => Ref hash - -> m Text +renderRef :: + (MonadIO m, MonadState s m, ClientState s, MonadThrow m) => + Ref hash -> + m [Node] renderRef ref = do url <- getRefURL ref let shortRef = T.take 7 . cs . show $ ref + let shortRefNode = leafNode (CODE shortRef) pure $ case url of - Just url_ -> "[[`" <> shortRef <> "`](" <> url_ <> ")]" - Nothing -> "[`" <> shortRef <> "`]" + Just url_ -> [textNode "[", parentNode (LINK url_ shortRef) [shortRefNode], textNode "]"] + Nothing -> [textNode "[", shortRefNode, textNode "]"] -renderConventionalCommit - :: (MonadIO m, MonadState s m, ClientState s, MonadThrow m) - => ConventionalGroup hash - -> m Text +renderConventionalCommit :: + (MonadIO m, MonadState s m, ClientState s, MonadThrow m) => + ConventionalGroup hash -> + m Node renderConventionalCommit (ref, commit, pc) = do renderedRef <- renderRef ref authorText <- renderAuthor commit ref - pure . mconcat $ - [ "- " - , renderedRef - , " " - , bold $ pc ^. ccScope <> ":" - , " " - , pc ^. ccSubject - , authorText - ] - -renderAuthor - :: (MonadIO m, MonadState s m, ClientState s, MonadThrow m) - => Commit hash - -> Ref hash - -> m Text + pure $ + listItemNode + [ paragraphNode $ + renderedRef + <> catMaybes + [ Just $ textNode " ", + Just . bold $ pc ^. ccScope <> ":", + Just $ textNode " ", + Just . textNode $ pc ^. ccSubject, + Just $ textNode " ", + authorText + ] + ] + +renderAuthor :: + (MonadIO m, MonadState s m, ClientState s, MonadThrow m) => + Commit hash -> + Ref hash -> + m (Maybe Node) renderAuthor commit ref = do authorInfo <- getAuthorInfo (commitAuthor commit) ref pure $ case authorInfo of - Just (username, authorUrl) -> " [(" <> username <> ")](" <> authorUrl <> ")" - Nothing -> "" + Just (username, authorUrl) -> Just $ parentNode (LINK authorUrl username) [textNode username] + Nothing -> Nothing -renderMiscCommit - :: (MonadIO m, MonadState s m, ClientState s, MonadThrow m) - => MiscGroup hash - -> m Text +renderMiscCommit :: + (MonadIO m, MonadState s m, ClientState s, MonadThrow m) => + MiscGroup hash -> + m Node renderMiscCommit (ref, commit, MiscCommit subject) = do renderedRef <- renderRef ref authorText <- renderAuthor commit ref - pure $ "- " <> renderedRef <> " " <> subject <> authorText - -renderMergeCommit - :: (MonadIO m, MonadState s m, ClientState s, MonadThrow m) - => MergeGroup hash - -> m Text + pure $ + listItemNode + [ paragraphNode $ + renderedRef + <> catMaybes + [ Just $ textNode subject, + Just $ textNode " ", + authorText + ] + ] + +renderMergeCommit :: + (MonadIO m, MonadState s m, ClientState s, MonadThrow m) => + MergeGroup hash -> + m Node renderMergeCommit (ref, commit, MergeCommit subject) = do renderedRef <- renderRef ref authorText <- renderAuthor commit ref - pure $ "- " <> renderedRef <> " Merge " <> subject <> authorText + pure $ + listItemNode + [ paragraphNode $ + renderedRef + <> catMaybes + [ Just $ textNode $ "Merge " <> subject, + authorText + ] + ] renderRange :: TagRef -> TagRef -> Text -renderRange tx ty = mconcat - [prettyV . _tVersioning $ tx, " to ", prettyV . _tVersioning $ ty] +renderRange tx ty = + mconcat + [prettyV . _tVersioning $ tx, " to ", prettyV . _tVersioning $ ty] -bold :: Text -> Text -bold x = "**" <> x <> "**" +bold :: Text -> Node +bold x = parentNode STRONG [textNode x] indented :: Int -> Text -> Text -indented levels x = foldl (\acc _ -> acc <> " ") "" [1..levels] <> x +indented levels x = foldl (\acc _ -> acc <> " ") "" [1 .. levels] <> x indentedL :: Int -> Text -> Text -indentedL levels x = "\\" <> foldl (\acc _ -> acc <> "-") "" [1..levels] <> x +indentedL levels x = "\\" <> foldl (\acc _ -> acc <> "-") "" [1 .. levels] <> x + +headerOne :: Text -> Node +headerOne t = parentNode (HEADING 1) [textNode t] + +headerTwo :: Text -> Node +headerTwo t = parentNode (HEADING 2) [textNode t] -linePadded :: Text -> Text -linePadded x = "\n" <> x <> "\n" +headerThree :: Text -> Node +headerThree t = parentNode (HEADING 3) [textNode t] -headerOne :: Text -> Text -headerOne = linePadded . (<>) "# " +textNode :: Text -> Node +textNode t = Node Nothing (TEXT t) [] -headerTwo :: Text -> Text -headerTwo = linePadded . (<>) "## " +leafNode :: NodeType -> Node +leafNode n = Node Nothing n [] -headerThree :: Text -> Text -headerThree = linePadded . (<>) "### " +parentNode :: NodeType -> [Node] -> Node +parentNode = Node Nothing + +listItemNode :: [Node] -> Node +listItemNode = parentNode ITEM + +paragraphNode :: [Node] -> Node +paragraphNode = parentNode PARAGRAPH + +printReport :: + (MonadIO m, MonadState s m, ClientState s, MonadThrow m) => + (TagRef, TagRef) -> + ChangeReport hash -> + m [Node] +printReport (rangeStart, rangeEnd) report = execWriterT $ do + tell [headerOne $ renderRange rangeStart rangeEnd] -printReport - :: (MonadIO m, MonadState s m, ClientState s, MonadThrow m) - => ChangeReport hash - -> m () -printReport report = do -- Print conventional commits conventionalSection "New features:" crFeatures conventionalSection "Fixes:" crFixes @@ -129,7 +169,7 @@ printReport report = do textSection "Merge commits:" mergeCommits where conventionalSection label sectionLens = do - let rawCommits = sortOn (\(_,_,pc) -> pc ^. ccScope) (report ^. sectionLens) + let rawCommits = sortOn (\(_, _, pc) -> pc ^. ccScope) (report ^. sectionLens) commits <- mapM renderConventionalCommit rawCommits textSection label commits @@ -137,7 +177,6 @@ printReport report = do textSection label commits = case commits of [] -> pure () _ -> do - liftIO . TIO.putStrLn . headerTwo $ label - - liftIO $ mapM_ (TIO.putStrLn . indented 1) commits + tell [headerTwo label] + tell [parentNode (LIST (ListAttributes BULLET_LIST False 0 PAREN_DELIM)) commits] diff --git a/src/Shift/Types.hs b/src/Shift/Types.hs index c31b0aaccd038677ea9c74660dde0393b932f624..46e9252e5f773c59d93af369cc2a03beea16b997 100644 --- a/src/Shift/Types.hs +++ b/src/Shift/Types.hs @@ -43,7 +43,6 @@ import GitHub (URL(URL)) import GitHub (getUrl) import GitHub (getUrl) import Data.Aeson (FromJSON) -import Data.Aeson (FromJSON) class ClientState s where getRefURL