rhbzquery (empty) → 0.1.1
raw patch · 11 files changed
+625/−0 lines, 11 filesdep +basedep +bytestringdep +config-ini
Dependencies added: base, bytestring, config-ini, directory, email-validate, extra, filepath, http-types, optparse-applicative, simple-cmd, simple-cmd-args, text
Files
- ChangeLog.md +11/−0
- LICENSE +30/−0
- README.md +36/−0
- rhbzquery.cabal +68/−0
- src/Bugzilla.hs +5/−0
- src/Fields.hs +87/−0
- src/Help.hs +154/−0
- src/Main.hs +71/−0
- src/ParseArg.hs +62/−0
- src/User.hs +63/−0
- test/tests.hs +38/−0
+ ChangeLog.md view
@@ -0,0 +1,11 @@+# rhbzquery releases++## 0.1.1 (2020-12-04)+- --file a bug+- --help: do not format FIELDS+- add testsuite and travis++## 0.1.0 (2020-12-02)+- initial release: supports field parameters, status, flags+- has --mine+- opens urls with xdg-open
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2020, Jens Petersen++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Jens Petersen nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,36 @@+# rhbzquery++A cli tool for querying bugzilla.redhat.com.++The tool outputs the bugzilla query url,+and if xdg-open is available will try to open the url+(unless --dryrun is given).++## Usage examples++`rhbzquery f33 xyz` : F33 bugs for package xyz++`rhbzquery closed rawhide xyz` : closed rawhide bugs for package xyz++`rhbzquery --mine` : your open bugs (reads userid from `.bugzillarc`)++`rhbzquery rhel8.3 bash` : RHEL 8.3 bash bugs++`rhbzquery "Package Review" reporter_realname="Your Name"` : open package reviews you reported++`rhbzquery --mine all flag=fedora-review+` : all your open and closed approved reviews++`rhbzquery summary=bugzilla` : open bugs with summary containing "bugzilla":++`rhbzquery --file f33 xyz` : file a bug against the xyz package in F33++### Help+`rhbzquery --help` : lists the many fields (note: not all well supported yet, eg timestamps).++## Installation+Run `stack install` or `cabal install`.++## Requests and feedback+Feature requests, bug reports and contributions are welcome.++Please open an issue at <https://github.com/juhp/rhbzquery>.
+ rhbzquery.cabal view
@@ -0,0 +1,68 @@+name: rhbzquery+version: 0.1.1+synopsis: Bugzilla query tool+description:+ A CLI tool for creating bugzilla queries for bugzilla.redhat.com.+license: BSD3+license-file: LICENSE+author: Jens Petersen+maintainer: petersen@redhat.com+copyright: 2020 Jens Petersen <petersen@redhat.com>+category: Utils+homepage: https://github.com/juhp/rhbzquery+bug-reports: https://github.com/juhp/rhbzquery/issues+build-type: Simple+extra-doc-files: ChangeLog.md+ README.md+cabal-version: 1.18+tested-with: GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.4, GHC == 8.10.2++source-repository head+ type: git+ location: https://github.com/juhp/rhbzquery.git++executable rhbzquery+ main-is: Main.hs+ other-modules: Bugzilla+ Fields+ Help+ ParseArg+ Paths_rhbzquery+ User+ build-depends: base <5+ , bytestring+ , config-ini+ , directory+ , email-validate+ , extra+ , filepath+ , http-types+ , optparse-applicative+ , simple-cmd+ , simple-cmd-args+ , text+ hs-source-dirs: src+ default-language: Haskell2010+ ghc-options: -Wall+ if impl(ghc >= 8.0)+ ghc-options: -Wcompat+ -Widentities+ -Wincomplete-uni-patterns+ -Wincomplete-record-updates+ -Wredundant-constraints+ if impl(ghc >= 8.2)+ ghc-options: -fhide-source-paths+ if impl(ghc >= 8.4)+ ghc-options: -Wmissing-export-lists+ -Wpartial-fields++test-suite test+ main-is: tests.hs+ type: exitcode-stdio-1.0+ hs-source-dirs: test++ default-language: Haskell2010++ ghc-options: -Wall+ build-depends: base >= 4 && < 5+ , simple-cmd
+ src/Bugzilla.hs view
@@ -0,0 +1,5 @@+module Bugzilla (brc)+where++brc :: String+brc = "bugzilla.redhat.com"
+ src/Fields.hs view
@@ -0,0 +1,87 @@+module Fields (+ BzFields(..),+ argToFields,+ argToSimpleField+ )+where++import Data.Version+import Numeric.Natural++import ParseArg (ArgType(..), ProductVersion(..))++data BzFields = BzProduct+ | BzVersion+ | BzComponent+ | BzParameter String+ | BzMeta Char Natural+ deriving Eq++instance Show BzFields where+ show BzProduct = "product"+ show BzVersion = "version"+ show BzComponent = "component"+ show (BzParameter f) = mapFields f+ show (BzMeta c n) = c: show n++mapFields :: String -> String+mapFields "itm" = "cf_internal_target_milestone"+mapFields "itr" = "cf_internal_target_release"+mapFields "status" = "bug_status"+mapFields "verified" = "cf_verified"+mapFields s = s++mapComplex :: String -> String+mapComplex "sst" = "agile_team.name"+mapComplex "summary" = "short_desc"+mapComplex "flag" = "flagtypes.name"+mapComplex "flags" = "flagtypes.name"+mapComplex p = p++argToSimpleField :: ArgType -> [(BzFields,String)]+argToSimpleField (ArgProdVer prodver) =+ productVersionQuery prodver+argToSimpleField (ArgParameter p v) =+ [(BzParameter (mapComplex p), v)]+argToSimpleField (ArgOther c) =+ [(BzComponent,c)]+-- FIXME or error for "all"?+argToSimpleField _ = []++argToFields :: Natural -> ArgType -> (Natural,[(BzFields,String)])+argToFields i arg =+ case arg of+ ArgProdVer prodver -> (i,productVersionQuery prodver)+ ArgStatusAll -> (i,[])+ ArgParameter "sst" v ->+ (i+1,[(BzMeta 'f' i, mapComplex "sst")+ ,(BzMeta 'o' i, "substr")+ ,(BzMeta 'v' i, "sst_" ++ v)])+ ArgParameter "summary" v ->+ (i+1,[(BzMeta 'f' i, mapComplex "summary")+ ,(BzMeta 'o' i, "substr")+ ,(BzMeta 'v' i, v)])+ ArgParameter param v ->+ let p = mapComplex param+ in if '.' `elem` p+ then (i+1,[(BzMeta 'f' i, p)+ ,(BzMeta 'o' i, "substr")+ ,(BzMeta 'v' i, v)])+ else (i,[(BzParameter p, v)])+ ArgOther c -> (i,[(BzComponent,c)])++productVersionQuery :: ProductVersion -> [(BzFields,String)]+productVersionQuery (Fedora Nothing) = [(BzProduct, "Fedora")]+productVersionQuery Rawhide = [(BzProduct, "Fedora")+ ,(BzVersion, "rawhide")]+productVersionQuery (Fedora (Just n)) = [(BzProduct, "Fedora")+ ,(BzVersion, show n)]+productVersionQuery (EPEL Nothing) = [(BzProduct, "Fedora EPEL")]+productVersionQuery (EPEL (Just n)) = [(BzProduct, "Fedora EPEL")+ ,(BzVersion, show n)]+productVersionQuery (RHEL ver) =+ case versionBranch ver of+ [] -> error "Can't search RHEL without version"+ [major] -> [(BzProduct, "Red Hat Enterprise Linux " ++ show major)]+ (major:_) -> [(BzProduct, "Red Hat Enterprise Linux " ++ show major)+ ,(BzVersion, showVersion ver)]
+ src/Help.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE CPP #-}++module Help (detailedHelp)+where++#if !MIN_VERSION_base(4,11,0)+import Data.Monoid ((<>))+#endif+import qualified Options.Applicative.Help.Pretty as P++import ParseArg (statusList)++detailedHelp :: P.Doc+detailedHelp =+ P.vcat+ [ P.text "Tool for generating bugzilla queries"+ , P.empty+ , P.text "FIELDS = " <> P.lbrace <> P.align (P.hsep (P.punctuate P.comma (map P.text allBzFields)) <> P.rbrace)+ , P.empty+ , P.text "STATUS = " <> P.lbrace <> P.align (P.fillCat (P.punctuate P.comma (map P.text (statusList ++ ["ALL"]))) <> P.rbrace)+ , P.empty+ , P.text "PRODUCTVERSION = " <> P.lbrace <> P.align (P.fillCat (P.punctuate P.comma (map P.text ["rawhide", "fedora", "fXY", "epel", "epelX", "rhel8", "rhel7", "rhelX.Z"])) <> P.rbrace)+ , P.empty+ , P.text "See https://github.com/juhp/rhbzquery#readme for examples"+ ]++-- FIXME: filter by @redhat.com+-- bugzilla query --json -b 1865911+allBzFields :: [String]+allBzFields =+-- from https://bugzilla.redhat.com/rest/field/bug+ ["agile_pool.name",+ "agile_team.name",+ "alias",+ "assigned_to",+ "assigned_to_realname",+ "attach_data.thedata",+ "attachments.description",+ "attachments.filename",+ "attachments.isobsolete",+ "attachments.ispatch",+ "attachments.isprivate",+ "attachments.mimetype",+ "attachments.submitter",+ "blocked",+ "bug_agile_pool.pool_id",+ "bug_agile_pool.pool_order",+ "bug_file_loc",+ "bug_group",+ "bug_id",+ "bug_severity",+ "bug_status",+ "cc",+ "cclist_accessible",+ "cf_approved_release",+ "cf_atomic",+ "cf_build_id",+ "cf_business_market_problem",+ "cf_category",+ "cf_clone_of",+ "cf_cloudforms_team",+ "cf_compliance_control_group",+ "cf_compliance_level",+ "cf_conditional_nak",+ "cf_crm",+ "cf_cust_facing",+ "cf_deadline",+ "cf_deadline_type",+ "cf_devdoctest",+ "cf_devel_whiteboard",+ "cf_doc_type",+ "cf_docs_score",+ "cf_documentation_action",+ "cf_environment",+ "cf_epm_cdp",+ "cf_epm_phd",+ "cf_epm_prf_state",+ "cf_epm_pri",+ "cf_epm_ptl",+ "cf_epm_put",+ "cf_final_deadline",+ "cf_fixed_in",+ "cf_internal_target_milestone",+ "cf_internal_target_release",+ "cf_internal_whiteboard",+ "cf_last_closed",+ "cf_mount_type",+ "cf_ovirt_team",+ "cf_pgm_internal",+ "cf_pm_score",+ "cf_qa_whiteboard",+ "cf_qe_conditional_nak",+ "cf_regression_status",+ "cf_release_notes",+ "cf_srtnotes",+ "cf_story_points",+ "cf_target_upstream_version",+ "cf_type",+ "cf_verified_branch",+ "cf_zstream_target_release",+ "classification",+ "comment_tag",+ "commenter",+ "component",+ "content",+ "creation_ts",+ "days_elapsed",+ "deadline",+ "delta_ts",+ "dependent_products",+ "dependson",+ "docs_contact",+ "docs_contact_realname",+ "estimated_time",+ "everconfirmed",+ "ext_bz_bug_map.ext_bz_bug_id",+ "ext_bz_bug_map.ext_status",+ "external_bugzilla.description",+ "external_bugzilla.url",+ "extra_components",+ "extra_versions",+ "flagtypes.name",+ "keywords",+ "last_visit_ts",+ "longdesc",+ "longdescs.count",+ "longdescs.isprivate",+ "op_sys",+ "owner_idle_time",+ "percentage_complete",+ "priority",+ "product",+ "qa_contact",+ "qa_contact_realname",+ "remaining_time",+ "rep_platform",+ "reporter",+ "reporter_accessible",+ "reporter_realname",+ "requestees.login_name",+ "resolution",+ "rh_rule",+ "rh_sub_components",+ "see_also",+ "setters.login_name",+ "short_desc",+ "status_whiteboard",+ "tag",+ "target_milestone",+ "target_release",+ "version",+ "view",+ "votes",+ "work_time"]
+ src/Main.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++import Control.Monad.Extra+import Data.Bifunctor+import qualified Data.ByteString.Char8 as B+import Data.Maybe+#if !MIN_VERSION_base(4,11,0)+import Data.Monoid ((<>))+#endif+import qualified Data.List as L+import Network.HTTP.Types+import Options.Applicative (fullDesc, header, progDescDoc,+#if !MIN_VERSION_simple_cmd_args(0,1,4)+ some+#endif+ )+import SimpleCmd+import SimpleCmdArgs+import System.Directory++import Bugzilla+import Fields+import Help+import ParseArg+import Paths_rhbzquery+import User++main :: IO ()+main =+ simpleCmdArgsWithMods (Just version) (fullDesc <> header "Bugzilla query tool" <> progDescDoc (Just detailedHelp)) $+ run <$>+ switchWith 'n' "dryrun" "Do not open url" <*>+ switchWith 'm' "mine" "My bugs" <*>+ switchWith 'f' "file" "File a bug" <*>+ some (strArg argHelp)+ where+ run :: Bool -> Bool -> Bool -> [String] -> IO ()+ run dryrun mine file args = do+ user <- if mine && not file+ then do+ mail <- getBzUser+ return [ArgParameter "assigned_to" mail]+ else return []+ let argtypes = mapMaybe readBzQueryArg args+ url = if file+ then+ let query = L.nub $ concatMap argToSimpleField argtypes+ in "https://" <> B.pack brc <> "/enter_bug.cgi" <> renderQuery True (bzQuery query)+ else+ let status = [ArgParameter "bug_status" "__open__" | not (hasStatusSet argtypes)]+ query = L.nub $ numberMetaFields $ status ++ user ++ argtypes+ in "https://" <> B.pack brc <> "/buglist.cgi" <> renderQuery True (bzQuery query)+ B.putStrLn url+ unless dryrun $ do+ whenJustM (findExecutable "xdg-open") $ \xdgOpen ->+ cmd_ xdgOpen [B.unpack url]++ hasStatusSet :: [ArgType] -> Bool+ hasStatusSet [] = False+ hasStatusSet (ArgStatusAll:_) = True+ hasStatusSet ((ArgParameter "bug_status" _):_) = True+ hasStatusSet ((ArgParameter "status" _):_) = True+ hasStatusSet (_:rest) = hasStatusSet rest++ numberMetaFields :: [ArgType] -> [(BzFields,String)]+ numberMetaFields =+ snd . foldr (\arg (i,flds) -> let (i',fld) = argToFields i arg in (i', fld ++ flds)) (0,[])++ bzQuery :: [(BzFields,String)] -> Query+ bzQuery = map (bimap (B.pack . show) (Just . B.pack))
+ src/ParseArg.hs view
@@ -0,0 +1,62 @@+module ParseArg (+ argHelp,+ readBzQueryArg,+ ArgType(..),+ ProductVersion(..),+ statusList+ )+where++import Control.Applicative ((<|>))+import Data.Char+import Data.List.Extra as L+import Data.Version.Extra+import Numeric.Natural++argHelp :: String+argHelp = "[COMPONENT|STATUS|PRODUCTVERSION|FIELD=VALUE]..."++data ArgType = ArgProdVer ProductVersion+ | ArgStatusAll+ | ArgParameter String String+ | ArgOther String++readBzQueryArg :: String -> Maybe ArgType+readBzQueryArg s =+ ArgProdVer <$> readProductVersion s <|>+ parseStatus s <|>+ parseParam s <|>+ pure (ArgOther s)++data ProductVersion = Fedora (Maybe Natural)+ | Rawhide+ | EPEL (Maybe Natural)+ | RHEL Version++readProductVersion :: String -> Maybe ProductVersion+readProductVersion "fedora" = Just (Fedora Nothing)+readProductVersion "rawhide" = Just Rawhide+readProductVersion ('f':ver) | all isDigit ver = Just $ Fedora (Just (read ver :: Natural))+readProductVersion "epel" = Just (EPEL Nothing)+readProductVersion ('e':'p':'e':'l':v) | all isDigit v = Just (EPEL (Just (read v :: Natural)))+readProductVersion ('r':'h':'e':'l':ver) = Just $ RHEL (readVersion ver)+readProductVersion _ = Nothing++statusList :: [String]+statusList = ["NEW", "ASSIGNED", "POST", "MODIFIED", "ON_QA", "VERIFIED", "RELEASE_PENDING", "CLOSED"]++parseStatus :: String -> Maybe ArgType+parseStatus s =+ let caps = upper s in+ if caps == "ALL"+ then Just ArgStatusAll+ else+ ArgParameter "bug_status" <$> find (== upper s) statusList++parseParam :: String -> Maybe ArgType+parseParam ps =+ case splitOn "=" ps of+ [a,b] -> if null a || null b+ then error $ "bad parameter: " ++ ps+ else Just (ArgParameter a b)+ _ -> Nothing
+ src/User.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE CPP #-}++module User (+ getBzUser+ )+where++import Control.Monad.Extra+import qualified Data.ByteString.Char8 as B+#if !MIN_VERSION_base(4,11,0)+import Data.Monoid ((<>))+#endif+import Data.Ini.Config+import qualified Data.Text as T+import qualified Data.Text.IO as T+import System.Directory+import System.Environment+import System.FilePath+import qualified Text.Email.Validate as Email++import Bugzilla++eitherBzUser :: IO (Either FilePath String)+eitherBzUser = do+ home <- getEnv "HOME"+ let rc = home </> ".bugzillarc"+ -- FIXME assumption if file exists then it has b.r.c user+ ifM (doesFileExist rc)+ (Right <$> readIniConfig rc rcParser id)+ (return $ Left rc)+ where+ rcParser :: IniParser String+ rcParser =+ section (T.pack brc) $+ fieldOf (T.pack "user") string++ readIniConfig :: FilePath -> IniParser a -> (a -> b) -> IO b+ readIniConfig inifile iniparser fn = do+ ini <- T.readFile inifile+ return $ either error fn $ parseIniFile ini iniparser++getBzUser :: IO String+getBzUser = do+ euser <- eitherBzUser+ case euser of+ Right user -> return user+ Left rc -> do+ email <- prompt "Bugzilla Username"+ when (emailIsValid email) $ do+ writeFile rc $ "[" <> brc <> "]\nuser = " <> email <> "\n"+ putStrLn $ "Saved in " ++ rc+ getBzUser+ where+ emailIsValid :: String -> Bool+ emailIsValid = Email.isValid . B.pack++prompt :: String -> IO String+prompt s = do+ putStr $ s ++ ": "+ inp <- getLine+ if null inp+ then prompt s+ else return inp
+ test/tests.hs view
@@ -0,0 +1,38 @@+import Control.Monad+import SimpleCmd++rhbzquery :: ([String], String) -> IO ()+rhbzquery (args,expect) = do+ out <- cmd "rhbzquery" ("-n" : args)+ let cgi = if "--file" `elem` args then "enter_bug" else "buglist"+ req = removePrefix ("https://bugzilla.redhat.com/" ++ cgi ++ ".cgi?") out+ unless (req == expect) $ do+ cmdN "rhbzquery" ("-n" : args)+ putStrLn $ "returned> " ++ req+ putStrLn $ "expected> " ++ expect+ error' "failed"++tests :: [([String],String)]+tests =+ [(["f33", "pango"],+ "bug_status=__open__&product=Fedora&version=33&component=pango")+ ,(["closed", "rawhide", "xyz"],+ "bug_status=CLOSED&product=Fedora&version=rawhide&component=xyz")+ ,(["rhel8.3", "bash"],+ "bug_status=__open__&product=Red%20Hat%20Enterprise%20Linux%208&version=8.3&component=bash")+ ,(["rhel8", "bash"],+ "bug_status=__open__&product=Red%20Hat%20Enterprise%20Linux%208&component=bash")+ ,(["Package Review", "reporter_realname=Your Name"],+ "bug_status=__open__&component=Package%20Review&reporter_realname=Your%20Name")+ ,(["flag=fedora-review+"],+ "bug_status=__open__&f0=flagtypes.name&o0=substr&v0=fedora-review%2B")+ ,(["summary=bugzilla"],+ "bug_status=__open__&f0=short_desc&o0=substr&v0=bugzilla")+ ,(["--file", "f33", "bugzilla"],+ "product=Fedora&version=33&component=bugzilla")+ ]++main :: IO ()+main = do+ mapM_ rhbzquery tests+ putStrLn $ show (length tests) ++ " tests run"