[pybugz/el6] Upload the code

Pavel Raiskup praiskup at fedoraproject.org
Tue Jan 22 16:50:19 UTC 2013


commit d2826e3f68a353b813460b53f8e9397a1247b13e
Author: Pavel Raiskup <praiskup at redhat.com>
Date:   Tue Jan 22 17:45:26 2013 +0100

    Upload the code
    
    Resolves: #902025

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


More information about the scm-commits mailing list