pagure-0.2.1: src/Fedora/Pagure.hs
{-# LANGUAGE CPP, OverloadedStrings #-}
{- |
Copyright: (c) 2020-2024 Jens Petersen
SPDX-License-Identifier: GPL-2.0-or-later
Maintainer: Jens Petersen <petersen@redhat.com>
Pagure REST client library
-}
module Fedora.Pagure
( pagureProjectInfo
, pagureListProjects
, pagureListProjectIssues
, IssueTitleStatus(..)
, pagureListProjectIssueTitlesStatus
, pagureProjectIssueInfo
, pagureListGitBranches
, pagureListGitBranchesWithCommits
, pagureListUsers
, pagureUserForks
, pagureUserInfo
, pagureUserRepos
, pagureListGroups
, pagureGroupInfo
, pagureGroupRepos
, pagureProjectGitURLs
, queryPagure
, queryPagureSingle
, queryPagureCount
, queryPagureCountPaged
, makeKey
, makeItem
, maybeKey
, Query
, QueryItem
, lookupKey
, lookupKey'
, getRepos
) where
import Control.Monad
import Data.Aeson.Types
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as T
import Network.HTTP.Query
import System.IO (hPutStrLn, stderr)
-- | Project info
--
-- @pagureProjectInfo server "<repo>"@
-- @pagureProjectInfo server "<namespace>/<repo>"@
--
-- https://pagure.io/api/0/#projects-tab
pagureProjectInfo :: String -- ^ server
-> String -- ^ project
-> IO (Either String Object)
pagureProjectInfo server project = do
let path = project
queryPagureSingle server path []
-- | List projects
--
-- https://pagure.io/api/0/#projects-tab
pagureListProjects :: String -- ^ server
-> Query -- ^ parameters
-> IO Object
pagureListProjects server params = do
let path = "projects"
queryPagure server path params
-- | List project issues
--
-- https://pagure.io/api/0/#issues-tab
pagureListProjectIssues :: String -- ^ server
-> String -- ^ project repo
-> Query -- ^ parameters
-> IO (Either String Object)
pagureListProjectIssues server repo params = do
let path = repo +/+ "issues"
queryPagureSingle server path params
data IssueTitleStatus =
IssueTitleStatus { pagureIssueId :: Integer
, pagureIssueTitle :: String
, pagureIssueStatus :: T.Text
, pagureIssueCloseStatus :: Maybe T.Text
}
-- | List project issue titles
--
-- https://pagure.io/api/0/#issues-tab
pagureListProjectIssueTitlesStatus :: String -- ^ server
-> String -- ^ repo
-> Query -- ^ parameters
-> IO (Either String [IssueTitleStatus])
pagureListProjectIssueTitlesStatus server repo params = do
let path = repo +/+ "issues"
res <- queryPagureSingle server path params
return $ case res of
Left e -> Left e
Right v -> Right $ mapMaybe parseIssue $ lookupKey' "issues" v
where
parseIssue :: Object -> Maybe IssueTitleStatus
parseIssue =
parseMaybe $ \obj -> do
id' <- obj .: "id"
title <- obj .: "title"
status <- obj .: "status"
mcloseStatus <- obj .:? "close_status"
return $ IssueTitleStatus id' (T.unpack title) status mcloseStatus
-- | Issue information
--
-- https://pagure.io/api/0/#issues-tab
pagureProjectIssueInfo :: String -- ^ server
-> String -- ^ repo
-> Int -- ^ issue number
-> IO (Either String Object)
pagureProjectIssueInfo server repo issue = do
let path = repo +/+ "issue" +/+ show issue
queryPagureSingle server path []
-- | List repo branches
--
-- https://pagure.io/api/0/#projects-tab
pagureListGitBranches :: String -- ^ server
-> String -- ^ repo
-> IO (Either String [String])
pagureListGitBranches server repo = do
let path = repo +/+ "git/branches"
res <- queryPagureSingle server path []
return $ case res of
Left e -> Left e
Right v -> map T.unpack <$> lookupKeyEither "branches" v
-- | List repo branches with commits
--
-- https://pagure.io/api/0/#projects-tab
pagureListGitBranchesWithCommits :: String -- ^ server
-> String -- ^ repo
-> IO (Either String Object)
pagureListGitBranchesWithCommits server repo = do
let path = repo +/+ "git/branches"
params = makeKey "with_commits" "1"
res <- queryPagureSingle server path params
return $ case res of
Left e -> Left e
Right v -> lookupKeyEither "branches" v
-- | List users
--
-- https://pagure.io/api/0/#users-tab
pagureListUsers :: String -- ^ server
-> String -- ^ pattern
-> IO Object
pagureListUsers server pat = do
let path = "users"
params = makeKey "pattern" pat
queryPagure server path params
-- | User information
--
-- https://pagure.io/api/0/#users-tab
pagureUserInfo :: String -- ^ server
-> String -- ^ user
-> Query -- ^ parameters
-> IO (Either String Object)
pagureUserInfo server user params = do
let path = "user" +/+ user
queryPagureSingle server path params
-- | List groups
--
-- https://pagure.io/api/0/#groups-tab
pagureListGroups :: String -- ^ server
-> Maybe String -- ^ optional pattern
-> Query -- ^ parameters
-> IO Object
pagureListGroups server mpat paging = do
let path = "groups"
params = maybeKey "pattern" mpat ++ paging
queryPagure server path params
-- | Group information
--
-- https://pagure.io/api/0/#groups-tab
pagureGroupInfo :: String -- ^ server
-> String -- ^ group
-> Query -- ^ parameters
-> IO (Either String Object)
pagureGroupInfo server group params = do
let path = "group" +/+ group
queryPagureSingle server path params
-- | Project Git URLs
--
-- https://pagure.io/api/0/#projects-tab
pagureProjectGitURLs :: String -- ^ server
-> String -- ^ repo
-> IO (Either String Object)
pagureProjectGitURLs server repo = do
let path = repo +/+ "git/urls"
queryPagureSingle server path []
-- | low-level query
queryPagure :: String -- ^ server
-> String -- ^ api path
-> Query -- ^ parameters
-> IO Object
queryPagure server path params =
let url = "https://" ++ server +/+ "api/0" +/+ path
in webAPIQuery url params
-- | single query
queryPagureSingle :: String -- ^ server
-> String -- ^ api path
-> Query -- ^ parameters
-> IO (Either String Object)
queryPagureSingle server path params = do
res <- queryPagure server path params
return $ case lookupKey "error" res of
Just err -> Left (T.unpack err)
Nothing -> Right res
-- | count total number of hits
queryPagureCount :: String -- ^ server
-> String -- ^ api path
-> Query -- ^ parameters
-> String -- ^ pagination name
-> IO (Maybe Integer)
queryPagureCount server path params pagination = do
eres <- queryPagureSingle server path (params ++ makeKey "per_page" "1")
case eres of
Left err -> do
warning err
return Nothing
Right res ->
return $ lookupKey (T.pack pagination) res >>= lookupKey "pages"
-- | get all pages of results
--
-- Warning: this can potentially download very large amounts of data.
-- For potentially large queries, it is a good idea to queryPagureCount first.
queryPagurePaged :: String -- ^ server
-> String -- ^ api path
-> Query -- ^ parameters
-> (String,String) -- ^ pagination and paging names
-> IO [Object]
queryPagurePaged server path params (pagination,paging) = do
-- FIXME allow overriding per_page
let maxPerPage = "100"
eres <- queryPagureSingle server path (params ++ makeKey "per_page" maxPerPage)
case eres of
Left err -> do
warning err
return []
Right res1 ->
case (lookupKey (T.pack pagination) res1 :: Maybe Object) >>= lookupKey "pages" :: Maybe Int of
Nothing -> return []
Just pages -> do
when (pages > 1) $
warning $ "receiving " ++ show pages ++ " pages × " ++ maxPerPage ++ " results..."
rest <- mapM nextPage [2..pages]
return $ res1 : rest
where
nextPage p =
queryPagure server path (params ++ makeKey "per_page" "100" ++ makeKey paging (show p))
-- | list user's repos
pagureUserRepos :: String -- ^ server
-> String -- ^ user
-> IO [Text]
pagureUserRepos server user = do
let path = "user" +/+ user
pages <- queryPagurePaged server path [] ("repos_pagination", "repopage")
return $ concatMap (getRepos "repos") pages
-- | list user's forks
pagureUserForks :: String -- ^ server
-> String -- ^ user
-> IO [Text]
pagureUserForks server user = do
let path = "user" +/+ user
pages <- queryPagurePaged server path [] ("forks_pagination", "forkpage")
return $ concatMap (getRepos "forks") pages
-- | list group's repos
pagureGroupRepos :: String -- ^ server
-> Bool -- ^ count
-> String -- ^ group
-> IO [Text]
pagureGroupRepos server count group = do
let path = "group" +/+ group
params = makeKey "projects" "1"
pages <- queryPagureCountPaged server count path params ("pagination", "page")
return $ concatMap (getRepos "projects") pages
-- | helper to extract fullnames of repos
getRepos :: Text -- ^ field (eg "repos")
-> Object -- ^ results page
-> [Text]
getRepos field obj =
map (lookupKey' "fullname") $ lookupKey' field obj
-- | Get count (with queryPagureCount) or full results (queryPagurePaged)
queryPagureCountPaged :: String -- ^ server
-> Bool -- ^ count
-> String -- ^ api path
-> Query -- ^ parameters
-> (String,String) -- ^ pagination and paging names
-> IO [Object]
queryPagureCountPaged server count path params (pagination,paging) =
if count
then do
mnum <- queryPagureCount server path params pagination
maybe (warning "no results found") print mnum
return []
else queryPagurePaged server path params (pagination,paging)
-- from simple-cmd
warning :: String -> IO ()
warning s = hPutStrLn stderr $! s