codeforces-cli-0.1.0: src/Codeforces/App/Commands/ContestCmds.hs
--------------------------------------------------------------------------------
-- | Contest-related commands.
module Codeforces.App.Commands.ContestCmds
( contestList
, contestInfo
, openContest
) where
import Codeforces.API
import Codeforces.App.Format
import Codeforces.App.Options
import Codeforces.App.Table
import Codeforces.App.Watcher
import Codeforces.Error
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import qualified Data.Map as M
import Data.Maybe
import Data.Text ( Text )
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Time
import Web.Browser
--------------------------------------------------------------------------------
contestList :: ContestOpts -> IO ()
contestList ContestOpts {..} = handleE $ runExceptT $ do
contests <- handleAPI $ getContests optIsGym
now <- lift getCurrentTime
let headers = [("#", 4), ("Name", 50), ("Date", 16), ("Duration", 10)]
rows = map
(\Contest {..} ->
plainCell
<$> [ showText $ unContestId contestId
, contestName
, fmtStartTime contestStartTime
, fmtDuration contestDuration
]
)
(filterContests optIsPast optIsUpcoming now contests)
lift $ mapM_ T.putStrLn $ makeTable headers rows
-- | 'filterContests' @onlyPast onlyUpcoming currentTime@ filters and orders a
-- list of contests depending on whether past or upcoming (or both/all) contests
-- should be shown.
filterContests :: Bool -> Bool -> UTCTime -> [Contest] -> [Contest]
filterContests False False _ = id
filterContests False True now = reverse . filter (not . isContestPast now)
filterContests True False now = filter (isContestPast now)
filterContests True True _ = id
-- | Whether the contest is in the past relative to the given time.
isContestPast :: UTCTime -> Contest -> Bool
isContestPast now c = maybe False (< now) (contestStartTime c)
fmtStartTime :: Maybe UTCTime -> Text
fmtStartTime =
maybe "" (T.pack . formatTime defaultTimeLocale "%H:%M %d-%b-%y")
fmtDuration :: DiffTime -> Text
fmtDuration = T.pack . formatTime defaultTimeLocale "%h:%0M hrs"
--------------------------------------------------------------------------------
contestInfo :: ContestId -> UserConfig -> InfoOpts -> IO ()
contestInfo cId cfg opts =
handleWatch (optInfoWatch opts) (contestInfoTable cId cfg opts)
-- | 'contestInfoTable' @problems submissions statistics@ fetches data about the
-- contest and constructs a table of its problems.
--
-- The table includes problem statistics, and if the user has made a submission,
-- their submission verdict for the problem.
--
contestInfoTable
:: ContestId -> UserConfig -> InfoOpts -> IO (Either CodeforcesError Table)
contestInfoTable cId cfg opts = runExceptT $ do
let handle = fromMaybe (cfgHandle cfg) (optHandle opts)
ps <- handleAPI $ getContestProblems cId
statMap <- handleAPI $ fmap problemStatsMap <$> getProblemStats []
subMap <-
handleAPI $ fmap submissionsMap <$> getContestSubmissions cId handle
let headers =
[ ("#" , 2)
, ("Problem", 30)
, ("Verdict", 35)
, ("Time" , 7)
, ("Memory" , 8)
, ("Solved" , 7)
]
rows = map
(\Problem {..} ->
let mSub = M.lookup problemIndex subMap
mStats = M.lookup problemIndex statMap
in [ plainCell problemIndex
, plainCell problemName
, contestSubmissionCell mSub
, plainCell $ maybeTimeTaken mSub
, plainCell $ maybeMemTaken mSub
, plainCell $ maybeSolved mStats
]
)
ps
pure $ makeTable headers rows
where
maybeTimeTaken = maybe "-" (fmtTimeConsumed . submissionTimeConsumed)
maybeMemTaken = maybe "-" (fmtMemoryConsumed . submissionMemoryConsumed)
maybeSolved = maybe "" (("x" <>) . showText . pStatSolvedCount)
-- | Shows the verdict of a contest submission.
contestSubmissionCell :: Maybe Submission -> Cell
contestSubmissionCell Nothing = plainCell "-"
contestSubmissionCell (Just Submission {..}) = verdictCell
submissionTestset
submissionPassedTestCount
submissionPoints
submissionVerdict
-- | 'problemStatsMap' @stats@ computes a map of each problem's index to the
-- corresponding 'ProblemStats' for it.
problemStatsMap :: [ProblemStats] -> M.Map ProblemIndex ProblemStats
problemStatsMap = M.fromList . map (pStatProblemIndex >>= (,))
-- | 'submissionsMap' @submissions@ computes a map of each problem's index to
-- the most recent submission for it.
submissionsMap :: [Submission] -> M.Map ProblemIndex Submission
submissionsMap =
M.fromListWith (const id) . map (problemIndex . submissionProblem >>= (,))
--------------------------------------------------------------------------------
-- | 'openContest' @contestId@ opens the URL to the specified contest in the
-- user's preferred web browser.
openContest :: ContestId -> IO ()
openContest cId =
openBrowser ("https://codeforces.com/contest/" <> show cId) >> pure ()
--------------------------------------------------------------------------------