@@ -6,12 +6,13 @@ build-depends:
66default-language: GHC2021
77ghc-options: -Wall -Wno-type-defaults
88-}
9+ {-# LANGUAGE ApplicativeDo #-}
910{-# LANGUAGE ImportQualifiedPost #-}
1011{-# LANGUAGE OverloadedStrings #-}
1112{-# LANGUAGE RecordWildCards #-}
12- {-# LANGUAGE ViewPatterns #-}
1313
1414import Control.Exception
15+ import Control.Monad (when )
1516import Data.Binary
1617import Data.ByteString qualified as B
1718import Data.ByteString.Lazy qualified as BL
@@ -20,39 +21,42 @@ import Data.List (sort)
2021import Data.Maybe (isNothing , mapMaybe )
2122import Data.Text (Text )
2223import Data.Text qualified as T
23- import Data.Time.Clock (UTCTime , diffUTCTime , getCurrentTime )
24+ import Data.Time.Calendar (Day , diffDays )
25+ import Data.Time.Clock (UTCTime (.. ), diffUTCTime , getCurrentTime )
2426import Data.Tuple
2527import Data.Vector qualified as V
2628import GitHub qualified as GH
2729import Options.Applicative
2830import System.IO
2931import Prelude hiding (until )
3032
31- data Config = Config {
32- since :: UTCTime
33- , until :: Maybe UTCTime
33+ data Config = Config
34+ { startTime :: Day
35+ , endTime :: Day
3436 , workMode :: WorkMode
3537 }
3638
3739data WorkMode
3840 = Offline FilePath
3941 | Online (Maybe B. ByteString ) (Maybe FilePath )
4042
41- configParser :: Parser Config
42- configParser = do
43+ configParser :: UTCTime -> Parser Config
44+ configParser currTime = do
45+ startTime <-
46+ option auto $
47+ long " since"
48+ <> metavar " DATE"
49+ <> help " The start date of the analysis"
50+ <> showDefault
51+ <> value (read " 2021-10-23" )
52+ endTime <-
53+ option auto $
54+ long " until"
55+ <> metavar " DATE"
56+ <> help " The end date of the analysis"
57+ <> showDefault
58+ <> value (utctDay currTime)
4359 let
44- parseStartTime =
45- option auto $
46- long " since"
47- <> metavar " SINCE"
48- <> help " The start date of the analysis (default: 2021-10-23)"
49- <> value (read " 2021-10-23 00:00:00 UTC" )
50- parseEndTime =
51- optional $
52- option auto $
53- long " until"
54- <> metavar " UNTIL"
55- <> help " The end date of the analysis"
5660 parseUsername =
5761 optional $
5862 strOption $
@@ -71,7 +75,8 @@ configParser = do
7175 <> metavar " FILE"
7276 <> help " Work offline using previously saved cache"
7377
74- Config <$> parseStartTime <*> parseEndTime <*> (Offline <$> parseOffline <|> Online <$> parseUsername <*> parseCacheFile)
78+ workMode <- Offline <$> parseOffline <|> Online <$> parseUsername <*> parseCacheFile
79+ pure Config {.. }
7580
7681getPassword :: IO B. ByteString
7782getPassword = do
@@ -90,10 +95,10 @@ getBasicAuth username = do
9095 password <- getPassword
9196 pure $ GH. BasicAuth username password
9297
93- getGithubIssues :: Config -> IO (V. Vector GH. Issue )
94- getGithubIssues (workMode -> ( Offline cacheFile) ) =
98+ getGithubIssues :: WorkMode -> IO (V. Vector GH. Issue )
99+ getGithubIssues (Offline cacheFile) =
95100 decode <$> BL. readFile cacheFile
96- getGithubIssues (workMode -> ( Online mUsername mCacheFile) ) = do
101+ getGithubIssues (Online mUsername mCacheFile) = do
97102 let githubOrg = " haskell"
98103 githubRepo = " core-libraries-committee"
99104 am <- traverse getBasicAuth mUsername
@@ -133,23 +138,23 @@ computeLifeTimeInDays Issue {..} = case issClosedAt of
133138 Nothing -> Nothing
134139 Just t -> Just $ realToFrac (diffUTCTime t issCreatedAt) / 86400
135140
136- computeDaysSinceCreation :: UTCTime -> Issue -> Double
141+ computeDaysSinceCreation :: Day -> Issue -> Double
137142computeDaysSinceCreation currTime Issue {.. } =
138- realToFrac (diffUTCTime currTime issCreatedAt) / 86400
143+ realToFrac (diffUTCTime ( UTCTime currTime 0 ) issCreatedAt) / 86400
139144
140145isApproved :: Issue -> Bool
141146isApproved Issue {.. } = " approved" `elem` issLabels
142147
143148isDeclined :: Issue -> Bool
144149isDeclined Issue {.. } = " declined" `elem` issLabels
145150
146- isMeta :: Issue -> Bool
147- isMeta Issue {.. } = " meta" `elem` issLabels
151+ isProposal :: Issue -> Bool
152+ isProposal Issue {.. } = not ( " meta" `elem` issLabels || " core-libraries " `elem` issLabels)
148153
149- isWithinTimeFrame :: UTCTime -> UTCTime -> Issue -> Bool
154+ isWithinTimeFrame :: Day -> Day -> Issue -> Bool
150155isWithinTimeFrame since to Issue {.. } =
151- issCreatedAt >= since
152- && issCreatedAt <= to
156+ issCreatedAt >= UTCTime since 0
157+ && issCreatedAt <= UTCTime to 0
153158
154159isBase :: Int -> Issue -> Bool
155160isBase n Issue {.. } = (" base-4." <> T. pack (show n)) `elem` issLabels
@@ -169,6 +174,7 @@ data Stat = Stat
169174 deriving (Show )
170175
171176collectStat :: (Issue -> Maybe Double ) -> [Issue ] -> Stat
177+ collectStat _ [] = error " collectStat: no issues found!"
172178collectStat f is = Stat {.. }
173179 where
174180 (statMinIssue, statMinMetric) = getMin is
@@ -189,26 +195,27 @@ collectStat f is = Stat {..}
189195
190196main :: IO ()
191197main = do
192- cnf <-
198+ currTime <- getCurrentTime
199+ Config {.. } <-
193200 execParser $
194201 info
195- (configParser <**> helper)
202+ (configParser currTime <**> helper)
196203 (fullDesc <> header " Collect statistics for CLC proposals" )
197- issues <- getGithubIssues cnf
198-
199- let startTime = since cnf
200- endTime <- maybe getCurrentTime pure (until cnf)
204+ issues <- getGithubIssues workMode
201205
202206 let proposals =
203207 filter (isWithinTimeFrame startTime endTime) $
204- filter ( not . isMeta) . map githubIssueToIssue $
208+ filter isProposal . map githubIssueToIssue $
205209 filter (isNothing . GH. issuePullRequest) $
206210 V. toList issues
207211 approvedProposals = filter isApproved proposals
208212 declinedProposals = filter isDeclined proposals
209213
214+ putStrLn $ " Timeframe: since " ++ show startTime ++ " until " ++ show endTime
215+ putStrLn " "
216+
210217 putStrLn $ " Total number of CLC proposals: " ++ show (length proposals)
211- putStrLn $ " Rate of proposals: " ++ show (round (fromIntegral (length proposals) * 86400 * 365.25 / 12 / realToFrac (diffUTCTime endTime startTime))) ++ " per month"
218+ putStrLn $ " Rate of proposals: " ++ show (round (fromIntegral (length proposals) * 365.25 / 12 / realToFrac (diffDays endTime startTime))) ++ " per month"
212219 putStrLn $ " Approved proposals: " ++ show (length approvedProposals)
213220 putStrLn $ " Declined proposals: " ++ show (length declinedProposals)
214221 putStrLn " "
@@ -243,9 +250,10 @@ main = do
243250 putStrLn " "
244251
245252 let openProposals = filter (isNothing . issClosedAt) proposals
246- putStrLn $ " Open proposals: " ++ show (length openProposals)
247- let openLifeTime = collectStat (Just . computeDaysSinceCreation endTime) openProposals
248- putStrLn $ " Median age for open proposals: " ++ show (statMed openLifeTime) ++ " days"
249- putStrLn $ " Average age for open proposals: " ++ show (statAvg openLifeTime) ++ " days"
250- putStrLn $ " Newest open proposal:\n\t " ++ show (round (statMinMetric openLifeTime)) ++ " days for " ++ show (issTitle (statMinIssue openLifeTime))
251- putStrLn $ " Oldest open proposal:\n\t " ++ show (round (statMaxMetric openLifeTime)) ++ " days for " ++ show (issTitle (statMaxIssue openLifeTime))
253+ when (not $ null openProposals) $ do
254+ putStrLn $ " Open proposals: " ++ show (length openProposals)
255+ let openLifeTime = collectStat (Just . computeDaysSinceCreation endTime) openProposals
256+ putStrLn $ " Median age for open proposals: " ++ show (statMed openLifeTime) ++ " days"
257+ putStrLn $ " Average age for open proposals: " ++ show (statAvg openLifeTime) ++ " days"
258+ putStrLn $ " Newest open proposal:\n\t " ++ show (round (statMinMetric openLifeTime)) ++ " days for " ++ show (issTitle (statMinIssue openLifeTime))
259+ putStrLn $ " Oldest open proposal:\n\t " ++ show (round (statMaxMetric openLifeTime)) ++ " days for " ++ show (issTitle (statMaxIssue openLifeTime))
0 commit comments