-- SPDX-License-Identifier: BSD-3-Clause
{-# Language LambdaCase #-}
module Main (main) where
import Control.Monad.Extra (foldM_, when)
import qualified Data.ByteString.Lazy.Char8 as B
import Data.Char ( isDigit )
import Data.Functor ((<&>))
import Data.List.Extra (isPrefixOf, lower, nub, sort, takeEnd)
import Data.Maybe (isJust, isNothing)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Time.LocalTime (getCurrentTimeZone, utcToZonedTime)
import Network.HTTP.Directory
( (+/+), httpDirectory', httpExists', httpLastModified', noTrailingSlash )
import Network.HTTP.Simple
( parseRequest, getResponseBody, httpLBS )
import SimpleCmdArgs
import SimplePrompt
import System.Console.ANSI
import Paths_fedora_composes (version)
data Limit = NoLimit | Limit Int
main :: IO ()
main =
simpleCmdArgs' (Just version)
"check status of fedora composes"
"description here" $
subcommands
[ Subcommand "list"
"List dirs/composes (by default only last compose)" $
listCmd
<$> debugOpt
<*> optional limitOpt
<*> switchWith 'r' "repos" "Only list target repos"
<*> optional dirOpt
<*> optional snapOpt
, Subcommand "status"
"Show compose status" $
statusCmd
<$> debugOpt
<*> optional limitOpt
<*> switchWith 'n' "no-more" "Do not prompt for more results"
<*> dirOpt
<*> optional snapOpt
]
where
debugOpt = switchWith 'd' "debug" "debug output"
limitOpt =
flagWith' NoLimit 'a' "all-composes" "All composes" <|>
Limit <$> flagWith' 1 'L' "latest" "Only latest compose" <|>
Limit <$> optionWith auto 'l' "limit" "LIMIT" "Max number of composes"
dirOpt = strArg "DIR"
snapOpt = strArg "SUBSTR"
topUrl :: String
topUrl = "https://kojipkgs.fedoraproject.org/compose"
httpDirectories :: String -> IO [Text]
httpDirectories = fmap (map noTrailingSlash) . httpDirectory'
listCmd :: Bool -> Maybe Limit -> Bool -> Maybe String
-> Maybe String -> IO ()
listCmd _ _ _ Nothing _ =
httpDirectories topUrl >>= mapM_ T.putStrLn
listCmd debug mlimit onlyrepos (Just dir) mpat =
getComposes debug mlimit onlyrepos dir mpat >>= mapM_ putStrLn
data Compose =
Compose {compDate :: Text, compRepo :: Text}
deriving (Eq, Ord, Show)
readCompose :: Text -> Compose
readCompose t =
case T.breakOnEnd (T.pack "-") t of
(repoDash,date) -> Compose date (T.init repoDash)
showCompose :: Compose -> String
showCompose (Compose d r) = T.unpack r <> "-" <> T.unpack d
-- data RepoComposes = RepoComposes Text [Text]
getComposes :: Bool -> Maybe Limit -> Bool -> FilePath
-> Maybe String -> IO [String]
getComposes debug mlimit onlyrepos dir mpat = do
let url = topUrl +/+ dir
when debug $ putStrLn url
repocomposes <-
limitComposes .
sort .
repoSubset .
map readCompose .
filter (\c -> isDigit (T.last c) && T.any (== '.') c) <$>
httpDirectories url
when debug $ print repocomposes
return $ reverse $ selectRepos url repocomposes
where
selectRepos :: String -> [Compose] -> [String]
selectRepos url =
if onlyrepos
then map T.unpack . nub . map compRepo
else map ((url +/+) . showCompose)
repoSubset :: [Compose] -> [Compose]
repoSubset = maybe id (\n -> filter ((T.pack (lower n) `T.isInfixOf`) . T.toLower . compRepo)) mpat
limitComposes =
flip (maybe (takeEnd 10)) mlimit $
\case
Limit n -> takeEnd n
NoLimit -> id
statusCmd :: Bool -> Maybe Limit -> Bool -> FilePath -> Maybe String
-> IO ()
statusCmd debug mlimit nomore dir mpat = do
tz <- getCurrentTimeZone
getComposes debug mlimit False dir mpat >>=
foldM_ (checkStatus tz) (Just False)
where
checkStatus tz mfinished snapurl =
if mfinished == Just True
then return (Just True)
else do
more <-
if isNothing mfinished
then
if nomore
then return False
else do
yes <- yesNoDefault False "Show more results"
cursorUp 1 >> clearFromCursorToLineEnd
return yes
else return True
if not more
then return (Just True)
else do
-- FIXME use formatTime?
mstart <- httpMaybeLastModified $ snapurl +/+ "COMPOSE_ID"
mfinish <- httpMaybeLastModified $ snapurl +/+ "STATUS"
status <-
if isJust mfinish
then B.unpack <$> getComposeFile snapurl "STATUS"
else return "STATUS missing"
putStrLn $ unlines $
(snapurl +/+ if status == "DOOMED" then "logs/global/" else "") :
[maybe "" show mstart | status /= "STARTED"] ++
[maybe "" show mfinish ++ " " ++ status]
return $ if "FINISHED" `isPrefixOf` status
then Nothing
else Just False
where
httpMaybeLastModified url = do
exists <- httpExists' url
if exists
then fmap (utcToZonedTime tz) <$> httpLastModified' url
else return Nothing
getComposeFile url file =
parseRequest (url +/+ file)
>>= httpLBS
<&> removeFinalNewLine . getResponseBody
removeFinalNewLine bs = if B.last bs == '\n' then B.init bs else bs
-- capitalize :: String -> String
-- capitalize "" = ""
-- capitalize (h:t) = toUpper h : t