packages feed

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 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"