From 7ef456b18416069bfb16f2a73f7555d3c14cb990 Mon Sep 17 00:00:00 2001 From: Pavel Raiskup Date: Sun, 20 Jan 2013 23:01:05 +0100 Subject: Fix Merge-Review problems --- .gitignore | 3 +- prepare-tarball.sh | 7 +- pybugz-0.10-1.git69cd7.fc17.src.rpm | Bin 0 -> 41131 bytes pybugz-0.10-git69cd7-downstream.patch | 1180 +++++++++++++++++++++++++++ pybugz-0.10-git69cd7-rhel-fedora-cust.patch | 22 + pybugz-0.10git69cd7-1.fc17.src.rpm | Bin 40652 -> 0 bytes pybugz-0.10git69cd7-downstream.patch | 1180 --------------------------- pybugz-0.10git69cd7-rhel-fedora-cust.patch | 22 - pybugz-0.10git69cd7.tar.gz | Bin 23093 -> 0 bytes pybugz.spec | 27 +- 10 files changed, 1224 insertions(+), 1217 deletions(-) create mode 100644 pybugz-0.10-1.git69cd7.fc17.src.rpm create mode 100644 pybugz-0.10-git69cd7-downstream.patch create mode 100644 pybugz-0.10-git69cd7-rhel-fedora-cust.patch delete mode 100644 pybugz-0.10git69cd7-1.fc17.src.rpm delete mode 100644 pybugz-0.10git69cd7-downstream.patch delete mode 100644 pybugz-0.10git69cd7-rhel-fedora-cust.patch delete mode 100644 pybugz-0.10git69cd7.tar.gz diff --git a/.gitignore b/.gitignore index eb6c867..23e0d91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -pybugz-0.10git69cd7/ +pybugz-*/ noarch *.src.rpm +*.tar.gz review-pybugz diff --git a/prepare-tarball.sh b/prepare-tarball.sh index a75c61a..84e7b02 100755 --- a/prepare-tarball.sh +++ b/prepare-tarball.sh @@ -2,20 +2,19 @@ # grep the spec file for version number VERSION=$( cat pybugz.spec | grep ^Version: | cut -d' ' -f 2- | tr -d ' ') +REV=$( cat pybugz.spec | grep '%global gitrev' | cut -d' ' -f 3- | tr -d ' ') -BASE=pybugz-$VERSION +BASE="pybugz-${VERSION}-git${REV}" TARBALL=$BASE.tar.gz DIR=$( mktemp -d ) GIT=https://github.com/williamh/pybugz.git -REVISION=`echo $VERSION | sed 's/.*git//'` - echo == preparing tarball for pybugz-$VERSION == pushd $DIR > /dev/null && \ git clone $GIT pybugz && \ cd pybugz && \ -git archive --prefix pybugz-$VERSION/ $REVISION | gzip > $TARBALL && \ +git archive --prefix $BASE/ $REV | gzip > $TARBALL && \ popd > /dev/null && \ cp $DIR/pybugz/$TARBALL . && \ echo == DONE == && \ diff --git a/pybugz-0.10-1.git69cd7.fc17.src.rpm b/pybugz-0.10-1.git69cd7.fc17.src.rpm new file mode 100644 index 0000000..556985b Binary files /dev/null and b/pybugz-0.10-1.git69cd7.fc17.src.rpm differ diff --git a/pybugz-0.10-git69cd7-downstream.patch b/pybugz-0.10-git69cd7-downstream.patch new file mode 100644 index 0000000..ee34dd6 --- /dev/null +++ b/pybugz-0.10-git69cd7-downstream.patch @@ -0,0 +1,1180 @@ +From a484ef7733937d564f65325ff0d87bbf6a3fa1f5 Mon Sep 17 00:00:00 2001 +From: Pavel Raiskup +Date: Sun, 20 Jan 2013 10:33:59 +0100 +Subject: [PATCH] Downstream patch to follow + https://github.com/praiskup/pybugz + +--- + bin/bugz | 30 +++---- + bugz/argparsers.py | 12 ++- + bugz/cli.py | 219 +++++++++++++++++++++++++++++------------------- + 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 ++++++++++++++++++++++++++++ + lbugz | 2 +- + man/bugz.1 | 18 ++-- + setup.py | 4 + + 13 files changed, 536 insertions(+), 237 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 94de59f..6f72fa1 100755 +--- a/bin/bugz ++++ b/bin/bugz +@@ -25,43 +25,43 @@ 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) + args.func(bugz, args) ++ return 0 + + except BugzError, e: +- print ' ! Error: %s' % e +- sys.exit(-1) ++ 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() +- sys.exit(-1) ++ return 1 + + except RuntimeError, e: +- print ' ! Error: %s' % e +- sys.exit(-1) ++ log_error(e) ++ return 1 + + except KeyboardInterrupt: + print + print 'Stopped.' +- sys.exit(-1) ++ return 1 + + except: + raise + + if __name__ == "__main__": +- main() ++ sys.exit(main()) +diff --git a/bugz/argparsers.py b/bugz/argparsers.py +index 2dbdb1a..e287352 100644 +--- a/bugz/argparsers.py ++++ b/bugz/argparsers.py +@@ -256,11 +256,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') +@@ -270,10 +271,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('--columns', ++ 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 a533da9..9a84519 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,36 +217,44 @@ 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') +- self.bz.User.login(params) ++ log_info('Logging in') ++ try: ++ self.bz.User.login(params) ++ except xmlrpclib.Fault as fault: ++ raise BugzError("Can't login: " + fault.faultString) + + if args is not None: + self.cookiejar.save() + os.chmod(self.cookiejar.filename, 0600) + + def logout(self, args): +- self.log('logging out') +- self.bz.User.logout() ++ log_info('logging out') ++ try: ++ self.bz.User.logout() ++ except xmlrpclib.Fault as fault: ++ raise BugzError("Failed to logout: " + fault.faultString) + + def search(self, args): + """Performs a search on the bugzilla database with the keywords given on the title (or the body if specified). +@@ -245,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) +@@ -287,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. +@@ -300,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. +@@ -318,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: +@@ -333,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: +@@ -342,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: +@@ -351,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: +@@ -360,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: +@@ -369,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: +@@ -378,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: +@@ -387,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: +@@ -396,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) + + # fixme: groups + +@@ -407,7 +456,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: +@@ -454,7 +503,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={} +@@ -480,7 +529,7 @@ class PrettyBugz: + params['cc'] = args.cc + + 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.)""" +@@ -586,16 +635,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] +@@ -603,7 +652,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'])) +@@ -653,7 +702,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: +@@ -672,7 +721,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/lbugz b/lbugz +index f1b1b7a..2329859 100755 +--- a/lbugz ++++ b/lbugz +@@ -25,4 +25,4 @@ if os.path.exists(pkg) and os.path.exists(script): + os.environ['PYTHONPATH'] = path + ':' + os.environ['PYTHONPATH'] + else: + os.environ['PYTHONPATH'] = path + ':' +- subprocess.call(args) ++ sys.exit(subprocess.call(args)) +diff --git a/man/bugz.1 b/man/bugz.1 +index 628eae9..34ef561 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 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-git69cd7-rhel-fedora-cust.patch b/pybugz-0.10-git69cd7-rhel-fedora-cust.patch new file mode 100644 index 0000000..8a05846 --- /dev/null +++ b/pybugz-0.10-git69cd7-rhel-fedora-cust.patch @@ -0,0 +1,22 @@ +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-0.10git69cd7-1.fc17.src.rpm b/pybugz-0.10git69cd7-1.fc17.src.rpm deleted file mode 100644 index 7ab5b60..0000000 Binary files a/pybugz-0.10git69cd7-1.fc17.src.rpm and /dev/null differ diff --git a/pybugz-0.10git69cd7-downstream.patch b/pybugz-0.10git69cd7-downstream.patch deleted file mode 100644 index ee34dd6..0000000 --- a/pybugz-0.10git69cd7-downstream.patch +++ /dev/null @@ -1,1180 +0,0 @@ -From a484ef7733937d564f65325ff0d87bbf6a3fa1f5 Mon Sep 17 00:00:00 2001 -From: Pavel Raiskup -Date: Sun, 20 Jan 2013 10:33:59 +0100 -Subject: [PATCH] Downstream patch to follow - https://github.com/praiskup/pybugz - ---- - bin/bugz | 30 +++---- - bugz/argparsers.py | 12 ++- - bugz/cli.py | 219 +++++++++++++++++++++++++++++------------------- - 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 ++++++++++++++++++++++++++++ - lbugz | 2 +- - man/bugz.1 | 18 ++-- - setup.py | 4 + - 13 files changed, 536 insertions(+), 237 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 94de59f..6f72fa1 100755 ---- a/bin/bugz -+++ b/bin/bugz -@@ -25,43 +25,43 @@ 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) - args.func(bugz, args) -+ return 0 - - except BugzError, e: -- print ' ! Error: %s' % e -- sys.exit(-1) -+ 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() -- sys.exit(-1) -+ return 1 - - except RuntimeError, e: -- print ' ! Error: %s' % e -- sys.exit(-1) -+ log_error(e) -+ return 1 - - except KeyboardInterrupt: - print - print 'Stopped.' -- sys.exit(-1) -+ return 1 - - except: - raise - - if __name__ == "__main__": -- main() -+ sys.exit(main()) -diff --git a/bugz/argparsers.py b/bugz/argparsers.py -index 2dbdb1a..e287352 100644 ---- a/bugz/argparsers.py -+++ b/bugz/argparsers.py -@@ -256,11 +256,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') -@@ -270,10 +271,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('--columns', -+ 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 a533da9..9a84519 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,36 +217,44 @@ 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') -- self.bz.User.login(params) -+ log_info('Logging in') -+ try: -+ self.bz.User.login(params) -+ except xmlrpclib.Fault as fault: -+ raise BugzError("Can't login: " + fault.faultString) - - if args is not None: - self.cookiejar.save() - os.chmod(self.cookiejar.filename, 0600) - - def logout(self, args): -- self.log('logging out') -- self.bz.User.logout() -+ log_info('logging out') -+ try: -+ self.bz.User.logout() -+ except xmlrpclib.Fault as fault: -+ raise BugzError("Failed to logout: " + fault.faultString) - - def search(self, args): - """Performs a search on the bugzilla database with the keywords given on the title (or the body if specified). -@@ -245,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) -@@ -287,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. -@@ -300,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. -@@ -318,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: -@@ -333,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: -@@ -342,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: -@@ -351,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: -@@ -360,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: -@@ -369,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: -@@ -378,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: -@@ -387,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: -@@ -396,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) - - # fixme: groups - -@@ -407,7 +456,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: -@@ -454,7 +503,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={} -@@ -480,7 +529,7 @@ class PrettyBugz: - params['cc'] = args.cc - - 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.)""" -@@ -586,16 +635,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] -@@ -603,7 +652,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'])) -@@ -653,7 +702,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: -@@ -672,7 +721,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/lbugz b/lbugz -index f1b1b7a..2329859 100755 ---- a/lbugz -+++ b/lbugz -@@ -25,4 +25,4 @@ if os.path.exists(pkg) and os.path.exists(script): - os.environ['PYTHONPATH'] = path + ':' + os.environ['PYTHONPATH'] - else: - os.environ['PYTHONPATH'] = path + ':' -- subprocess.call(args) -+ sys.exit(subprocess.call(args)) -diff --git a/man/bugz.1 b/man/bugz.1 -index 628eae9..34ef561 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 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.10git69cd7-rhel-fedora-cust.patch b/pybugz-0.10git69cd7-rhel-fedora-cust.patch deleted file mode 100644 index 8a05846..0000000 --- a/pybugz-0.10git69cd7-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-0.10git69cd7.tar.gz b/pybugz-0.10git69cd7.tar.gz deleted file mode 100644 index f121ab9..0000000 Binary files a/pybugz-0.10git69cd7.tar.gz and /dev/null differ diff --git a/pybugz.spec b/pybugz.spec index d35f2b8..28af878 100644 --- a/pybugz.spec +++ b/pybugz.spec @@ -1,7 +1,11 @@ +%global gitrev 69cd7 +%global posttag git%{gitrev} +%global snapshot %{version}-%{posttag} + Name: pybugz Summary: Command line interface for Bugzilla written in Python -Version: 0.10git69cd7 -Release: 1%{?dist} +Version: 0.10 +Release: 1.%{posttag}%{?dist} Group: Applications/Communications License: GPLv2 URL: https://github.com/williamh/pybugz @@ -10,9 +14,6 @@ BuildArch: noarch Requires: python2 BuildRequires: python2-devel -# we don't need debuginfo package -%global debug_package %{nil} - %if ! 0%{?rhel} # no bash-completion for RHEL %global bash_completion 1 @@ -25,12 +26,12 @@ BuildRequires: bash-completion pkgconfig # There is possible to download upstream tarball generated by github, but it is # quite old now. For HOWTO obtain correct tarball see the "prepare-tarball.sh" # script. -Source0: %{name}-%{version}.tar.gz +Source0: %{name}-%{snapshot}.tar.gz # follow https://github.com/praiskup/pybugz changes (until accepted by upstream) -Patch0: %{name}-%{version}-downstream.patch +Patch0: %{name}-%{snapshot}-downstream.patch # make the installation better satisfy RHEL / Fedora purposes -Patch1: %{name}-%{version}-rhel-fedora-cust.patch +Patch1: %{name}-%{snapshot}-rhel-fedora-cust.patch %description Pybugz was conceived as a tool to speed up the work-flow for Gentoo Linux @@ -40,7 +41,7 @@ quickly. Developers alike can easily extract attachments and close bugs comfortably from the command line. %prep -%setup -q +%setup -q -n %{name}-%{snapshot} %patch0 -p1 -b .downstream %patch1 -p1 -b .rhel-fedora-cust @@ -55,7 +56,7 @@ comfortably from the command line. %if %{?bash_completion} # find the proper directory to install bash-completion script mkdir -p %{buildroot}%{bash_cmpl_dir} - cp %{_builddir}/%{name}-%{version}/contrib/bash-completion \ + cp %{_builddir}/%{name}-%{snapshot}/contrib/bash-completion \ %{buildroot}%{bash_cmpl_dir}/bugz %endif @@ -77,6 +78,12 @@ mkdir -p %{buildroot}%{_docdir} %doc README LICENSE %changelog +* Sun Jan 20 2013 Pavel Raiskup - 0.10-1.git69cd7 +- changes for problems spotted/fixed by Scott Tsai in merge-review bug: +- important change - move git revision behind the release number +- reflect that ^^^ change in changelog +- remove statement disabling debuginfo (it is not needed) + * Sun Jan 20 2013 Pavel Raiskup - 0.10git69cd7-1 - apply downstream patches to reflect https://github.com/praiskup/pybugz it allows hierarchy of configuration files and a bit better error handling -- cgit