summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPavel Raiskup <pavel@raiskup.cz>2013-01-20 23:46:21 +0100
committerPavel Raiskup <pavel@raiskup.cz>2013-01-20 23:46:21 +0100
commit0261ba15f9d370f05be40b819752a2b25ed3980b (patch)
tree69b85d20031c92ec277a468a1d8598c9550db596
parentfa487f26b8bcf1749623e2e3dc0af509d96560c5 (diff)
downloadpybugz-master.tar.gz
pybugz-master.tar.xz
pybugz-master.zip
Remove old patch.HEADmaster
-rw-r--r--pybugz-0.10-git69cd7-downstream.patch1180
1 files changed, 0 insertions, 1180 deletions
diff --git a/pybugz-0.10-git69cd7-downstream.patch b/pybugz-0.10-git69cd7-downstream.patch
deleted file mode 100644
index ee34dd6..0000000
--- a/pybugz-0.10-git69cd7-downstream.patch
+++ /dev/null
@@ -1,1180 +0,0 @@
-From a484ef7733937d564f65325ff0d87bbf6a3fa1f5 Mon Sep 17 00:00:00 2001
-From: Pavel Raiskup <pavel@raiskup.cz>
-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 <alastair@liquidx.net>.
-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
-