packages feed

pagure-cli 0.2 → 0.2.1

raw patch · 6 files changed

+299/−304 lines, 6 filesdep +aeson-prettydep +paguredep +unordered-containersdep −http-conduitdep −lensdep −lens-aesonsetup-changed

Dependencies added: aeson-pretty, pagure, unordered-containers, yaml

Dependencies removed: http-conduit, lens, lens-aeson, microlens, microlens-aeson

Files

CHANGELOG.md view
@@ -1,5 +1,14 @@ # Changelog +## 0.2.1 (2022-02-17)+- new commands: 'project', 'issue', 'userinfo'+- drop --url and use aeson-pretty+- use new pagure library+- 'user': new '--fork" option+- support aeson-2.0 and drop lens dependency+- 'branches' and 'username' now error if entity does not exist+- errors for non-existent API paths (eg unknown repo or user)+ ## 0.2 (2020-04-06) - new commands: 'branches', 'issues', 'users', 'username', 'groups', 'git-url' - 'list': new options --namespace, --username, --include-forks, --only-forks
− Main.hs
@@ -1,277 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}--module Main (main) where--import Control.Applicative (-#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,8,0))-#else-                            (<$>), (<*>),-#endif-#if (defined(MIN_VERSION_simple_cmd_args) && MIN_VERSION_simple_cmd_args(0,1,3))-#else-                            (<|>)-#endif-                           )-import Control.Monad (unless, when)-import Data.Aeson (eitherDecode)-import Data.Aeson.Types-#if (defined(MIN_VERSION_http_conduit) && MIN_VERSION_http_conduit(2,3,1))-#else-import Data.ByteString (ByteString)-#endif-#if (defined(VERSION_lens_aeson))-import Control.Lens-import Data.Aeson.Lens-#else-import Lens.Micro-import Lens.Micro.Aeson-#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 Network.HTTP.Conduit (queryString)-import Network.HTTP.Simple-import SimpleCmdArgs-import System.FilePath ((</>))--import Paths_pagure_cli (version)--main :: IO ()-main =-  simpleCmdArgs (Just version) "Pagure client" "Simple pagure CLI" $-  subcommands-  [ Subcommand "list" "list projects" $-    listProjects <$> serverOpt <*> countOpt <*> urlOpt <*> jsonOpt <*> forksOpt <*> optional namespaceOpt <*> optional packagerOpt <*> optional (strArg "PATTERN")-  , Subcommand "user" "user repos" $-    userRepos <$> serverOpt <*> countOpt <*> urlOpt <*> jsonOpt <*> strArg "USER"-  , Subcommand "branches" "list project branches" $-    repoBranches <$> serverOpt <*> urlOpt <*> jsonOpt <*> strArg "REPO"-  , Subcommand "issues" "list project issues" $-    projectIssues <$> serverOpt <*> countOpt <*> urlOpt <*> jsonOpt <*> 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 <*> urlOpt <*> jsonOpt <*> strArg "PATTERN"-  , Subcommand "username" "fullname of user" $-    username <$> serverOpt <*> urlOpt <*> jsonOpt <*> strArg "USERNAME"-  , Subcommand "groups" "list groups" $-    groups <$> serverOpt <*> countOpt <*> urlOpt <*> jsonOpt <*> optional (strArg "PATTERN")-  , Subcommand "git-url" "show project repo's git urls" $-    gitUrl <$> serverOpt <*> urlOpt <*> jsonOpt <*> strArg "REPO"-  ]-  where-    countOpt = switchWith 'c' "count" "Show number only"-    jsonOpt = switchWith 'j' "json" "Print raw json response"-    -- FIXME should this be no-op?-    urlOpt = switchWith 'U' "url" "Print API url"-    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 Packager = Owner String | Committer String--data Forks = NoForks | IncludeForks | OnlyForks--srcFedoraprojectOrg :: String-srcFedoraprojectOrg = "src.fedoraproject.org"--#if (defined(MIN_VERSION_http_conduit) && MIN_VERSION_http_conduit(2,3,1))-#else-type Query = [(ByteString, Maybe ByteString)]-#endif--listProjects :: String -> Bool -> Bool -> Bool -> Forks -> Maybe String -> Maybe Packager -> Maybe String -> IO ()-listProjects server count showurl json 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 showurl json path params ("pagination", "page")-  unless json $-    mapM_ 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 :: Value -> IO ()-    printPage result =-      let key' = if isJust mnamespace then "name" else "fullname" in-      mapM_ T.putStrLn $ result ^.. key "projects" . values . key (T.pack key') . _String--userRepos :: String -> Bool -> Bool -> Bool -> String -> IO ()-userRepos server count showurl json user = do-  let path = "user" </> user-  pages <- queryPaged server count showurl json path [] ("repos_pagination", "repopage")-  unless json $-    mapM_ printPage pages-  where-    printPage :: Value -> IO ()-    printPage result = do-      let repos = result ^.. key (T.pack "repos") . values . _Object-      mapM_ printRepo repos--    printRepo :: Object -> IO ()-    printRepo repo = do-      let mfields = parseRepo repo-      case mfields of-        Nothing -> error' "parsing repo failed"-        Just (mnamespace,name) ->-          T.putStrLn $ maybe "" (<> "/") mnamespace  <> name--    parseRepo :: Object -> Maybe (Maybe Text, Text)-    parseRepo =-      parseMaybe $ \obj -> do-        namespace <- obj .:? "namespace"-        name <- obj .: "name"-        return (namespace,name)--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 -> Bool -> Bool -> String -> Bool -> Maybe String -> Maybe String -> Maybe String -> IO ()-projectIssues server count showurl json repo allstatus mauthor msince mpat = do-  let path = repo </> "issues"-      params = [("status", Just "all") | allstatus] ++-               maybeKey "author" mauthor ++ maybeKey "since" msince-  results <- queryPaged server count showurl json path params ("pagination", "page")-  unless json $-    mapM_ printIssues results-  where-    printIssues :: Value -> IO ()-    printIssues result = do-      let issues = result ^.. key (T.pack "issues") . values . _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 -> Bool -> Bool -> String -> Query -> (String,String) -> IO [Value]-queryPaged server count showurl json path params (pagination,paging) = do-  res1 <- pagureQuery showurl server json path (params ++ makeKey "per_page" (if count then "1" else "100"))-  let mpages = res1 ^? key (T.pack pagination) . key "pages" . _Integer-  if count-    then do-    print $ fromMaybe (error' "pages not found") mpages-    return []-    else do-    rest <- mapM nextPage [2..(fromMaybe 0 mpages)]-    return $ res1 : rest-      where-        nextPage p =-          pagureQuery False server json path (params ++ makeKey "per_page" "100" ++ makeKey paging (show p))--pagureQuery :: Bool -> String -> Bool -> String -> Query -> IO Value-pagureQuery showurl server json path params = do-  let url = "https://" <> server </> "api/0" </> path-  req <- setRequestQueryString params <$> parseRequest url-  when showurl $ putStrLn $ url ++ B.unpack (queryString req)-  if json then do-    res <- getResponseBody <$> httpLBS req-    BL.putStrLn res-    case eitherDecode res of-      Left e -> error' e-      Right v -> return v-    else getResponseBody <$> httpJSON req--repoBranches :: String -> Bool -> Bool -> String -> IO ()-repoBranches server showurl json repo = do-  let namespace =-        if server == srcFedoraprojectOrg && '/' `notElem` repo-        then "rpms/" else ""-      path = namespace ++ repo </> "git/branches"-  res <- pagureQuery showurl server json path []-  unless json $-    printKeyList "branches" res--users :: String -> Bool -> Bool -> String -> IO ()-users server showurl json pat = do-  let path = "users"-      params = makeKey "pattern" pat-  res <- pagureQuery showurl server json path params-  unless json $-    printKeyList "users" res--username :: String -> Bool -> Bool -> String -> IO ()-username server showurl json user = do-  let path = "user" </> user-  res <- pagureQuery showurl server json path $ makeKey "per_page" "1"-  unless json $-    case res ^? key "user" . key "fullname" . _String of-      Nothing -> error' "User fullname not found"-      Just fn -> T.putStrLn fn--groups :: String -> Bool -> Bool -> Bool -> Maybe String -> IO ()-groups server count showurl json mpat = do-  let path = "groups"-      params = maybeKey "pattern" mpat-  results <- queryPaged server count showurl json path params ("pagination", "page")-  unless json $-    mapM_ (printKeyList "groups") results--printKeyList :: String -> Value -> IO ()-printKeyList key' res =-  mapM_ T.putStrLn $ res ^.. key (T.pack key') . values . _String--gitUrl :: String -> Bool -> Bool -> String -> IO ()-gitUrl server showurl json repo = do-  let namespace =-        if server == srcFedoraprojectOrg && '/' `notElem` repo-        then "rpms/" else ""-      path = namespace ++ repo </> "git/urls"-  res <- pagureQuery showurl server json path []-  unless json $-    printURLs res-  where-    printURLs :: Value -> IO ()-    printURLs result =-      mapM_ T.putStrLn $ result ^.. key (T.pack "urls") . members . _String---- from simple-cmd-error' :: String -> a-#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0))-error' = errorWithoutStackTrace-#else-error' = error-#endif
README.md view
@@ -4,8 +4,6 @@ [![GPL-2 license](https://img.shields.io/badge/license-GPL--2-blue.svg)](LICENSE) [![Stackage Lts](http://stackage.org/package/pagure-cli/badge/lts)](http://stackage.org/lts/package/pagure-cli) [![Stackage Nightly](http://stackage.org/package/pagure-cli/badge/nightly)](http://stackage.org/nightly/package/pagure-cli)-[![Build status](https://secure.travis-ci.org/juhp/pagure-cli.svg)](https://travis-ci.org/juhp/pagure-cli)-[![Copr build](https://copr.fedorainfracloud.org/coprs/petersen/pagure-cli/package/pagure-cli/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/petersen/pagure-cli/)  A [pagure](https://docs.pagure.org/pagure/) client for querying projects and users.@@ -59,7 +57,7 @@  ``` $ pagure list --help-Usage: pagure list [-s|--server SERVER] [-c|--count] [-U|--url] [-j|--json]+Usage: pagure list [-s|--server SERVER] [-c|--count] [(-j|--json) | (-y|--yaml)]                    [(-F|--only-forks) | (-f|--include-forks)]                    [-n|--namespace NAMESPACE]                    [(-o|--owner OWNER) | (-u|--username USERNAME)] [PATTERN]@@ -68,8 +66,8 @@ Available options:   -s,--server SERVER       Pagure server   -c,--count               Show number only-  -U,--url                 Print API url-  -j,--json                Print raw json response+  -j,--json                Output JSON+  -y,--yaml                YAML output   -F,--only-forks          Only list forks   -f,--include-forks       Include forks [default: ignore forks]   -n,--namespace NAMESPACE Specify project repo namespace@@ -88,8 +86,7 @@  ## Binaries -If you are using Fedora you can install the package from my-[copr repo](https://copr.fedorainfracloud.org/coprs/petersen/pagure-cli/).+pagure-cli is packaged in Fedora (since F34).  ## Contributions @@ -100,3 +97,5 @@ ## Other clients After writing the initial version I discovered that Ricky Elrod (relrod) had made <https://github.com/fedora-infra/pagure-cli>.++I discovered a client in Rust <https://pagure.io/ironthree/bodhi-cli> by decathorpe.
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
pagure-cli.cabal view
@@ -1,52 +1,44 @@-cabal-version:       2.0+cabal-version:       1.18 name:                pagure-cli-version:             0.2+version:             0.2.1 synopsis:            Pagure client-description:         A commandline Pagure client for querying projects and users.+description:         A command-line Pagure client for querying projects and users. homepage:            https://github.com/juhp/pagure-cli bug-reports:         https://github.com/juhp/pagure-cli/issues license:             GPL-2 license-file:        LICENSE author:              Jens Petersen <juhpetersen@gmail.com> maintainer:          Jens Petersen <juhpetersen@gmail.com>-copyright:           2019 Jens Petersen+copyright:           2019-2020,2022 Jens Petersen category:            Utility build-type:          Simple extra-doc-files:     README.md                    , CHANGELOG.md tested-with:         GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5,-                     GHC == 8.8.3+                     GHC == 8.8.4, GHC == 8.10.4, GHC == 9.0.2  source-repository head   type:                git   location:            https://github.com/juhp/pagure-cli.git --flag microlens-  description:       Use smaller microlens instead of lens-  default:           False- executable pagure-  main-is:             Main.hs+  main-is:             src/Main.hs    build-depends:       aeson+                     , aeson-pretty                      , base < 5                      , bytestring                      , filepath-                     , http-conduit                      , optparse-applicative+                     , pagure >= 0.1.1                      , simple-cmd-args >= 0.1.6                      , text-  if flag(microlens)-      build-depends:   microlens, microlens-aeson-      cpp-options:   -DMICROLENS-  else-      build-depends:   lens, lens-aeson+                     , unordered-containers+                     , yaml    if impl(ghc<8.0)       build-depends: semigroups -  autogen-modules:     Paths_pagure_cli   other-modules:       Paths_pagure_cli    ghc-options:         -Wall
+ src/Main.hs view
@@ -0,0 +1,274 @@+{-# 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