diff --git a/.gitignore b/.gitignore index 7a55d68..413a1f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/pybugz-0.10-git89df2.tar.gz +/pybugz-0.10-git683dd.tar.gz diff --git a/pybugz-0.10-git683dd-rh-specific.patch b/pybugz-0.10-git683dd-rh-specific.patch new file mode 100644 index 0000000..e3db0d0 --- /dev/null +++ b/pybugz-0.10-git683dd-rh-specific.patch @@ -0,0 +1,58 @@ +diff --git a/bugz/cli.py b/bugz/cli.py +index aa45d3b..12e2224 100644 +--- a/bugz/cli.py ++++ b/bugz/cli.py +@@ -240,7 +240,10 @@ class PrettyBugz: + log_info(log_msg) + + if not 'status' in params.keys(): +- params['status'] = ['CONFIRMED', 'IN_PROGRESS', 'UNCONFIRMED'] ++ params['status'] = ['CONFIRMED', 'IN_PROGRESS', ++ 'UNCONFIRMED', 'NEW', 'ASSIGNED', 'ON_DEV', ++ 'POST', 'MODIFIED', 'ON_QA', 'VERIFIED', ++ 'RELEASE_PENDING'] + elif 'ALL' in params['status']: + del params['status'] + +diff --git a/bugzrc.example b/bugzrc.example +index f516bf0..e8e1234 100644 +--- a/bugzrc.example ++++ b/bugzrc.example +@@ -59,3 +59,20 @@ + # + # All parameters listed above can be used in the default section if you + # only use one bugzilla installation. ++ ++[gentoo] ++base: https://bugs.gentoo.org/xmlrpc.cgi ++columns: 80 ++encoding: utf-8 ++# user: myname@my.project.com ++# password: secret2 ++ ++[redhat] ++base: https://bugzilla.redhat.com/xmlrpc.cgi ++columns: 80 ++encoding: utf-8 ++# user: myname@my.project.com ++# password: secret2 ++ ++[default] ++connection: redhat +diff --git a/man/bugz.1 b/man/bugz.1 +index 5a88494..365ab7e 100644 +--- a/man/bugz.1 ++++ b/man/bugz.1 +@@ -28,6 +28,12 @@ This man page is a stub; the bugz program has extensive built in help. + will show the help for the global options and + .B bugz [subcommand] -h + will show the help for a specific subcommand. ++.SH CONFIGURATION ++PyBugz is still configured just using the ~/.bugzrc file. For simple template ++look at the bugzrc.example ++.B (rpm -qd pybugz | grep example) ++- you may copy it directly into your home directory to make PyBugz work by ++default with Red Hat bugzilla. + .SH BUGS + .PP + The home page of this project is http://www.github.com/williamh/pybugz. diff --git a/pybugz-0.10-git89df2-downstream.patch b/pybugz-0.10-git89df2-downstream.patch deleted file mode 100644 index fc95094..0000000 --- a/pybugz-0.10-git89df2-downstream.patch +++ /dev/null @@ -1,1145 +0,0 @@ -From ce7591bee38eb3b94f9b68cb5699a597195ff8fa Mon Sep 17 00:00:00 2001 -From: Pavel Raiskup -Date: Sun, 20 Jan 2013 23:20:50 +0100 -Subject: [PATCH] Downstream patch to follow - https://github.com/praiskup/pybugz - ---- - bin/bugz | 19 ++--- - bugz/argparsers.py | 10 ++- - bugz/cli.py | 209 ++++++++++++++++++++++++++++------------------ - bugz/configfile.py | 217 +++++++++++++++++++++++++++++++++--------------- - bugz/errhandling.py | 6 ++ - bugz/log.py | 72 ++++++++++++++++ - bugzrc.example | 61 -------------- - conf/conf.d/gentoo.conf | 3 + - conf/conf.d/redhat.conf | 3 + - conf/pybugz.conf | 126 ++++++++++++++++++++++++++++ - man/bugz.1 | 18 ++-- - setup.py | 4 + - 12 files changed, 520 insertions(+), 228 deletions(-) - create mode 100644 bugz/errhandling.py - create mode 100644 bugz/log.py - delete mode 100644 bugzrc.example - create mode 100644 conf/conf.d/gentoo.conf - create mode 100644 conf/conf.d/redhat.conf - create mode 100644 conf/pybugz.conf - -diff --git a/bin/bugz b/bin/bugz -index 4e61ddf..6f72fa1 100755 ---- a/bin/bugz -+++ b/bin/bugz -@@ -25,17 +25,16 @@ import sys - import traceback - - from bugz.argparsers import make_parser --from bugz.cli import BugzError, PrettyBugz --from bugz.configfile import get_config -+from bugz.cli import PrettyBugz -+from bugz.errhandling import BugzError -+from bugz.log import * - - def main(): -- parser = make_parser() - - # parse options -+ args = None -+ parser = make_parser() - args = parser.parse_args() -- get_config(args) -- if getattr(args, 'columns') is None: -- setattr(args, 'columns', 0) - - try: - bugz = PrettyBugz(args) -@@ -43,17 +42,17 @@ def main(): - return 0 - - except BugzError, e: -- print ' ! Error: %s' % e -+ log_error(e) - return 1 - - except TypeError, e: -- print ' ! Error: Incorrect number of arguments supplied' -- print -+ # where this comes from? -+ log_error('Incorrect number of arguments supplied') - traceback.print_exc() - return 1 - - except RuntimeError, e: -- print ' ! Error: %s' % e -+ log_error(e) - return 1 - - except KeyboardInterrupt: -diff --git a/bugz/argparsers.py b/bugz/argparsers.py -index d14dd84..4cf3936 100644 ---- a/bugz/argparsers.py -+++ b/bugz/argparsers.py -@@ -258,11 +258,12 @@ def make_parser(): - parser = argparse.ArgumentParser( - epilog = 'use -h after a sub-command for sub-command specific help') - parser.add_argument('--config-file', -+ default = None, - help = 'read an alternate configuration file') - parser.add_argument('--connection', - help = 'use [connection] section of your configuration file') - parser.add_argument('-b', '--base', -- default = 'https://bugs.gentoo.org/xmlrpc.cgi', -+ default = None, - help = 'base URL of Bugzilla') - parser.add_argument('-u', '--user', - help = 'username for commands requiring authentication') -@@ -272,10 +273,15 @@ def make_parser(): - help = 'password command to evaluate for commands requiring authentication') - parser.add_argument('-q', '--quiet', - action='store_true', -+ default=None, - help = 'quiet mode') -+ parser.add_argument('-d', '--debug', -+ type=int, -+ default=None, -+ help = 'debug level (from 0 to 3)') - parser.add_argument('--columns', - type = int, -- help = 'maximum number of columns output should use') -+ help = 'maximum number of columns output should use (0 = unlimited)') - parser.add_argument('--encoding', - help = 'output encoding (default: utf-8).') - parser.add_argument('--skip-auth', -diff --git a/bugz/cli.py b/bugz/cli.py -index 62ba540..7112387 100644 ---- a/bugz/cli.py -+++ b/bugz/cli.py -@@ -1,5 +1,3 @@ --#!/usr/bin/env python -- - import commands - import getpass - from cookielib import CookieJar, LWPCookieJar -@@ -12,6 +10,11 @@ import sys - import tempfile - import textwrap - import xmlrpclib -+import pdb -+ -+from bugz.configfile import discover_configs -+from bugz.log import * -+from bugz.errhandling import BugzError - - try: - import readline -@@ -28,8 +31,8 @@ BUGZ: Any line beginning with 'BUGZ:' will be ignored. - BUGZ: --------------------------------------------------- - """ - --DEFAULT_COOKIE_FILE = '.bugz_cookie' - DEFAULT_NUM_COLS = 80 -+DEFAULT_CONFIG_FILE = '/etc/pybugz/pybugz.conf' - - # - # Auxiliary functions -@@ -119,23 +122,63 @@ def block_edit(comment, comment_from = ''): - else: - return '' - --# --# Bugz specific exceptions --# -- --class BugzError(Exception): -- pass -- - class PrettyBugz: -+ enc = "utf-8" -+ columns = 0 -+ quiet = None -+ skip_auth = None -+ -+ # TODO: -+ # * make this class more library-like (allow user to script on the python -+ # level using this PrettyBugz class) -+ # * get the "__init__" phase into main() and change parameters to accept -+ # only 'settings' structure - def __init__(self, args): -- self.quiet = args.quiet -- self.columns = args.columns or terminal_width() -- self.user = args.user -- self.password = args.password -- self.passwordcmd = args.passwordcmd -- self.skip_auth = args.skip_auth -- -- cookie_file = os.path.join(os.environ['HOME'], DEFAULT_COOKIE_FILE) -+ -+ sys_config = DEFAULT_CONFIG_FILE -+ home_config = getattr(args, 'config_file') -+ setDebugLvl(getattr(args, 'debug')) -+ settings = discover_configs(sys_config, home_config) -+ -+ # use the default connection name -+ conn_name = settings['default'] -+ -+ # check for redefinition by --connection -+ opt_conn = getattr(args, 'connection') -+ if opt_conn != None: -+ conn_name = opt_conn -+ -+ if not conn_name in settings['connections']: -+ raise BugzError("can't find connection '{0}'".format(conn_name)) -+ -+ # get proper 'Connection' instance -+ connection = settings['connections'][conn_name] -+ -+ def fix_con(con, name,opt): -+ if opt != None: -+ setattr(con, name, opt) -+ con.option_change = True -+ -+ fix_con(connection, "base", args.base) -+ fix_con(connection, "quiet", args.quiet) -+ fix_con(connection, "columns", args.columns) -+ connection.columns = int(connection.columns) or terminal_width() -+ fix_con(connection, "user", args.user) -+ fix_con(connection, "password", args.password) -+ fix_con(connection, "password_cmd", args.passwordcmd) -+ fix_con(connection, "skip_auth", args.skip_auth) -+ fix_con(connection, "encoding", args.encoding) -+ -+ # now must the "connection" be complete -+ -+ # propagate layout settings to 'self' -+ self.enc = connection.encoding -+ self.skip_auth = connection.skip_auth -+ self.columns = connection.columns -+ -+ setQuiet(connection.quiet) -+ -+ cookie_file = os.path.expanduser(connection.cookie_file) - self.cookiejar = LWPCookieJar(cookie_file) - - try: -@@ -143,9 +186,7 @@ class PrettyBugz: - except IOError: - pass - -- if getattr(args, 'encoding'): -- self.enc = args.encoding -- else: -+ if not self.enc: - try: - self.enc = locale.getdefaultlocale()[1] - except: -@@ -153,19 +194,9 @@ class PrettyBugz: - if not self.enc: - self.enc = 'utf-8' - -- self.log("Using %s " % args.base) -- self.bz = BugzillaProxy(args.base, cookiejar=self.cookiejar) -- -- def log(self, status_msg, newline = True): -- if not self.quiet: -- if newline: -- print ' * %s' % status_msg -- else: -- print ' * %s' % status_msg, -- -- def warn(self, warn_msg): -- if not self.quiet: -- print ' ! Warning: %s' % warn_msg -+ self.bz = BugzillaProxy(connection.base, cookiejar=self.cookiejar) -+ connection.dump() -+ self.connection = connection - - def get_input(self, prompt): - return raw_input(prompt) -@@ -186,27 +217,29 @@ class PrettyBugz: - """Authenticate a session. - """ - # prompt for username if we were not supplied with it -- if not self.user: -- self.log('No username given.') -- self.user = self.get_input('Username: ') -+ if not self.connection.user: -+ log_info('No username given.') -+ self.connection.user = self.get_input('Username: ') - - # prompt for password if we were not supplied with it -- if not self.password: -- if not self.passwordcmd: -- self.log('No password given.') -- self.password = getpass.getpass() -+ if not self.connection.password: -+ if not self.connection.password_cmd: -+ log_info('No password given.') -+ self.connection.password = getpass.getpass() - else: -- process = subprocess.Popen(self.passwordcmd.split(), shell=False, -- stdout=subprocess.PIPE) -+ cmd = self.connection.password_cmd.split() -+ stdout = stdout=subprocess.PIPE -+ process = subprocess.Popen(cmd, shell=False, stdout=stdout) - self.password, _ = process.communicate() -+ self.connection.password, _ = process.communicate() - - # perform login - params = {} -- params['login'] = self.user -- params['password'] = self.password -+ params['login'] = self.connection.user -+ params['password'] = self.connection.password - if args is not None: - params['remember'] = True -- self.log('Logging in') -+ log_info('Logging in') - try: - self.bz.User.login(params) - except xmlrpclib.Fault as fault: -@@ -217,7 +250,7 @@ class PrettyBugz: - os.chmod(self.cookiejar.filename, 0600) - - def logout(self, args): -- self.log('logging out') -+ log_info('logging out') - try: - self.bz.User.logout() - except xmlrpclib.Fault as fault: -@@ -251,29 +284,39 @@ class PrettyBugz: - else: - log_msg = 'Searching for bugs ' - -- if search_opts: -- self.log(log_msg + 'with the following options:') -- for opt, val in search_opts: -- self.log(' %-20s = %s' % (opt, val)) -- else: -- self.log(log_msg) -- - if not 'status' in params.keys(): -- params['status'] = ['CONFIRMED', 'IN_PROGRESS', 'UNCONFIRMED'] -- elif 'ALL' in params['status']: -+ if self.connection.query_statuses: -+ params['status'] = self.connection.query_statuses -+ else: -+ # this seems to be most portable among bugzillas as each -+ # bugzilla may have its own set of statuses. -+ params['status'] = ['ALL'] -+ -+ if 'ALL' in params['status']: - del params['status'] - -+ if len(params): -+ log_info(log_msg + 'with the following options:') -+ for opt, val in params.items(): -+ log_info(' %-20s = %s' % (opt, str(val))) -+ else: -+ log_info(log_msg) -+ - result = self.bzcall(self.bz.Bug.search, params)['bugs'] - - if not len(result): -- self.log('No bugs found.') -+ log_info('No bugs found.') - else: - self.listbugs(result, args.show_status) - - def get(self, args): - """ Fetch bug details given the bug id """ -- self.log('Getting bug %s ..' % args.bugid) -- result = self.bzcall(self.bz.Bug.get, {'ids':[args.bugid]}) -+ log_info('Getting bug %s ..' % args.bugid) -+ try: -+ result = self.bzcall(self.bz.Bug.get, {'ids':[args.bugid]}) -+ except xmlrpclib.Fault as fault: -+ raise BugzError("Can't get bug #" + str(args.bugid) + ": " \ -+ + fault.faultString) - - for bug in result['bugs']: - self.showbuginfo(bug, args.attachments, args.comments) -@@ -293,7 +336,7 @@ class PrettyBugz: - (args.description_from, e)) - - if not args.batch: -- self.log('Press Ctrl+C at any time to abort.') -+ log_info('Press Ctrl+C at any time to abort.') - - # - # Check all bug fields. -@@ -306,14 +349,14 @@ class PrettyBugz: - while not args.product or len(args.product) < 1: - args.product = self.get_input('Enter product: ') - else: -- self.log('Enter product: %s' % args.product) -+ log_info('Enter product: %s' % args.product) - - # check for component - if not args.component: - while not args.component or len(args.component) < 1: - args.component = self.get_input('Enter component: ') - else: -- self.log('Enter component: %s' % args.component) -+ log_info('Enter component: %s' % args.component) - - # check for version - # FIXME: This default behaviour is not too nice. -@@ -324,14 +367,14 @@ class PrettyBugz: - else: - args.version = 'unspecified' - else: -- self.log('Enter version: %s' % args.version) -+ log_info('Enter version: %s' % args.version) - - # check for title - if not args.summary: - while not args.summary or len(args.summary) < 1: - args.summary = self.get_input('Enter title: ') - else: -- self.log('Enter title: %s' % args.summary) -+ log_info('Enter title: %s' % args.summary) - - # check for description - if not args.description: -@@ -339,7 +382,7 @@ class PrettyBugz: - if len(line): - args.description = line - else: -- self.log('Enter bug description: %s' % args.description) -+ log_info('Enter bug description: %s' % args.description) - - # check for operating system - if not args.op_sys: -@@ -348,7 +391,7 @@ class PrettyBugz: - if len(line): - args.op_sys = line - else: -- self.log('Enter operating system: %s' % args.op_sys) -+ log_info('Enter operating system: %s' % args.op_sys) - - # check for platform - if not args.platform: -@@ -357,7 +400,7 @@ class PrettyBugz: - if len(line): - args.platform = line - else: -- self.log('Enter hardware platform: %s' % args.platform) -+ log_info('Enter hardware platform: %s' % args.platform) - - # check for default priority - if args.priority is None: -@@ -366,7 +409,7 @@ class PrettyBugz: - if len(line): - args.priority = line - else: -- self.log('Enter priority (optional): %s' % args.priority) -+ log_info('Enter priority (optional): %s' % args.priority) - - # check for default severity - if args.severity is None: -@@ -375,7 +418,7 @@ class PrettyBugz: - if len(line): - args.severity = line - else: -- self.log('Enter severity (optional): %s' % args.severity) -+ log_info('Enter severity (optional): %s' % args.severity) - - # check for default alias - if args.alias is None: -@@ -384,7 +427,7 @@ class PrettyBugz: - if len(line): - args.alias = line - else: -- self.log('Enter alias (optional): %s' % args.alias) -+ log_info('Enter alias (optional): %s' % args.alias) - - # check for default assignee - if args.assigned_to is None: -@@ -393,7 +436,7 @@ class PrettyBugz: - if len(line): - args.assigned_to = line - else: -- self.log('Enter assignee (optional): %s' % args.assigned_to) -+ log_info('Enter assignee (optional): %s' % args.assigned_to) - - # check for CC list - if args.cc is None: -@@ -402,7 +445,7 @@ class PrettyBugz: - if len(line): - args.cc = line.split(', ') - else: -- self.log('Enter a CC list (optional): %s' % args.cc) -+ log_info('Enter a CC list (optional): %s' % args.cc) - - # check for URL - if args.url is None: -@@ -422,7 +465,7 @@ class PrettyBugz: - if args.append_command is None: - args.append_command = self.get_input('Append the output of the following command (leave blank for none): ') - else: -- self.log('Append command (optional): %s' % args.append_command) -+ log_info('Append command (optional): %s' % args.append_command) - - # raise an exception if mandatory fields are not specified. - if args.product is None: -@@ -470,7 +513,7 @@ class PrettyBugz: - if len(confirm) < 1: - confirm = args.default_confirm - if confirm[0] not in ('y', 'Y'): -- self.log('Submission aborted') -+ log_info('Submission aborted') - return - - params={} -@@ -498,7 +541,7 @@ class PrettyBugz: - params['url'] = args.url - - result = self.bzcall(self.bz.Bug.create, params) -- self.log('Bug %d submitted' % result['id']) -+ log_info('Bug %d submitted' % result['id']) - - def modify(self, args): - """Modify an existing bug (eg. adding a comment or changing resolution.)""" -@@ -604,16 +647,16 @@ class PrettyBugz: - for bug in result['bugs']: - changes = bug['changes'] - if not len(changes): -- self.log('Added comment to bug %s' % bug['id']) -+ log_info('Added comment to bug %s' % bug['id']) - else: -- self.log('Modified the following fields in bug %s' % bug['id']) -+ log_info('Modified the following fields in bug %s' % bug['id']) - for key in changes.keys(): -- self.log('%-12s: removed %s' %(key, changes[key]['removed'])) -- self.log('%-12s: added %s' %(key, changes[key]['added'])) -+ log_info('%-12s: removed %s' %(key, changes[key]['removed'])) -+ log_info('%-12s: added %s' %(key, changes[key]['added'])) - - def attachment(self, args): - """ Download or view an attachment given the id.""" -- self.log('Getting attachment %s' % args.attachid) -+ log_info('Getting attachment %s' % args.attachid) - - params = {} - params['attachment_ids'] = [args.attachid] -@@ -621,7 +664,7 @@ class PrettyBugz: - result = result['attachments'][args.attachid] - - action = {True:'Viewing', False:'Saving'} -- self.log('%s attachment: "%s"' % -+ log_info('%s attachment: "%s"' % - (action[args.view], result['file_name'])) - safe_filename = os.path.basename(re.sub(r'\.\.', '', - result['file_name'])) -@@ -671,7 +714,7 @@ class PrettyBugz: - params['comment'] = comment - params['is_patch'] = is_patch - result = self.bzcall(self.bz.Bug.add_attachment, params) -- self.log("'%s' has been attached to bug %s" % (filename, bugid)) -+ log_info("'%s' has been attached to bug %s" % (filename, bugid)) - - def listbugs(self, buglist, show_status=False): - for bug in buglist: -@@ -690,7 +733,7 @@ class PrettyBugz: - except UnicodeDecodeError: - print line[:self.columns] - -- self.log("%i bug(s) found." % len(buglist)) -+ log_info("%i bug(s) found." % len(buglist)) - - def showbuginfo(self, bug, show_attachments, show_comments): - FIELDS = ( -diff --git a/bugz/configfile.py b/bugz/configfile.py -index a900245..43a502c 100644 ---- a/bugz/configfile.py -+++ b/bugz/configfile.py -@@ -1,70 +1,155 @@ - import ConfigParser --import os -+import os, glob - import sys -+import pdb - --DEFAULT_CONFIG_FILE = '~/.bugzrc' -- --def config_option(parser, get, section, option): -- if parser.has_option(section, option): -- try: -- if get(section, option) != '': -- return get(section, option) -- else: -- print " ! Error: "+option+" is not set" -- sys.exit(1) -- except ValueError, e: -- print " ! Error: option "+option+" is not in the right format: "+str(e) -- sys.exit(1) -- --def fill_config_option(args, parser, get, section, option): -- value = config_option(parser, get, section, option) -- if value is not None: -- setattr(args, option, value) -- --def fill_config(args, parser, section): -- fill_config_option(args, parser, parser.get, section, 'base') -- fill_config_option(args, parser, parser.get, section, 'user') -- fill_config_option(args, parser, parser.get, section, 'password') -- fill_config_option(args, parser, parser.get, section, 'passwordcmd') -- fill_config_option(args, parser, parser.getint, section, 'columns') -- fill_config_option(args, parser, parser.get, section, 'encoding') -- fill_config_option(args, parser, parser.getboolean, section, 'quiet') -- --def get_config(args): -- config_file = getattr(args, 'config_file') -- if config_file is None: -- config_file = DEFAULT_CONFIG_FILE -- section = getattr(args, 'connection') -- parser = ConfigParser.ConfigParser() -- config_file_name = os.path.expanduser(config_file) -- -- # try to open config file -- try: -- file = open(config_file_name) -- except IOError: -- if getattr(args, 'config_file') is not None: -- print " ! Error: Can't find user configuration file: "+config_file_name -- sys.exit(1) -- else: -- return -- -- # try to parse config file -+from bugz.errhandling import BugzError -+from bugz.log import * -+ -+class Connection: -+ name = "default" -+ base = 'https://bugs.gentoo.org/xmlrpc.cgi' -+ columns = 0 -+ user = None -+ password = None -+ password_cmd = None -+ dbglvl = 0 -+ quiet = None -+ skip_auth = None -+ encoding = "utf-8" -+ cookie_file = "~/.bugz_cookie" -+ option_change = False -+ query_statuses = [] -+ -+ def dump(self): -+ log_info("Using [{0}] ({1})".format(self.name, self.base)) -+ log_debug("User: '{0}'".format(self.user), 3) -+ # loglvl == 4, only for developers (&& only by hardcoding) -+ log_debug("Pass: '{0}'".format(self.password), 10) -+ log_debug("Columns: {0}".format(self.columns), 3) -+ -+def handle_default(settings, newDef): -+ oldDef = str(settings['default']) -+ if oldDef != newDef: -+ log_debug("redefining default connection from '{0}' to '{1}'". \ -+ format(oldDef, newDef), 2) -+ settings['default'] = newDef -+ -+def handle_settings(settings, context, stack, cp, sec_name): -+ log_debug("contains SETTINGS section named [{0}]".format(sec_name), 3) -+ -+ if cp.has_option(sec_name, 'homeconf'): -+ settings['homeconf'] = cp.get(sec_name, 'homeconf') -+ -+ if cp.has_option(sec_name, 'default'): -+ handle_default(settings, cp.get(sec_name, 'default')) -+ -+ # handle 'confdir' ~> explore and push target files into the stack -+ if cp.has_option(sec_name, 'confdir'): -+ confdir = cp.get(sec_name, 'confdir') -+ full_confdir = os.path.expanduser(confdir) -+ wildcard = os.path.join(full_confdir, '*.conf') -+ log_debug("adding wildcard " + wildcard, 3) -+ for cnffile in glob.glob(wildcard): -+ log_debug(" ++ " + cnffile, 3) -+ if cnffile in context['included']: -+ log_debug("skipping (already included)") -+ break -+ stack.append(cnffile) -+ -+def handle_connection(settings, context, stack, parser, name): -+ log_debug("reading connection '{0}'".format(name), 2) -+ connection = None -+ -+ if name in settings['connections']: -+ log_debug("redefining connection '{0}'".format(name), 2) -+ connection = settings['connections'][name] -+ else: -+ connection = Connection() -+ connection.name = name -+ -+ def fill(conn, id): -+ if parser.has_option(name, id): -+ val = parser.get(name, id) -+ setattr(conn, id, val) -+ log_debug("has {0} - {1}".format(id, val), 3) -+ -+ fill(connection, "base") -+ fill(connection, "user") -+ fill(connection, "password") -+ fill(connection, "encoding") -+ fill(connection, "columns") -+ fill(connection, "quiet") -+ -+ if parser.has_option(name, 'query_statuses'): -+ line = parser.get(name, 'query_statuses') -+ lines = line.split() -+ connection.query_statuses = lines -+ -+ settings['connections'][name] = connection -+ -+def parse_file(settings, context, stack): -+ file_name = stack.pop() -+ full_name = os.path.expanduser(file_name) -+ -+ context['included'][full_name] = None -+ -+ log_debug("parsing '" + file_name + "'", 1) -+ -+ cp = ConfigParser.ConfigParser() -+ parsed = None - try: -- parser.readfp(file) -- sections = parser.sections() -- except ConfigParser.ParsingError, e: -- print " ! Error: Can't parse user configuration file: "+str(e) -- sys.exit(1) -- -- # parse the default section first -- if "default" in sections: -- fill_config(args, parser, "default") -- if section is None: -- section = config_option(parser, parser.get, "default", "connection") -- -- # parse a specific section -- if section in sections: -- fill_config(args, parser, section) -- elif section is not None: -- print " ! Error: Can't find section ["+section+"] in configuration file" -- sys.exit(1) -+ parsed = cp.read(full_name) -+ if parsed != [ full_name ]: -+ raise BugzError("problem with file '" + file_name + "'") -+ except ConfigParser.Error, err: -+ msg = err.message -+ raise BugzError("can't parse: '" + file_name + "'\n" + msg ) -+ -+ # successfully parsed file -+ -+ for sec in cp.sections(): -+ sectype = "connection" -+ -+ if cp.has_option(sec, 'type'): -+ sectype = cp.get(sec, 'type') -+ -+ if sectype == "settings": -+ handle_settings(settings, context, stack, cp, sec) -+ -+ if sectype == "connection": -+ handle_connection(settings, context, stack, cp, sec) -+ -+def discover_configs(file, homeConf=None): -+ settings = { -+ # where to look for user's configuration -+ 'homeconf' : '~/.bugzrc', -+ # list of objects of Connection -+ 'connections' : {}, -+ # the default Connection name -+ 'default' : None, -+ } -+ context = { -+ 'where' : 'sys', -+ 'homeparsed' : False, -+ 'included' : {}, -+ } -+ stack = [ file ] -+ -+ # parse sys configs -+ while len(stack) > 0: -+ parse_file(settings, context, stack) -+ -+ if not homeConf: -+ # the command-line option must win -+ homeConf = settings['homeconf'] -+ -+ if not os.path.isfile(os.path.expanduser(homeConf)): -+ return settings -+ -+ # parse home configs -+ stack = [ homeConf ] -+ while len(stack) > 0: -+ parse_file(settings, context, stack) -+ -+ return settings -diff --git a/bugz/errhandling.py b/bugz/errhandling.py -new file mode 100644 -index 0000000..d3fec06 ---- /dev/null -+++ b/bugz/errhandling.py -@@ -0,0 +1,6 @@ -+# -+# Bugz specific exceptions -+# -+ -+class BugzError(Exception): -+ pass -diff --git a/bugz/log.py b/bugz/log.py -new file mode 100644 -index 0000000..df4bb9a ---- /dev/null -+++ b/bugz/log.py -@@ -0,0 +1,72 @@ -+# TODO: use the python's 'logging' feature? -+ -+dbglvl = 0 -+quiet = False -+ -+LogSettins = { -+ 'W' : { -+ 'symb' : '!', -+ 'word' : 'Warn', -+ }, -+ 'E' : { -+ 'symb' : '#', -+ 'word' : 'Error', -+ }, -+ 'D' : { -+ 'symb' : '~', -+ 'word' : 'Dbg', -+ }, -+ 'I' : { -+ 'symb' : '*', -+ 'word' : 'Info', -+ }, -+ '!' : { -+ 'symb' : '!', -+ 'word' : 'UNKNWN', -+ }, -+} -+ -+def setQuiet(newQuiet): -+ global quiet -+ quiet = newQuiet -+ -+def setDebugLvl(newlvl): -+ global dbglvl -+ if not newlvl: -+ return -+ if newlvl > 3: -+ log_warn("bad debug level '{0}', using '3'".format(str(newlvl))) -+ dbglvl = 3 -+ else: -+ dbglvl = newlvl -+ -+def formatOut(msg, id='!'): -+ lines = str(msg).split('\n') -+ start = True -+ symb=LogSettins[id]['symb'] -+ word=LogSettins[id]['word'] + ":" -+ -+ for line in lines: -+ print ' ' + symb + ' ' + line -+ -+def log_error(string): -+ formatOut(string, 'E') -+ return -+ -+def log_warn(string): -+ formatOut(string, 'W') -+ return -+ -+def log_info(string): -+ global quiet -+ global dbglvl -+ # debug implies info -+ if not quiet or dbglvl: -+ formatOut(string, 'I') -+ return -+ -+def log_debug(string, verboseness=1): -+ global dbglvl -+ if dbglvl >= verboseness: -+ formatOut(string, 'D') -+ return -diff --git a/bugzrc.example b/bugzrc.example -deleted file mode 100644 -index f516bf0..0000000 ---- a/bugzrc.example -+++ /dev/null -@@ -1,61 +0,0 @@ --# --# bugzrc.example - an example configuration file for pybugz --# --# This file consists of sections which define parameters for each --# bugzilla you plan to use. --# --# Each section begins with a name in square brackets. This is also the --# name that should be used with the --connection parameter to the bugz --# command. --# --# Each section of this file consists of lines in the form: --# key: value --# as listed below. --# --# [sectionname] --# --# The base url of the bugzilla you wish to use. --# This must point to the xmlrpc.cgi script on the bugzilla installation. --# --# base: http://my.project.com/bugzilla/xmlrpc.cgi --# --# It is also possible to encode a username and password into this URL --# for basic http authentication as follows: --# --# base: http://myhttpname:myhttppasswd@my.project.com/bugzilla/xmlrpc.cgi --# --# Next are your username and password for this bugzilla. If you do not --# provide these, you will be prompted for them. --# --# user: myname@my.project.com --# password: secret2 --# --# As an alternative to keeping your password in this file you can provide a --# password command. It is evaluated and pybugz expects this command to output --# the password to standard out. E.g.: --# --# passwordcmd: gpg2 --decrypt /myhome/.my-encrypted-password.gpg --# --# The number of columns your terminal can display. --# Most of the time you should not have to set this. --# --# columns: 80 --# --# Set the output encoding for pybugz. --# --# encoding: utf-8 --# --# Run in quiet mode. --# --# quiet: True --# --# The special section named 'default' may also be used. Other sections will --# override any values specified here. The optional special key 'connection' is --# used to name the default connection, to use when no --connection parameter is --# specified to the bugz command. --# --# [default] --# connection: sectionname --# --# All parameters listed above can be used in the default section if you --# only use one bugzilla installation. -diff --git a/conf/conf.d/gentoo.conf b/conf/conf.d/gentoo.conf -new file mode 100644 -index 0000000..42fae46 ---- /dev/null -+++ b/conf/conf.d/gentoo.conf -@@ -0,0 +1,3 @@ -+[Gentoo] -+base = https://bugs.gentoo.org/xmlrpc.cgi -+query_statuses = CONFIRMED IN_PROGRESS UNCONFIRMED -diff --git a/conf/conf.d/redhat.conf b/conf/conf.d/redhat.conf -new file mode 100644 -index 0000000..0f50fb7 ---- /dev/null -+++ b/conf/conf.d/redhat.conf -@@ -0,0 +1,3 @@ -+[RedHat] -+base = https://bugzilla.redhat.com/xmlrpc.cgi -+query_statuses = NEW ASSIGNED MODIFIED ON_DEV POST -diff --git a/conf/pybugz.conf b/conf/pybugz.conf -new file mode 100644 -index 0000000..3a486b4 ---- /dev/null -+++ b/conf/pybugz.conf -@@ -0,0 +1,126 @@ -+# =========================================================================== -+# The "root" configuration file of PyBugz bugzilla interface. -+# =========================================================================== -+# -+# Overview -+# ======== -+# PyBugz is configured by hierarchy of *.conf files. All the configuration -+# job starts from this file. User specific configuration is by default in -+# file ~/.bugzrc. This is specially usefull to allow user to redefine some -+# system configuration (an example could be adding user's credentials -+# for specific connection — see the following text). -+# -+# Syntax -+# ====== -+# The syntax is similar to Windows INI files. For more info, see the -+# documentation for python's ConfigParser library class — this class is used -+# for parsing configuration files here. Quickly, each file consists of -+# sections (section's name in brackets, e.g. [section]). Each section -+# consists of set of configuration options separated by newlines. -+# -+# [sectionName] -+# optionA = value A -+# optionB = this is value of B # comments are possible -+# -+# Section types -+# ============= -+# Currently, there are implemented two types of sections in PyBugz. Those -+# are 'connection' (default type of section) and 'settings'. -+# Type 'settings' has purpose for setting up some global feature of PyBugz. -+# The type 'connection', however, describes attributes of particular -+# connection to some concrete instance of bugzilla. -+# -+# +------------------------+ -+# | 1. "type = connection" | -+# +------------------------+ -+# -+# Important property of this type is its section identifier (name of -+# section). By passing this name as an argument of --connection option is -+# PyBugz's user able to select which connection will be used. -+# -+# Accepted options / semantics -+# ---------------------------- -+# -+# Note that you may specify each section of type 'connection' multiple -+# times (using the same ID). All settings are combined among same named -+# sections with one rule: the last one wins. This is important when you -+# want to specify some defaults system wide and let particular user -+# redefine (or correct) concrete connection — user's configuration is -+# loaded _later_ than system's. -+# -+# * type -+# May be set optionally to 'connection', but it is the default in each -+# section. -+# -+# * base -+# Sets up the xmlrpc entrance into bugzilla, for example: -+# https://bugzilla.redhat.com/xmlrpc.cgi -+# -+# * user & password -+# These two options let you specify your login information to bugzilla -+# instance (you must be registered there of course). It is also -+# possible to encode a user (usually user's email) and password into -+# base: -+# http://myhttpname:myhttppasswd@my.project.com/bugzilla/xmlrpc.cgi -+# Note that if you don't specify your login information, you will be -+# prompted for them. -+# -+# * passwordcmd -+# As an alternative to keeping your password in this file you can -+# provide a password command. It is evaluated and pybugz expects this -+# command to output the password to standard out. E.g.: -+# -+# passwordcmd = gpg2 --decrypt /myhome/.my-encrypted-password.gpg -+# -+# * columns -+# The number of columns your terminal can display (or you want to be -+# displayed) during using of this connection. Expects integer number. -+# -+# * query_statuses -+# List of bug-statuses to be displayed by default (when *not* redefined by -+# --status option). Accepts list of properly spelled statuses separated -+# by single space, e.g.: query_statuses = ASSIGNED CLOSED -+# -+# * encoding -+# Set the output encoding for PyBugz. Default is utf-8. -+# -+# * quiet -+# Run this connection in quiet mode when: quiet = True. -+# -+# * inherit (to be done in future) -+# -+# +----------------------+ -+# | 2. "type = settings" | -+# +----------------------+ -+# -+# Again, this lets you define PyBugz "global" settings (among all -+# connections). The name of section is not important here. Same as -+# 'connection' type, even this type of section you may define multiple -+# times — options are combined then (and the latest wins). -+# -+# There are several accepted options (now): -+# -+# * type -+# Here the type must be set to 'settings'. This is requirement for pybugz -+# to interpret this section as you want. -+# -+# * default -+# Lets you define the default connection (when the --connection option is -+# not passed). -+# -+# * homeconf -+# Let's you define where to look for user's configuration file. This is -+# by default ~/.bugzrc file. Note that this option makes sense only for -+# system-wide configuration file. -+# -+# * confdir -+# This option lets you define the configuration directory. This directory -+# is searched for *.conf files, and these files (if any) are parsed -+# immediately after specifying configuration file. -+ -+[settings] -+ -+type = settings -+homeconf = ~/.bugzrc -+confdir = /etc/pybugz/conf.d/ -+default = Gentoo -diff --git a/man/bugz.1 b/man/bugz.1 -index 628eae9..fbf2e6c 100644 ---- a/man/bugz.1 -+++ b/man/bugz.1 -@@ -1,8 +1,8 @@ - .\" Hey, Emacs! This is an -*- nroff -*- source file. --.\" Copyright (c) 2011 William Hubbs -+.\" Copyright (c) 2011, 2012, 2013 William Hubbs - .\" This is free software; see the GNU General Public Licence version 2 - .\" or later for copying conditions. There is NO warranty. --.TH bugz 1 "17 Feb 2011" "0.9.0" -+.TH bugz 1 "20 Jan 2013" "0.10.2" - .nh - .SH NAME - bugz \(em command line interface to bugzilla -@@ -20,10 +20,10 @@ bugz \(em command line interface to bugzilla - .\" .B \-o value, \-\^\-long=value - .\" Describe the option. - .SH DESCRIPTION --Bugz is a cprogram which gives you access to the features of the -+Bugz is a program which gives you access to the features of the - bugzilla bug tracking system from the command line. - .PP --This man page is a stub; the bugs program has extensive built in help. -+This man page is a stub; the bugz program has extensive built in help. - .B bugz -h - will show the help for the global options and - .B bugz [subcommand] -h -@@ -32,8 +32,14 @@ will show the help for a specific subcommand. - .PP - The home page of this project is http://www.github.com/williamh/pybugz. - Bugs should be reported to the bug tracker there. --.\" .SH SEE ALSO --.\" .PP -+.SH SEE ALSO -+.PP -+For documentation how to configure PyBugz take a look into distributed -+.B pybugz.conf -+file. User specific configuration may be defined in -+.B -+~/.bugzrc -+file. - .SH AUTHOR - .PP - The original author is Alastair Tse . -diff --git a/setup.py b/setup.py -index e9a8a52..15f004c 100644 ---- a/setup.py -+++ b/setup.py -@@ -18,5 +18,9 @@ setup( - platforms = ['any'], - packages = ['bugz'], - scripts = ['bin/bugz'], -+ data_files = [ -+ ('/etc/pybugz', ['conf/pybugz.conf']), -+ ('/etc/pybugz/conf.d', ['conf/conf.d/redhat.conf', 'conf/conf.d/gentoo.conf']), -+ ], - cmdclass = {'build_py': build_py, 'build_scripts': build_scripts}, - ) --- -1.7.11.7 - diff --git a/pybugz-0.10-git89df2-rhel-fedora-cust.patch b/pybugz-0.10-git89df2-rhel-fedora-cust.patch deleted file mode 100644 index 8a05846..0000000 --- a/pybugz-0.10-git89df2-rhel-fedora-cust.patch +++ /dev/null @@ -1,22 +0,0 @@ -From e6ceb40155df600cbc2bba6a593c55fe327a3987 Mon Sep 17 00:00:00 2001 -From: Pavel Raiskup -Date: Sun, 20 Jan 2013 15:20:45 +0100 -Subject: [PATCH] Refine for Fedora purposes - ---- - conf/pybugz.conf | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/conf/pybugz.conf b/conf/pybugz.conf -index 3a486b4..1bd5176 100644 ---- a/conf/pybugz.conf -+++ b/conf/pybugz.conf -@@ -123,4 +123,4 @@ - type = settings - homeconf = ~/.bugzrc - confdir = /etc/pybugz/conf.d/ --default = Gentoo -+default = RedHat --- -1.7.11.7 - diff --git a/pybugz.spec b/pybugz.spec index 9037eb4..58fc588 100644 --- a/pybugz.spec +++ b/pybugz.spec @@ -1,11 +1,11 @@ -%global gitrev 89df2 +%global gitrev 683dd %global posttag git%{gitrev} %global snapshot %{version}-%{posttag} Name: pybugz Summary: Command line interface for Bugzilla written in Python Version: 0.10 -Release: 2.%{posttag}%{?dist} +Release: 3.%{posttag}%{?dist} Group: Applications/Communications License: GPLv2 URL: https://github.com/williamh/pybugz @@ -28,10 +28,9 @@ BuildRequires: bash-completion pkgconfig # script (in dist-git). Source0: %{name}-%{snapshot}.tar.gz -# follow https://github.com/praiskup/pybugz changes (until accepted by upstream) -Patch0: %{name}-%{snapshot}-downstream.patch -# make the installation better satisfy RHEL / Fedora purposes -Patch1: %{name}-%{snapshot}-rhel-fedora-cust.patch +# make the installation better useful for bugzilla.redhat.com users +# ~> downstream +Patch0: %{name}-0.10-git683dd-rh-specific.patch %description Pybugz was conceived as a tool to speed up the work-flow for Gentoo Linux @@ -42,8 +41,7 @@ comfortably from the command line. %prep %setup -q -n %{name}-%{snapshot} -%patch0 -p1 -b .downstream -%patch1 -p1 -b .rhel-fedora-cust +%patch0 -p1 -b .rh-specific %build %{__python} setup.py build @@ -74,10 +72,13 @@ mkdir -p %{buildroot}%{_docdir} %endif %{python_sitelib}/%{name}-*.egg-info %{_mandir}/man1/bugz.1.gz -%config(noreplace) %{_sysconfdir}/pybugz -%doc README LICENSE +%doc README LICENSE bugzrc.example %changelog +* Wed Mar 13 2013 Pavel Raiskup - 0.10-3.git683dd +- remove downstream patches for now - I'll reapply again if it becomes upstream +- package the latest git version + * Thu Feb 14 2013 Fedora Release Engineering - 0.10-2.git89df2 - Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild diff --git a/sources b/sources index 8410195..5d175a3 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -1b92781ac57ad7cd8b967a8a0ced3557 pybugz-0.10-git89df2.tar.gz +8bc25cc5d117e6e6a4801dcf8089c8a6 pybugz-0.10-git683dd.tar.gz