fedora-composes 0.1 → 0.2
raw patch · 5 files changed
+179/−103 lines, 5 filesdep +ansi-terminaldep +simple-prompt
Dependencies added: ansi-terminal, simple-prompt
Files
- ChangeLog.md +9/−0
- README.md +61/−36
- fedora-composes.cabal +7/−2
- src/Main.hs +95/−58
- test/tests.hs +7/−7
ChangeLog.md view
@@ -1,4 +1,13 @@ # Version history for fedora-composes +## 0.2 (2024-02-14)+- output compose urls and use multiple lines for status+- status: default to showing latest finished and prompt to show older+- if STARTED only print STATUS time+- handle missing STATUS and COMPOSE_ID+- default to up to 10 composes+- add --latest option+- append logs/global to url if doomed+ ## 0.1 (2022-08-11) - initial release with list and status commands and filtering
README.md view
@@ -4,25 +4,47 @@ ## Examples -```shellsession-$ fedora-composes list rawhide-Fedora-Rawhide-20220809.n.0-$ fedora-composes list branched-Fedora-36-20220506.n.0-Fedora-37-20220811.n.0-$ fedora-composes status branched 37-2022-08-11 08:58:46 +08 -> 2022-08-11 08:58:48 +08 STARTED Fedora-37-20220811.n.0+`$ fedora-composes list --latest rawhide` ```+https://kojipkgs.fedoraproject.org/compose/rawhide/Fedora-Rawhide-20240214.n.1+```+`$ fedora-composes list -l2 branched`+```+https://kojipkgs.fedoraproject.org/compose/branched/Fedora-40-20240214.n.0+https://kojipkgs.fedoraproject.org/compose/branched/Fedora-40-20240213.n.1+```+`$ fedora-composes status --no-more branched`+```+https://kojipkgs.fedoraproject.org/compose/branched/Fedora-40-20240214.n.0/logs/global/+2024-02-14 15:19:10 +08+2024-02-14 15:31:52 +08 DOOMED +https://kojipkgs.fedoraproject.org/compose/branched/Fedora-40-20240213.n.1+2024-02-14 03:40:26 +08+2024-02-14 08:52:37 +08 FINISHED_INCOMPLETE++```++`$ fedora-composes status -n updates 39`+```+https://kojipkgs.fedoraproject.org/compose/updates/Fedora-39-updates-testing-20240214.0+2024-02-14 09:25:36 +08+2024-02-14 10:03:51 +08 FINISHED++```+ ## Usage -```shellsession-$ fedora-composes --version-0.1-$ fedora-composes --help+`$ fedora-composes --version`+```+0.2+```+`$ fedora-composes --help`+``` check status of fedora composes Usage: fedora-composes [--version] COMMAND+ description here Available options:@@ -34,10 +56,10 @@ status Show compose status ``` -There is the notation of repos and composes.+There is the notion of repos and composes. -For example `Fedora-36-updates` is a repo-and `Fedora-36-updates-20220810.0` is a compose for it.+For example `Fedora-39-updates` is a repo+and `Fedora-39-updates-20230810.0` is a compose for it. Filtering is case insensitive. @@ -51,19 +73,19 @@ `fedora-composes list branched` shows latest branched composes -```shellsession-$ fedora-composes list --help-Usage: fedora-composes list [-d|--debug] [(-a|--all-repos) | (-n|--num NOREPOS)]- [(-A|--all-composes) | (-l|--limit LIMIT)]- [-r|--repos] [DIR] [SUBSTR]+`$ fedora-composes list --help`+```+Usage: fedora-composes list [-d|--debug] + [(-a|--all-composes) | (-L|--latest) | + (-l|--limit LIMIT)] [-r|--repos] [DIR] [SUBSTR]+ List dirs/composes (by default only last compose) Available options: -d,--debug debug output- -a,--all-repos All repos- -n,--num NOREPOS Number of repos (default: 6)- -A,--all-composes All composes- -l,--limit LIMIT Number of composes (default: 1)+ -a,--all-composes All composes+ -L,--latest Only latest compose+ -l,--limit LIMIT Max number of composes -r,--repos Only list target repos -h,--help Show this help text ```@@ -72,27 +94,30 @@ `fedora-composes status rawhide` shows time and status of newest rawhide -`fedora-composes status updates fedora-36` shows time and status of updates push+`fedora-composes status updates fedora-39` shows time and status of updates push -`fedora-composes status branched 37` shows time and status of branched compose+`fedora-composes status branched 39` shows time and status of branched compose -```shellsession-$ fedora-composes status --help-Usage: fedora-composes status [-d|--debug]- [(-a|--all-repos) | (-n|--num NOREPOS)]- [(-A|--all-composes) | (-l|--limit LIMIT)] DIR- [SUBSTR]+`$ fedora-composes status --help`+```+Usage: fedora-composes status [-d|--debug] + [(-a|--all-composes) | (-L|--latest) | + (-l|--limit LIMIT)] [-n|--no-more] DIR [SUBSTR]+ Show compose status Available options: -d,--debug debug output- -a,--all-repos All repos- -n,--num NOREPOS Number of repos (default: 6)- -A,--all-composes All composes- -l,--limit LIMIT Number of composes (default: 1)+ -a,--all-composes All composes+ -L,--latest Only latest compose+ -l,--limit LIMIT Max number of composes+ -n,--no-more Do not prompt for more results -h,--help Show this help text ``` ## Installation+fedora-composes rpm builds are available from Copr:+<https://copr.fedorainfracloud.org/coprs/petersen/fedora-composes/> +## Build from source stack/cabal/cabal-rpm install
fedora-composes.cabal view
@@ -1,5 +1,5 @@ name: fedora-composes-version: 0.1+version: 0.2 synopsis: Query Fedora composes description: CLI tool to list and check status of Fedora composes@@ -7,7 +7,7 @@ license-file: LICENSE author: Jens Petersen <juhpetersen@gmail.com> maintainer: Jens Petersen <juhpetersen@gmail.com>-copyright: 2022 Jens Petersen <juhpetersen@gmail.com>+copyright: 2022-2024 Jens Petersen <juhpetersen@gmail.com> category: Utility homepage: https://github.com/juhp/fedora-composes bug-reports: https://github.com/juhp/fedora-composes/issues@@ -19,6 +19,9 @@ || == 8.8.4 || == 8.10.7 || == 9.0.2+ || == 9.2.8+ || == 9.4.8+ || == 9.6.4 source-repository head type: git@@ -30,11 +33,13 @@ autogen-modules: Paths_fedora_composes hs-source-dirs: src build-depends: base < 5,+ ansi-terminal, bytestring, extra, http-conduit, http-directory >= 0.1.9, simple-cmd-args >= 0.1.7,+ simple-prompt >= 0.2, text, time default-language: Haskell2010
src/Main.hs view
@@ -1,24 +1,30 @@ -- SPDX-License-Identifier: BSD-3-Clause+{-# Language LambdaCase #-} module Main (main) where -import Control.Monad.Extra (when)+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 ( lower, groupOn, sort, sortOn, takeEnd)+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', httpLastModified', noTrailingSlash )+ ( (+/+), 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)@@ -29,8 +35,7 @@ "List dirs/composes (by default only last compose)" $ listCmd <$> debugOpt- <*> numOpt- <*> limitOpt+ <*> optional limitOpt <*> switchWith 'r' "repos" "Only list target repos" <*> optional dirOpt <*> optional snapOpt@@ -38,21 +43,18 @@ "Show compose status" $ statusCmd <$> debugOpt- <*> numOpt- <*> limitOpt+ <*> optional limitOpt+ <*> switchWith 'n' "no-more" "Do not prompt for more results" <*> dirOpt <*> optional snapOpt ] where debugOpt = switchWith 'd' "debug" "debug output" - numOpt =- flagWith' Nothing 'a' "all-repos" "All repos" <|>- Just <$> optionalWith auto 'n' "num" "NOREPOS" "Number of repos (default: 6)" 6- limitOpt =- flagWith' Nothing 'A' "all-composes" "All composes" <|>- Just <$> optionalWith auto 'l' "limit" "LIMIT" "Number of composes (default: 1)" 1+ 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" @@ -64,71 +66,106 @@ httpDirectories :: String -> IO [Text] httpDirectories = fmap (map noTrailingSlash) . httpDirectory' -listCmd :: Bool -> Maybe Int -> Maybe Int -> Bool -> Maybe String+listCmd :: Bool -> Maybe Limit -> Bool -> Maybe String -> Maybe String -> IO ()-listCmd _ _ _ _ Nothing _ =+listCmd _ _ _ Nothing _ = httpDirectories topUrl >>= mapM_ T.putStrLn-listCmd debug mrepos mlimit onlyrepos (Just dir) mpat =- getComposes debug mrepos mlimit onlyrepos dir mpat >>= mapM_ T.putStrLn+listCmd debug mlimit onlyrepos (Just dir) mpat =+ getComposes debug mlimit onlyrepos dir mpat >>= mapM_ putStrLn -getComposes :: Bool -> Maybe Int -> Maybe Int -> Bool -> FilePath- -> Maybe String -> IO [Text]-getComposes debug mrepos mlimit onlyrepos dir mpat = do+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 <-- groupOn (T.dropWhileEnd (/= '-')) .- sortOn (T.dropWhileEnd (/= '-')) . subset .+ limitComposes .+ sort .+ repoSubset .+ map readCompose . filter (\c -> isDigit (T.last c) && T.any (== '.') c) <$> httpDirectories url- when debug $ print $ map last repocomposes- return $- (if onlyrepos- then mconcat . map limitRepos . groupOn removeRelease . map removeDate- else mconcat . map (sort . limitComposes) . limitRepos) repocomposes+ when debug $ print repocomposes+ return $ reverse $ selectRepos url repocomposes where- subset = maybe id (\n -> filter ((T.pack (lower n) `T.isInfixOf`) . T.toLower)) mpat-- limitRepos = maybe id takeEnd mrepos- limitComposes = maybe id takeEnd mlimit+ selectRepos :: String -> [Compose] -> [String]+ selectRepos url =+ if onlyrepos+ then map T.unpack . nub . map compRepo+ else map ((url +/+) . showCompose) - removeDate = T.init . T.dropWhileEnd (/= '-') . head+ repoSubset :: [Compose] -> [Compose]+ repoSubset = maybe id (\n -> filter ((T.pack (lower n) `T.isInfixOf`) . T.toLower . compRepo)) mpat - removeRelease t =- let reldash = (T.dropWhileEnd isDigit . T.dropWhileEnd (not . isDigit)) t- in if T.null reldash- then t- else T.init reldash+ limitComposes =+ flip (maybe (takeEnd 10)) mlimit $+ \case+ Limit n -> takeEnd n+ NoLimit -> id --- FIXME sort output by timestamp-statusCmd :: Bool -> Maybe Int -> Maybe Int -> FilePath -> Maybe String+statusCmd :: Bool -> Maybe Limit -> Bool -> FilePath -> Maybe String -> IO ()-statusCmd debug mrepos mlimit dir mpat = do+statusCmd debug mlimit nomore dir mpat = do tz <- getCurrentTimeZone- getComposes debug mrepos mlimit False dir mpat >>=- mapM (checkStatus tz) >>= mapM_ putStrLn . sort+ getComposes debug mlimit False dir mpat >>=+ foldM_ (checkStatus tz) (Just False) where- checkStatus tz compose = do- let snapurl = topUrl +/+ dir +/+ T.unpack compose- when debug $ putStrLn snapurl- status <- getComposeFile snapurl "STATUS"--- putChar ' '- -- FIXME use formatTime- mstart <- fmap (utcToZonedTime tz) <$>- httpLastModified' (snapurl +/+ "COMPOSE_ID")- mfinish <- fmap (utcToZonedTime tz) <$>- httpLastModified' (snapurl +/+ "STATUS")- composeId <- getComposeFile snapurl "COMPOSE_ID"- return $ unwords [maybe "" ((++ " ->") . show) mstart,- maybe "" show mfinish,- B.unpack status,- B.unpack composeId]+ 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
test/tests.hs view
@@ -15,17 +15,17 @@ [ ("list", [["rawhide"]- ,["-r", "-n4", "updates"]+ ,["-r", "updates"] ,["-l2", "updates"]- ,["updates", "fedora-36"]+ ,["updates", "fedora-39"] ]) , ("status",- [["updates"]- ,["updates", "fedora-36"]- ,["rawhide"]- ,["branched"]- ,["branched", "37"]+ [["updates", "-n"]+ ,["updates", "-n", "fedora-38"]+ ,["rawhide", "-L"]+ ,["branched", "-l2"]+ ,["branched", "-n", "39"] ]) ]