[Reportbug-commits] r627 - in branches/manual_merge/reportbug (14 files)
morph-guest at users.alioth.debian.org
morph-guest at users.alioth.debian.org
Sat Aug 16 21:53:51 UTC 2008
Date: Saturday, August 16, 2008 @ 21:53:50
Author: morph-guest
Revision: 627
pre-merge: step 3: reportbuglib -> reportbug
Added:
branches/manual_merge/reportbug/__init__.py
(from rev 625, branches/manual_merge/reportbuglib/__init__.py)
branches/manual_merge/reportbug/checkbuildd.py
(from rev 625, branches/manual_merge/reportbuglib/checkbuildd.py)
branches/manual_merge/reportbug/checkversions.py
(from rev 625, branches/manual_merge/reportbuglib/checkversions.py)
branches/manual_merge/reportbug/debianbts.py
(from rev 625, branches/manual_merge/reportbuglib/debianbts.py)
branches/manual_merge/reportbug/hiermatch.py
(from rev 625, branches/manual_merge/reportbuglib/hiermatch.py)
branches/manual_merge/reportbug/rbtempfile.py
(from rev 625, branches/manual_merge/reportbuglib/rbtempfile.py)
branches/manual_merge/reportbug/reportbug.py
(from rev 625, branches/manual_merge/reportbuglib/reportbug.py)
branches/manual_merge/reportbug/reportbug_exceptions.py
(from rev 625, branches/manual_merge/reportbuglib/reportbug_exceptions.py)
branches/manual_merge/reportbug/reportbug_submit.py
(from rev 625, branches/manual_merge/reportbuglib/reportbug_submit.py)
branches/manual_merge/reportbug/reportbug_ui_newt.py
(from rev 625, branches/manual_merge/reportbuglib/reportbug_ui_newt.py)
branches/manual_merge/reportbug/reportbug_ui_text.py
(from rev 625, branches/manual_merge/reportbuglib/reportbug_ui_text.py)
branches/manual_merge/reportbug/reportbug_ui_urwid.py
(from rev 625, branches/manual_merge/reportbuglib/reportbug_ui_urwid.py)
branches/manual_merge/reportbug/urlutils.py
(from rev 625, branches/manual_merge/reportbuglib/urlutils.py)
Deleted:
branches/manual_merge/reportbuglib/
Copied: branches/manual_merge/reportbug/__init__.py (from rev 625, branches/manual_merge/reportbuglib/__init__.py)
===================================================================
Property changes on: branches/manual_merge/reportbug/__init__.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/checkbuildd.py (from rev 625, branches/manual_merge/reportbuglib/checkbuildd.py)
===================================================================
--- branches/manual_merge/reportbug/checkbuildd.py (rev 0)
+++ branches/manual_merge/reportbug/checkbuildd.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,89 @@
+#
+# reportbuglib/checkbuildd.py
+# Check buildd.debian.org for successful past builds
+#
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 2002 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# Version ##VERSION##; see changelog for revision history
+
+import sgmllib
+import commands
+
+from urlutils import open_url
+from reportbug_exceptions import (
+ NoNetwork,
+ )
+
+BUILDD_URL = 'http://buildd.debian.org/build.php?arch=%s&pkg=%s'
+
+# This is easy; just look for succeeded in an em block...
+
+class BuilddParser(sgmllib.SGMLParser):
+ def __init__(self):
+ sgmllib.SGMLParser.__init__(self)
+ self.versions = {}
+ self.savedata = None
+ self.found_succeeded = False
+
+ # --- Formatter interface, taking care of 'savedata' mode;
+ # shouldn't need to be overridden
+
+ def handle_data(self, data):
+ if self.savedata is not None:
+ self.savedata = self.savedata + data
+
+ # --- Hooks to save data; shouldn't need to be overridden
+ def save_bgn(self):
+ self.savedata = ''
+
+ def save_end(self, mode=0):
+ data = self.savedata
+ self.savedata = None
+ if not mode and data is not None: data = ' '.join(data.split())
+ return data
+
+ def start_em(self, attrs):
+ self.save_bgn()
+
+ def end_em(self):
+ data = self.save_end()
+ if data and 'successful' in data:
+ self.found_succeeded=True
+
+def archname():
+ return commands.getoutput('dpkg --print-architecture')
+
+def check_built(src_package, arch=None, http_proxy=None):
+ if not arch:
+ arch = archname()
+
+ try:
+ page = open_url(BUILDD_URL % (arch, src_package), http_proxy)
+ except NoNetwork:
+ return {}
+ if not page:
+ return {}
+
+ parser = BuilddParser()
+ parser.feed(page.read())
+ parser.close()
+ page.close()
+
+ return parser.found_succeeded
Property changes on: branches/manual_merge/reportbug/checkbuildd.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/checkversions.py (from rev 625, branches/manual_merge/reportbuglib/checkversions.py)
===================================================================
--- branches/manual_merge/reportbug/checkversions.py (rev 0)
+++ branches/manual_merge/reportbug/checkversions.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,337 @@
+#
+# reportbuglib/checkversions.py
+# Find if the installed version of a package is the latest
+#
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 2002-06 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# $Id: checkversions.py,v 1.6.2.3 2006-10-16 18:52:41 lawrencc Exp $
+#
+# Version ##VERSION##; see changelog for revision history
+
+import sys
+import os
+import re
+import urllib2
+import sgmllib
+import gc
+import time
+
+import reportbug
+from urlutils import open_url
+from reportbug_exceptions import (
+ NoNetwork,
+ )
+
+PACKAGES_URL = 'http://packages.debian.org/%s'
+INCOMING_URL = 'http://incoming.debian.org/'
+NEWQUEUE_URL = 'http://ftp-master.debian.org/new.html'
+
+# The format is an unordered list
+
+class BaseParser(sgmllib.SGMLParser):
+ def __init__(self):
+ sgmllib.SGMLParser.__init__(self)
+ self.savedata = None
+
+ # --- Formatter interface, taking care of 'savedata' mode;
+ # shouldn't need to be overridden
+
+ def handle_data(self, data):
+ if self.savedata is not None:
+ self.savedata = self.savedata + data
+
+ # --- Hooks to save data; shouldn't need to be overridden
+ def save_bgn(self):
+ self.savedata = ''
+
+ def save_end(self, mode=0):
+ data = self.savedata
+ self.savedata = None
+ if not mode and data is not None: data = ' '.join(data.split())
+ return data
+
+class PackagesParser(BaseParser):
+ def __init__(self, arch='i386'):
+ BaseParser.__init__(self)
+ self.versions = {}
+ self.row = None
+ arch = r'\s(all|'+re.escape(arch)+r')\b'
+ self.arch = re.compile(arch)
+ self.dist = None
+
+ def start_li(self, attrs):
+ if self.row is not None:
+ self.end_li()
+ self.row = []
+
+ def start_a(self, attrs):
+ if self.row is not None:
+ self.save_bgn()
+
+ def end_a(self):
+ if self.row is not None and self.savedata:
+ self.dist = self.save_end()
+
+ def lineend(self):
+ line = self.save_end().strip()
+ if self.arch.search(line):
+ version = line.split(': ', 1)
+ self.versions[self.dist] = version[0]
+
+ def start_br(self, attrs):
+ if self.savedata:
+ self.lineend()
+ self.save_bgn()
+
+ def end_li(self):
+ if self.savedata:
+ self.lineend()
+ self.row = None
+
+class IncomingParser(sgmllib.SGMLParser):
+ def __init__(self, package, arch='i386'):
+ sgmllib.SGMLParser.__init__(self)
+ self.found = []
+ self.savedata = None
+ arch = r'(?:all|'+re.escape(arch)+')'
+ self.package = re.compile(re.escape(package)+r'_([^_]+)_'+arch+'.deb')
+
+ def start_a(self, attrs):
+ for attrib, value in attrs:
+ if attrib.lower() != 'href':
+ continue
+
+ mob = self.package.match(value)
+ if mob:
+ self.found.append(mob.group(1))
+
+class NewQueueParser(BaseParser):
+ def __init__(self, package, arch='i386'):
+ BaseParser.__init__(self)
+ self.package = package
+ self.row = None
+ arch = r'\s(all|'+re.escape(arch)+r')\b'
+ self.arch = re.compile(arch)
+ self.versions = {}
+
+ def start_tr (self, attrs):
+ for name, value in attrs:
+ if name == 'class' and value in ("odd", "even"):
+ self.row = []
+
+ def end_tr (self):
+ if self.row is not None:
+ # row (name, versions, architectures, distribution)
+ dist = "%s (new queue)" % self.row[3]
+ for version in self.row[1].split():
+ self.versions[dist] = version
+ self.row = None
+
+ def start_td (self, attrs):
+ if self.row is None:
+ return
+ self.save_bgn()
+
+ def end_td (self):
+ if self.row is None:
+ return
+ data = self.save_end()
+ l = len(self.row)
+ if l == 0:
+ # package name
+ if self.package == data:
+ # found package name
+ self.row.append(data)
+ else:
+ self.row = None
+ elif l == 2:
+ # architecture
+ if self.arch.search(data):
+ self.row.append(data)
+ else:
+ self.row = None
+ else:
+ self.row.append(data)
+
+def compare_versions(current, upstream):
+ """Return 1 if upstream is newer than current, -1 if current is
+ newer than upstream, and 0 if the same."""
+ if not upstream: return 0
+ rc = os.system('dpkg --compare-versions %s lt %s' % (current, upstream))
+ rc2 = os.system('dpkg --compare-versions %s gt %s' % (current, upstream))
+ if not rc:
+ return 1
+ elif not rc2:
+ return -1
+ return 0
+
+def later_version(a, b):
+ if compare_versions(a, b) > 0:
+ return b
+ return a
+
+def get_versions_available(package, dists=None, http_proxy=None, arch='i386'):
+ if not dists:
+ dists = ('stable', 'testing', 'unstable')
+
+ try:
+ page = open_url(PACKAGES_URL % package, http_proxy)
+ except NoNetwork:
+ return {}
+ except urllib2.HTTPError, x:
+ print >> sys.stderr, "Warning:", x
+ return {}
+ if not page:
+ return {}
+
+ parser = PackagesParser(arch)
+ for line in page:
+ parser.feed(line)
+ parser.close()
+ try:
+ page.fp._sock.recv = None
+ except:
+ pass
+ page.close()
+
+## content = page.read()
+## parser.feed(content)
+## parser.close()
+## page.close()
+
+ versions = {}
+ for dist in dists:
+ if dist in parser.versions:
+ versions[dist] = parser.versions[dist]
+ del parser
+ del page
+
+ return versions
+
+def get_newqueue_available(package, dists=None, http_proxy=None, arch='i386'):
+ if dists is None:
+ dists = ('unstable (new queue)', )
+ try:
+ page = open_url(NEWQUEUE_URL, http_proxy)
+ except NoNetwork:
+ return {}
+ except urllib2.HTTPError, x:
+ print >> sys.stderr, "Warning:", x
+ return {}
+ if not page:
+ return {}
+ parser = NewQueueParser(package, arch)
+ for line in page:
+ parser.feed(line)
+ parser.close()
+ try:
+ page.fp._sock.recv = None
+ except:
+ pass
+ page.close()
+
+ #print repr(page)
+
+ versions = {}
+ for dist in dists:
+ if dist in parser.versions:
+ versions[dist] = parser.versions[dist]
+
+ del parser
+ del page
+ #print 'HERE', gc.garbage
+ return versions
+
+def get_incoming_version(package, http_proxy=None, arch='i386'):
+ try:
+ page = open_url(INCOMING_URL, http_proxy)
+ except NoNetwork:
+ return None
+ except urllib2.HTTPError, x:
+ print >> sys.stderr, "Warning:", x
+ return None
+ if not page:
+ return None
+
+ parser = IncomingParser(package, arch)
+ for line in page:
+ parser.feed(line)
+ parser.close()
+ try:
+ page.fp._sock.recv = None
+ except:
+ pass
+ page.close()
+
+ if parser.found:
+ found = parser.found
+ del parser
+ return reduce(later_version, found, '0')
+
+ del page
+ del parser
+ return None
+
+def check_available(package, version, dists=None, check_incoming=True,
+ check_newqueue=True,
+ http_proxy=None, arch='i386'):
+ avail = {}
+
+ if check_incoming:
+ iv = get_incoming_version(package, http_proxy, arch)
+ if iv:
+ avail['incoming'] = iv
+ stuff = get_versions_available(package, dists, http_proxy, arch)
+ avail.update(stuff)
+ if check_newqueue:
+ srcpackage = reportbug.get_source_name(package)
+ if srcpackage is None:
+ srcpackage = package
+ stuff = get_newqueue_available(srcpackage, dists, http_proxy, arch)
+ avail.update(stuff)
+ #print gc.garbage, stuff
+
+ new = {}
+ newer = 0
+ for dist in avail:
+ if dist == 'incoming':
+ if ':' in version:
+ ver = version.split(':', 1)[1]
+ else:
+ ver = version
+ comparison = compare_versions(ver, avail[dist])
+ else:
+ comparison = compare_versions(version, avail[dist])
+ if comparison > 0:
+ new[dist] = avail[dist]
+ elif comparison < 0:
+ newer += 1
+ too_new = (newer and newer == len(avail))
+ return new, too_new
+
+if __name__=='__main__':
+ gc.set_debug(gc.DEBUG_LEAK)
+ print get_newqueue_available('reportbug')
+ print gc.garbage
+ print check_available('reportbug', '3.7', arch='s390')
+ #print check_available('openssh-server', '1:4.2p1-8', arch='i386')
+ #print check_available('openssh-server', '1:4.2p1-8', arch='kfreebsd-i386')
+ time.sleep(1000)
+ #print check_available('dpkg', '1.10.2', arch='sparc')
Property changes on: branches/manual_merge/reportbug/checkversions.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/debianbts.py (from rev 625, branches/manual_merge/reportbuglib/debianbts.py)
===================================================================
--- branches/manual_merge/reportbug/debianbts.py (rev 0)
+++ branches/manual_merge/reportbug/debianbts.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,1013 @@
+#
+# reportbuglib/debianbts.py
+# Routines to deal with the debbugs web pages
+#
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 1999-2006 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# Version ##VERSION##; see changelog for revision history
+#
+# $Id: debianbts.py,v 1.24.2.15 2008-04-18 05:38:27 lawrencc Exp $
+
+import sys
+import mailbox
+import email
+import email.Errors
+import cStringIO
+import sgmllib
+import glob
+import os
+import re
+import time
+import urllib
+import textwrap
+import pprint
+
+import reportbug
+import checkversions
+from reportbug_exceptions import (
+ NoNetwork,
+ )
+from urlutils import open_url
+
+def msgfactory(fp):
+ try:
+ return email.message_from_file(fp)
+ except email.Errors.MessageParseError:
+ # Don't return None since that will
+ # stop the mailbox iterator
+ return ''
+
+class Error(Exception):
+ pass
+
+# Severity levels
+SEVERITIES = {
+ 'critical' : """makes unrelated software on the system (or the
+ whole system) break, or causes serious data loss, or introduces a
+ security hole on systems where you install the package.""",
+ 'grave' : """makes the package in question unusable by most or all users,
+ or causes data loss, or introduces a security hole allowing access
+ to the accounts of users who use the package.""",
+ 'serious' : """is a severe violation of Debian policy (that is,
+ the problem is a violation of a 'must' or 'required' directive);
+ may or may not affect the usability of the package. Note that non-severe
+ policy violations may be 'normal,' 'minor,' or 'wishlist' bugs.
+ (Package maintainers may also designate other bugs as 'serious' and thus
+ release-critical; however, end users should not do so.)""",
+ 'important' : """a bug which has a major effect on the usability
+ of a package, without rendering it completely unusable to
+ everyone.""",
+ 'does-not-build' : """a bug that stops the package from being built
+ from source. (This is a 'virtual severity'.)""",
+ 'normal' : """a bug that does not undermine the usability of the
+ whole package; for example, a problem with a particular option or
+ menu item.""",
+ 'minor' : """things like spelling mistakes and other minor
+ cosmetic errors that do not affect the core functionality of the
+ package.""",
+ 'wishlist' : "suggestions and requests for new features.",
+ }
+
+# justifications for critical bugs
+JUSTIFICATIONS = {
+ 'critical' : (
+ ('breaks unrelated software', """breaks unrelated software on the system
+ (packages that have a dependency relationship are not unrelated)"""),
+ ('breaks the whole system', """renders the entire system unusable (e.g.,
+ unbootable, unable to reach a multiuser runlevel, etc.)"""),
+ ('causes serious data loss', """causes loss of important, irreplaceable
+ data"""),
+ ('root security hole', """introduces a security hole allowing access to
+ root (or another privileged system account), or data normally
+ accessible only by such accounts"""),
+ ('unknown', """not sure, or none of the above"""),
+ ),
+ 'grave' : (
+ ('renders package unusable', """renders the package unusable, or mostly
+ so, on all or nearly all possible systems on which it could be installed
+ (i.e., not a hardware-specific bug); or renders package uninstallable
+ or unremovable without special effort"""),
+ ('causes non-serious data loss', """causes the loss of data on the system
+ that is unimportant, or restorable without resorting to backup media"""),
+ ('user security hole', """introduces a security hole allowing access to
+ user accounts or data not normally accessible"""),
+ ('unknown', """not sure, or none of the above"""),
+ )
+ }
+
+
+# Ordering for justifications
+JUSTORDER = {
+ 'critical' : ['breaks unrelated software',
+ 'breaks the whole system',
+ 'causes serious data loss',
+ 'root security hole',
+ 'unknown'],
+ 'grave' : ['renders package unusable',
+ 'causes non-serious data loss',
+ 'user security hole',
+ 'unknown']
+ }
+
+SEVERITIES_gnats = {
+ 'critical' : 'The product, component or concept is completely'
+ 'non-operational or some essential functionality is missing. No'
+ 'workaround is known.',
+ 'serious' : 'The product, component or concept is not working'
+ 'properly or significant functionality is missing. Problems that'
+ 'would otherwise be considered ''critical'' are rated ''serious'' when'
+ 'a workaround is known.',
+ 'non-critical' : 'The product, component or concept is working'
+ 'in general, but lacks features, has irritating behavior, does'
+ 'something wrong, or doesn''t match its documentation.',
+ }
+
+# Rank order of severities, for sorting
+SEVLIST = ['critical', 'grave', 'serious', 'important', 'does-not-build',
+ 'normal', 'non-critical', 'minor', 'wishlist', 'fixed']
+
+def convert_severity(severity, type='debbugs'):
+ "Convert severity names if needed."
+ if type == 'debbugs':
+ return {'non-critical' : 'normal'}.get(severity, severity)
+ elif type == 'gnats':
+ return {'grave' : 'critical',
+ 'important' : 'serious',
+ 'normal' : 'non-critical',
+ 'minor' : 'non-critical',
+ 'wishlist' : 'non-critical'}.get(severity, severity)
+ else:
+ return severity
+
+# These packages are virtual in Debian; we don't look them up...
+debother = {
+ 'base' : 'General bugs in the base system',
+# Actually a real package, but most people don't have boot-floppies installed for good reason
+# 'boot-floppy' : '(Obsolete, please use boot-floppies instead.)',
+# 'boot-floppies' : 'Bugs in the woody installation subsystem',
+ 'bugs.debian.org' : 'The bug tracking system, @bugs.debian.org',
+ 'cdimage.debian.org' : 'CD Image issues',
+ 'cdrom' : 'Problems with installation from CD-ROMs',
+# dpkg-iwj -- The dpkg branch maintained by Ian Jackson
+ 'debian-policy' : 'Proposed changes in the Debian policy documentation',
+ 'ftp.debian.org' : 'Problems with the FTP site and Package removal requests',
+ 'general' : 'General problems (e.g., that many manpages are mode 755)',
+# 'install' : 'Problems with the sarge installer.',
+# 'installation' : 'General installation problems not covered otherwise.',
+ 'installation-reports' : 'Problems with installing Debian',
+ 'kernel' : 'IMPORTANT: Obsolete, please use "linux-image" instead (linux-image will be used in any case)',
+ 'linux-image' : 'Problems with the Linux kernel, or the kernel shipped with Debian',
+ 'listarchives' : 'Problems with the WWW mailing list archives',
+ 'lists.debian.org' : 'The mailing lists, debian-*@lists.debian.org.',
+ 'mirrors' : 'Problems with Debian archive mirrors.',
+ 'nm.debian.org' : 'New Maintainer process and nm.debian.org website',
+ 'press' : 'Press release issues',
+ 'project' : 'Problems related to project administration',
+ 'qa.debian.org' : 'Problems related to the quality assurance group',
+#slink-cd -- Slink CD
+ 'release.debian.org' : 'Requests regarding Debian releases and release team tools',
+ 'release-notes' : 'Problems with the release notes',
+ 'security-tracker' : 'The Debian Security Bug Tracker',
+ 'security.debian.org' : 'Problems with the security updates server',
+ 'spam' : 'Spam (reassign spam to here so we can complain about it)',
+ 'tech-ctte' : 'Issues to be referred to the technical committee',
+ 'upgrade-reports' : 'Reports of successful and unsucessful upgrades',
+ 'wiki.debian.org' : 'Problems with the Debian wiki',
+ 'wnpp' : 'Work-Needing and Prospective Packages list',
+ 'www.debian.org' : 'Problems with the WWW site (including other *.debian.org sites)'
+ }
+
+progenyother = {
+ 'debian-general' : 'Any non-package-specific bug',
+ }
+
+def handle_debian_ftp(package, bts, ui, fromaddr, online=True, http_proxy=None):
+ body = reason = archs = ''
+ suite = 'unstable'
+ headers = []
+ pseudos = []
+ query = True
+
+ tag = ui.menu('What sort of request is this? (If none of these '
+ 'things mean anything to you, or you are trying to report '
+ 'a bug in an existing package, please press Enter to '
+ 'exit reportbug.)', {
+ 'ROM' :
+ "Package removal - Request Of Maintainer.",
+ 'RoQA' :
+ "Package removal - Requested by the QA team.",
+ 'ROP' :
+ "Package removal - Request of Porter.",
+ 'ROSRM' :
+ "Package removal - Request of Stable Release Manager.",
+ 'NBS' :
+ "Package removal - Not Built [by] Source.",
+ 'NPOASR' :
+ "Package removal - Never Part Of A Stable Release.",
+ 'NVIU' :
+ "Package removal - Newer Version In Unstable.",
+ 'ANAIS' :
+ "Package removal - Architecture Not Allowed In Source.",
+ 'ICE' :
+ "Package removal - Internal Compiler Error.",
+ 'other' :
+ "Not a package removal request, report other problems.",
+ }, 'Choose the request type: ', empty_ok=True)
+ if not tag:
+ ui.long_message('To report a bug in a package, use the name of the package, not ftp.debian.org.\n')
+ raise SystemExit
+
+ severity = 'normal'
+ if tag == 'other':
+ return
+ else:
+ prompt = 'Please enter the name of the package: '
+ package = ui.get_string(prompt)
+ if not package:
+ ui.ewrite('You seem to want to report a generic bug, not request a removal\n')
+ return
+
+ ui.ewrite('Checking status database...\n')
+ info = reportbug.get_package_status(package)
+ available = info[1]
+
+ query = False
+ if not available:
+ info = reportbug.get_source_package(package)
+ if info:
+ info = reportbug.get_package_status(info[0][0])
+
+ if not info:
+ cont = ui.select_options(
+ "This package doesn't appear to exist; continue?",
+ 'yN', {'y': 'Ignore this problem and continue.',
+ 'n': 'Exit without filing a report.' })
+ if cont == 'n':
+ sys.exit(1)
+ else:
+ package = info[12] or package
+
+ suite = ui.menu('Is the removal to be done in a suite other than'
+ ' "unstable"? Please press Enter for "unstable"', {
+ 'oldstable' : "Old stable.",
+ 'oldstable-proposed-updates' : "Old stable proposed updates.",
+ 'stable' : "Stable.",
+ 'stable-proposed-updates' : "Stable proposed updates.",
+ 'testing' : "Testing only (NOT unstable)",
+ 'testing-proposed-updates' : "Testing proposed updates",
+ 'experimental' : "Experimental.",
+ }, 'Choose the suite: ', empty_ok=True)
+ if not suite:
+ suite = 'unstable'
+
+ if suite not in ('testing', 'unstable', 'experimental'):
+ headers.append('X-Debbugs-CC: debian-release at lists.debian.org')
+ ui.ewrite('Your report will be carbon-copied to debian-release.\n')
+
+ why = 'Please enter the reason for removal: '
+ reason = ui.get_string(why)
+ if not reason: return
+
+ partial = ui.select_options(
+ "Is this removal request for specific architectures?",
+ 'yN', {'y': 'This is a partial (specific architectures) removal.',
+ 'n': 'This removal is for all architectures.' })
+ if partial == 'y':
+ prompt = 'Please enter the arch list separated by a space: '
+ archs = ui.get_string(prompt)
+ if not archs:
+ ui.long_message('Partial removal requests must have a list of architectures.\n')
+ raise SystemExit
+
+ if archs:
+ if suite != 'unstable':
+ subject = 'RM: %s/%s [%s] -- %s; %s' % (package, suite, archs, tag, reason)
+ else:
+ subject = 'RM: %s [%s] -- %s; %s' % (package, archs, tag, reason)
+ else:
+ if suite != 'unstable':
+ subject = 'RM: %s/%s -- %s; %s' % (package, suite, tag, reason)
+ else:
+ subject = 'RM: %s -- %s; %s' % (package, tag, reason)
+
+ if suite == 'testing':
+ ui.ewrite('Please use the following subject and send a mail to '
+ 'debian-release at lists.debian.org to request removal.\n')
+ ui.ewrite(subject)
+ sys.exit(1)
+
+ return (subject, severity, headers, pseudos, body, query)
+
+
+itp_template = textwrap.dedent(u"""\
+ * Package name : %(package)s
+ Version : x.y.z
+ Upstream Author : Name <somebody at example.org>
+ * URL : http://www.example.org/
+ * License : (GPL, LGPL, BSD, MIT/X, etc.)
+ Programming Lang: (C, C++, C#, Perl, Python, etc.)
+ Description : %(short_desc)s
+
+ (Include the long description here.)
+""")
+
+
+def handle_wnpp(package, bts, ui, fromaddr, online=True, http_proxy=None):
+ short_desc = body = ''
+ headers = []
+ pseudos = []
+ query = True
+
+ tag = ui.menu('What sort of request is this? (If none of these '
+ 'things mean anything to you, or you are trying to report '
+ 'a bug in an existing package, please press Enter to '
+ 'exit reportbug.)', {
+ 'O' :
+ "The package has been `Orphaned'. It needs a new maintainer as soon as possible.",
+ 'RFA' :
+ "This is a `Request for Adoption'. Due to lack of time, resources, interest or something similar, the current maintainer is asking for someone else to maintain this package. They will maintain it in the meantime, but perhaps not in the best possible way. In short: the package needs a new maintainer.",
+ 'RFH' :
+ "This is a `Request For Help'. The current maintainer wants to continue to maintain this package, but they needs some help to do this, because their time is limited or the package is quite big and needs several maintainers.",
+ 'ITP' :
+ "This is an `Intent To Package'. Please submit a package description along with copyright and URL in such a report.",
+ 'RFP' :
+ "This is a `Request For Package'. You have found an interesting piece of software and would like someone else to maintain it for Debian. Please submit a package description along with copyright and URL in such a report.",
+ }, 'Choose the request type: ', empty_ok=True)
+ if not tag:
+ ui.long_message('To report a bug in a package, use the name of the package, not wnpp.\n')
+ raise SystemExit
+
+ if tag in ('RFP', 'ITP'):
+ prompt = 'Please enter the proposed package name: '
+ else:
+ prompt = 'Please enter the name of the package: '
+ package = ui.get_string(prompt)
+ if not package: return
+
+ ui.ewrite('Checking status database...\n')
+ info = reportbug.get_package_status(package)
+ available = info[1]
+
+ severity = 'normal'
+ if tag in ('ITP', 'RFP'):
+ if available and (not online or checkversions.check_available(
+ package, '0', http_proxy=http_proxy)):
+ if not ui.yes_no(
+ ('A package called %s already appears to exist (at least on '
+ 'your system); continue?' % package),
+ 'Ignore this problem and continue. If you have '
+ 'already locally created a package with this name, this '
+ 'warning message may have been produced in error.',
+ 'Exit without filing a report.', default=0):
+ sys.exit(1)
+
+ severity = 'wishlist'
+
+ short_desc = ui.get_string(
+ 'Please briefly describe this package; this should be an '
+ 'appropriate short description for the eventual package: ')
+ if not short_desc:
+ return
+
+ if tag == 'ITP':
+ headers.append('X-Debbugs-CC: debian-devel at lists.debian.org')
+ pseudos.append(u'Owner: %s' % fromaddr.decode('utf-8', 'replace'))
+ ui.ewrite('Your report will be carbon-copied to debian-devel, '
+ 'per Debian policy.\n')
+
+ body = itp_template % vars()
+ elif tag in ('O', 'RFA', 'RFH'):
+ severity = 'normal'
+ query = False
+ if not available:
+ info = reportbug.get_source_package(package)
+ if info:
+ info = reportbug.get_package_status(info[0][0])
+
+ if not info:
+ cont = ui.select_options(
+ "This package doesn't appear to exist; continue?",
+ 'yN', {'y': 'Ignore this problem and continue.',
+ 'n': 'Exit without filing a report.' })
+ if cont == 'n':
+ sys.exit(1)
+ short_desc = long_desc = ''
+ else:
+ short_desc = info[11] or ''
+ package = info[12] or package
+ long_desc = info[13]
+
+ if tag == 'O' and info and info[9] in \
+ ('required', 'important', 'standard'):
+ severity = 'important'
+
+ if tag == 'RFH':
+ headers.append('X-Debbugs-CC: debian-devel at lists.debian.org')
+ ui.ewrite('Your request will be carbon-copied to debian-devel, '
+ 'per Debian policy.\n')
+
+ if long_desc:
+ orphstr = 'intend to orphan'
+ if tag == 'RFA':
+ orphstr = 'request an adopter for'
+ elif tag == 'RFH':
+ orphstr = 'request assistance with maintaining'
+
+ body = ('I %s the %s package.\n\n'
+ 'The package description is:\n') % (orphstr, package)
+ body = body + long_desc + '\n'
+
+ if short_desc:
+ subject = '%s: %s -- %s' % (tag, package, short_desc)
+ else:
+ subject = '%s: %s' % (tag, package)
+
+ return (subject, severity, headers, pseudos, body, query)
+
+# Supported servers
+# Theoretically support for GNATS and Jitterbug could be added here.
+SYSTEMS = { 'debian' :
+ { 'name' : 'Debian', 'email': '%s at bugs.debian.org',
+ 'btsroot' : 'http://www.debian.org/Bugs/',
+ 'otherpkgs' : debother,
+ 'nonvirtual' : ['linux-image', 'kernel-image'],
+ 'specials' :
+ { 'wnpp': handle_wnpp,
+ 'ftp.debian.org': handle_debian_ftp },
+ # Dependency packages
+ 'deppkgs' : ('gcc', 'g++', 'cpp', 'gcj', 'gpc', 'gobjc',
+ 'chill', 'gij', 'g77', 'python', 'python-base',
+ 'x-window-system-core', 'x-window-system'),
+ 'cgiroot' : 'http://bugs.debian.org/cgi-bin/' },
+ 'progeny' :
+ { 'name' : 'Progeny', 'email' : 'bugs at progeny.com',
+ 'type' : 'gnats', 'otherpkgs' : progenyother },
+ 'ubuntu' :
+ { 'name' : 'Ubuntu', 'email' : 'ubuntu-users at lists.ubuntu.com',
+ 'type' : 'mailto' },
+ 'guug' :
+ { 'name' : 'GUUG (German Unix User Group)',
+ 'email' : '%s at bugs.guug.de', 'query-dpkg' : False },
+ }
+
+CLASSES = {
+ 'sw-bug' : 'The problem is a bug in the software or code. For'
+ 'example, a crash would be a sw-bug.',
+ 'doc-bug' : 'The problem is in the documentation. For example,'
+ 'an error in a man page would be a doc-bug.',
+ 'change-request' : 'You are requesting a new feature or a change'
+ 'in the behavior of software, or are making a suggestion. For'
+ 'example, if you wanted reportbug to be able to get your local'
+ 'weather forecast, as well as report bugs, that would be a'
+ 'change-request.',
+ }
+
+CLASSLIST = ['sw-bug', 'doc-bug', 'change-request']
+
+CRITICAL_TAGS = {
+ 'security' : 'This problem is a security vulnerability in Debian.',
+}
+
+TAGS = {
+ 'patch' : 'You are including a patch to fix this problem.',
+## 'upstream' : 'You believe this problem is not specific to Debian.',
+## 'potato' : 'This bug only applies to the potato release (Debian 2.2).',
+## 'woody' : 'This bug only applies to the woody release (Debian 3.0).',
+## 'sarge' : 'This bug only applies to the sarge release (Debian 3.1).',
+## 'sid' : 'This bug only applies to the unstable branch of Debian.',
+ "l10n" : "This bug reports a localization/internationalization issue.",
+## 'done' : 'No more tags.',
+ }
+
+EXTRA_TAGS = ['potato', 'woody', 'sarge', 'security', 'sid', 'upstream']
+
+TAGLIST = ['l10n', 'patch']
+CRITICAL_TAGLIST = ['security']
+
+def yn_bool(setting):
+ if setting:
+ if str(setting) == 'no':
+ return 'no'
+ return 'yes'
+ else:
+ return 'no'
+
+def cgi_report_url(system, number, archived=False, mbox=False):
+ root = SYSTEMS[system].get('cgiroot')
+ if root:
+ return '%sbugreport.cgi?bug=%d&archived=%s&mbox=%s' % (
+ root, number, archived, yn_bool(mbox))
+ return None
+
+def cgi_package_url(system, package, archived=False, source=False,
+ repeatmerged=True, version=None):
+ root = SYSTEMS[system].get('cgiroot')
+ if not root: return None
+
+ #package = urllib.quote_plus(package.lower())
+ if source:
+ query = {'src' : package.lower()}
+ else:
+ query = {'pkg' : package.lower()}
+
+ query['repeatmerged'] = yn_bool(repeatmerged)
+ query['archived'] = yn_bool(archived)
+
+ if version:
+ query['version'] = str(version)
+
+ qstr = urllib.urlencode(query)
+ #print qstr
+ return '%spkgreport.cgi?%s' % (root, qstr)
+
+def package_url(system, package, mirrors=None, source=False,
+ repeatmerged=True):
+ btsroot=get_btsroot(system, mirrors)
+ package = urllib.quote_plus(package.lower())
+ return btsroot+('db/pa/l%s.html' % package)
+
+def report_url(system, number, mirrors=None):
+ number = str(number)
+ if len(number) < 2: return None
+ btsroot=get_btsroot(system, mirrors)
+ return btsroot+('db/%s/%s.html' % (number[:2], number))
+
+def get_package_url(system, package, mirrors=None, source=False,
+ archived=False, repeatmerged=True):
+ return (cgi_package_url(system, package, archived, source, repeatmerged) or
+ package_url(system, package, mirrors, source, repeatmerged))
+
+def get_report_url(system, number, mirrors=None, archived=False, mbox=False):
+ return (cgi_report_url(system, number, archived, mbox) or
+ report_url(system, number, mirrors))
+
+def parse_bts_url(url):
+ bits = url.split(':', 1)
+ if len(bits) != 2: return None
+
+ type, loc = bits
+ if loc.startswith('//'): loc = loc[2:]
+ while loc.endswith('/'): loc = loc[:-1]
+ return type, loc
+
+# Dynamically add any additional systems found
+for origin in glob.glob('/etc/dpkg/origins/*'):
+ try:
+ fp = file(origin)
+ system = os.path.basename(origin)
+ SYSTEMS[system] = SYSTEMS.get(system, { 'otherpkgs' : {},
+ 'query-dpkg' : True,
+ 'mirrors' : {},
+ 'cgiroot' : None } )
+ for line in fp:
+ try:
+ (header, content) = line.split(': ', 1)
+ header = header.lower()
+ content = content.strip()
+ if header == 'vendor':
+ SYSTEMS[system]['name'] = content
+ elif header == 'bugs':
+ (type, root) = parse_bts_url(content)
+ SYSTEMS[system]['type'] = type
+ if type == 'debbugs':
+ SYSTEMS[system]['btsroot'] = 'http://'+root+'/'
+ SYSTEMS[system]['email'] = '%s@'+root
+ elif type == 'mailto':
+ SYSTEMS[system]['btsroot'] = None
+ SYSTEMS[system]['email'] = root
+ else:
+ # We don't know what to do...
+ pass
+ except ValueError:
+ pass
+ fp.close()
+ except IOError:
+ pass
+
+# For summary pages, we want to keep:
+#
+# - Contents of <title>...</title>
+# - Contents of <h2>...</h2>
+# - Contents of each <li>
+#
+# For individual bugs, we want to keep:
+# - Contents of <title>...</title>
+# - Contents of every <pre>...</pre> after a <h2>....</h2> tag.
+
+class BTSParser(sgmllib.SGMLParser):
+ def __init__(self, mode='summary', cgi=False, followups=False):
+ sgmllib.SGMLParser.__init__(self)
+ self.hierarchy = []
+ self.lidata = None
+ self.lidatalist = None
+ self.savedata = None
+ self.title = None
+ self.bugcount = 0
+ self.mode = mode
+ self.cgi = cgi
+ self.followups = followups
+ self.inbuglist = self.intrailerinfo = False
+ self.bugtitle = None
+ if followups:
+ self.preblock = []
+ else:
+ self.preblock = ''
+ self.endh2 = False
+
+ # --- Formatter interface, taking care of 'savedata' mode;
+ # shouldn't need to be overridden
+
+ def handle_data(self, data):
+ if self.savedata is not None:
+ self.savedata += data
+
+ # --- Hooks to save data; shouldn't need to be overridden
+
+ def save_bgn(self):
+ self.savedata = ''
+
+ def save_end(self, mode=False):
+ data = self.savedata
+ if not mode and data:
+ data = ' '.join(data.split())
+ self.savedata = None
+ return data
+
+ def start_h1(self, attrs):
+ self.save_bgn()
+ self.oldmode = self.mode
+ self.mode = 'title'
+
+ def end_h1(self):
+ self.title = self.save_end()
+ self.mode = self.oldmode
+
+ def start_h2(self, attrs):
+ if self.lidata: self.check_li()
+
+ self.save_bgn()
+
+ def end_h2(self):
+ if self.mode == 'summary':
+ hiertitle = self.save_end()
+ if 'bug' in hiertitle:
+ self.hierarchy.append( (hiertitle, []) )
+ self.endh2 = True # We are at the end of a title, flag <pre>
+
+ def start_ul(self, attrs):
+ if self.mode == 'summary':
+ for k, v in attrs:
+ if k == 'class' and v == 'bugs':
+ self.inbuglist = True
+
+ def end_ul(self):
+ if self.inbuglist:
+ self.check_li()
+
+ self.inbuglist = False
+
+ def do_br(self, attrs):
+ if self.mode == 'title':
+ self.savedata = ""
+ elif self.mode == 'summary' and self.inbuglist and not self.intrailerinfo:
+ self.bugtitle = self.save_end()
+ self.intrailerinfo = True
+ self.save_bgn()
+
+ def check_li(self):
+ if self.mode == 'summary':
+ if not self.intrailerinfo:
+ self.bugtitle = self.save_end()
+ trailinfo = ''
+ else:
+ trailinfo = self.save_end()
+
+ match = re.search(r'fixed:\s+([\w.+~-]+(\s+[\w.+~:-]+)?)', trailinfo)
+ if match:
+ title = self.bugtitle
+ bits = re.split(r':\s+', title, 1)
+ if len(bits) > 1:
+ buginfo = '%s [FIXED %s]: %s' % (
+ bits[0], match.group(1), bits[1])
+ else:
+ if title.endswith(':'):
+ title = title[:-1]
+
+ buginfo = '%s [FIXED %s]' % (title, match.group(1))
+ else:
+ buginfo = self.bugtitle
+
+ self.lidatalist.append(buginfo)
+ self.bugcount += 1
+
+ self.lidata = self.intrailerinfo = False
+
+ def do_li(self, attrs):
+ if self.mode == 'summary' and self.inbuglist:
+ if self.lidata: self.check_li()
+
+ self.lidata = True
+ if self.hierarchy:
+ self.lidatalist = self.hierarchy[-1][1]
+ else:
+ self.lidatalist = []
+ self.save_bgn()
+
+ def start_pre(self, attrs):
+ "Save <pre> when we follow a </h2>"
+ if self.followups:
+ if not self.endh2: return
+ else:
+ if self.cgi and self.preblock: return
+
+ self.save_bgn()
+
+ def end_pre(self):
+ if self.followups:
+ if not self.endh2: return
+ self.endh2 = False # Done with a report, reset </h2>.
+ stuff = self.save_end(1)
+ if not self.cgi:
+ self.preblock.insert(0, stuff)
+ else:
+ self.preblock.append(stuff)
+ elif not (self.preblock and self.cgi):
+ self.preblock = self.save_end(1)
+
+ def reorganize(self):
+ if not self.hierarchy:
+ return
+
+ newhierarchy = []
+ fixed = []
+ fixedfinder = re.compile(r'\[FIXED ([^\]]+)\]')
+ resolvedfinder = re.compile(r'Resolved')
+
+ for (title, buglist) in self.hierarchy:
+ if 'Resolved' in title:
+ newhierarchy.append( (title, buglist) )
+ continue
+
+ bugs = []
+ for bug in buglist:
+ if fixedfinder.search(bug):
+ fixed.append(bug)
+ else:
+ bugs.append(bug)
+
+ if bugs:
+ title = ' '.join(title.split()[:-2])
+ if len(bugs) != 1:
+ title += ' (%d bugs)' % len(bugs)
+ else:
+ title += ' (1 bug)'
+
+ newhierarchy.append( (title, bugs) )
+
+ if fixed:
+ self.hierarchy = [('Bugs fixed in subsequent releases (%d bugs)' % len(fixed), fixed)] + newhierarchy
+
+def parse_html_report(number, url, http_proxy, followups=False, cgi=True):
+ page = open_url(url, http_proxy)
+ if not page:
+ return None
+
+ parser = BTSParser(cgi=cgi, followups=followups)
+ for line in page:
+ parser.feed(line)
+ parser.close()
+
+ try:
+ page.fp._sock.recv = None
+ except:
+ pass
+ page.close()
+
+ items = parser.preblock
+ title = "#%d: %s" % (number, parser.title)
+
+ if not followups:
+ items = [items]
+
+ output = []
+ for stuff in items:
+ parts = stuff.split('\n\n')
+ match = re.search('^Date: (.*)$', parts[0], re.M | re.I)
+ date_submitted = ''
+ if match:
+ date_submitted = 'Date: %s\n' % match.group(1)
+
+ stuff = ('\n\n'.join(parts[1:])).rstrip()
+ if not stuff:
+ continue
+
+ item = date_submitted+stuff+os.linesep
+ output.append(item)
+
+ if not output:
+ return None
+
+ return (title, output)
+
+# XXX: Need to handle charsets properly
+def parse_mbox_report(number, url, http_proxy, followups=False):
+ page = open_url(url, http_proxy)
+ if not page:
+ return None
+
+ # Make this seekable
+ wholefile = cStringIO.StringIO(page.read())
+
+ try:
+ page.fp._sock.recv = None
+ except:
+ pass
+ page.close()
+
+ mbox = mailbox.UnixMailbox(wholefile, msgfactory)
+ title = ''
+
+ output = []
+ for message in mbox:
+ if not message:
+ pass
+
+ subject = message.get('Subject')
+ if not title:
+ title = subject
+
+ date = message.get('Date')
+ fromhdr = message.get('From')
+
+ body = entry = ''
+ for part in message.walk():
+ if part.get_content_type() == 'text/plain' and not body:
+ body = part.get_payload(None, True)
+
+ if fromhdr:
+ entry += 'From: %s%s' % (fromhdr, os.linesep)
+
+ if subject and subject != title:
+ entry += 'Subject: %s%s' % (subject, os.linesep)
+
+ if date:
+ entry += 'Date: %s%s' % (date, os.linesep)
+
+ if entry:
+ entry += os.linesep
+
+ entry += body.rstrip('\n') + os.linesep
+
+ output.append(entry)
+
+ if not output:
+ return None
+
+ title = "#%d: %s" % (number, title)
+ return (title, output)
+
+def get_cgi_reports(package, system='debian', http_proxy='', archived=False,
+ source=False, version=None):
+ try:
+ page = open_url(cgi_package_url(system, package, archived, source,
+ version=version), http_proxy)
+ except:
+ raise NoNetwork
+
+ if not page:
+ return (0, None, None)
+
+ #content = page.read()
+ #if 'Maintainer' not in content:
+ # return (0, None, None)
+
+ parser = BTSParser(cgi=True)
+ for line in page:
+ parser.feed(line)
+ parser.close()
+ try:
+ page.fp._sock.recv = None
+ except:
+ pass
+ page.close()
+
+ # Reorganize hierarchy to put recently-fixed bugs at top
+ parser.reorganize()
+
+ # Morph @ 2008-08-15; due to BTS output format changes
+ try:
+ parser.hierarchy.remove(('Select bugs', []))
+ except:
+ pass
+
+ data = (parser.bugcount, parser.title, parser.hierarchy)
+ del parser
+
+ return data
+
+def get_cgi_report(number, system='debian', http_proxy='', archived=False,
+ followups=False):
+ number = int(number)
+
+ url = cgi_report_url(system, number, archived='no', mbox=True)
+ return parse_mbox_report(number, url, http_proxy, followups)
+ #return parse_html_report(number, url, http_proxy, followups, cgi=True)
+
+def get_btsroot(system, mirrors=None):
+ if mirrors:
+ alternates = SYSTEMS[system].get('mirrors')
+ for mirror in mirrors:
+ if alternates.has_key(mirror):
+ return alternates[mirror]
+ return SYSTEMS[system].get('btsroot', '')
+
+def get_reports(package, system='debian', mirrors=None, version=None,
+ http_proxy='', archived=False, source=False):
+ if isinstance(package, basestring):
+ if SYSTEMS[system].get('cgiroot'):
+ try:
+ result = get_cgi_reports(package, system, http_proxy, archived,
+ source, version=version)
+ except:
+ raise NoNetwork
+ if result: return result
+
+ url = package_url(system, package, mirrors, source)
+ try:
+ page = open_url(url, http_proxy)
+ except:
+ raise NoNetwork
+ if not page:
+ return (0, None, None)
+
+ #content = page.read()
+ #if 'Maintainer' not in content:
+ # return (0, None, None)
+
+ parser = BTSParser()
+ for line in page:
+ parser.feed(line)
+ parser.close()
+ try:
+ page.fp._sock.recv = None
+ except:
+ pass
+ page.close()
+
+ return parser.bugcount, parser.title, parser.hierarchy
+
+ # A list of bug numbers
+ this_hierarchy = []
+ package = [int(x) for x in package]
+ package.sort()
+ for bug in package:
+ result = get_report(bug, system, mirrors, http_proxy, archived)
+ if result:
+ title, body = result
+ this_hierarchy.append(title)
+ #print title
+
+ title = "Multiple bug reports"
+ bugcount = len(this_hierarchy)
+ hierarchy = [('Reports', this_hierarchy)]
+
+ return bugcount, title, hierarchy
+
+def get_report(number, system='debian', mirrors=None,
+ http_proxy='', archived=False, followups=False):
+ number = int(number)
+ if SYSTEMS[system].get('cgiroot'):
+ result = get_cgi_report(number, system, http_proxy, archived,
+ followups)
+ if result: return result
+
+ url = report_url(system, number, mirrors)
+ if not url: return None
+
+ return parse_html_report(number, url, http_proxy, followups, cgi=False)
+
+class NullParser(sgmllib.SGMLParser):
+ def __init__(self):
+ sgmllib.SGMLParser.__init__(self)
+
+if __name__ == '__main__':
+ data = get_cgi_reports('reportbug')
+ pprint.pprint(data)
+ time.sleep(1000)
Property changes on: branches/manual_merge/reportbug/debianbts.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/hiermatch.py (from rev 625, branches/manual_merge/reportbuglib/hiermatch.py)
===================================================================
--- branches/manual_merge/reportbug/hiermatch.py (rev 0)
+++ branches/manual_merge/reportbug/hiermatch.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,55 @@
+# Doing match on a list of string or a hierarchy.
+
+import re
+
+from reportbuglib import reportbug_exceptions
+
+def egrep_list(strlist, pattern_str, subindex=None):
+ """Use the pattern_str to find any match in a list of strings."""
+ """Return: a list of index for the matchs into the origin list."""
+
+ if strlist is None:
+ return None
+
+ try:
+ pat = re.compile(pattern_str, re.I|re.M)
+ except:
+ raise reportbug_exceptions.InvalidRegex
+
+ resultlist = []
+ if subindex is None:
+ subindex = range(len(strlist))
+ for i in subindex:
+ if pat.search(strlist[i]):
+ resultlist.append(i)
+ return resultlist
+
+def egrep_hierarchy(hier, pattern_str, subhier=None, nth=1):
+ """Grep the nth item of a hierarchy [(x, [a, b]),...]."""
+ """Return a subhier like [[n, m],[],...], n, m string index."""
+ resulthier = []
+
+ for i in range(len(hier)):
+ if subhier:
+ if subhier[i]: # Only if have something to match.
+ resultlist = egrep_list(hier[i][nth], pattern_str, subhier[i])
+ else:
+ resultlist = []
+ else:
+ resultlist = egrep_list(hier[i][nth], pattern_str)
+
+ resulthier.append(resultlist)
+ return resulthier
+
+def matched_hierarchy(hier, pattern_str):
+ """Actually create a new hierarchy from a pattern matching."""
+ mhier = []
+ result = egrep_hierarchy(hier, pattern_str)
+ for i in range(len(result)):
+ if result[i]:
+ item = [hier[i][1][y] for y in result[i]]
+ mhier.append((hier[i][0], item))
+ return mhier
+
+# vim:ts=8:sw=4:expandtab:
+
Property changes on: branches/manual_merge/reportbug/hiermatch.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/rbtempfile.py (from rev 625, branches/manual_merge/reportbuglib/rbtempfile.py)
===================================================================
--- branches/manual_merge/reportbug/rbtempfile.py (rev 0)
+++ branches/manual_merge/reportbug/rbtempfile.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,91 @@
+#
+# reportbuglib/rbtempfile module - Temporary file handling for reportbug
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 1999-2004 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# Version ##VERSION##; see changelog for revision history
+#
+# $Id: rbtempfile.py,v 1.5 2004-09-13 01:13:10 lawrencc Exp $
+
+import os
+import tempfile
+import time
+
+def tempfile_prefix(package=None, extra=None):
+ if extra:
+ if package:
+ package = '%s-%s' % (package, extra)
+ else:
+ package = extra
+
+ if package:
+ return 'reportbug-%s-%s-%d-' % (
+ package, time.strftime('%Y%m%d'), os.getpid())
+ return 'reportbug-%s-%d-' % (time.strftime('%Y%m%d'), os.getpid())
+
+template = tempfile_prefix()
+
+# Derived version of mkstemp that returns a Python file object
+_text_openflags = os.O_RDWR | os.O_CREAT | os.O_EXCL
+if hasattr(os, 'O_NOINHERIT'):
+ _text_openflags |= os.O_NOINHERIT
+if hasattr(os, 'O_NOFOLLOW'):
+ _text_openflags |= os.O_NOFOLLOW
+
+_bin_openflags = _text_openflags
+if hasattr(os, 'O_BINARY'):
+ _bin_openflags |= os.O_BINARY
+
+# Safe open, prevents filename races in shared tmp dirs
+# Based on python-1.5.2/Lib/tempfile.py
+def open_write_safe(filename, mode='w+b', bufsize=-1):
+ if 'b' in mode:
+ fd = os.open(filename, _bin_openflags, 0600)
+ else:
+ fd = os.open(filename, _text_openflags, 0600)
+
+ try:
+ return os.fdopen(fd, mode, bufsize)
+ except:
+ os.close(fd)
+ raise
+
+# Wrapper for mkstemp; main difference is that text defaults to True, and it
+# returns a Python file object instead of an os-level file descriptor
+def TempFile(suffix="", prefix=template, dir=None, text=True,
+ mode="w+", bufsize=-1):
+ fh, filename = tempfile.mkstemp(suffix, prefix, dir, text)
+ fd = os.fdopen(fh, mode, bufsize)
+ return (fd, filename)
+
+def cleanup_temp_file(temp_filename):
+ """ Clean up a temporary file.
+
+ :parameters:
+ `temp_filename`
+ Full filename of the file to clean up.
+
+ :return value:
+ None
+
+ Removes (unlinks) the named file if it exists.
+
+ """
+ if os.path.exists(temp_filename):
+ os.unlink(temp_filename)
Property changes on: branches/manual_merge/reportbug/rbtempfile.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/reportbug.py (from rev 625, branches/manual_merge/reportbuglib/reportbug.py)
===================================================================
--- branches/manual_merge/reportbug/reportbug.py (rev 0)
+++ branches/manual_merge/reportbug/reportbug.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,1056 @@
+#
+# reportbuglib/reportbug.py
+# Common functions for reportbug and greportbug
+#
+# Written by Chris Lawrence <lawrencc at debian.org>
+# Copyright (C) 1999-2006 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# Version ##VERSION##; see changelog for revision history
+#
+# $Id: reportbug.py,v 1.35.2.24 2008-04-18 05:38:28 lawrencc Exp $
+
+VERSION = "reportbug ##VERSION##"
+VERSION_NUMBER = "##VERSION##"
+COPYRIGHT = VERSION + '\nCopyright (C) 1999-2006 Chris Lawrence <lawrencc at debian.org>'
+
+import sys
+import os
+import re
+import pwd
+import commands
+import shlex
+import rfc822
+import socket
+import subprocess
+import imp
+
+import debianbts
+
+# Paths for dpkg
+DPKGLIB = '/var/lib/dpkg'
+AVAILDB = os.path.join(DPKGLIB, 'available')
+STATUSDB = os.path.join(DPKGLIB, 'status')
+
+# Headers other than these become email headers for debbugs servers
+PSEUDOHEADERS = ('Package', 'Version', 'Severity', 'File', 'Tags',
+ 'Justification', 'Followup-For', 'Owner', 'User', 'Usertags')
+
+VALID_UIS = ['newt', 'text', 'gnome2', 'urwid']
+AVAILABLE_UIS = VALID_UIS
+for ui in VALID_UIS:
+ module_name = 'reportbug_ui_%(ui)s' % vars()
+ try:
+ imp.find_module(module_name)
+ except ImportError:
+ AVAILABLE_UIS.remove(ui)
+
+UIS = {'text': 'A text-oriented console interface',
+ 'urwid': 'A menu-based console interface',
+ 'gnome2': 'A graphical (Gnome 2) interface'}
+
+MODES = {'novice': 'Offer simple prompts, bypassing technical questions.',
+ 'standard': 'Offer more extensive prompts, including asking about '
+ 'things that a moderately sophisticated user would be expected to '
+ 'know about Debian.',
+ 'advanced' : 'Like standard, but assumes you know a bit more about '
+ 'Debian, including "incoming".',
+ 'expert': 'Bypass most handholding measures and preliminary triage '
+ 'routines. This mode should not be used by people unfamiliar with '
+ 'Debian\'s policies and operating procedures.'}
+MODELIST = ['novice', 'standard', 'advanced', 'expert']
+for mode in MODELIST:
+ exec 'MODE_%s=%d' % (mode.upper(), MODELIST.index(mode))
+del mode
+
+NEWBIELINE = '*** Please type your report below this line ***'
+
+fhs_directories = ['/', '/usr', '/usr/share', '/var', '/usr/X11R6',
+ '/usr/man', '/usr/doc', '/usr/bin']
+
+def realpath(filename):
+ filename = os.path.abspath(filename)
+
+ bits = filename.split('/')
+ for i in range(2, len(bits)+1):
+ component = '/'.join(bits[0:i])
+ if component in fhs_directories:
+ continue
+
+ if os.path.islink(component):
+ resolved = os.readlink(component)
+ (dir, file) = os.path.split(component)
+ resolved = os.path.normpath(os.path.join(dir, resolved))
+ newpath = apply(os.path.join, [resolved] + bits[i:])
+ return realpath(newpath)
+
+ return filename
+
+pathdirs = ['/usr/sbin', '/usr/bin', '/sbin', '/bin', '/usr/X11R6/bin',
+ '/usr/games']
+
+def search_path_for(filename):
+ d, f = os.path.split(filename)
+ if d: return realpath(filename)
+
+ path = os.environ.get("PATH", os.defpath).split('/')
+ for d in pathdirs:
+ if not d in path:
+ path.append(d)
+
+ for d in path:
+ fullname = os.path.join(d, f)
+ if os.path.exists(fullname):
+ return realpath(fullname)
+ return None
+
+def which_editor(specified_editor=None):
+ """ Determine which editor program to use.
+
+ :parameters:
+ `specified_editor`
+ Specified editor for reportbug, to be used in preference
+ to other settings.
+
+ :return value:
+ Command to invoke for selected editor program.
+
+ """
+ debian_default_editor = "/usr/bin/sensible-editor"
+ for editor in [
+ specified_editor,
+ os.environ.get("VISUAL"),
+ os.environ.get("EDITOR"),
+ debian_default_editor]:
+ if editor:
+ break
+
+ return editor
+
+def glob_escape(filename):
+ filename = re.sub(r'([*?\[\]])', r'\\\1', filename)
+ return filename
+
+def search_pipe(searchfile, use_dlocate=True):
+ arg = commands.mkarg(searchfile)
+ if use_dlocate and os.path.exists('/usr/bin/dlocate'):
+ pipe = os.popen('COLUMNS=79 dlocate -S %s 2>/dev/null' % arg)
+ else:
+ use_dlocate = False
+ pipe = os.popen('COLUMNS=79 dpkg --search %s 2>/dev/null' % arg)
+ return (pipe, use_dlocate)
+
+def query_dpkg_for(filename, use_dlocate=True):
+ try:
+ x = os.getcwd()
+ except OSError:
+ os.chdir('/')
+ searchfilename = glob_escape(filename)
+ (pipe, dlocate_used) = search_pipe(searchfilename, use_dlocate)
+ packages = {}
+
+ for line in pipe:
+ line = line.strip()
+ # Ignore diversions
+ if 'diversion by' in line: continue
+
+ (package, path) = line.split(':', 1)
+ path = path.strip()
+ packlist = package.split(', ')
+ for package in packlist:
+ if packages.has_key(package):
+ packages[package].append(path)
+ else:
+ packages[package] = [path]
+ pipe.close()
+ # Try again without dlocate if no packages found
+ if not packages and dlocate_used:
+ return query_dpkg_for(filename, use_dlocate=False)
+
+ return filename, packages
+
+def find_package_for(filename, pathonly=False):
+ """Find the package(s) containing this file."""
+ packages = {}
+ if filename[0] == '/':
+ fn, pkglist = query_dpkg_for(filename)
+ if pkglist: return fn, pkglist
+
+ newfilename = search_path_for(filename)
+ if pathonly and not newfilename:
+ return (filename, None)
+ return query_dpkg_for(newfilename or filename)
+
+def find_rewritten(username):
+ for filename in ['/etc/email-addresses']:
+ if os.path.exists(filename):
+ try:
+ fp = file(filename)
+ except IOError:
+ continue
+ for line in fp:
+ line = line.strip().split('#')[0]
+ if not line:
+ continue
+ try:
+ name, alias = line.split(':')
+ if name.strip() == username:
+ return alias.strip()
+ except ValueError:
+ print 'Invalid entry in %s' % filename
+ return None
+
+def get_email_addr(addr):
+ addr = rfc822.AddressList(addr)
+ return addr.addresslist[0]
+
+def get_email(email='', realname=''):
+ return get_email_addr(get_user_id(email, realname))
+
+def get_user_id(email='', realname='', charset='utf-8'):
+ uid = os.getuid()
+ info = pwd.getpwuid(uid)
+ email = (os.environ.get('REPORTBUGEMAIL', email) or
+ os.environ.get('DEBEMAIL') or os.environ.get('EMAIL'))
+
+ email = email or find_rewritten(info[0]) or info[0]
+
+ if '@' not in email:
+ if os.path.exists('/etc/mailname'):
+ domainname = file('/etc/mailname').readline().strip()
+ else:
+ domainname = socket.getfqdn()
+
+ email = email+'@'+domainname
+
+ # Handle EMAIL if it's formatted as 'Bob <bob at host>'.
+ if '<' in email or '(' in email:
+ realname, email = get_email_addr(email)
+
+ if not realname:
+ realname = (os.environ.get('DEBFULLNAME') or os.environ.get('DEBNAME')
+ or os.environ.get('NAME'))
+ if not realname:
+ realname = info[4].split(',', 1)[0]
+ # Convert & in gecos field 4 to capitalized logname: #224231
+ realname = realname.replace('&', info[0].upper())
+
+ if not realname:
+ return email
+
+ # Decode the realname from the charset -
+ # but only if it is not already in Unicode
+ if isinstance(realname, str):
+ realname = realname.decode(charset, 'replace')
+
+ if re.match(r'[\w\s]+$', realname):
+ return u'%s <%s>' % (realname, email)
+
+ addr = rfc822.dump_address_pair( (realname, email) )
+ if isinstance(addr, str):
+ addr = addr.decode('utf-8', 'replace')
+ return addr
+
+statuscache = {}
+def get_package_status(package, avail=False):
+ if not avail and package in statuscache:
+ return statuscache[package]
+
+ versionre = re.compile('Version: ')
+ packagere = re.compile('Package: ')
+ priorityre = re.compile('Priority: ')
+ dependsre = re.compile('(Pre-)?Depends: ')
+ recsre = re.compile('Recommends: ')
+ suggestsre = re.compile('Suggests: ')
+ conffilesre = re.compile('Conffiles: ')
+ maintre = re.compile('Maintainer: ')
+ statusre = re.compile('Status: ')
+ originre = re.compile('Origin: ')
+ bugsre = re.compile('Bugs: ')
+ descre = re.compile('Description: ')
+ fullre = re.compile(' ')
+ srcre = re.compile('Source: ')
+
+ pkgversion = pkgavail = maintainer = status = origin = None
+ bugs = vendor = priority = desc = src_name = None
+ conffiles = []
+ fulldesc = []
+ depends = []
+ recommends = []
+ suggests = []
+ confmode = False
+ state = ''
+
+ try:
+ x = os.getcwd()
+ except OSError:
+ os.chdir('/')
+
+ packarg = commands.mkarg(package)
+ if avail:
+ output = commands.getoutput(
+ "COLUMNS=79 dpkg --print-avail %s 2>/dev/null" % packarg)
+ else:
+ output = commands.getoutput(
+ "COLUMNS=79 dpkg --status %s 2>/dev/null" % packarg)
+
+ # dpkg output is in UTF-8 format
+ output = output.decode('utf-8', 'replace')
+
+ for line in output.split(os.linesep):
+ line = line.rstrip()
+ if not line: continue
+
+ if confmode:
+ if line[0] != '/':
+ confmode = False
+ else:
+ conffiles = conffiles + (line.split(),)
+
+ if versionre.match(line):
+ (crud, pkgversion) = line.split(": ", 1)
+ elif statusre.match(line):
+ (crud, status) = line.split(": ", 1)
+ elif priorityre.match(line):
+ (crud, priority) = line.split(": ", 1)
+ elif packagere.match(line):
+ (crud, pkgavail) = line.split(": ", 1)
+ elif originre.match(line):
+ (crud, origin) = line.split(": ", 1)
+ elif bugsre.match(line):
+ (crud, bugs) = line.split(": ", 1)
+ elif descre.match(line):
+ (crud, desc) = line.split(": ", 1)
+ elif dependsre.match(line):
+ (crud, thisdepends) = line.split(": ", 1)
+ # Remove versioning crud
+ thisdepends = [[y.split()[0] for y in x.split('|')]
+ for x in (thisdepends.split(', '))]
+ depends.extend(thisdepends)
+ elif recsre.match(line):
+ (crud, thisdepends) = line.split(": ", 1)
+ # Remove versioning crud
+ thisdepends = [[y.split()[0] for y in x.split('|')]
+ for x in (thisdepends.split(', '))]
+ recommends.extend(thisdepends)
+ elif suggestsre.match(line):
+ (crud, thisdepends) = line.split(": ", 1)
+ # Remove versioning crud
+ thisdepends = [[y.split()[0] for y in x.split('|')]
+ for x in (thisdepends.split(', '))]
+ suggests.extend(thisdepends)
+ elif conffilesre.match(line):
+ confmode = True
+ elif maintre.match(line):
+ crud, maintainer = line.split(": ", 1)
+ elif srcre.match(line):
+ crud, src_name = line.split(": ", 1)
+ src_name = src_name.split()[0]
+ elif desc and line[0]==' ':
+ fulldesc.append(line)
+
+ installed = False
+ if status:
+ state = status.split()[2]
+ installed = (state not in ('config-files', 'not-installed'))
+
+ reportinfo = None
+ if bugs:
+ reportinfo = debianbts.parse_bts_url(bugs)
+ elif origin:
+ if debianbts.SYSTEMS.has_key(origin):
+ vendor = debianbts.SYSTEMS[origin]['name']
+ reportinfo = (debianbts.SYSTEMS[origin].get('type', 'debbugs'),
+ debianbts.SYSTEMS[origin]['btsroot'])
+ else:
+ vendor = origin.capitalize()
+ else:
+ vendor = ''
+
+ info = (pkgversion, pkgavail, tuple(depends), tuple(recommends),
+ tuple(conffiles),
+ maintainer, installed, origin, vendor, reportinfo, priority,
+ desc, src_name, os.linesep.join(fulldesc), state, tuple(suggests))
+
+ if not avail:
+ statuscache[package] = info
+ return info
+
+#dbase = []
+#avail = []
+
+# Object that essentially chunkifies the output of apt-cache dumpavail
+class AvailDB(object):
+ def __init__(self, fp=None, popenob=None):
+ self.popenob = popenob
+ if fp:
+ self.fp = fp
+ elif popenob:
+ self.fp = popenob.stdout
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ chunk = u''
+ while True:
+ if self.popenob:
+ if self.popenob.returncode:
+ break
+
+ line = self.fp.readline()
+ if not line:
+ break
+
+ if line == '\n':
+ return chunk
+ chunk += line.decode('utf-8', 'replace')
+
+ if chunk:
+ return chunk
+
+ raise StopIteration
+
+ def __del__(self):
+ #print >> sys.stderr, 'availdb cleanup', repr(self.popenob), repr(self.fp)
+ if self.popenob:
+ # Clear the pipe before shutting it down
+ while True:
+ if self.popenob.returncode:
+ break
+ stuff = self.fp.read(65536)
+ if not stuff:
+ break
+ self.popenob.wait()
+ if self.fp:
+ self.fp.close()
+
+def get_dpkg_database():
+ if os.path.exists(STATUSDB):
+ fp = open(STATUSDB)
+ if fp:
+ return AvailDB(fp=fp)
+
+ print >> sys.stderr, 'Unable to open', STATUSDB
+ sys.exit(1)
+
+def get_avail_database():
+ #print >> sys.stderr, 'Searching available database'
+ subp = subprocess.Popen(('apt-cache', 'dumpavail'), stdout=subprocess.PIPE)
+ return AvailDB(popenob=subp)
+
+def available_package_description(package):
+ data = commands.getoutput('apt-cache show'+commands.mkarg(package))
+ data = data.decode('utf-8', 'replace')
+ descre = re.compile(r'^Description: (.*)$')
+ for line in data.split('\n'):
+ m = descre.match(line)
+ if m:
+ return m.group(1)
+ return None
+
+def get_source_name(package):
+ packages = []
+
+ data = commands.getoutput('apt-cache showsrc'+commands.mkarg(package))
+ data = data.decode('utf-8', 'replace')
+ packre = re.compile(r'^Package: (.*)$')
+ for line in data.split('\n'):
+ m = packre.match(line)
+ if m:
+ return m.group(1)
+ return None
+
+def get_source_package(package):
+ packages = []
+ retlist = []
+ found = {}
+
+ data = commands.getoutput('apt-cache showsrc'+commands.mkarg(package))
+ data = data.decode('utf-8', 'replace')
+ binre = re.compile(r'^Binary: (.*)$')
+ for line in data.split('\n'):
+ m = binre.match(line)
+ if m:
+ packs = m.group(1)
+ packlist = re.split(r',\s*', packs)
+ packages += packlist
+
+ for p in packages:
+ desc = available_package_description(p)
+ if desc and (p not in found):
+ retlist += [(p, desc)]
+ found[p] = desc
+
+ retlist.sort()
+ return retlist
+
+def get_package_info(packages, skip_notfound=False):
+ if not packages:
+ return []
+
+ packinfo = get_dpkg_database()
+ pkgname = r'(?:[\S]+(?:$|,\s+))'
+
+ groupfor = {}
+ searchpkgs = []
+ searchbits = []
+ for (group, package) in packages:
+ groupfor[package] = group
+ escpkg = re.escape(package)
+ searchpkgs.append(escpkg)
+
+ searchbits = [
+ # Package regular expression
+ r'^(?P<hdr>Package):\s+('+'|'.join(searchpkgs)+')$',
+ # Provides regular expression
+ r'^(?P<hdr>Provides):\s+'+pkgname+r'*(?P<pkg>'+'|'.join(searchpkgs)+
+ r')(?:$|,\s+)'+pkgname+'*$'
+ ]
+
+ groups = groupfor.values()
+ found = {}
+
+ searchobs = [re.compile(x, re.MULTILINE) for x in searchbits]
+ packob = re.compile('^Package: (?P<pkg>.*)$', re.MULTILINE)
+ statob = re.compile('^Status: (?P<stat>.*)$', re.MULTILINE)
+ versob = re.compile('^Version: (?P<vers>.*)$', re.MULTILINE)
+ descob = re.compile('^Description: (?P<desc>.*)$', re.MULTILINE)
+
+ ret = []
+ for p in packinfo:
+ for ob in searchobs:
+ m = ob.search(p)
+ if m:
+ pack = packob.search(p).group('pkg')
+ stat = statob.search(p).group('stat')
+ sinfo = stat.split()
+ stat = sinfo[0][0] + sinfo[2][0]
+ if stat[1] != 'i':
+ continue
+
+ if m.group('hdr') == 'Provides':
+ provides = m.group('pkg')
+ else:
+ provides = None
+
+ vers = versob.search(p).group('vers')
+ desc = descob.search(p).group('desc')
+
+ info = (pack,stat,vers,desc,provides)
+ ret.append(info)
+ group = groupfor.get(pack)
+ if group:
+ for item in group:
+ found[item] = True
+ if provides not in found:
+ found[provides] = True
+
+ if skip_notfound:
+ return ret
+
+ for group in groups:
+ notfound = [x for x in group if x not in found]
+ if len(notfound) == len(group):
+ if group not in found:
+ ret.append( (' | '.join(group), 'pn', '<none>',
+ '(no description available)', None) )
+
+ return ret
+
+def packages_providing(package):
+ aret = get_package_info([((package,), package)], skip_notfound=True)
+ ret = []
+ for pkg in aret:
+ ret.append( (pkg[0], pkg[3]) )
+
+ return ret
+
+def get_dependency_info(package, depends, rel="depends on"):
+ if not depends:
+ return ('\n%s %s no packages.\n' % (package, rel))
+
+ dependencies = []
+ for dep in depends:
+ for bit in dep:
+ dependencies.append( (tuple(dep), bit) )
+
+ depinfo = "\nVersions of packages %s %s:\n" % (package, rel)
+
+ packs = {}
+ for info in get_package_info(dependencies):
+ pkg = info[0]
+ if pkg not in packs:
+ packs[pkg] = info
+ elif info[4]:
+ if not packs[pkg][4]:
+ packs[pkg] = info
+
+ deplist = packs.values()
+ deplist.sort()
+ maxlen = max([len(x[2]) for x in deplist] + [10])
+
+ for (pack, status, vers, desc, provides) in deplist:
+ if provides:
+ pack += ' [' + provides + ']'
+
+ packstuff = '%-*.*s %s' % (39-maxlen, 39-maxlen, pack, vers)
+
+ info = '%-3.3s %-40.40s %-.34s\n' % (status, packstuff, desc)
+ depinfo += info
+
+ return depinfo
+
+def get_changed_config_files(conffiles, nocompress=False):
+ confinfo = {}
+ changed = []
+ for (filename, md5sum) in conffiles:
+ try:
+ fp = file(filename)
+ except IOError, msg:
+ confinfo[filename] = msg
+ continue
+
+ filemd5 = commands.getoutput('md5sum ' + commands.mkarg(filename)).split()[0]
+ if filemd5 == md5sum: continue
+
+ changed.append(filename)
+ thisinfo = 'changed:\n'
+ for line in fp:
+ if not line: continue
+
+ if line == '\n' and not nocompress: continue
+ if line[0] == '#' and not nocompress: continue
+
+ thisinfo += line
+
+ confinfo[filename] = thisinfo
+
+ return confinfo, changed
+
+DISTORDER = ['stable', 'testing', 'unstable', 'experimental']
+
+def get_debian_release_info():
+ debvers = debinfo = verfile = warn = ''
+ dists = []
+ output = commands.getoutput('apt-cache policy 2>/dev/null')
+ if output:
+ mre = re.compile('\s+(\d+)\s+.*$\s+release\s.*o=(Ubuntu|Debian),a=([^,]+),', re.MULTILINE)
+ found = {}
+ ## XXX: When Python 2.4 rolls around, rewrite this
+ for match in mre.finditer(output):
+ pword, distname = match.group(1, 3)
+ if distname in DISTORDER:
+ pri, dist = int(pword), DISTORDER.index(distname)
+ else:
+ pri, dist = int(pword), len(DISTORDER)
+
+ found[(pri, dist, distname)] = True
+
+ if found:
+ dists = found.keys()
+ dists.sort()
+ dists.reverse()
+ dists = [(x[0], x[2]) for x in dists]
+ debvers = dists[0][1]
+
+ if os.path.exists('/etc/debian_version'):
+ fob = open('/etc/debian_version')
+ verfile = fob.readline().strip()
+ fob.close()
+
+ if verfile:
+ debinfo += 'Debian Release: '+verfile+'\n'
+ if debvers:
+ debinfo += ' APT prefers '+debvers+'\n'
+ if dists:
+ # Should wrap this eventually...
+ #policystr = pprint.pformat(dists)
+ policystr = ', '.join([str(x) for x in dists])
+ debinfo += ' APT policy: %s\n' % policystr
+ if warn:
+ debinfo += warn
+
+ return debinfo
+
+def get_arch():
+ arch = commands.getoutput('COLUMNS=79 dpkg --print-installation-architecture 2>/dev/null')
+ if not arch:
+ un = os.uname()
+ arch = un[4]
+ arch = re.sub(r'i[456]86', 'i386', arch)
+ arch = re.sub(r's390x', 's390', arch)
+ arch = re.sub(r'ppc', 'powerpc', arch)
+ return arch
+
+def generate_blank_report(package, pkgversion, severity, justification,
+ depinfo, confinfo, foundfile='', incfiles='',
+ system='debian', exinfo=0, type=None, klass='',
+ subject='', tags='', body='', mode=MODE_EXPERT,
+ pseudos=None):
+ un = os.uname()
+ utsmachine = un[4]
+ debinfo = u''
+ shellpath = realpath('/bin/sh')
+
+ locinfo = []
+ langsetting = os.environ.get('LANG', 'C')
+ allsetting = os.environ.get('LC_ALL', '')
+ for setting in ('LANG', 'LC_CTYPE'):
+ if setting == 'LANG':
+ env = langsetting
+ else:
+ env = '%s (charmap=%s)' % (os.environ.get(setting, langsetting), commands.getoutput("locale charmap"))
+
+ if allsetting and env:
+ env = "%s (ignored: LC_ALL set to %s)" % (env, allsetting)
+ else:
+ env = allsetting or env
+ locinfo.append('%s=%s' % (setting, env))
+
+ locinfo = ', '.join(locinfo)
+
+ if debianbts.SYSTEMS[system].has_key('namefmt'):
+ package = debianbts.SYSTEMS[system]['namefmt'] % package
+
+ if pseudos:
+ headers = u'\n'.join(pseudos)+u'\n'
+ else:
+ headers = u''
+
+ if pkgversion:
+ headers += u'Version: %s\n' % pkgversion
+
+ if not exinfo:
+ if severity:
+ headers += u'Severity: %s\n' % severity
+
+ if justification:
+ headers += u'Justification: %s\n' % justification
+
+ if tags:
+ headers += u'Tags: %s\n' % tags
+
+ if foundfile:
+ headers += u'File: %s\n' % foundfile
+
+ if mode < MODE_ADVANCED:
+ body = NEWBIELINE+u'\n\n'+body
+
+ report = "\n"
+ if not exinfo:
+ if type == 'gnats':
+ report = ">Synopsis: %s\n>Confidential: no\n" % subject
+ if package == 'debian-general':
+ report += ">Category: %s\n" % package
+ else:
+ report += ">Category: debian-packages\n"\
+ ">Release: %s_%s\n" % (package, pkgversion)
+
+ if severity:
+ report += ">" + ('Severity: %s\n' % severity)
+
+ if klass:
+ report += ">Class: %s\n" % klass
+ report += (
+ ">Description:\n\n"
+ " <describe the bug here; use as many lines as you need>\n\n"
+ ">How-To-Repeat:\n\n"
+ " <show how the bug is triggered>\n\n"
+ ">Fix:\n\n"
+ " <if you have a patch or solution, put it here>\n\n"
+ ">Environment:\n")
+ else:
+ report = "Package: %s\n%s\n" % (package, headers)
+ else:
+ report = "Followup-For: Bug #%d\nPackage: %s\n%s\n" % (
+ exinfo, package, headers)
+
+ if not body:
+ body = u"\n"
+
+ if debianbts.SYSTEMS[system].get('query-dpkg', True):
+ debinfo += get_debian_release_info()
+ debarch = get_arch()
+ if debarch:
+ if utsmachine == debarch:
+ debinfo += u'Architecture: %s\n' % (debarch)
+ else:
+ debinfo += u'Architecture: %s (%s)\n' % (debarch, utsmachine)
+ else:
+ debinfo += u'Architecture: ? (%s)\n' % utsmachine
+
+
+ # Gather system info only for bugs not wnpp
+ if package not in ('wnpp'):
+ debinfo += u'\n'
+
+ if un[0] == 'GNU':
+ # Use uname -v on Hurd
+ uname_string = un[3]
+ else:
+ kern = un[0]
+ if kern.startswith('GNU/'):
+ kern = kern[4:]
+
+ uname_string = '%s %s' % (kern, un[2])
+ if kern == 'Linux':
+ kinfo = []
+
+ if 'SMP' in un[3]:
+ cores = get_cpu_cores()
+ if cores > 1:
+ kinfo += ['SMP w/%d CPU cores' % cores]
+ else:
+ kinfo += ['SMP w/1 CPU core']
+ if 'PREEMPT' in un[3]:
+ kinfo += ['PREEMPT']
+
+ if kinfo:
+ uname_string = '%s (%s)' % (uname_string, '; '.join(kinfo))
+
+ if uname_string:
+ debinfo += u'Kernel: %s\n' % uname_string
+ if locinfo:
+ debinfo += u'Locale: %s\n' % locinfo
+ if shellpath != '/bin/sh':
+ debinfo += u'Shell: /bin/sh linked to %s\n' % shellpath
+
+ return u"""%s%s%s
+-- System Information:
+%s%s%s""" % (report, body, incfiles, debinfo, depinfo, confinfo)
+
+def get_cpu_cores():
+ cpucount = 0
+ fob = open('/proc/cpuinfo')
+ for line in fob:
+ if line.startswith('processor'):
+ cpucount += 1
+ #print repr(line), cpucount
+ fob.close()
+
+ return max(cpucount, 1)
+
+class our_lex(shlex.shlex):
+ def get_token(self):
+ token = shlex.shlex.get_token(self)
+ if not len(token): return token
+ if (token[0] == token[-1]) and token[0] in self.quotes:
+ token = token[1:-1]
+ return token
+
+USERFILE = os.path.expanduser('~/.reportbugrc')
+FILES = ('/etc/reportbug.conf', USERFILE)
+
+CONFIG_ARGS = (
+ 'sendto', 'severity', 'mua', 'mta', 'email', 'realname', 'bts', 'verify',
+ 'replyto', 'http_proxy', 'smtphost', 'editor', 'debconf', 'justification',
+ 'sign', 'nocc', 'nocompress', 'dontquery', 'noconf', 'mirrors', 'keyid',
+ 'headers', 'interface', 'template', 'mode', 'check_available', 'query_src',
+ 'printonly', 'offline', 'check_uid', 'smtptls', 'smtpuser', 'smtppasswd',
+ 'paranoid')
+
+MUA = {
+ 'mutt' : 'mutt -H',
+ 'af' : 'af -EH < ',
+ 'mh' : '/usr/bin/mh/comp -use -file',
+ 'gnus' : 'REPORTBUG=%s emacs -l /usr/share/reportbug/reportbug.el -f tfheen-reportbug-insert-template',
+ }
+MUA['nmh'] = MUA['mh']
+
+def first_run():
+ return not os.path.exists(USERFILE)
+
+def parse_config_files():
+ args = {}
+ for filename in FILES:
+ if os.path.exists(filename):
+ try:
+ lex = our_lex(file(filename))
+ except IOError, msg:
+ continue
+
+ lex.wordchars = lex.wordchars + '-.@/:<>'
+
+ token = lex.get_token().lower()
+ while token:
+ if token in ('quiet', 'maintonly', 'submit'):
+ args['sendto'] = token
+ elif token == 'severity':
+ token = lex.get_token().lower()
+ if token in debianbts.SEVERITIES.keys():
+ args['severity'] = token
+ elif token == 'header':
+ args['headers'] = args.get('headers', []) + \
+ [lex.get_token()]
+ elif token in ('no-cc', 'cc'):
+ args['nocc'] = (token == 'no-cc')
+ elif token in ('no-compress', 'compress'):
+ args['nocompress'] = (token == 'no-compress')
+ elif token in ('no-query-bts', 'query-bts'):
+ args['dontquery'] = (token == 'no-query-bts')
+ elif token in ('config-files', 'no-config-files'):
+ args['noconf'] = (token == 'no-config-files')
+ elif token in ('ldap', 'no-ldap'):
+ pass
+ elif token in ('printonly', 'template', 'offline'):
+ args[token] = True
+ elif token in ('email', 'realname', 'replyto', 'http_proxy',
+ 'smtphost', 'editor', 'mua', 'mta', 'smtpuser',
+ 'smtppasswd', 'justification', 'keyid'):
+ bit = lex.get_token()
+ args[token] = bit.decode('utf-8', 'replace')
+ elif token in ('no-smtptls', 'smtptls'):
+ args['smtptls'] = (token == 'smtptls')
+ elif token == 'sign':
+ token = lex.get_token().lower()
+ if token in ('pgp', 'gpg'):
+ args['sign'] = token
+ elif token == 'gnupg':
+ args['sign'] = 'gpg'
+ elif token == 'none':
+ args['sign'] = ''
+ elif token == 'ui':
+ token = lex.get_token().lower()
+ if token in AVAILABLE_UIS:
+ args['interface'] = token
+ elif token == 'mode':
+ arg = lex.get_token().lower()
+ if arg in MODES.keys():
+ args[token] = arg
+ elif token == 'bts':
+ token = lex.get_token().lower()
+ if token in debianbts.SYSTEMS.keys():
+ args['bts'] = token
+ elif token == 'mirror':
+ args['mirrors'] = args.get('mirrors', []) + \
+ [lex.get_token()]
+ elif token in ('no-check-available', 'check-available'):
+ args['check_available'] = (token == 'check-available')
+ elif token == 'reportbug_version':
+ # Currently ignored; might be used for compat purposes
+ # eventually
+ w_version = lex.get_token().lower()
+ elif token in MUA:
+ args['mua'] = MUA[token]
+ elif token in ('query-source', 'no-query-source'):
+ args['query_src'] = (token == 'query-source')
+ elif token in ('debconf', 'no-debconf'):
+ args['debconf'] = (token == 'debconf')
+ elif token in ('verify', 'no-verify'):
+ args['verify'] = (token == 'verify')
+ elif token in ('check-uid', 'no-check-uid'):
+ args['check_uid'] = (token == 'check-uid')
+ elif token in ('paranoid', 'no-paranoid'):
+ args['paranoid'] = (token == 'paranoid')
+ else:
+ sys.stderr.write('Unrecognized token: %s\n' % token)
+
+ token = lex.get_token().lower()
+
+ return args
+
+def parse_bug_control_file(filename):
+ submitas = submitto = None
+ reportwith = []
+ supplemental = []
+ fh = file(filename)
+ for line in fh:
+ line = line.strip()
+ parts = line.split(': ')
+ if len(parts) != 2:
+ continue
+
+ header, data = parts[0].lower(), parts[1]
+ if header == 'submit-as':
+ submitas = data
+ elif header == 'send-to':
+ submitto = data
+ elif header == 'report-with':
+ reportwith += data.split(' ')
+ elif header == 'package-status':
+ supplemental += data.split(' ')
+
+ return submitas, submitto, reportwith, supplemental
+
+def cleanup_msg(dmessage, headers, type):
+ pseudoheaders = []
+ # Handle non-pseduo-headers
+ headerre = re.compile(r'^([^:]+):\s*(.*)$', re.I)
+ newsubject = message = ''
+ parsing = lastpseudo = True
+
+ # Include the headers that were passed in too!
+ newheaders = []
+ for header in headers:
+ mob = headerre.match(header)
+ if mob:
+ newheaders.append(mob.groups())
+
+ for line in dmessage.split(os.linesep):
+ if not line and parsing:
+ parsing = False
+ elif parsing:
+ mob = headerre.match(line)
+ # GNATS and debbugs have different ideas of what a pseudoheader
+ # is...
+ if mob and ((type == 'debbugs' and
+ mob.group(1) not in PSEUDOHEADERS) or
+ (type == 'gnats' and mob.group(1)[0] != '>')):
+ newheaders.append(mob.groups())
+ lastpseudo = False
+ continue
+ elif mob:
+ # Normalize pseudo-header
+ lastpseudo = False
+ key, value = mob.groups()
+ if key[0] != '>':
+ # Normalize hyphenated headers to capitalize each word
+ key = '-'.join([x.capitalize() for x in key.split('-')])
+ pseudoheaders.append((key, value))
+ elif not lastpseudo and len(newheaders):
+ # Assume we have a continuation line
+ lastheader = newheaders[-1]
+ newheaders[-1] = (lastheader[0], lastheader[1] + '\n' + line)
+ continue
+ else:
+ # Permit bogus headers in the pseudoheader section
+ headers.append(re.split(':\s+', line, 1))
+ elif line.strip() != NEWBIELINE:
+ message += line + '\n'
+
+ ph = []
+ if type == 'gnats':
+ for header, content in pseudoheaders:
+ if content:
+ ph += ["%s: %s" % (header, content)]
+ else:
+ ph += [header]
+ else:
+ ph2 = {}
+ for header, content in pseudoheaders:
+ if header in PSEUDOHEADERS:
+ ph2[header] = content
+ else:
+ newheaders.append( (header, content) )
+
+ for header in PSEUDOHEADERS:
+ if header in ph2:
+ ph += ['%s: %s' % (header, ph2[header])]
+
+ return message, newheaders, ph
Property changes on: branches/manual_merge/reportbug/reportbug.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/reportbug_exceptions.py (from rev 625, branches/manual_merge/reportbuglib/reportbug_exceptions.py)
===================================================================
--- branches/manual_merge/reportbug/reportbug_exceptions.py (rev 0)
+++ branches/manual_merge/reportbug/reportbug_exceptions.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,61 @@
+# reportbuglib/reportbug_exceptions.py
+# Exceptions for reportbug
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 2002-04 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# $Id: reportbug_exceptions.py,v 1.3.2.1 2006-08-26 01:57:29 lawrencc Exp $
+
+class reportbug_exception(Exception):
+ pass
+
+class reportbug_ui_exception(reportbug_exception):
+ pass
+
+# Can't initialize interface
+class UINotImportable(reportbug_ui_exception):
+ pass
+
+# No package found
+class NoPackage(reportbug_ui_exception):
+ pass
+
+# No bugs found
+class NoBugs(reportbug_ui_exception):
+ pass
+
+# Nothing to report
+class NoReport(reportbug_ui_exception):
+ pass
+
+# Code is not implemented
+class UINotImplemented(reportbug_ui_exception):
+ pass
+
+# Other exceptions
+# No network access
+class NoNetwork(reportbug_exception):
+ pass
+
+# Invalid regular expression
+class InvalidRegex(reportbug_exception):
+ pass
+
+# Lame empty exception used later to save some coding
+class NoMessage(reportbug_exception):
+ pass
Property changes on: branches/manual_merge/reportbug/reportbug_exceptions.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/reportbug_submit.py (from rev 625, branches/manual_merge/reportbuglib/reportbug_submit.py)
===================================================================
--- branches/manual_merge/reportbug/reportbug_submit.py (rev 0)
+++ branches/manual_merge/reportbug/reportbug_submit.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,489 @@
+# reportbuglib/reportbug_submit module - email and GnuPG functions
+# Written by Chris Lawrence <lawrencc at debian.org>
+# Copyright (C) 1999-2006 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# Version ##VERSION##; see changelog for revision history
+#
+# $Id: reportbug_submit.py,v 1.20.2.6 2007-04-19 21:21:31 lawrencc Exp $
+
+import sys
+import os
+import re
+import commands
+import rfc822
+import smtplib
+import socket
+import email
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEText import MIMEText
+from email.MIMEAudio import MIMEAudio
+from email.MIMEImage import MIMEImage
+from email.MIMEBase import MIMEBase
+from email.MIMEMessage import MIMEMessage
+from email.Header import Header
+import mimetypes
+
+from reportbuglib import reportbug
+from reportbuglib.reportbug import VERSION, VERSION_NUMBER
+from reportbuglib import debianbts
+from reportbuglib.rbtempfile import (
+ TempFile,
+ open_write_safe,
+ tempfile_prefix,
+ )
+from reportbuglib.reportbug_exceptions import (
+ NoMessage,
+ )
+from reportbuglib import reportbug_ui_text as ui
+
+quietly = False
+
+# Obscene hack :)
+def system(cmdline):
+ try:
+ x = os.getcwd()
+ except OSError:
+ os.chdir('/')
+ os.system(cmdline)
+
+ascii_range = ''.join([chr(ai) for ai in range(32,127)])
+notascii = re.compile(r'[^'+re.escape(ascii_range)+']')
+notascii2 = re.compile(r'[^'+re.escape(ascii_range)+r'\s]')
+
+# Wrapper for MIMEText
+class BetterMIMEText(MIMEText):
+ def __init__(self, _text, _subtype='plain', _charset=None):
+ MIMEText.__init__(self, _text, _subtype, 'us-ascii')
+ # Only set the charset paraemeter to non-ASCII if the body
+ # includes unprintable characters
+ if notascii2.search(_text):
+ self.set_param('charset', _charset)
+
+def encode_if_needed(text, charset, encoding='q'):
+ needed = False
+
+ if notascii.search(text):
+ # Fall back on something vaguely sensible if there are high chars
+ # and the encoding is us-ascii
+ if charset == 'us-ascii':
+ charset = 'iso-8859-15'
+ return Header(text, charset)
+ else:
+ return Header(text, 'us-ascii')
+
+def rfc2047_encode_address(addr, charset, mua=None):
+ newlist = []
+ addresses = rfc822.AddressList(addr).addresslist
+ for (realname, address) in addresses:
+ if realname:
+ newlist.append( email.Utils.formataddr(
+ (str(rfc2047_encode_header(realname, charset, mua)), address)))
+ else:
+ newlist.append( address )
+ return ', '.join(newlist)
+
+def rfc2047_encode_header(header, charset, mua=None):
+ if mua: return header
+ #print repr(header), repr(charset)
+
+ return encode_if_needed(header, charset)
+
+# Cheat for now.
+# ewrite() may put stuff on the status bar or in message boxes depending on UI
+def ewrite(*args):
+ return quietly or ui.log_message(*args)
+
+def sign_message(body, fromaddr, package='x', pgp_addr=None, sign='gpg'):
+ '''Sign message with pgp key.'''
+ ''' Return: a signed body.
+ On failure, return None.
+ kw need to have the following keys
+ '''
+ if not pgp_addr:
+ pgp_addr = reportbug.get_email_addr(fromaddr)[1]
+
+ # Make the unsigned file first
+ (unsigned, file1) = TempFile(prefix=tempfile_prefix(package, 'unsigned'))
+ unsigned.write(body)
+ unsigned.close()
+
+ # Now make the signed file
+ (signed, file2) = TempFile(prefix=tempfile_prefix(package, 'signed'))
+ signed.close()
+
+ if sign == 'gpg':
+ signcmd = "gpg --local-user '%s' --clearsign" % pgp_addr
+ else:
+ signcmd = "pgp -u '%s' -fast" % pgp_addr
+
+ signcmd += '<'+commands.mkarg(file1)+' >'+commands.mkarg(file2)
+ try:
+ os.system(signcmd)
+ x = file(file2, 'r')
+ signedbody = x.read()
+ x.close()
+
+ if os.path.exists(file1):
+ os.unlink(file1)
+ if os.path.exists(file2):
+ os.unlink(file2)
+
+ if not signedbody:
+ raise NoMessage
+ body = signedbody
+ except (NoMessage, IOError, OSError):
+ fh, tmpfile2 = TempFile(prefix=tempfile_prefix(package))
+ fh.write(body)
+ fh.close()
+ ewrite('gpg/pgp failed; input file in %s\n', tmpfile2)
+ body = None
+ return body
+
+def mime_attach(body, attachments, charset, body_charset=None):
+ mimetypes.init()
+
+ message = MIMEMultipart('mixed')
+ bodypart = BetterMIMEText(body, _charset=(body_charset or charset))
+ bodypart.add_header('Content-Disposition', 'inline')
+ message.preamble = 'This is a multi-part MIME message sent by reportbug.\n\n'
+ message.epilogue = ''
+ message.attach(bodypart)
+ failed = False
+ for attachment in attachments:
+ try:
+ fp = file(attachment)
+ fp.close()
+ except EnvironmentError, x:
+ ewrite("Warning: opening '%s' failed: %s.\n", attachment,
+ x.strerror)
+ failed = True
+ continue
+ ctype = None
+ cset = charset
+ info = commands.getoutput('file --mime --brief' +
+ commands.mkarg(attachment) +
+ ' 2>/dev/null')
+ if info:
+ match = re.match(r'([^;, ]*)(,[^;]+)?(?:; )?(.*)', info)
+ if match:
+ ctype, junk, extras = match.groups()
+ match = re.search(r'charset=([^,]+|"[^,"]+")', extras)
+ if match:
+ cset = match.group(1)
+ # If we didn't get a real MIME type, fall back
+ if '/' not in ctype:
+ ctype = None
+ # If file doesn't work, try to guess based on the extension
+ if not ctype:
+ ctype, encoding = mimetypes.guess_type(
+ attachment, strict=False)
+ if not ctype:
+ ctype = 'application/octet-stream'
+
+ maintype, subtype = ctype.split('/', 1)
+ if maintype == 'text':
+ fp = file(attachment, 'rU')
+ part = BetterMIMEText(fp.read(), _subtype=subtype,
+ _charset=cset)
+ fp.close()
+ elif maintype == 'message':
+ fp = file(attachment, 'rb')
+ part = MIMEMessage(email.message_from_file(fp),
+ _subtype=subtype)
+ fp.close()
+ elif maintype == 'image':
+ fp = file(attachment, 'rb')
+ part = MIMEImage(fp.read(), _subtype=subtype)
+ fp.close()
+ elif maintype == 'audio':
+ fp = file(attachment, 'rb')
+ part = MIMEAudio(fp.read(), _subtype=subtype)
+ fp.close()
+ else:
+ fp = file(attachment, 'rb')
+ part = MIMEBase(maintype, subtype)
+ part.set_payload(fp.read())
+ fp.close()
+ email.Encoders.encode_base64(part)
+ part.add_header('Content-Disposition', 'attachment',
+ filename=os.path.basename(attachment))
+ message.attach(part)
+ return (message, failed)
+
+def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
+ headers, package='x', charset="us-ascii", mailing=True,
+ sysinfo=None,
+ rtype='debbugs', exinfo=None, replyto=None, printonly=False,
+ template=False, outfile=None, mta='', kudos=False,
+ smtptls=False, smtphost='localhost',
+ smtpuser=None, smtppasswd=None, paranoid=False):
+ '''Send a report.'''
+
+ failed = using_sendmail = False
+ msgname = ''
+ # Disable smtphost if mua is set
+ if mua and smtphost:
+ smtphost = ''
+
+ # No, I'm not going to do a full MX lookup on every address... get a
+ # real MTA!
+ if kudos and smtphost == 'bugs.debian.org':
+ smtphost = 'packages.debian.org'
+
+ body_charset = charset
+ if isinstance(body, unicode):
+ # Since the body is Unicode, utf-8 seems like a sensible body encoding
+ # to choose pretty much all the time.
+ body = body.encode('utf-8', 'replace')
+ body_charset = 'utf-8'
+
+ tfprefix = tempfile_prefix(package)
+ if attachments and not mua:
+ (message, failed) = mime_attach(body, attachments, charset, body_charset)
+ if failed:
+ ewrite("Error: Message creation failed, not sending\n")
+ mua = mta = smtphost = None
+ else:
+ message = BetterMIMEText(body, _charset=body_charset)
+
+ # Standard headers
+ message['From'] = rfc2047_encode_address(fromaddr, 'utf-8', mua)
+ message['To'] = rfc2047_encode_address(sendto, charset, mua)
+
+ for (header, value) in headers:
+ if header in ['From', 'To', 'Cc', 'Bcc', 'X-Debbugs-CC', 'Reply-To',
+ 'Mail-Followup-To']:
+ message[header] = rfc2047_encode_address(value, charset, mua)
+ else:
+ message[header] = rfc2047_encode_header(value, charset, mua)
+
+ if ccaddr:
+ message['Cc'] = rfc2047_encode_address(ccaddr, charset, mua)
+
+ if bccaddr:
+ message['Bcc'] = rfc2047_encode_address(bccaddr, charset, mua)
+
+ replyto = os.environ.get("REPLYTO", replyto)
+ if replyto:
+ message['Reply-To'] = rfc2047_encode_address(replyto, charset, mua)
+
+ if mailing:
+ message['Message-ID'] = email.Utils.make_msgid('reportbug')
+ message['X-Mailer'] = VERSION
+ message['Date'] = email.Utils.formatdate(localtime=True)
+ elif mua and not (printonly or template):
+ message['X-Reportbug-Version'] = VERSION_NUMBER
+
+ addrs = [str(x) for x in (message.get_all('To', []) +
+ message.get_all('Cc', []) +
+ message.get_all('Bcc', []))]
+ alist = email.Utils.getaddresses(addrs)
+
+ cclist = [str(x) for x in message.get_all('X-Debbugs-Cc', [])]
+ debbugs_cc = email.Utils.getaddresses(cclist)
+ if cclist:
+ del message['X-Debbugs-Cc']
+ addrlist = ', '.join(cclist)
+ message['X-Debbugs-Cc'] = rfc2047_encode_address(addrlist, charset, mua)
+
+ # Drop any Bcc headers from the message to be sent
+ if not outfile and not mua:
+ try:
+ del message['Bcc']
+ except:
+ pass
+
+ message = message.as_string()
+ if paranoid and not (template or printonly):
+ pager = os.environ.get('PAGER', 'sensible-pager')
+ os.popen(pager, 'w').write(message)
+ if not ui.yes_no('Does your report seem satisfactory', 'Yes, send it.',
+ 'No, don\'t send it.'):
+ smtphost = mta = None
+
+ filename = None
+ if template or printonly:
+ pipe = sys.stdout
+ elif mua:
+ pipe, filename = TempFile(prefix=tfprefix)
+ elif outfile or not mta or not os.path.exists(mta):
+ msgname = outfile or ('/var/tmp/%s.bug' % package)
+ if os.path.exists(msgname):
+ try:
+ os.rename(msgname, msgname+'~')
+ except OSError:
+ ewrite('Unable to rename existing %s as %s~\n',
+ msgname, msgname)
+ try:
+ pipe = open_write_safe(msgname, 'w')
+ except OSError:
+ fh, newmsgname = TempFile(prefix=tfprefix)
+ fh.write(message.as_string())
+ fh.close()
+ ewrite('Writing to %s failed; '
+ 'wrote bug report to %s\n', msgname, newmsgname)
+ msgname = newmsgname
+ elif mta and not smtphost:
+ try:
+ x = os.getcwd()
+ except OSError:
+ os.chdir('/')
+
+ malist = [commands.mkarg(a[1]) for a in alist]
+ jalist = ' '.join(malist)
+
+ faddr = rfc822.parseaddr(fromaddr)[1]
+ ewrite("Sending message via %s...\n", mta)
+ pipe = os.popen('%s -f %s -oi -oem %s' % (
+ mta, commands.mkarg(faddr), jalist), 'w')
+ using_sendmail = True
+
+ if smtphost:
+ toaddrs = [x[1] for x in alist]
+ smtp_message = re.sub(r'(?m)^[.]', '..', message)
+
+ tryagain = True
+ refused = None
+ while tryagain:
+ tryagain = False
+ ewrite("Connecting to %s via SMTP...\n", smtphost)
+ try:
+ conn = smtplib.SMTP(smtphost)
+ response = conn.ehlo()
+ if not (200 <= response[0] <= 299):
+ conn.helo()
+ if smtptls:
+ conn.starttls()
+ response = conn.ehlo()
+ if not (200 <= response[0] <= 299):
+ conn.helo()
+ if smtpuser:
+ if not smtppasswd:
+ smtppasswd = ui.get_password(
+ 'Enter SMTP password for %s@%s: ' %
+ (smtpuser, smtphost))
+ conn.login(smtpuser, smtppasswd)
+ refused = conn.sendmail(fromaddr, toaddrs, smtp_message)
+ conn.quit()
+ except (socket.error, smtplib.SMTPException), x:
+ # If wrong password, try again...
+ if isinstance(x, smtplib.SMTPAuthenticationError):
+ ewrite('SMTP error: authentication failed. Try again.\n')
+ tryagain = True
+ smtppasswd = None
+ continue
+
+ failed = True
+ ewrite('SMTP send failure: %s\n', x)
+
+ fh, msgname = TempFile(prefix=tfprefix)
+ fh.write(message)
+ fh.close()
+
+ ewrite('Wrote bug report to %s\n', msgname)
+ # Handle when some recipients are refused.
+ if refused:
+ for (addr, err) in refused.iteritems():
+ ewrite('Unable to send report to %s: %d %s\n', addr, err[0],
+ err[1])
+ fh, msgname = TempFile(prefix=tfprefix)
+ fh.write(message)
+ fh.close()
+
+ ewrite('Wrote bug report to %s\n', msgname)
+ else:
+ try:
+ pipe.write(message)
+ pipe.flush()
+ if msgname:
+ ewrite("Bug report written as %s\n", msgname)
+ except IOError:
+ failed = True
+ pipe.close()
+
+ if failed or (pipe.close() and using_sendmail):
+ failed = True
+ fh, msgname = TempFile(prefix=tfprefix)
+ fh.write(message)
+ fh.close()
+ ewrite('Original write failed, wrote bug report to %s\n', msgname)
+
+ if mua:
+ for bit in mua.split():
+ if '%s' not in bit: break
+ ewrite("Spawning %s...\n", bit or mua)
+ if '%s' not in mua:
+ mua += ' %s'
+ system(mua % commands.mkarg(filename)[1:])
+ elif not failed and (using_sendmail or smtphost):
+ if kudos:
+ ewrite('\nMessage sent to: %s\n', sendto)
+ else:
+ ewrite("\nBug report submitted to: %s\n", sendto)
+
+ addresses = []
+ for addr in alist:
+ if addr[1] != rfc822.parseaddr(sendto)[1]:
+ addresses.append(addr)
+
+ if len(addresses):
+ ewrite("Copies sent to:\n")
+ for address in addresses:
+ ewrite(' %s\n', rfc822.dump_address_pair(address))
+
+ if debbugs_cc and rtype == 'debbugs':
+ ewrite("Copies will be sent after processing to:\n")
+ for address in debbugs_cc:
+ ewrite(' %s\n', rfc822.dump_address_pair(address))
+
+ if not (exinfo or kudos) and rtype == 'debbugs' and sysinfo:
+ ewrite('\n')
+ ui.long_message(
+"""If you want to provide additional information, please wait to
+receive the bug tracking number via email; you may then send any extra
+information to %s (e.g. %s), where n is the bug number. Normally you
+will receive an acknowledgement via email including the bug report number
+within an hour.\n""",
+ (sysinfo['email'] % 'n'), (sysinfo['email'] % '999999'))
+
+ # If we've stored more than one copy of the message, delete the
+ # one without the SMTP headers.
+ if filename and os.path.exists(msgname) and os.path.exists(filename):
+ try:
+ os.unlink(filename)
+ except:
+ pass
+
+ if filename and os.path.exists(filename) and not mua:
+ # Message is misleading if an MUA is used.
+ ewrite("A copy of the report is stored as: %s\n" % filename)
+ return
+
+def main():
+ 'o'
+
+if __name__ == '__main__':
+ try:
+ main()
+ except KeyboardInterrupt:
+ ewrite("\nreportbug: exiting due to user interrupt.\n")
+ except debianbts.Error, x:
+ ewrite('error accessing BTS: %s\n' % x)
+
+# vim:ts=8:sw=4:expandtab
Property changes on: branches/manual_merge/reportbug/reportbug_submit.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/reportbug_ui_newt.py (from rev 625, branches/manual_merge/reportbuglib/reportbug_ui_newt.py)
===================================================================
--- branches/manual_merge/reportbug/reportbug_ui_newt.py (rev 0)
+++ branches/manual_merge/reportbug/reportbug_ui_newt.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,301 @@
+# Newt user interface for reportbug
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 2001-06 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# $Id: reportbug_ui_newt.py,v 1.5.2.4 2007-04-17 19:42:56 lawrencc Exp $
+
+import sys
+import commands
+import string
+
+import debianbts
+from reportbug_exceptions import (
+ UINotImportable,
+ NoPackage, NoBugs, NoNetwork,
+ )
+from urlutils import launch_browser
+
+try:
+ import snack
+except ImportError:
+ raise UINotImportable, 'Please install the python-newt package to use this interface.'
+
+ISATTY = sys.stdin.isatty()
+
+try:
+ r, c = string.split(commands.getoutput('stty size'))
+ rows, columns = int(r) or 24, int(c) or 79
+except:
+ rows, columns = 24, 79
+
+def ewrite(message, *args):
+ # ewrite shouldn't do anything on newt... maybe should log to a file
+ # if specified.
+ pass
+
+log_message = ewrite
+display_failure = ewrite
+
+# Utility functions for common newt dialogs
+def newt_screen():
+ "Start a newt windowing session."
+ return snack.SnackScreen()
+
+def newt_infobox(text, height=6, width=50, title="", screen=None):
+ "Display a message and go on."
+ if not screen:
+ s = snack.SnackScreen()
+ else:
+ s = screen
+
+ t = snack.TextboxReflowed(width, text, maxHeight = s.height - 12)
+ g = snack.GridForm(s, title[:width], 1, 2)
+ g.add(t, 0, 0, padding = (0, 0, 0, 1))
+ g.draw()
+ s.refresh()
+ if not screen:
+ s.finish()
+
+def newt_dialog(text, buttons=('Ok', 'Cancel'), width=50,
+ title="", screen=None):
+ "Display a message and wait for a response from the user."
+ if not screen:
+ s = snack.SnackScreen()
+ else:
+ s = screen
+
+ selected = snack.ButtonChoiceWindow(s, title[:width], text, buttons,
+ width=width)
+
+ if not screen:
+ s.finish()
+ return selected
+
+def newt_msgbox(text, width=50, title="", screen=None):
+ "Display a message and wait for an OK from the user."
+ return newt_dialog(text, ['Ok'], width, title, screen)
+
+def long_message(message, *args):
+ if args:
+ message = message % tuple(args)
+ newt_msgbox(message)
+
+final_message = long_message
+
+def newt_menu(text, height=20, width=60, menuheight=15, menu=None,
+ title="", scroll=0, screen=None, startpos=1):
+ "Display a menu of choices for the user."
+ if not menu: return None
+ if not screen:
+ s = snack.SnackScreen()
+ else:
+ s = screen
+ items = []
+ for item in menu:
+ if item[0]:
+ items.append(('%6s: %s' % item)[:width])
+ else:
+ items.append(item[1])
+
+ if len(items) > menuheight: scroll=1
+ res = startpos
+ while 1:
+ button, res = snack.ListboxChoiceWindow(s, title[:width], text, items,
+ width=width, height=menuheight,
+ scroll=scroll, default=res,
+ buttons=('View', 'Quit'))
+ if button == 'quit':
+ if not screen:
+ s.finish()
+ return None
+ elif menu[res][0]:
+ selected = menu[res][0]
+ break
+
+ if not screen:
+ s.finish()
+ return selected
+
+# XXX - From here on out needs to be rewritten for newt
+def select_options(msg, ok, help=None, allow_numbers=0):
+ return None
+
+def get_string(prompt, options=None, title=None, force_prompt=0):
+ return None
+
+def get_multiline(prompt, options=None, title=None, force_prompt=0):
+ return None
+
+def menu(par, options, prompt, default=None, title=None, any_ok=0, order=None):
+ return None
+
+# Things that are very UI dependent go here
+def show_report(number, system, mirrors, http_proxy, screen=None, queryonly=0,
+ title='', archived='no'):
+ s = screen
+ if not s:
+ s = newt_screen()
+
+ sysinfo = debianbts.SYSTEMS[system]
+ newt_infobox('Retrieving report #%d from %s bug tracking system...' % (
+ number, sysinfo['name']), title=title, screen=s)
+
+ width = columns-8
+ info = debianbts.get_report(number, system, mirrors=mirrors,
+ http_proxy=http_proxy, archived=archived)
+ if not info:
+ s.popWindow()
+ newt_msgbox('Bug report #%d not found.' % number,
+ screen=s, title=title)
+ if not screen:
+ s.finish()
+ return
+
+ buttons = ['Ok', 'More details (launch browser)', 'Quit']
+ if not queryonly:
+ buttons.append('Submit more information')
+
+ s.popWindow()
+ while 1:
+ (bugtitle, bodies) = info
+ body = bodies[0]
+
+ lines = string.split(body, '\n')
+ lines = map(lambda x, y=width: x[:y], lines)
+ body = string.join(lines, '\n')
+
+ r = newt_dialog(text=body, title=bugtitle, screen=s, width=width,
+ buttons=buttons)
+ if not r or (r == 'ok'):
+ break
+ elif r == 'quit':
+ if not screen:
+ s.finish()
+ return -1
+ elif r == 'submit more information':
+ if not screen:
+ s.finish()
+ return number
+
+ s.suspend()
+ # print chr(27)+'c'
+ # os.system('stty sane; clear')
+ launch_browser(debianbts.get_report_url(system, number, archived))
+ s.resume()
+
+ if not screen:
+ s.finish()
+ return
+
+def handle_bts_query(package, bts, mirrors=None, http_proxy="",
+ queryonly=0, screen=None, title="", archived='no',
+ source=0):
+ sysinfo = debianbts.SYSTEMS[bts]
+ root = sysinfo.get('btsroot')
+ if not root:
+ ewrite("%s bug tracking system has no web URL; bypassing query.\n",
+ sysinfo['name'])
+ return
+
+ scr = screen
+ if not scr:
+ scr = newt_screen()
+
+ if isinstance(package, basestring):
+ if source:
+ newt_infobox('Querying %s bug tracking system for reports on'
+ ' src:%s\n' % (debianbts.SYSTEMS[bts]['name'],
+ package),
+ screen=scr, title=title)
+ else:
+ newt_infobox('Querying %s bug tracking system for reports on %s\n'%
+ (debianbts.SYSTEMS[bts]['name'], package),
+ screen=scr, title=title)
+ else:
+ newt_infobox('Querying %s bug tracking system for reports %s\n' %
+ (debianbts.SYSTEMS[bts]['name'], ' '.join([str(x) for x in
+ package])),
+ screen=scr, title=title)
+
+ result = None
+ try:
+ (count, sectitle, hierarchy) = debianbts.get_reports(
+ package, bts, mirrors=mirrors,
+ http_proxy=http_proxy, archived=archived, source=source)
+
+ if not count:
+ scr.popWindow()
+ if hierarchy == None:
+ raise NoPackage
+ else:
+ raise NoBugs
+ else:
+ if count > 1:
+ sectitle = '%d bug reports found' % (count)
+ else:
+ sectitle = '%d bug report found' % (count)
+
+ list = []
+ for (t, bugs) in hierarchy:
+ bcount = len(bugs)
+ list.append( ('', t) )
+ for bug in bugs:
+ bits = string.split(bug[1:], ':', 1)
+ tag, info = bits
+ info = string.strip(info)
+ if not info:
+ info = '(no subject)'
+ list.append( (tag, info) )
+
+ p = 1
+ scr.popWindow()
+ while True:
+ info = newt_menu('Select a bug to read the report:', rows-6,
+ columns-10, rows-15, list, sectitle,
+ startpos=p, screen=scr)
+ if not info:
+ break
+ else:
+ p = i = 0
+ for (number, subject) in list:
+ if number == info: p = i
+ i += 1
+
+ if ' ' in info:
+ info, blah = info.split(' ', 1)
+
+ res = show_report(int(info), bts, mirrors,
+ http_proxy, screen=scr,
+ queryonly=queryonly, title=title)
+ if res:
+ result = res
+ break
+
+ except (IOError, NoNetwork):
+ scr.popWindow()
+ newt_msgbox('Unable to connect to %s BTS.' % sysinfo['name'],
+ screen=scr, title=title)
+ except NoPackage:
+ #scr.popWindow()
+ newt_msgbox('No record of this package found.',
+ screen=scr, title=title)
+
+ if not screen:
+ scr.finish()
+ return result
Property changes on: branches/manual_merge/reportbug/reportbug_ui_newt.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/reportbug_ui_text.py (from rev 625, branches/manual_merge/reportbuglib/reportbug_ui_text.py)
===================================================================
--- branches/manual_merge/reportbug/reportbug_ui_text.py (rev 0)
+++ branches/manual_merge/reportbug/reportbug_ui_text.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,940 @@
+# reportbuglib/reportbug_ui_text.py
+# Text user interface for reportbug
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 2001-06 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# $Id: reportbug_ui_text.py,v 1.19.2.9 2008-04-18 05:38:28 lawrencc Exp $
+
+import sys
+import os
+import commands
+import re
+import math
+import string
+import errno
+import glob
+import getpass
+import textwrap
+try:
+ import readline
+except ImportError:
+ readline = None
+
+import reportbug
+import debianbts
+from reportbug_exceptions import (
+ NoReport, NoPackage, NoBugs, NoNetwork,
+ InvalidRegex,
+ )
+from urlutils import launch_browser
+import hiermatch
+
+ISATTY = sys.stdin.isatty()
+charset = 'us-ascii'
+
+try:
+ r, c = commands.getoutput('stty size').split()
+ rows, columns = int(r) or 24, int(c) or 79
+except:
+ rows, columns = 24, 79
+
+def ewrite(message, *args):
+ if not ISATTY:
+ return
+
+ if args:
+ message = message % args
+
+ if isinstance(message, unicode):
+ message = message.encode(charset, 'replace')
+
+ sys.stderr.write(message)
+ sys.stderr.flush()
+
+log_message = ewrite
+display_failure = ewrite
+
+def indent_wrap_text(text, starttext='', indent=0, linelen=None):
+ """Wrapper for textwrap.fill to the existing API."""
+ if not linelen:
+ linelen = columns-1
+
+ if indent:
+ si = ' '*indent
+ else:
+ si = ''
+
+ text = ' '.join(text.split())
+ if not text:
+ return starttext+'\n'
+
+ output = textwrap.fill(text, width=linelen, initial_indent=starttext,
+ subsequent_indent=si)
+ if output.endswith('\n'):
+ return output
+ return output + '\n'
+
+# Readline support, if available
+if readline is not None:
+ readline.parse_and_bind("tab: complete")
+ try:
+ # minimize the word delimeter list if possible
+ readline.set_completer_delims(' ')
+ except:
+ pass
+
+class our_completer(object):
+ def __init__(self, completions=None):
+ self.completions = None
+ if completions:
+ self.completions = tuple(map(str, completions))
+
+ def complete(self, text, i):
+ if not self.completions: return None
+
+ matching = [x for x in self.completions if x.startswith(text)]
+ if i < len(matching):
+ return matching[i]
+ else:
+ return None
+
+def our_raw_input(prompt = None, completions=None, completer=None):
+ istty = sys.stdout.isatty()
+ if not istty:
+ sys.stderr.write(prompt)
+ sys.stderr.flush()
+ if readline:
+ if completions and not completer:
+ completer = our_completer(completions).complete
+ if completer:
+ readline.set_completer(completer)
+
+ try:
+ if istty:
+ ret = raw_input(prompt)
+ else:
+ ret = raw_input()
+ except EOFError:
+ ewrite('\nUser interrupt (^D).\n')
+ raise SystemExit
+
+ if readline:
+ readline.set_completer(None)
+ return ret.strip()
+
+def select_options(msg, ok, help, allow_numbers=None, nowrap=False):
+ err_message = ''
+ for option in ok:
+ if option in string.ascii_uppercase:
+ default=option
+ break
+
+ if not help: help = {}
+
+ if '?' not in ok: ok = ok+'?'
+
+ if nowrap:
+ longmsg = msg+' ['+'|'.join(ok)+']?'+' '
+ else:
+ longmsg = indent_wrap_text(msg+' ['+'|'.join(ok)+']?').strip()+' '
+ ch = our_raw_input(longmsg, allow_numbers)
+ # Allow entry of a bug number here
+ if allow_numbers:
+ while ch and ch[0] == '#': ch = ch[1:]
+ if type(allow_numbers) == type(1):
+ try:
+ return str(int(ch))
+ except ValueError:
+ pass
+ else:
+ try:
+ number = int(ch)
+ if number in allow_numbers:
+ return str(number)
+ else:
+ nums = list(allow_numbers)
+ nums.sort()
+ err_message = 'Only the following entries are allowed: '+\
+ ', '.join(map(str, nums))
+ except (ValueError, TypeError):
+ pass
+
+ if not ch: ch = default
+ ch = ch[0]
+ if ch=='?':
+ help['?'] = 'Display this help.'
+ for ch in ok:
+ if ch in string.ascii_uppercase:
+ desc = '(default) '
+ else:
+ desc = ''
+ desc += help.get(ch, help.get(ch.lower(),
+ 'No help for this option.'))
+ ewrite(indent_wrap_text(desc+'\n', '%s - '% ch, 4))
+ return select_options(msg, ok, help, allow_numbers)
+ elif (ch.lower() in ok) or (ch.upper() in ok):
+ return ch.lower()
+ elif err_message:
+ ewrite(indent_wrap_text(err_message))
+ else:
+ ewrite('Invalid selection.\n')
+
+ return select_options(msg, ok, help, allow_numbers, nowrap)
+
+def yes_no(msg, yeshelp, nohelp, default=True, nowrap=False):
+ "Return True for yes, False for no."
+ if default:
+ ok = 'Ynq'
+ else:
+ ok = 'yNq'
+
+ res = select_options(msg, ok, {'y': yeshelp, 'n': nohelp, 'q' : 'Quit.'},
+ nowrap=nowrap)
+ if res == 'q':
+ raise SystemExit
+ return (res == 'y')
+
+def long_message(text, *args):
+ if args:
+ ewrite(indent_wrap_text(text % args))
+ else:
+ ewrite(indent_wrap_text(text))
+
+final_message = long_message
+
+def get_string(prompt, options=None, title=None, force_prompt=False,
+ default='', completer=None):
+ if prompt and (len(prompt) < 2*columns/3) and not force_prompt:
+ if default:
+ prompt = '%s [%s]: ' % (prompt, default)
+ response = our_raw_input(prompt, options, completer) or default
+ else:
+ response = our_raw_input(prompt, options, completer)
+ else:
+ if prompt:
+ ewrite(indent_wrap_text(prompt))
+ if default:
+ response = our_raw_input('[%s]> ' % default, options, completer) or default
+ else:
+ response = our_raw_input('> ', options, completer)
+
+ # Translate the response into a Unicode string
+ if response is not None:
+ response = unicode(response, charset, 'replace')
+
+ return response
+
+def get_multiline(prompt):
+ ewrite('\n')
+ ewrite(indent_wrap_text(prompt + " Press ENTER on a blank line to continue.\n"))
+ l = []
+ while 1:
+ entry = get_string('', force_prompt=True).strip()
+ if not entry:
+ break
+ l.append(entry)
+ ewrite('\n')
+ return l
+
+def get_password(prompt=None):
+ return getpass.getpass(prompt)
+
+def FilenameCompleter(text, i):
+ paths = glob.glob(text+'*')
+ if not paths: return None
+
+ if i < len(paths):
+ entry = paths[i]
+ if os.path.isdir(entry):
+ return entry+'/'
+ return entry
+ else:
+ return None
+
+def get_filename(prompt, title=None, force_prompt=False, default=''):
+ return get_string(prompt, title=title, force_prompt=force_prompt,
+ default=default, completer=FilenameCompleter)
+
+def select_multiple(par, options, prompt, title=None, order=None, extras=None):
+ return menu(par, options, prompt, title=title, order=order, extras=extras,
+ multiple=True, empty_ok=False)
+
+def menu(par, options, prompt, default=None, title=None, any_ok=False,
+ order=None, extras=None, multiple=False, empty_ok=False):
+ selected = {}
+
+ if not extras:
+ extras = []
+ else:
+ extras = list(extras)
+
+ if title:
+ ewrite(title+'\n\n')
+
+ ewrite(indent_wrap_text(par, linelen=columns)+'\n')
+
+ if isinstance(options, dict):
+ options = options.copy()
+ # Convert to a list
+ if order:
+ olist = []
+ for key in order:
+ if options.has_key(key):
+ olist.append( (key, options[key]) )
+ del options[key]
+
+ # Append anything out of order
+ options = options.items()
+ options.sort()
+ for option in options:
+ olist.append( option )
+ options = olist
+ else:
+ options = options.items()
+ options.sort()
+
+ if multiple:
+ options.append( ('none', '') )
+ default = 'none'
+ extras += ['done']
+
+ allowed = map(lambda x: x[0], options)
+ allowed = allowed + extras
+
+ maxlen_name = min(max(map(len, allowed)), columns/3)
+ digits = int(math.ceil(math.log10(len(options)+1)))
+
+ i = 1
+ for name, desc in options:
+ text = indent_wrap_text(desc, indent=(maxlen_name+digits+3),
+ starttext=('%*d %-*.*s ' % (
+ digits, i, maxlen_name, maxlen_name, name)))
+ ewrite(text)
+ if len(options) < 5:
+ ewrite('\n')
+ i = i+1
+ if len(options) >= 5:
+ ewrite('\n')
+
+ if multiple:
+ prompt += '(one at a time) '
+
+ while 1:
+ if default:
+ aprompt = prompt + '[%s] ' % default
+ else:
+ aprompt = prompt
+
+ response = our_raw_input(aprompt, allowed)
+ if not response: response = default
+
+ try:
+ num = int(response)
+ if 1 <= num <= len(options):
+ response = options[num-1][0]
+ except (ValueError, TypeError):
+ pass
+
+ if response in allowed or (response == default and response):
+ if multiple:
+ if response == 'done':
+ return selected.keys()
+ elif response == 'none':
+ return []
+ elif selected.get(response):
+ del selected[response]
+ else:
+ selected[response]=1
+ ewrite('- selected: %s\n' % ', '.join(selected.keys()))
+ if len(selected):
+ default = 'done'
+ else:
+ default = 'none'
+ continue
+ else:
+ return response
+
+ if any_ok and response:
+ return response
+ elif empty_ok and not response:
+ return
+
+ ewrite('Invalid entry.\n')
+ return
+
+# Things that are very UI dependent go here
+def show_report(number, system, mirrors,
+ http_proxy, screen=None, queryonly=False, title='',
+ archived='no'):
+ sysinfo = debianbts.SYSTEMS[system]
+ ewrite('Retrieving report #%d from %s bug tracking system...\n',
+ number, sysinfo['name'])
+
+ try:
+ info = debianbts.get_report(number, system, mirrors=mirrors,
+ followups=1,
+ http_proxy=http_proxy, archived=archived)
+ except:
+ info = None
+
+ if not info:
+ ewrite('No report available: #%s\n', number)
+ return
+
+ (title, messages) = info
+ current_message = 0
+ skip_pager = False
+
+ while 1:
+ if current_message:
+ text = 'Followup %d - %s\n\n%s' % (current_message, title,
+ messages[current_message])
+ else:
+ text = 'Original report - %s\n\n%s' % (title, messages[0])
+
+ if not skip_pager:
+ fd = os.popen('sensible-pager', 'w')
+ try:
+ fd.write(text)
+ fd.close()
+ except IOError, x:
+ if x.errno == errno.EPIPE:
+ pass
+ else:
+ raise
+ skip_pager = False
+
+ options = 'xOrbq'
+
+ if (current_message+1) < len(messages):
+ options = 'N'+options.lower()
+ if (current_message):
+ options = 'p'+options
+
+ x = select_options("What do you want to do now?", options,
+ {'x' : 'Provide extra information.',
+ 'o' : 'Show other bug reports (return to '
+ 'bug listing).',
+ 'n' : 'Show next message (followup).',
+ 'p' : 'Show previous message (followup).',
+ 'r' : 'Redisplay this message.',
+ 'b' : 'Launch web browser to read '
+ 'full log.',
+ 'q' : "I'm bored; quit please."},
+ allow_numbers = range(1, len(messages)+1))
+ if x == 'x':
+ return number
+ elif x == 'q':
+ raise NoReport
+ elif x == 'b':
+ launch_browser(debianbts.get_report_url(
+ system, number, mirrors, archived))
+ skip_pager = True
+ elif x == 'o':
+ break
+ elif x == 'n':
+ current_message += 1
+ elif x == 'p':
+ current_message -= 1
+ return
+
+def handle_bts_query(package, bts, mirrors=None, http_proxy="",
+ queryonly=False, title="", screen=None, archived='no',
+ source=False, version=None):
+ root = debianbts.SYSTEMS[bts].get('btsroot')
+ if not root:
+ ewrite('%s bug tracking system has no web URL; bypassing query\n',
+ debianbts.SYSTEMS[bts]['name'])
+ return
+
+ srcstr = ""
+ if source:
+ srcstr = " (source)"
+
+ if isinstance(package, basestring):
+ long_message('Querying %s BTS for reports on %s%s...\n',
+ debianbts.SYSTEMS[bts]['name'], package, srcstr)
+ else:
+ long_message('Querying %s BTS for reports on %s%s...\n',
+ debianbts.SYSTEMS[bts]['name'],
+ ' '.join([str(x) for x in package]), srcstr)
+
+ bugs = []
+ try:
+ (count, title, hierarchy)=debianbts.get_reports(
+ package, bts, mirrors=mirrors, version=version,
+ source=source, http_proxy=http_proxy, archived=archived)
+ if debianbts.SYSTEMS[bts].has_key('namefmt'):
+ package2 = debianbts.SYSTEMS[bts]['namefmt'] % package
+ (count2, title2, hierarchy2) = \
+ debianbts.get_reports(package2, bts,
+ mirrors=mirrors, source=source,
+ http_proxy=http_proxy,
+ version=version)
+ count = count+count2
+ for entry in hierarchy2:
+ hierarchy.append( (package2+' '+entry[0], entry[1]) )
+
+ exp = re.compile(r'#(\d+)[ :]')
+ for entry in hierarchy or []:
+ for bug in entry[1]:
+ match = exp.match(bug)
+ if match:
+ bugs.append(int(match.group(1)))
+
+ if not count:
+ if hierarchy == None:
+ raise NoPackage
+ else:
+ raise NoBugs
+ elif count == 1:
+ ewrite('%d bug report found:\n\n', count)
+ else:
+ ewrite('%d bug reports found:\n\n', count)
+
+ return browse_bugs(hierarchy, count, bugs, bts, queryonly,
+ mirrors, http_proxy, screen, title)
+
+ except (IOError, NoNetwork):
+ ewrite('Unable to connect to %s BTS; ', debianbts.SYSTEMS[bts]['name'])
+ res = select_options('continue', 'yN',
+ {'y': 'Keep going.',
+ 'n': 'Abort.'})
+ if res == 'n':
+ raise NoNetwork
+
+def browse_bugs(hierarchy, count, bugs, bts, queryonly, mirrors,
+ http_proxy, screen, title):
+ endcount = catcount = 0
+ scount = startcount = 1
+ category = hierarchy[0]
+ lastpage = []
+ digits = len(str(len(bugs)))
+ linefmt = ' %'+str(digits)+'d) %s\n'
+ while category:
+ scount = scount + 1
+ catname, reports = category[0:2]
+ while catname.endswith(':'): catname = catname[:-1]
+ total = len(reports)
+
+ while len(reports):
+ these = reports[:rows-2]
+ reports = reports[rows-2:]
+ remain = len(reports)
+
+ tplural = rplural = 's'
+ if total == 1: tplural=''
+ if remain != 1: rplural=''
+
+ if remain:
+ lastpage.append(' %s: %d remain%s\n' %
+ (catname, remain, rplural))
+ else:
+ lastpage.append(catname+'\n')
+
+ oldscount, oldecount = scount, endcount
+ for report in these:
+ scount = scount + 1
+ endcount = endcount + 1
+ lastpage.append(linefmt % (endcount,report[:columns-digits-5]))
+
+ if category == hierarchy[-1] or \
+ (scount >= (rows - len(hierarchy[catcount+1][1]) - 1)):
+ skipmsg = ' (s to skip rest)'
+ if endcount == count:
+ skipmsg = ''
+
+ options = 'yNmrqsf'
+ if queryonly: options = 'Nmrqf'
+
+ rstr = "(%d-%d/%d) " % (startcount, endcount, count)
+ pstr = rstr + "Is the bug you found listed above"
+ if queryonly:
+ pstr = rstr + "What would you like to do next"
+ allowed = bugs+range(1, count+1)
+ helptext = {
+ 'y' : 'Problem already reported; optionally '
+ 'add extra information.',
+ 'n' : 'Problem not listed above; possibly '
+ 'check more.',
+ 'm' : 'Get more information about a bug (you '
+ 'can also enter a number\n'
+ ' without selecting "m" first).',
+ 'r' : 'Redisplay the last bugs shown.',
+ 'q' : "I'm bored; quit please.",
+ 's' : 'Skip remaining problems; file a new '
+ 'report immediately.',
+ 'f' : 'Filter bug list using a pattern.'}
+ if skipmsg:
+ helptext['n'] = helptext['n'][:-1]+' (skip to Next page).'
+
+ while 1:
+ sys.stderr.writelines(lastpage)
+ x = select_options(pstr, options, helptext,
+ allow_numbers=allowed)
+ if x == 'n':
+ lastpage = []
+ break
+ elif x == 'r':
+ continue
+ elif x == 'q':
+ raise NoReport
+ elif x == 's':
+ return
+ elif x == 'y':
+ if queryonly:
+ return
+
+ if len(bugs) == 1:
+ number = '1'
+ else:
+ number = our_raw_input(
+ 'Enter the number of the bug report '
+ 'you want to give more info on,\n'
+ 'or press ENTER to exit: #', allowed)
+ while number and number[0] == '#':
+ number=number[1:]
+ if number:
+ try:
+ number = int(number)
+ if number not in bugs and 1 <= number <= len(bugs):
+ number = bugs[number-1]
+ return number
+ except ValueError:
+ ewrite('Invalid report number: %s\n',
+ number)
+ else:
+ raise NoReport
+ elif x == 'f':
+ # Do filter. Recursive done.
+ retval = search_bugs(hierarchy,bts, queryonly, mirrors,
+ http_proxy, screen, title)
+ if retval in ["FilterEnd", "Top"]:
+ continue
+ else:
+ return retval
+ else:
+ if x == 'm' or x == 'i':
+ if len(bugs) == 1:
+ number = '1'
+ else:
+ number = our_raw_input(
+ 'Please enter the number of the bug '
+ 'you would like more info on: #',
+ allowed)
+ else:
+ number = x
+
+ while number and number[0] == '#':
+ number=number[1:]
+
+ if number:
+ try:
+ number = int(number)
+ if number not in bugs and 1 <= number <= len(bugs):
+ number = bugs[number-1]
+ res = show_report(number, bts, mirrors,
+ http_proxy,
+ queryonly=queryonly,
+ screen=screen,
+ title=title)
+ if res:
+ return res
+ except ValueError:
+ ewrite('Invalid report number: %s\n',
+ number)
+
+ startcount = endcount+1
+ scount = 0
+
+ # these now empty
+
+ if category == hierarchy[-1]: break
+
+ catcount = catcount+1
+ category = hierarchy[catcount]
+ if scount:
+ lastpage.append('\n')
+ scount = scount + 1
+
+def proc_hierarchy(hierarchy):
+ """Find out bug count and bug # in the hierarchy."""
+
+ lenlist = [len(i[1]) for i in hierarchy]
+ if lenlist:
+ count = reduce(lambda x, y: x+y, lenlist)
+ else:
+ return 0, 0
+
+ # Copy & paste from handle_bts_query()
+ bugs = []
+ exp = re.compile(r'\#(\d+)[ :]')
+ for entry in hierarchy or []:
+ for bug in entry[1]:
+ match = exp.match(bug)
+ if match:
+ bugs.append(int(match.group(1)))
+ return count, bugs
+
+def search_bugs(hierarchyfull, bts, queryonly, mirrors,
+ http_proxy, screen, title):
+ """Search for the bug list using a pattern."""
+ """Return string "FilterEnd" when we are done with search."""
+
+ pattern = our_raw_input(
+ 'Enter the search pattern (a Perl-compatible regular expression)\n'
+ 'or press ENTER to exit: ')
+ if not pattern:
+ return "FilterEnd"
+
+ " Create new hierarchy match the pattern."
+ try:
+ hierarchy = hiermatch.matched_hierarchy(hierarchyfull, pattern)
+ except InvalidRegex:
+ our_raw_input('Invalid regular expression, press ENTER to continue.')
+ return "FilterEnd"
+
+ count, bugs = proc_hierarchy(hierarchy)
+ exp = re.compile(r'\#(\d+):')
+
+ if not count:
+ our_raw_input('No match found, press ENTER to continue.')
+ return "FilterEnd"
+
+ endcount = catcount = 0
+ scount = startcount = 1
+ category = hierarchy[0]
+ lastpage = []
+ digits = len(str(len(bugs)))
+ linefmt = ' %'+str(digits)+'d) %s\n'
+ while category:
+ scount = scount + 1
+ catname, reports = category[0:2]
+ while catname.endswith(':'): catname = catname[:-1]
+ total = len(reports)
+
+ while len(reports):
+ these = reports[:rows-2]
+ reports = reports[rows-2:]
+ remain = len(reports)
+
+ tplural = rplural = 's'
+ if total == 1: tplural=''
+ if remain != 1: rplural=''
+
+ if remain:
+ lastpage.append(' %s: %d report%s (%d remain%s)\n' %
+ (catname, total, tplural, remain, rplural))
+ else:
+ lastpage.append(' %s: %d report%s\n' %
+ (catname, total, tplural))
+
+ oldscount, oldecount = scount, endcount
+ for report in these:
+ scount = scount + 1
+ endcount = endcount + 1
+ lastpage.append(linefmt % (endcount,report[:columns-digits-5]))
+
+ if category == hierarchy[-1] or \
+ (scount >= (rows - len(hierarchy[catcount+1][1]) - 1)):
+ skipmsg = ' (s to skip rest)'
+ if endcount == count:
+ skipmsg = ''
+
+ options = 'yNmrqsfut'
+ if queryonly: options = 'Nmrqfut'
+
+ rstr = "(%d-%d/%d) " % (startcount, endcount, count)
+ pstr = rstr + "Is the bug you found listed above"
+ if queryonly:
+ pstr = rstr + "What would you like to do next"
+ allowed = bugs+range(1, count+1)
+ helptext = {
+ 'y' : 'Problem already reported; optionally '
+ 'add extra information.',
+ 'n' : 'Problem not listed above; possibly '
+ 'check more.',
+ 'm' : 'Get more information about a bug (you '
+ 'can also enter a number\n'
+ ' without selecting "m" first).',
+ 'r' : 'Redisplay the last bugs shown.',
+ 'q' : "I'm bored; quit please.",
+ 's' : 'Skip remaining problems; file a new '
+ 'report immediately.',
+ 'f' : 'Filter (search) bug list using a pattern.',
+ 'u' : 'Up one level of filter.',
+ 't' : 'Top of the bug list (remove all filters).'}
+ if skipmsg:
+ helptext['n'] = helptext['n'][:-1]+' (skip to Next page).'
+
+ while 1:
+ sys.stderr.writelines(lastpage)
+ x = select_options(pstr, options, helptext,
+ allow_numbers=allowed)
+ if x == 'n':
+ lastpage = []
+ break
+ elif x == 'r':
+ continue
+ elif x == 'q':
+ raise NoReport
+ elif x == 's':
+ return
+ elif x == 'y':
+ if queryonly:
+ return
+
+ number = our_raw_input(
+ 'Enter the number of the bug report '
+ 'you want to give more info on,\n'
+ 'or press ENTER to exit: #', allowed)
+ while number and number[0] == '#':
+ number=number[1:]
+ if number:
+ try:
+ number = int(number)
+ if number not in bugs and 1 <= number <= len(bugs):
+ number = bugs[number-1]
+ return number
+ except ValueError:
+ ewrite('Invalid report number: %s\n',
+ number)
+ else:
+ raise NoReport
+ elif x == 'f':
+ # Do filter. Recursive done.
+ retval = search_bugs(hierarchy, bts, queryonly, mirrors,
+ http_proxy, screen, title)
+ if retval == "FilterEnd":
+ continue
+ else:
+ return retval
+ elif x == 'u':
+ # Up a level
+ return "FilterEnd"
+ elif x == 't':
+ # go back to the Top level.
+ return "Top"
+ else:
+ if x == 'm' or x == 'i':
+ number = our_raw_input(
+ 'Please enter the number of the bug '
+ 'you would like more info on: #',
+ allowed)
+ else:
+ number = x
+
+ while number and number[0] == '#':
+ number=number[1:]
+
+ if number:
+ try:
+ number = int(number)
+ if number not in bugs and 1 <= number <= len(bugs):
+ number = bugs[number-1]
+ res = show_report(number, bts, mirrors,
+ http_proxy,
+ queryonly=queryonly,
+ screen=screen,
+ title=title)
+ if res:
+ return res
+ except ValueError:
+ ewrite('Invalid report number: %s\n',
+ number)
+
+ startcount = endcount+1
+ scount = 0
+
+ # these now empty
+
+ if category == hierarchy[-1]: break
+
+ catcount = catcount+1
+ category = hierarchy[catcount]
+ if scount:
+ lastpage.append('\n')
+ scount = scount + 1
+ return "FilterEnd"
+
+def display_report(text, use_pager=True):
+ if not use_pager:
+ ewrite(text)
+ return
+
+ pager = os.environ.get('PAGER', 'sensible-pager')
+ try:
+ os.popen(pager, 'w').write(text)
+ except IOError:
+ pass
+
+def spawn_editor(message, filename, editor, charset='utf-8'):
+ if not editor:
+ ewrite('No editor found!\n')
+ return (message, 0)
+
+ edname = os.path.basename(editor.split()[0])
+
+ # Move the cursor for lazy buggers like me; add your editor here...
+ ourline = 0
+ for (lineno, line) in enumerate(file(filename)):
+ if line == '\n' and not ourline:
+ ourline = lineno + 2
+ elif line.strip() == reportbug.NEWBIELINE:
+ ourline = lineno + 2
+
+ opts = ''
+ if 'vim' in edname:
+ # Force *vim to edit the file in the foreground, instead of forking
+ opts = '-f '
+
+ if ourline:
+ if edname in ('vi', 'nvi', 'vim', 'elvis', 'gvim', 'kvim'):
+ opts = '-c :%d' % ourline
+ elif (edname in ('elvis-tiny', 'gnuclient', 'ee', 'pico', 'nano', 'zile') or
+ 'emacs' in edname):
+ opts = '+%d' % ourline
+ elif edname in ('jed', 'xjed'):
+ opts = '-g %d' % ourline
+ elif edname == 'kate':
+ opts = '--line %d' % ourline
+
+ if '&' in editor or edname == 'kate':
+ ewrite("Spawning %s in background; please press Enter when done "
+ "editing.\n", edname)
+ else:
+ ewrite("Spawning %s...\n", edname)
+
+ result = os.system("%s %s '%s'" % (editor, opts, filename))
+
+ if result:
+ ewrite('Warning: possible error exit from %s: %d\n', edname, result)
+
+ if not os.path.exists(filename):
+ ewrite('Bug report file %s removed!', filename)
+ sys.exit(1)
+
+ if '&' in editor: return (None, 1)
+
+ newmessage = file(filename).read().decode(charset, 'replace')
+
+ if newmessage == message:
+ ewrite('No changes were made in the editor.\n')
+
+ return (newmessage, newmessage != message)
Property changes on: branches/manual_merge/reportbug/reportbug_ui_text.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/reportbug_ui_urwid.py (from rev 625, branches/manual_merge/reportbuglib/reportbug_ui_urwid.py)
===================================================================
--- branches/manual_merge/reportbug/reportbug_ui_urwid.py (rev 0)
+++ branches/manual_merge/reportbug/reportbug_ui_urwid.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,686 @@
+# reportbuglib/reportbug_ui_urwid.py
+# urwid user interface for reportbug
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 2006 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# Portions of this file are licensed under the Lesser GNU Public License
+# (LGPL) Version 2.1 or later. On Debian systems, this license is available
+# in /usr/share/common-licenses/LGPL
+#
+# $Id: reportbug_ui_urwid.py,v 1.3.2.19 2007-04-17 20:02:41 lawrencc Exp $
+
+import sys
+import re
+import getpass
+import reportbug
+from reportbug_exceptions import (
+ UINotImportable,
+ NoPackage, NoBugs, NoNetwork, NoReport,
+ )
+from urlutils import launch_browser
+from reportbug_ui_text import (
+ ewrite,
+ spawn_editor,
+ )
+
+try:
+ import urwid.raw_display
+ import urwid
+except ImportError:
+ raise UINotImportable, 'Please install the python-urwid package to use this interface.'
+
+ISATTY = sys.stdin.isatty()
+
+log_message = ewrite
+display_failure = ewrite
+
+# Start a urwid session
+def initialize_urwid_ui():
+ ui = urwid.raw_display.Screen()
+ ui.register_palette(palette)
+ # Improve responsiveness of UI
+ ui.set_input_timeouts(max_wait=0.1)
+ return ui
+
+# Empty function to satisfy ui.run_wrapper()
+def nullfunc():
+ pass
+
+# Widgets ripped mercilessly from urwid examples (dialog.py)
+class buttonpush(Exception):
+ pass
+
+def button_press(button):
+ raise buttonpush, button.exitcode
+
+class SelectableText(urwid.Edit):
+ def valid_char(self, ch):
+ return False
+
+class dialog(object):
+ def __init__(self, message, body=None, width=None, height=None,
+ title='', long_message=''):
+ self.body = body
+
+ self.scrollmode=False
+ if not body:
+ if long_message:
+ box = SelectableText(edit_text=long_message)
+ box.set_edit_pos(0)
+ self.body = body = urwid.ListBox([box])
+ self.scrollmode=True
+ else:
+ self.body = body = urwid.Filler(urwid.Divider(), 'top')
+
+ if not width:
+ width = ('relative', 80)
+
+ if not height:
+ height = ('relative', 80)
+
+ self.frame = urwid.Frame(body, focus_part='footer')
+ if message:
+ self.frame.header = urwid.Pile([urwid.Text(message),
+ urwid.Divider()])
+
+ w = self.frame
+ # pad area around listbox
+ w = urwid.Padding(w, ('fixed left',2), ('fixed right',2))
+ w = urwid.Filler(w, ('fixed top',1), ('fixed bottom',1))
+ w = urwid.AttrWrap(w, 'body')
+
+ if title:
+ w = urwid.Frame(w)
+ w.header = urwid.Text( ('title', title) )
+
+ # "shadow" effect
+ w = urwid.Columns( [w, ('fixed', 1, urwid.AttrWrap( urwid.Filler(urwid.Text(('border',' ')), "top") ,'shadow'))])
+ w = urwid.Frame( w, footer = urwid.AttrWrap(urwid.Text(('border',' ')),'shadow'))
+ # outermost border area
+ w = urwid.Padding(w, 'center', width )
+ w = urwid.Filler(w, 'middle', height )
+ w = urwid.AttrWrap( w, 'border' )
+ self.view = w
+
+ def add_buttons(self, buttons, default=0, vertical=False):
+ l = []
+ for name, exitcode in buttons:
+ if exitcode == '---':
+ # Separator is just a text label
+ b = urwid.Text(name)
+ b = urwid.AttrWrap( b, 'scrolllabel' )
+ else:
+ b = urwid.Button( name, self.button_press )
+ b.exitcode = exitcode
+ b = urwid.AttrWrap( b, 'selectable','focus' )
+ l.append( b )
+
+ if vertical:
+ box = urwid.ListBox(l)
+ box.set_focus(default or 0)
+ self.buttons = urwid.Frame(urwid.AttrWrap(box, 'selectable'))
+ self.frame.footer = urwid.BoxAdapter(self.buttons, min(len(l), 10))
+ else:
+ self.buttons = urwid.GridFlow(l, 12, 3, 1, 'center')
+ self.buttons.set_focus(default or 0)
+ self.frame.footer = urwid.Pile( [ urwid.Divider(), self.buttons ],
+ focus_item = 1 )
+
+ def button_press(self, button):
+ raise buttonpush, button.exitcode
+
+ def run(self):
+ #self.ui.set_mouse_tracking()
+ size = self.ui.get_cols_rows()
+ try:
+ while True:
+ canvas = self.view.render( size, focus=True )
+ self.ui.draw_screen( size, canvas )
+ keys = None
+ while not keys:
+ keys = self.ui.get_input()
+ for k in keys:
+ if urwid.is_mouse_event(k):
+ event, button, col, row = k
+ self.view.mouse_event( size,
+ event, button, col, row,
+ focus=True)
+ if k == 'window resize':
+ size = self.ui.get_cols_rows()
+ k = self.view.keypress( size, k )
+ if k:
+ self.unhandled_key( size, k)
+ except buttonpush, e:
+ return self.on_exit( e.args[0] )
+
+ def on_exit(self, exitcode):
+ return exitcode
+
+ def unhandled_key(self, size, k):
+ if k in ('tab', 'shift tab'):
+ focus = self.frame.focus_part
+ if focus == 'footer':
+ self.frame.set_focus('body')
+ else:
+ self.frame.set_focus('footer')
+
+ if k in ('up','page up', 'down', 'page down'):
+ if self.scrollmode:
+ self.frame.set_focus('body')
+ self.body.keypress(size, k)
+ elif k in ('up', 'page up'):
+ self.frame.set_focus('body')
+ else:
+ self.frame.set_focus('footer')
+
+ if k == 'enter':
+ # pass enter to the "ok" button
+ self.frame.set_focus('footer')
+ self.view.keypress( size, k )
+
+ def main(self, ui=None):
+ if ui:
+ self.ui = ui
+ else:
+ self.ui = initialize_urwid_ui()
+ return self.ui.run_wrapper(self.run)
+
+class displaybox(dialog):
+ def show(self, ui=None):
+ if ui:
+ self.ui = ui
+ else:
+ self.ui = initialize_urwid_ui()
+ size = self.ui.get_cols_rows()
+ canvas = self.view.render( size, focus=True )
+ self.ui.start()
+ self.ui.draw_screen( size, canvas )
+ self.ui.stop()
+
+class textentry(dialog):
+ def __init__(self, text, width=None, height=None, multiline=False,
+ title=''):
+ self.edit = urwid.Edit(multiline=multiline)
+ body = urwid.ListBox([self.edit])
+ body = urwid.AttrWrap(body, 'selectable', 'focustext')
+ if not multiline:
+ body = urwid.Pile( [('fixed', 1, body), urwid.Divider()] )
+ body = urwid.Filler(body)
+
+ dialog.__init__(self, text, body, width, height, title)
+
+ self.frame.set_focus('body')
+
+ def on_exit(self, exitcode):
+ return exitcode, self.edit.get_edit_text()
+
+class listdialog(dialog):
+ def __init__(self, text, widgets, has_default=False, width=None,
+ height=None, title='', buttonwidth=12):
+ l = []
+ self.items = []
+ for (w, label) in widgets:
+ self.items.append(w)
+ if label:
+ w = urwid.Columns( [('fixed', buttonwidth, w),
+ urwid.Text(label)], 2 )
+ w = urwid.AttrWrap(w, 'selectable','focus')
+ l.append(w)
+
+ lb = urwid.ListBox(l)
+ lb = urwid.AttrWrap( lb, "selectable" )
+ dialog.__init__(self, text, height=height, width=width, body=lb,
+ title=title)
+
+ self.frame.set_focus('body')
+
+ def on_exit(self, exitcode):
+ """Print the tag of the item selected."""
+ if exitcode:
+ return exitcode, None
+
+ for i in self.items:
+ if hasattr(i, 'get_state') and i.get_state():
+ return exitcode, i.get_label()
+ return exitcode, None
+
+class checklistdialog(listdialog):
+ def on_exit(self, exitcode):
+ """
+ Mimick dialog(1)'s --checklist exit.
+ Put each checked item in double quotes with a trailing space.
+ """
+ if exitcode:
+ return exitcode, []
+
+ l = []
+ for i in self.items:
+ if i.get_state():
+ l.append(i.get_label())
+ return exitcode, l
+
+def display_message(message, *args, **kwargs):
+ if args:
+ message = message % tuple(args)
+
+ if 'title' in kwargs:
+ title = kwargs['title']
+ else:
+ title = ''
+
+ if 'ui' in kwargs:
+ ui = kwargs['ui']
+ else:
+ ui = None
+
+ # Rewrap the message
+ chunks = re.split('\n\n+', message)
+ chunks = [re.sub(r'\s+', ' ', x).strip() for x in chunks]
+ message = '\n\n'.join(chunks).strip()
+
+ box = displaybox('', long_message=message, title=title or reportbug.VERSION)
+ box.show(ui)
+
+def long_message(message, *args, **kwargs):
+ if args:
+ message = message % tuple(args)
+
+ if 'title' in kwargs:
+ title = kwargs['title']
+ else:
+ title = ''
+
+ if 'ui' in kwargs:
+ ui = kwargs['ui']
+ else:
+ ui = None
+
+ # Rewrap the message
+ chunks = re.split('\n\n+', message)
+ chunks = [re.sub(r'\s+', ' ', x).strip() for x in chunks]
+ message = '\n\n'.join(chunks).strip()
+
+ box = dialog('', long_message=message, title=title or reportbug.VERSION)
+ box.add_buttons([ ("OK", 0) ])
+ box.main(ui)
+
+final_message = long_message
+display_report = long_message
+
+def select_options(msg, ok, help=None, allow_numbers=False, nowrap=False,
+ ui=None, title=None):
+ box = dialog('', long_message=msg, height=('relative', 80),
+ title=title or reportbug.VERSION)
+ if not help:
+ help = {}
+
+ buttons = []
+ default = None
+ for i, option in enumerate(ok):
+ if option.isupper():
+ default = i
+ option = option.lower()
+ buttons.append( (help.get(option, option), option) )
+
+ box.add_buttons(buttons, default, vertical=True)
+ result = box.main(ui)
+ return result
+
+def yes_no(msg, yeshelp, nohelp, default=True, nowrap=False, ui=None):
+ box = dialog('', long_message=msg+"?", title=reportbug.VERSION)
+ box.add_buttons([ ('Yes', True), ('No', False) ], default=1-int(default))
+ result = box.main(ui)
+ return result
+
+def get_string(prompt, options=None, title=None, force_prompt=False,
+ default='', ui=None):
+ if title:
+ title = '%s: %s' % (reportbug.VERSION, title)
+ else:
+ title = reportbug.VERSION
+
+ box = textentry(prompt, title=title)
+ box.add_buttons([ ("OK", 0) ])
+ code, text = box.main(ui)
+ return text or default
+
+def get_multiline(prompt, options=None, title=None, force_prompt=False,
+ ui=None):
+ if title:
+ title = '%s: %s' % (reportbug.VERSION, title)
+ else:
+ title = reportbug.VERSION
+
+ box = textentry(prompt, multiline=True)
+ box.add_buttons([ ("OK", 0) ])
+ code, text = box.main(ui)
+ l = text.split('\n')
+ return l
+
+def get_password(prompt=None):
+ return getpass.getpass(prompt)
+
+def menu(par, options, prompt, default=None, title=None, any_ok=False,
+ order=None, extras=None, multiple=False, empty_ok=False, ui=None,
+ oklabel='Ok', cancellabel='Cancel', quitlabel=None):
+ if not extras:
+ extras = []
+ else:
+ extras = list(extras)
+
+ if not default:
+ default = ''
+
+ if title:
+ title = '%s: %s' % (reportbug.VERSION, title)
+ else:
+ title = reportbug.VERSION
+
+ if isinstance(options, dict):
+ options = options.copy()
+ # Convert to a list
+ if order:
+ olist = []
+ for key in order:
+ if options.has_key(key):
+ olist.append( (key, options[key]) )
+ del options[key]
+
+ # Append anything out of order
+ options = options.items()
+ options.sort()
+ for option in options:
+ olist.append( option )
+ options = olist
+ else:
+ options = options.items()
+ options.sort()
+
+ opts = []
+ for option, desc in options:
+ if desc:
+ opts.append((option, re.sub(r'\s+', ' ', desc)))
+ else:
+ opts.append((option, desc))
+ options = opts
+
+ if multiple:
+ widgets = [(urwid.CheckBox(option, state=(option == default)),
+ desc or '') for (option, desc) in options]
+ box = checklistdialog(par, widgets, height=('relative', 80),
+ title=title)
+ if quitlabel:
+ box.add_buttons( [(oklabel, 0), (cancellabel, -1),
+ (quitlabel, -2)] )
+ else:
+ box.add_buttons( [(oklabel, 0), (cancellabel, -1)] )
+ result, chosen = box.main(ui)
+ if result < 0:
+ return []
+ return chosen
+
+ # Single menu option only
+ def label_button(option, desc):
+ return option
+
+ widgets = []
+ rlist = []
+ for option, desc in options:
+ if option == '---':
+ # Separator is just a text label
+ b = urwid.Text(desc)
+ b = urwid.AttrWrap( b, 'scrolllabel' )
+ desc = ''
+ else:
+ b = urwid.RadioButton( rlist, label_button(option, desc) )
+ b.exitcode = option
+ b = urwid.AttrWrap( b, 'selectable','focus' )
+ widgets.append((b, desc))
+
+## if any_ok:
+## editbox = urwid.Edit(multiline=False)
+## e = urwid.ListBox([editbox])
+## e = urwid.AttrWrap(e, 'selectable', 'focustext')
+## e = urwid.Pile( [('fixed', 1, e)] )
+## e = urwid.Filler(e)
+## widgets.append((e, None))
+
+ box = listdialog(par, widgets, height=('relative', 80),
+ title=title, buttonwidth=12)
+ if quitlabel:
+ box.add_buttons( [(oklabel, 0), (cancellabel, -1), (quitlabel, -2)] )
+ else:
+ box.add_buttons( [(oklabel, 0), (cancellabel, -1)] )
+ focus = 0
+ if default:
+ for i, opt in enumerate(options):
+ if opt[0] == default:
+ focus = i
+ break
+
+ result, chosen = box.main(ui)
+ if result < 0:
+ return result
+
+ return chosen
+
+# A real file dialog would be nice here
+def get_filename(prompt, title=None, force_prompt=False, default=''):
+ return get_string(prompt, title=title, force_prompt=force_prompt,
+ default=default)
+
+def select_multiple(par, options, prompt, title=None, order=None, extras=None):
+ return menu(par, options, prompt, title=title, order=order, extras=extras,
+ multiple=True, empty_ok=False)
+
+# Things that are very UI dependent go here
+def show_report(number, system, mirrors,
+ http_proxy, screen=None, queryonly=False, title='',
+ archived='no'):
+ import debianbts
+
+ ui = screen
+ if not ui:
+ ui = initialize_urwid_ui()
+
+ sysinfo = debianbts.SYSTEMS[system]
+ display_message('Retrieving report #%d from %s bug tracking system...',
+ number, sysinfo['name'], title=title, ui=ui)
+
+ info = debianbts.get_report(number, system, mirrors=mirrors,
+ http_proxy=http_proxy, archived=archived)
+ if not info:
+ long_message('Bug report #%d not found.', number, title=title, ui=ui)
+ return
+
+ options = dict(o='Ok', d='More details (launch browser)',
+ m='Submit more information', q='Quit')
+ valid = 'Odmq'
+
+ while 1:
+ (bugtitle, bodies) = info
+ body = bodies[0]
+
+ r = select_options(body, valid, title=bugtitle, ui=ui, help=options)
+ ui = None
+ if not r or (r == 'o'):
+ break
+ elif r == 'q':
+ return -1
+ elif r == 'm':
+ return number
+
+ launch_browser(debianbts.get_report_url(system, number, archived))
+ return
+
+def handle_bts_query(package, bts, mirrors=None, http_proxy="",
+ queryonly=False, screen=None, title="", archived='no',
+ source=False, version=None):
+ import debianbts
+
+ sysinfo = debianbts.SYSTEMS[bts]
+ root = sysinfo.get('btsroot')
+ if not root:
+ ewrite("%s bug tracking system has no web URL; bypassing query.\n",
+ sysinfo['name'])
+ return
+
+ ui = screen
+ if not ui:
+ ui = initialize_urwid_ui()
+
+ if isinstance(package, basestring):
+ pkgname = package
+ if source:
+ pkgname += ' (source)'
+
+ display_message('Querying %s bug tracking system for reports on %s',
+ debianbts.SYSTEMS[bts]['name'], pkgname,
+ ui=ui, title=title)
+ else:
+ display_message('Querying %s bug tracking system for reports %s',
+ debianbts.SYSTEMS[bts]['name'],
+ ' '.join([str(x) for x in package]), ui=ui,title=title)
+
+ result = None
+ try:
+ (count, sectitle, hierarchy) = debianbts.get_reports(
+ package, bts, mirrors=mirrors, version=version,
+ http_proxy=http_proxy, archived=archived, source=source)
+
+ if not count:
+ ui.run_wrapper(nullfunc)
+ if hierarchy == None:
+ raise NoPackage
+ else:
+ raise NoBugs
+ else:
+ if count > 1:
+ sectitle = '%d bug reports found' % (count,)
+ else:
+ sectitle = '%d bug report found' % (count,)
+
+ buglist = []
+ for (t, bugs) in hierarchy:
+ bcount = len(bugs)
+ buglist.append( ('---', t) )
+ for bug in bugs:
+ bits = re.split(r'[: ]', bug[1:], 1)
+ if len(bits) > 1:
+ tag, info = bits
+ info = info.strip()
+ if not info:
+ info = '(no subject)'
+ else:
+ tag = bug[1:]
+ info = '(no subject)'
+ buglist.append( (tag, info) )
+
+ p = buglist[1][0]
+ #scr.popWindow()
+ if queryonly:
+ cancellabel = 'Exit'
+ quitlabel = None
+ else:
+ cancellabel = 'Continue'
+ quitlabel='Quit'
+
+ while True:
+ info = menu('Select a bug to read the report:', buglist,
+ '', ui=ui, title=sectitle, default=p,
+ oklabel='Get info',
+ cancellabel=cancellabel,
+ quitlabel=quitlabel)
+ ui = None
+ if info < 0:
+ if info == -1:
+ result = None
+ else:
+ result = info
+ break
+ else:
+ p = info
+ res = show_report(int(p), bts, mirrors, http_proxy,
+ queryonly=queryonly)
+ if res:
+ result = res
+ break
+
+ except (IOError, NoNetwork):
+ ui.run_wrapper(nullfunc)
+ long_message('Unable to connect to %s BTS.', sysinfo['name'],
+ title=title)
+ except NoPackage:
+ ui.run_wrapper(nullfunc)
+ long_message('No record of this package found.', title=title)
+ raise NoPackage
+
+ if result and result < 0:
+ raise NoReport
+
+ return result
+
+palette = [
+ ('body','black','light gray', 'standout'),
+ ('border','black','dark blue'),
+ ('shadow','white','black'),
+ ('selectable','black', 'dark cyan'),
+ ('focus','white','dark blue','bold'),
+ ('focustext','light gray','dark blue'),
+ ('title', 'dark red', 'light gray'),
+ ('scrolllabel', 'white', 'dark cyan'),
+ ]
+
+def test():
+ import time
+
+ fp = sys.stdout
+
+ long_message('This is a test. This is only a test.\nPlease do not adjust your set.')
+ time.sleep(1)
+## output = get_string('Tell me your name, biatch.')
+## print >> fp, output
+## output = get_multiline('List all of your aliases now.')
+## print >> fp, output
+## result = select_options('This is really lame', 'ynM', {
+## 'y' : 'You bet', 'n' : 'Never!', 'm' : 'Maybe'})
+## print >> fp, result
+## yn = yes_no('Do you like green eggs and ham?', 'Yes sireee', 'No way!')
+## print >> fp, yn
+
+ mailers = [(x, '') for x in reportbug.MUA.keys()]
+ mailers.sort()
+ mailer = menu('Choose a mailer for your report', mailers,
+ 'Select mailer: ', default='mutt', empty_ok=True)
+ print >> fp, mailer
+
+ import debianbts
+
+ tags = debianbts.TAGS.copy()
+ tags.update(debianbts.CRITICAL_TAGS)
+ tagorder = debianbts.TAGLIST + debianbts.CRITICAL_TAGLIST
+
+ taglist = select_multiple(
+ 'Do any of the following apply to this report?', tags,
+ 'Please select tags: ', order=tagorder,
+ extras=debianbts.EXTRA_TAGS)
+ print >> fp, taglist
+
+if __name__ == '__main__':
+ test()
Property changes on: branches/manual_merge/reportbug/reportbug_ui_urwid.py
___________________________________________________________________
Name: svn:mergeinfo
+
Copied: branches/manual_merge/reportbug/urlutils.py (from rev 625, branches/manual_merge/reportbuglib/urlutils.py)
===================================================================
--- branches/manual_merge/reportbug/urlutils.py (rev 0)
+++ branches/manual_merge/reportbug/urlutils.py 2008-08-16 21:53:50 UTC (rev 627)
@@ -0,0 +1,182 @@
+#
+# urlutils.py - Simplified urllib handling
+#
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 1999-2006 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# Version ##VERSION##; see changelog for revision history
+
+import httplib
+import urllib
+import urllib2
+import getpass
+import re
+import socket
+import commands
+import os
+import sys
+import webbrowser
+
+from reportbug_exceptions import (
+ NoNetwork,
+ )
+
+UA_STR = 'reportbug/##VERSION## (Debian)'
+
+def decode (page):
+ "gunzip or deflate a compressed page"
+ #print page.info().headers
+ encoding = page.info().get("Content-Encoding")
+ if encoding in ('gzip', 'x-gzip', 'deflate'):
+ from cStringIO import StringIO
+ # cannot seek in socket descriptors, so must get content now
+ content = page.read()
+ if encoding == 'deflate':
+ import zlib
+ fp = StringIO(zlib.decompress(content))
+ else:
+ import gzip
+ fp = gzip.GzipFile('', 'rb', 9, StringIO(content))
+ # remove content-encoding header
+ headers = httplib.HTTPMessage(StringIO(""))
+ ceheader = re.compile(r"(?i)content-encoding:")
+ for h in page.info().keys():
+ if not ceheader.match(h):
+ headers[h] = page.info()[h]
+ newpage = urllib.addinfourl(fp, headers, page.geturl())
+ # Propagate code, msg through
+ if hasattr(page, 'code'):
+ newpage.code = page.code
+ if hasattr(page, 'msg'):
+ newpage.msg = page.msg
+ return newpage
+ return page
+
+class HttpWithGzipHandler (urllib2.HTTPHandler):
+ "support gzip encoding"
+ def http_open (self, req):
+ return decode(urllib2.HTTPHandler.http_open(self, req))
+
+if hasattr(httplib, 'HTTPS'):
+ class HttpsWithGzipHandler (urllib2.HTTPSHandler):
+ "support gzip encoding"
+ def http_open (self, req):
+ return decode(urllib2.HTTPSHandler.http_open(self, req))
+
+class handlepasswd(urllib2.HTTPPasswordMgrWithDefaultRealm):
+ def find_user_password(self, realm, authurl):
+ user, password = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(self, realm, authurl)
+ if user is not None:
+ return user, password
+
+ user = raw_input('Enter username for %s at %s: ' % (realm, authurl))
+ password = getpass.getpass(
+ "Enter password for %s in %s at %s: " % (user, realm, authurl))
+ self.add_password(realm, authurl, user, password)
+ return user, password
+
+_opener = None
+def urlopen(url, proxies=None, data=None):
+ global _opener
+
+ if not proxies:
+ proxies = urllib.getproxies()
+
+ headers = {'User-Agent': UA_STR,
+ 'Accept-Encoding' : 'gzip;q=1.0, deflate;q=0.9, identity;q=0.5'}
+
+ req = urllib2.Request(url, data, headers)
+
+ proxy_support = urllib2.ProxyHandler(proxies)
+ if _opener is None:
+ pwd_manager = handlepasswd()
+ handlers = [proxy_support,
+ urllib2.UnknownHandler, HttpWithGzipHandler,
+ urllib2.HTTPBasicAuthHandler(pwd_manager),
+ urllib2.ProxyBasicAuthHandler(pwd_manager),
+ urllib2.HTTPDigestAuthHandler(pwd_manager),
+ urllib2.ProxyDigestAuthHandler(pwd_manager),
+ urllib2.HTTPDefaultErrorHandler, urllib2.HTTPRedirectHandler,
+ ]
+ if hasattr(httplib, 'HTTPS'):
+ handlers.append(HttpsWithGzipHandler)
+ _opener = urllib2.build_opener(*handlers)
+ # print _opener.handlers
+ urllib2.install_opener(_opener)
+
+ return _opener.open(req)
+
+# Global useful URL opener; returns None if the page is absent, otherwise
+# like urlopen
+def open_url(url, http_proxy=None):
+ proxies = urllib.getproxies()
+ if http_proxy:
+ proxies['http'] = http_proxy
+
+ try:
+ page = urlopen(url, proxies)
+ except urllib2.HTTPError, x:
+ if x.code in (404, 500, 503):
+ return None
+ else:
+ raise
+ except (socket.gaierror, socket.error, urllib2.URLError), x:
+ raise NoNetwork
+ except IOError, data:
+ if data and data[0] == 'http error' and data[1] == 404:
+ return None
+ else:
+ raise NoNetwork
+ except TypeError:
+ print >> sys.stderr, "http_proxy environment variable must be formatted as a valid URI"
+ raise NoNetwork
+ except httplib.HTTPException, exc:
+ exc_name = exc.__class__.__name__
+ message = (
+ "Failed to open %(url)r"
+ " (%(exc_name)s: %(exc)s)"
+ ) % vars()
+ raise NoNetwork(message)
+ return page
+
+def launch_browser(url):
+ if not os.system('command -v sensible-browser &> /dev/null'):
+ cmd = 'sensible-browser' + commands.mkarg(url)
+ os.system(cmd)
+ return
+
+ if webbrowser:
+ webbrowser.open(url)
+ return
+
+ X11BROWSER = os.environ.get('X11BROWSER', 'mozilla-firefox')
+ CONSOLEBROWSER = os.environ.get('CONSOLEBROWSER', 'lynx')
+
+ if (os.environ.has_key('DISPLAY') and
+ not os.system('command -v '+X11BROWSER+' &> /dev/null')):
+ cmd = "%s %s &" % (X11BROWSER, commands.mkarg(url))
+ else:
+ cmd = "%s %s" % (CONSOLEBROWSER, commands.mkarg(url))
+
+ os.system(cmd)
+
+if __name__ == '__main__':
+ page = open_url('http://bugs.debian.org/reportbug')
+ content = page.read()
+ print page.info().headers
Property changes on: branches/manual_merge/reportbug/urlutils.py
___________________________________________________________________
Name: svn:mergeinfo
+
More information about the Reportbug-commits
mailing list