{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} module Main (main) where import Control.Applicative ( #if !MIN_VERSION_base(4,8,0) (<$>), (<*>), #endif #if !MIN_VERSION_simple_cmd_args(0,1,3) (<|>) #endif ) import Control.Monad (unless, when) import Data.Aeson.Encode.Pretty (encodePretty) import Data.Aeson.Types #if MIN_VERSION_aeson(2,0,0) import qualified Data.Aeson.KeyMap as M #else import qualified Data.HashMap.Lazy as M #endif import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as BL import Data.Maybe #if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,11,0)) #else import Data.Semigroup ((<>)) #endif import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Yaml (encode) import SimpleCmdArgs import System.FilePath (()) import Fedora.Pagure import Paths_pagure_cli (version) main :: IO () main = simpleCmdArgs (Just version) "Pagure client" "Simple pagure CLI" $ subcommands [ Subcommand "list" "list projects" $ listProjects <$> serverOpt <*> countOpt <*> formatOpt <*> forksOpt <*> optional namespaceOpt <*> optional packagerOpt <*> optional (strArg "PATTERN") , Subcommand "user" "user repos" $ userRepos <$> serverOpt <*> countOpt <*> switchWith 'f' "forks" "List user's forks" <*> strArg "USER" , Subcommand "branches" "list project branches" $ repoBranches <$> serverOpt <*> formatOpt <*> strArg "REPO" , Subcommand "issues" "list project issues" $ projectIssues <$> serverOpt <*> countOpt <*> formatOpt <*> strArg "REPO" <*> switchWith 'A' "all" "list Open and Closed issues" <*> optional (strOptionWith 'a' "author" "AUTHOR" "Filter issues by creator") <*> optional (strOptionWith 'S' "since" "Y-M-D" "Filter issues updated after date") <*> optional (strOptionWith 't' "title" "pattern" "Filter issues by title") , Subcommand "users" "list users" $ users <$> serverOpt <*> formatOpt <*> strArg "PATTERN" , Subcommand "username" "fullname of user" $ username <$> serverOpt <*> formatOpt <*> strArg "USERNAME" , Subcommand "groups" "list groups" $ groups <$> serverOpt <*> countOpt <*> formatOpt <*> optional (strArg "PATTERN") , Subcommand "git-url" "show project repo's git urls" $ gitUrl <$> serverOpt <*> formatOpt <*> strArg "REPO" , Subcommand "project" "show project details" $ projectInfo <$> serverOpt <*> formatOpt <*> strArg "PROJECT" , Subcommand "issue" "show project issue" $ projectIssue <$> serverOpt <*> formatOpt <*> strArg "REPO" <*> argumentWith auto "ISSUE" , Subcommand "userinfo" "show user details" $ userInfo <$> serverOpt <*> formatOpt <*> strArg "USERNAME" ] where countOpt = switchWith 'c' "count" "Show number only" formatOpt = flagWith' FormatJson 'j' "json" "Output JSON" <|> flagWith FormatDefault FormatYaml 'y' "yaml" "YAML output" namespaceOpt = strOptionWith 'n' "namespace" "NAMESPACE" "Specify project repo namespace" packagerOpt = Owner <$> ownerOpt <|> Committer <$> usernameOpt usernameOpt = strOptionWith 'u' "username" "USERNAME" "Projects to which username can commit" ownerOpt = strOptionWith 'o' "owner" "OWNER" "Projects with certain owner" serverOpt = strOptionalWith 's' "server" "SERVER" "Pagure server" srcFedoraprojectOrg forksOpt = flagWith' OnlyForks 'F' "only-forks" "Only list forks" <|> flagWith NoForks IncludeForks 'f' "include-forks" "Include forks [default: ignore forks]" data OutputFormat = FormatDefault | FormatJson | FormatYaml data Packager = Owner String | Committer String data Forks = NoForks | IncludeForks | OnlyForks srcFedoraprojectOrg :: String srcFedoraprojectOrg = "src.fedoraproject.org" defaultPrinter :: OutputFormat -> (Object -> IO ()) -> Object -> IO () defaultPrinter FormatDefault pr = pr defaultPrinter FormatJson _ = BL.putStrLn . encodePretty defaultPrinter FormatYaml _ = B.putStrLn . encode listProjects :: String -> Bool -> OutputFormat -> Forks -> Maybe String -> Maybe Packager -> Maybe String -> IO () listProjects server count format forks mnamespace mpackager mpattern = do unless (count || isJust mpackager || isJust mpattern) $ error' "Please give a package pattern, --count, or --owner/--username" let path = "projects" params = makeKey "short" "1" ++ fork ++ packager ++ maybeKey "namespace" mnamespace ++ maybeKey "pattern" mpattern pages <- queryPaged server count path params ("pagination", "page") mapM_ (defaultPrinter format printPage) pages where -- (!orphan only works on pagure >=0.29) packager = case mpackager of Nothing -> boolKey "owner" (server == srcFedoraprojectOrg) "!orphan" Just (Owner o) -> makeKey "owner" o Just (Committer c) -> makeKey "username" c fork = case forks of NoForks -> makeKey "fork" "0" IncludeForks -> [] OnlyForks -> makeKey "fork" "1" printPage :: Object -> IO () printPage result = do let key' = if isJust mnamespace then "name" else "fullname" projects = lookupKey' "projects" result (mapM_ T.putStrLn . mapMaybe (lookupKey key')) projects userRepos :: String -> Bool -> Bool -> String -> IO () userRepos server count forks user = if count then do let path = "user" user mcnt <- queryPagureCount server path [] $ if forks then "forks_pagination" else "repos_pagination" print $ fromMaybe (error' ("number of " ++ (if forks then "forks" else "repos") ++ " could not be determined")) mcnt else do repos <- (if forks then pagureUserForks else pagureUserRepos) server user mapM_ T.putStrLn repos -- maybeKey :: String -> Maybe String -> Query -- maybeKey _ Nothing = [] -- maybeKey k mval = [(B.pack k, fmap B.pack mval)] -- makeKey :: String -> String -> Query -- makeKey k val = [(B.pack k, Just (B.pack val))] boolKey :: String -> Bool -> String -> Query boolKey _ False _ = [] boolKey k True val = makeKey k val -- FIXME limit max number of issues projectIssues :: String -> Bool -> OutputFormat -> String -> Bool -> Maybe String -> Maybe String -> Maybe String -> IO () projectIssues server count format repo allstatus mauthor msince mpat = do let path = repo "issues" params = [("status", Just "all") | allstatus] ++ maybeKey "author" mauthor ++ maybeKey "since" msince pages <- queryPaged server count path params ("pagination", "page") mapM_ (defaultPrinter format printIssues) pages where printIssues :: Object -> IO () printIssues result = do let issues = lookupKey' "issues" result :: [Object] mapM_ printIssue issues printIssue :: Object -> IO () printIssue issue = do let mfields = parseIssue issue case mfields of Nothing -> putStrLn "parsing issue failed" Just (id',title,status) -> when (isNothing mpat || T.pack (fromJust mpat) `T.isInfixOf` title) $ putStrLn $ "https://" <> server repo "issue" show id' <> " (" <> T.unpack status <> "): " <> T.unpack title parseIssue :: Object -> Maybe (Integer, Text, Text) parseIssue = parseMaybe $ \obj -> do id' <- obj .: "id" title <- obj .: "title" status <- obj .: "status" return (id',title,status) -- FIXME limit max number of pages (10?) or --pages queryPaged :: String -> Bool -> String -> Query -> (String,String) -> IO [Object] queryPaged server count path params (pagination,paging) = if count then do mnum <- queryPagureCount server path params pagination print $ fromMaybe (error' "pages not found") mnum return [] else queryPagurePaged server path params (pagination,paging) repoBranches :: String -> OutputFormat -> String -> IO () repoBranches server format repo = do let namespace = if server == srcFedoraprojectOrg && '/' `notElem` repo then "rpms/" else "" path = namespace ++ repo "git/branches" eres <- queryPagureSingle server path [] either error' (defaultPrinter format (printKeyList "branches")) eres users :: String -> OutputFormat -> String -> IO () users server format pat = do let path = "users" params = makeKey "pattern" pat res <- queryPagure server path params defaultPrinter format (printKeyList "users") res username :: String -> OutputFormat -> String -> IO () username server format user = do let path = "user" user res <- queryPagure' server path $ makeKey "per_page" "1" defaultPrinter format printName res where printName res = case lookupKey "user" res >>= lookupKey "fullname" of Nothing -> error' "User fullname not found" Just fn -> T.putStrLn fn groups :: String -> Bool -> OutputFormat -> Maybe String -> IO () groups server count format mpat = do let path = "groups" params = maybeKey "pattern" mpat pages <- queryPaged server count path params ("pagination", "page") mapM_ (defaultPrinter format (printKeyList "groups")) pages printKeyList :: String -> Object -> IO () printKeyList key' res = mapM_ T.putStrLn (lookupKey' (T.pack key') res :: [Text]) gitUrl :: String -> OutputFormat -> String -> IO () gitUrl server format repo = do let namespace = if server == srcFedoraprojectOrg && '/' `notElem` repo then "rpms/" else "" path = namespace ++ repo "git/urls" res <- queryPagure server path [] defaultPrinter format printURLs res where printURLs result = mapM_ T.putStrLn $ M.elems $ localLookupKey "urls" result localLookupKey :: String -> Object -> #if MIN_VERSION_aeson(2,0,0) M.KeyMap Text #else M.HashMap Text Text #endif localLookupKey = lookupKey' . T.pack -- from simple-cmd error' :: String -> a #if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0)) error' = errorWithoutStackTrace #else error' = error #endif yamlPrinter :: OutputFormat -> Object -> IO () yamlPrinter FormatDefault = yamlPrinter FormatYaml yamlPrinter FormatJson = BL.putStrLn . encodePretty yamlPrinter FormatYaml = B.putStrLn . encode projectInfo :: String -> OutputFormat -> String -> IO () projectInfo server format repo = do let namespace = if server == srcFedoraprojectOrg && '/' `notElem` repo then "rpms/" else "" path = namespace ++ repo eval <- pagureProjectInfo server path either error' (yamlPrinter format) eval projectIssue :: String -> OutputFormat -> String -> Int -> IO () projectIssue server format repo issue = do eval <- pagureProjectIssueInfo server repo issue either error' (yamlPrinter format) eval userInfo :: String -> OutputFormat -> String -> IO () userInfo server format user = do eval <- pagureUserInfo server user [] either error' (yamlPrinter format) eval