{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} module Main (main) where import Control.Applicative ((<$>), (<*>)) import Control.Arrow (first, second, (&&&)) import qualified Data.ByteString.Char8 as BS8 import Data.List (foldl') import qualified Data.Map as Map import qualified Data.Maybe as Maybe import Data.Monoid ((<>)) import Data.String (fromString) import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Vector as V import qualified GitHub import Network.HTTP.Client (newManager) import Network.HTTP.Client.TLS (tlsManagerSettings) import System.Environment (getArgs, lookupEnv) -- import Text.Groom (groom) import Requests type ChangeLog = [(Text, [Text], [Text])] formatChangeLog :: Text -> ChangeLog -> Text formatChangeLog name = (<> "\n") . foldl' (<>) ("# Changelog for " <> name) . map formatMilestone where formatMilestone (milestone, issues, pulls) = "\n\n## " <> milestone <> closedIssues issues <> mergedPrs pulls closedIssues [] = "" closedIssues issues = foldl' (<>) "\n\n### Closed issues:\n" . itemise $ issues mergedPrs [] = "" mergedPrs pulls = foldl' (<>) "\n\n### Merged PRs:\n" . itemise $ pulls itemise = map ("\n- " <>) data ChangeLogItemKind = PullRequest | Issue deriving Show data ChangeLogItem = ChangeLogItem { clMilestone :: Text , clTitle :: Text , clNumber :: Int } formatChangeLogItem :: GitHub.Name GitHub.Owner -> GitHub.Name GitHub.Repo -> ChangeLogItem -> Text formatChangeLogItem ownerName repoName item = "[#" <> number <> "](https://github.com/" <> GitHub.untagName ownerName <> "/" <> GitHub.untagName repoName <> "/issues/" <> number <> ") " <> clTitle item where number = Text.pack . show . clNumber $ item makeChangeLog :: GitHub.Name GitHub.Owner -> GitHub.Name GitHub.Repo -> [GitHub.SimplePullRequest] -> [GitHub.Issue] -> ChangeLog makeChangeLog ownerName repoName pulls issues = Map.foldlWithKey (\changes milestone (msIssues, msPulls) -> ( milestone , map (formatChangeLogItem ownerName repoName) msIssues , map (formatChangeLogItem ownerName repoName) msPulls ) : changes ) [] . groupByMilestone (first . (:)) changeLogIssues . groupByMilestone (second . (:)) changeLogPrs $ Map.empty where mergedPrs = filter (\case GitHub.SimplePullRequest { GitHub.simplePullRequestMergedAt = Just _ } -> True _ -> False ) pulls closedItems = filter (\case GitHub.Issue { GitHub.issueMilestone = Just GitHub.Milestone { GitHub.milestoneState = "closed" } } -> True _ -> False ) issues milestoneByIssueId = Map.fromList . map (GitHub.issueNumber &&& GitHub.milestoneTitle . Maybe.fromJust . GitHub.issueMilestone) $ closedItems changeLogIssues :: [ChangeLogItem] changeLogIssues = Maybe.mapMaybe (\case -- filter out PRs GitHub.Issue { GitHub.issuePullRequest = Just _ } -> Nothing issue -> Just $ ChangeLogItem { clMilestone = GitHub.milestoneTitle . Maybe.fromJust . GitHub.issueMilestone $ issue , clTitle = GitHub.issueTitle issue , clNumber = GitHub.issueNumber issue } ) closedItems changeLogPrs :: [ChangeLogItem] changeLogPrs = Maybe.mapMaybe (\issue -> do milestone <- flip Map.lookup milestoneByIssueId . GitHub.simplePullRequestNumber $ issue return $ ChangeLogItem { clMilestone = milestone , clTitle = GitHub.simplePullRequestTitle issue , clNumber = GitHub.simplePullRequestNumber issue } ) mergedPrs groupByMilestone add = flip $ foldr (\item group -> Map.insert (clMilestone item) ( case Map.lookup (clMilestone item) group of Just old -> add item old Nothing -> add item ([], []) ) group ) fetchChangeLog :: GitHub.Name GitHub.Owner -> GitHub.Name GitHub.Repo -> IO ChangeLog fetchChangeLog ownerName repoName = do -- Get auth token from the $GITHUB_TOKEN environment variable. auth <- fmap (GitHub.OAuth . BS8.pack) <$> lookupEnv "GITHUB_TOKEN" -- Initialise HTTP manager so we can benefit from keep-alive connections. mgr <- newManager tlsManagerSettings let pulls = V.toList <$> request auth mgr (GitHub.pullRequestsForR ownerName repoName GitHub.stateClosed GitHub.FetchAll) let issues = V.toList <$> request auth mgr (GitHub.issuesForRepoR ownerName repoName (GitHub.stateClosed <> GitHub.optionsAnyMilestone) GitHub.FetchAll) -- issues >>= putStrLn . groom makeChangeLog ownerName repoName <$> pulls <*> issues main :: IO () main = do (ownerName, repoName) <- getArgs >>= repoLocation let name = (GitHub.untagName ownerName) <> "/" <> (GitHub.untagName repoName) fetchChangeLog ownerName repoName >>= putStr . Text.unpack . formatChangeLog name where repoLocation [] = return ("TokTok", "c-toxcore") repoLocation [ownerName, repoName] = return (fromString ownerName, fromString repoName) repoLocation _ = fail "Usage: changelog "