Browse Source

Merge branch 'suites/jessie-proposed' into devuan-jessie-backports

tags/reportbug-6.6.6+devuan1.3
KatolaZ 4 years ago
parent
commit
7947023296
37 changed files with 2780 additions and 2296 deletions
  1. +1
    -1
      Makefile
  2. +38
    -37
      bin/querybts
  3. +267
    -231
      bin/reportbug
  4. +139
    -0
      debian/changelog
  5. +4
    -3
      debian/control
  6. +1
    -8
      debian/copyright
  7. +1
    -1
      debian/gbp.conf
  8. +1
    -6
      man/querybts.1
  9. +10
    -0
      man/reportbug.1
  10. +4
    -4
      reportbug/__init__.py
  11. +20
    -19
      reportbug/bugreport.py
  12. +20
    -18
      reportbug/checkbuildd.py
  13. +35
    -25
      reportbug/checkversions.py
  14. +354
    -299
      reportbug/debbugs.py
  15. +29
    -14
      reportbug/exceptions.py
  16. +7
    -5
      reportbug/hiermatch.py
  17. +77
    -45
      reportbug/submit.py
  18. +19
    -14
      reportbug/tempfiles.py
  19. +18
    -17
      reportbug/ui/__init__.py
  20. +946
    -903
      reportbug/ui/gtk2_ui.py
  21. +233
    -215
      reportbug/ui/text_ui.py
  22. +120
    -134
      reportbug/ui/urwid_ui.py
  23. +45
    -48
      reportbug/urlutils.py
  24. +163
    -95
      reportbug/utils.py
  25. +1
    -1
      setup.py
  26. +1
    -1
      share/script
  27. +3
    -3
      test/test_bugreport.py
  28. +2
    -2
      test/test_checkbuildd.py
  29. +9
    -10
      test/test_checkversions.py
  30. +103
    -72
      test/test_debbugs.py
  31. +1
    -0
      test/test_exception.py
  32. +1
    -0
      test/test_hiermatch.py
  33. +1
    -4
      test/test_tempfiles.py
  34. +1
    -0
      test/test_ui.py
  35. +3
    -3
      test/test_ui_gtk2.py
  36. +3
    -2
      test/test_urlutils.py
  37. +99
    -56
      test/test_utils.py

+ 1
- 1
Makefile View File

@@ -23,7 +23,7 @@ coverhtml: coverage
codechecks: pep8 pyflakes pylint

pep8:
pep8 --verbose --repeat --show-source --filename=*.py,reportbug,querybts . --statistics
pep8 --verbose --repeat --show-source --filename=*.py,reportbug,querybts . --statistics --ignore=E501

pyflakes:
pyflakes . bin/*


+ 38
- 37
bin/querybts View File

@@ -3,23 +3,23 @@
# querybts - Examine the state of a debbugs server
# Written by Chris Lawrence <lawrencc@debian.org>
# (C) 1999-2008 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# 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.
# 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.

import sys
import os
@@ -30,27 +30,28 @@ from reportbug import utils
from reportbug.exceptions import (
UINotImportable,
NoPackage, NoBugs, NoReport, NoNetwork,
)
)
from reportbug import debbugs
from reportbug import urlutils

from reportbug.ui import AVAILABLE_UIS

import reportbug.ui.text_ui as ui

ui_mode = 'text'

from reportbug import VERSION_NUMBER

VERSION = "querybts %s" % VERSION_NUMBER

def main():

def main():
# default values for cli options
defaults = dict(system = 'debian', archived = False,
http_proxy = '', interface = 'text',
use_browser = False, source = False,
mirrors = None, mbox = False, buglist = False,
mbox_reader_cmd = None)
defaults = dict(system='debian', archived=False,
http_proxy='', interface='text',
use_browser=False, source=False,
mirrors=None, mbox=False, buglist=False,
mbox_reader_cmd=None)

# parse config file to update default options
args = utils.parse_config_files()
@@ -75,7 +76,7 @@ def main():
help='Display a bugs list for the given package.')
parser.add_option('-B', '--bts', dest='system',
help='Specify an alternate debbugs BTS; available values: %s ' %
', '.join([k for k in debbugs.SYSTEMS if debbugs.SYSTEMS[k].get('btsroot')]))
', '.join([k for k in debbugs.SYSTEMS if debbugs.SYSTEMS[k].get('btsroot')]))
parser.add_option('-m', '--mbox', action='store_true', dest='mbox',
help='generate mbox')
parser.add_option('--proxy', '--http_proxy', dest='http_proxy',
@@ -93,7 +94,6 @@ def main():
parser.add_option('--latest-first', action='store_true', dest='latest_first', default=False,
help='Order bugs to show the latest first')


# parse cli options
(options, args) = parser.parse_args()

@@ -118,12 +118,13 @@ def main():
print

# initialize the selected UI
ui.initialize ()
ui.initialize()

# system must be one of those supported
if options.system not in [k for k in debbugs.SYSTEMS if debbugs.SYSTEMS[k].get('btsroot')]:
parser.error('Allowed arguments to --bts: \n' +
'\n'.join([' %s (%s)' % (k, debbugs.SYSTEMS[k]['name']) for k in debbugs.SYSTEMS if debbugs.SYSTEMS[k].get('btsroot')]))
'\n'.join([' %s (%s)' % (k, debbugs.SYSTEMS[k]['name'])
for k in debbugs.SYSTEMS if debbugs.SYSTEMS[k].get('btsroot')]))
else:
# set the system info to those of the one selected
sysinfo = debbugs.SYSTEMS[options.system]
@@ -159,10 +160,10 @@ def main():
package = bugnum
m = re.match('^#?(\d+)$', bugnum)
if not m:
mboxbuglist = []
mboxbuglist = ui.handle_bts_query(package, options.system, options.timeout, options.mirrors, options.http_proxy,
queryonly=True, title=VERSION, archived=options.archived,
source=options.source, mbox=options.mbox, latest_first=options.latest_first)
mboxbuglist = ui.handle_bts_query(package, options.system, options.timeout, options.mirrors,
options.http_proxy, queryonly=True, title=VERSION,
archived=options.archived, source=options.source, mbox=options.mbox,
latest_first=options.latest_first)
for num in mboxbuglist:
url = debbugs.get_report_url(options.system, num, options.archived, mbox=True)
try:
@@ -181,7 +182,6 @@ def main():
sys.exit(1)
return


reportre = re.compile(r'^#?(\d+)$')
try:
if len(args) > 1:
@@ -198,13 +198,13 @@ def main():
match = reportre.match(package)
if match:
report = int(match.group(1))
while 1 :
while 1:
retvalue = ui.show_report(report, options.system, options.mirrors,
options.http_proxy, options.timeout,
queryonly=True,
title=VERSION,
archived=options.archived,
mbox_reader_cmd=options.mbox_reader_cmd)
options.http_proxy, options.timeout,
queryonly=True,
title=VERSION,
archived=options.archived,
mbox_reader_cmd=options.mbox_reader_cmd)
ui.long_message('This option is not available while using querybts alone.\n')
x = ui.select_options('What do you want to do now?', 'Qb',
{'q': 'Exit querybts.',
@@ -235,13 +235,14 @@ def main():
except NoNetwork:
ui.long_message('Cannot connect to network.\n')


if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print "querybts: exiting due to user interrupt."
except debbugs.Error, x:
print 'error accessing BTS: '+str(x)
print 'error accessing BTS: ' + str(x)
except SystemExit:
pass
except:


+ 267
- 231
bin/reportbug
File diff suppressed because it is too large
View File


+ 139
- 0
debian/changelog View File

@@ -1,3 +1,142 @@
reportbug (6.6.6+devuan1.1) unstable; urgency=medium

[ KatolaZ ]
* Again
* Solved a little problem with version+devuan1.1 in debian/rules

[ Vincenzo (KatolaZ) Nicosia ]

-- Vincenzo (KatolaZ) Nicosia <katolaz@freaknet.org> Wed, 29 Mar 2017 15:33:33 +0100

reportbug (6.6.6+devuan1.1) unstable; urgency=medium

* First version of reportbug for Devuan

-- Vincenzo (KatolaZ) Nicosia <katolaz@freaknet.org> Wed, 29 Mar 2017 15:19:17 +0100

reportbug (6.6.6~bpo8+1) jessie-backports; urgency=medium

* Rebuild for jessie-backports.

-- Sandro Tosi <morph@debian.org> Mon, 08 Feb 2016 23:32:30 +0000

reportbug (6.6.6) unstable; urgency=medium

* reportbug/utils.py
- Update the SUITES2DISTS mapping for the jessie release; patch by Paul Wise
- 'Control' pseudo-header can be repeated/present multiple times in the
report; Closes: #687679
* man/querybts.1
- document http_proxy environment variable in querybts manpage; report and
patch by Jakub Wilk; Closes: #800092
* Consolidate Debian release codenames handling in a single place, updating
how release.d.o is using this information; Closes: #804504
* bin/reportbug
- rephrase slightly the orphan check warning; Closes: #544624
- where there are modified conffiles, clarify the menu entry to view such
files that a "q" is required to exit it, making it more clear in the GTK
UI; Closes: #732334
- default '--exitprompt' to False, should avoid a spurious message in GTK UI
when reporting additional information; Closes: #745514
* reportbug/submit.py
- in paranoid mode, handle the case when the pager exists without having
processed all the text we are sending and it generates a SIGPIPE;
Closes: #777010, #686922
- decode email addresses when printing the recipients recap at submit time;
Closes: #546914
- dont escape a single dot line surrounded by newlines (the End-Of-Message
in SMTP), it is done by sendmail() automatically; Closes: #808742
- print the "If you want to provide additional information" text only if an
email is specified in sysinfo, this prevents a crash if we are sending
reports to a non-debbugs instance; Closes: #789332
- save a backup of the bug report, that will prevent data loss in case of
crash or unexpected errors; Closes: #736214
* debian/control
- add dep on 'file' for python-reportbug; Closes: #803764
* reportbug/ui/text_ui.py
- dont crash when selecting "Providing additional information" after
filtering the bugs list more than once; Closes: #804130
* reportbug/ui/gtk2_ui.py
- switch from gtkspell (now removed) to gtkspellcheck; Closes: #802387
* man/querybts.1
- remove reference to BROWSER env variable, we use xdg-open; Closes: #690759
* reportbug/urlutils.py
- remove handling of X11BROWSER and CONSOLEBROWSER, obsolete
- explicitly set the timeout when requesting a URL, that happens in
particular when checking for newer versions; Closes: #784840
* reportbug/ui/*
- dont crash if we cant access the BTS: Closes: #694634

-- Sandro Tosi <morph@debian.org> Sat, 02 Jan 2016 01:47:46 +0000

reportbug (6.6.5) unstable; urgency=medium

* reportbug/debbugs.py
- use 'ANY' as an alias for "all architectures" when requesting a binNMU;
thanks to Mehdi for report and patch; Closes: #795687
* debian/control
- set dep on python-debianbts to the version implementing the paged access
to BTS; Closes: #796759
* bin/reportbug, reportbug/{debbugs.py, exceptions.py}
- in case of an error accessing the BTS, throw a new exception, and catch it
in the main program, and display a message about it
* bin/reportbug
- special-case also the short alias -T along with --tag; this fixes a crash
in 'debian-bug' in Emacs; thanks to Sven Joachim for report and patch;
Closes: #794590
* reportbug/utils.py
- correctly identify conffiles marked as obsolete; thanks to Jakub Wilk for
the report; Closes: #791577
* PEP8-fied the source code (except for "line too long", which we ignore)

-- Sandro Tosi <morph@debian.org> Fri, 18 Sep 2015 21:38:56 +0100

reportbug (6.6.4) unstable; urgency=medium

* reportbug/debbugs.py
- fix usage of singular they in RFH description; thanks to Doug Torrance for
the report; Closes: #776015
- clarify the menu for partial/all archs for binNMUs and RMs; thanks to
Kamaraju Kusumanchi for the report; Closes: #773606
- clarify to not select any item in the suite menu for ftp.debian.org to
choose 'unstable', that should work on every UI available; thanks to
Rafael Laboissiere for the report; Closes: #765546
- extend release.d.o binNMUs handling to include a menu to request the suite
the binNMU is for; thanks to Andreas Beckmann for the report and Niels
Thykier, Adam D. Barratt from release team for help; Closes: #769355
* share/script
- report PAGER environment variable when filing a bug against reportbug
* reportbug/ui/gtk2_ui.py
- correctly acquire GDK mutual exclusion lock
* reportbug/submit.py
- correctly handle the case where there is not MTA or smtphost defined;
Closes: #782319, #780825, #769055
- use ui.final_message() when printing the last message and then exit
* bin/reportbug
- suggest to contact Debian support channels when reporting bugs against
generic packages; thanks to Tomas Pospisek for the patch; Closes: #661563
- set default encoding to UTF-8, should prevent all the "UnicodeDecodeError:
'ascii' codec can't decode" bugs; Closes: #769013, #739507, #761367,
#771833, #672487, #739507, #612712, #690882, #779200, #642096, #666116
- --tag cli option now correctly generates the list of available tags based
on the selected mode, which includes 'security'; Closes: #685826
* reportbug/{debbugs.py, ui/text_ui.py}, test/test_{debbugs.py, urlutils.py}
- use HTTPS when connecting to Debian BTS
* bin/reportbug, reportbug/ui/urwid_ui.py
- make the Cancel button in Urwid interface works for tags and severity
lists; thanks to Ryan Kavanagh for report and patch; Closes: #702521
* reportbug/utils.py
- support LANG declined Description-xx field in package information; thanks
to Robert Luberda for the report; Closes: #683116
* bin/reportbug, man/reportbug.1
- add --{no-}security-team, to declare to send (or not) the report only to
the security team since it is (or not) an undisclosed vulnerability;
Closes: #685829
* bin/reportbug, reportbug/debbugs.py
- remove 'base' pseudo-package, to be deprecated in BTS too; Closes: #761206

-- Sandro Tosi <morph@debian.org> Mon, 03 Aug 2015 20:46:01 +0100

reportbug (6.6.3) unstable; urgency=medium

* reportbug/debbugs.py


+ 4
- 3
debian/control View File

@@ -1,7 +1,8 @@
Source: reportbug
Section: utils
Priority: standard
Maintainer: Vincenzo (KatolaZ) Nicosia <katolaz@freaknet.org>
Maintainer: Vincenzo (KatolaZ) Nicosia <katolaz@freaknet.org>
Uploaders: Sandro Tosi <morph@debian.org>, Chris Lawrence <lawrencc@debian.org>
Standards-Version: 3.9.6
Build-Depends: debhelper (>= 9), python (>= 2.5), dh-python
Build-Depends-Indep: python-nose, python-setuptools, python-mock
@@ -13,7 +14,7 @@ Homepage: http://alioth.debian.org/projects/reportbug/
Package: reportbug
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}, apt, python-reportbug (= ${source:Version})
Suggests: postfix | exim4 | mail-transport-agent, gnupg | pgp, debconf-utils (>> 1.1.0), debsums (>= 2.0.47), file (>> 1.30), dlocate, python-urwid, python-gtk2, python-vte, python-gtkspell, xdg-utils, emacs23-bin-common | emacs24-bin-common, claws-mail (>= 3.8.0)
Suggests: postfix | exim4 | mail-transport-agent, gnupg | pgp, debconf-utils (>> 1.1.0), debsums (>= 2.0.47), file (>> 1.30), dlocate, python-urwid, python-gtk2, python-vte, python-gtkspellcheck, xdg-utils, emacs23-bin-common | emacs24-bin-common, claws-mail (>= 3.8.0)
Description: reports bugs in the Debian distribution
reportbug is a tool designed to make the reporting of bugs in Debian
and derived distributions relatively painless. Its features include:
@@ -36,7 +37,7 @@ Description: reports bugs in the Debian distribution
Package: python-reportbug
Section: python
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}, apt, python-debian, python-debianbts
Depends: ${misc:Depends}, ${python:Depends}, apt, python-debian, python-debianbts (>= 1.13), file
Suggests: reportbug
Description: Python modules for interacting with bug tracking systems
reportbug is a tool designed to make the reporting of bugs in Debian


+ 1
- 8
debian/copyright View File

@@ -8,7 +8,7 @@ Packaged-Date: Fri, 14 May 1999 01:07:35 -0500
Files: *
Copyright:
© 1999-2006 Chris Lawrence
Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
License: other
# This program is freely distributable per the following license:
#
@@ -38,13 +38,6 @@ License: GPL-any
On Debian systems, the complete text of the GNU General Public License
can be found in file "/usr/share/common-licenses/GPL".

Files: checks/compare_pseudo-pkgs_lists.py
Copyright:
© 2008 Sandro Tosi <morph@debian.org>
License: PD
checks/compare_pseudo-pkgs_lists.py was placed in public domain by Sandro
Tosi <morph@debian.org>

Files: reportbug/ui/gtk2_ui.py
Copyright:
Copyright (C) 2006 Philipp Kern <pkern@debian.org>


+ 1
- 1
debian/gbp.conf View File

@@ -1,2 +1,2 @@
[DEFAULT]
debian-branch=suites/jessie-proposed
debian-branch=suites/jessie-backports-proposed

+ 1
- 6
man/querybts.1 View File

@@ -96,12 +96,7 @@ information.
Options only supported by \fBreportbug\fP will be silently ignored.
.SH ENVIRONMENT
.TP
.B BROWSER
Colon-separated list of web browsers to try; they will be tried in
order, until one succeeds. You can use \fB%s\fP to substitute the URL
to be used, and \fB%%\fP to insert a literal percent sign. If no
\fB%s\fP is specified, the URL will be supplied as a normal argument.
.TP
.B http_proxy
Provides the address of a proxy server to handle the BTS query. This
should be a valid \fBhttp\fP URL for a proxy server, including any
required port number (simply specifying a hostname, or omitting a port


+ 10
- 0
man/reportbug.1 View File

@@ -388,6 +388,16 @@ Set the subject of the bug report (i.e. a brief explanation of the
problem, less than 60 characters). If you do not specify this switch,
you will be prompted for a subject.
.TP
.B \-\-security\-team
If the 'security' tag is set, this option will explicitly specify to send the
report only to the Debian Security Team, as this is an undisclosed
vulnerability.
.TP
.B \-\-no\-security\-team
If the 'security' tag is set, this option will explicitly specify to not send
the report only to the Debian Security Team, as this is not an undisclosed
vulnerability.
.TP
.B \-S SEVERITY, \-\-severity=SEVERITY
Specify a severity level, from \fBcritical\fP, \fBgrave\fP,
\fBserious\fP, \fBimportant\fP, \fBnormal\fP, \fBminor\fP, and


+ 4
- 4
reportbug/__init__.py View File

@@ -3,11 +3,11 @@
# reportbug - Report a bug in the Debian distribution.
# Written by Chris Lawrence <lawrencc@debian.org>
# Copyright (C) 1999-2008 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# This program is freely distributable per the following license:
#
LICENSE="""\
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
@@ -25,9 +25,9 @@ SOFTWARE."""
__all__ = ['bugreport', 'utils', 'urlutils', 'checkbuildd', 'checkversions',
'debbugs', 'exceptions', 'submit', 'tempfile']

VERSION_NUMBER = "6.6.3+devuan1.1"
VERSION_NUMBER = "6.6.6+devuan1.1"

VERSION = "reportbug "+VERSION_NUMBER
VERSION = "reportbug " + VERSION_NUMBER
COPYRIGHT = VERSION + '\nCopyright (C) 1999-2008 Chris Lawrence <lawrencc@debian.org>' + \
'\nCopyright (C) 2008-2014 Sandro Tosi <morph@debian.org>' + \
'\nCopyright (C) 2017 Vincenzo (KatolaZ) Nicosia <katolaz@freaknet.org>'

+ 20
- 19
reportbug/bugreport.py View File

@@ -2,23 +2,23 @@
# bugreport module - object containing bug stuff for reporting
# Written by Chris Lawrence <lawrencc@debian.org>
# Copyright (C) 1999-2008 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# 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.
# 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.

import os

@@ -31,12 +31,13 @@ from exceptions import *
# to print errors
import ui.text_ui as ui


class bugreport(object):
"Encapsulates a bug report into a convenient object we can pass around."

# Default character set for str(x)
charset = 'utf-8'
def __init__(self, package, subject='', body='', system='debian',
incfiles='', sysinfo=True,
followup=False, type='debbugs', mode=utils.MODE_STANDARD,
@@ -101,7 +102,7 @@ class bugreport(object):

ph = getattr(self, 'pseudoheaders', None)
if ph:
headers = u'\n'.join(ph)+u'\n'
headers = u'\n'.join(ph) + u'\n'
else:
headers = u''

@@ -117,7 +118,7 @@ class bugreport(object):
# thinking about those systems that don't have 'specials' dict
if self.mode < utils.MODE_ADVANCED and self.package not in \
debbugs.SYSTEMS[self.system].get('specials', {}).keys():
body = utils.NEWBIELINE+u'\n\n'+body
body = utils.NEWBIELINE + u'\n\n' + body
elif not body:
body = u'\n'

@@ -134,7 +135,7 @@ class bugreport(object):
a = getattr(self, attr, None)
if a:
headers += u'%s: %s\n' % (name, a)
report = u"%s: %s\n%s\n" % (reportto, self.package, headers)
else:
report = "Followup-For: Bug #%d\n%s: %s\n%s\n" % (
@@ -197,7 +198,7 @@ class bugreport(object):

def __str__(self):
return unicode(self).encode(charset, 'replace')
def __repr__(self):
params = ['%s=%s' % (k, self.k) for k in dir(self)]
return 'bugreport(%s)' % ', '.join(params)

+ 20
- 18
reportbug/checkbuildd.py View File

@@ -3,23 +3,23 @@
#
# Written by Chris Lawrence <lawrencc@debian.org>
# (C) 2002-08 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# 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.
# 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.

import sgmllib
import commands
@@ -28,12 +28,12 @@ import utils
from urlutils import open_url
from reportbug.exceptions import (
NoNetwork,
)
)

BUILDD_URL = 'https://buildd.debian.org/build.php?arch=%s&pkg=%s'

# Check for successful in a 'td' block

# Check for successful in a 'td' block
class BuilddParser(sgmllib.SGMLParser):
def __init__(self):
sgmllib.SGMLParser.__init__(self)
@@ -55,7 +55,8 @@ class BuilddParser(sgmllib.SGMLParser):
def save_end(self, mode=0):
data = self.savedata
self.savedata = None
if not mode and data is not None: data = ' '.join(data.split())
if not mode and data is not None:
data = ' '.join(data.split())
return data

def start_td(self, attrs):
@@ -64,7 +65,8 @@ class BuilddParser(sgmllib.SGMLParser):
def end_td(self):
data = self.save_end()
if data and 'successful' in data.lower():
self.found_succeeded=True
self.found_succeeded = True


def check_built(src_package, timeout, arch=None, http_proxy=None):
"""Return True if built in the past, False otherwise (even error)"""


+ 35
- 25
reportbug/checkversions.py View File

@@ -3,23 +3,23 @@
#
# Written by Chris Lawrence <lawrencc@debian.org>
# (C) 2002-08 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# 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.
# 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.

import sys
import os
@@ -33,7 +33,7 @@ import utils
from urlutils import open_url
from reportbug.exceptions import (
NoNetwork,
)
)

# needed to parse new.822
from debian.deb822 import Deb822
@@ -43,6 +43,7 @@ RMADISON_URL = 'http://qa.debian.org/madison.php?package=%s&text=on'
INCOMING_URL = 'http://incoming.debian.org/'
NEWQUEUE_URL = 'http://ftp-master.debian.org/new.822'


# The format is an unordered list

class BaseParser(sgmllib.SGMLParser):
@@ -64,16 +65,18 @@ class BaseParser(sgmllib.SGMLParser):
def save_end(self, mode=0):
data = self.savedata
self.savedata = None
if not mode and data is not None: data = ' '.join(data.split())
if not mode and data is not None:
data = ' '.join(data.split())
return data


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')
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:
@@ -84,17 +87,21 @@ class IncomingParser(sgmllib.SGMLParser):
if mob:
self.found.append(mob.group(1))


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 current or not upstream: return 0
if not current or not upstream:
return 0
return debian_support.version_compare(upstream, current)


def later_version(a, b):
if compare_versions(a, b) > 0:
return b
return a


def get_versions_available(package, timeout, dists=None, http_proxy=None, arch='i386'):
if not dists:
dists = ('oldstable', 'stable', 'testing', 'unstable', 'experimental')
@@ -127,14 +134,15 @@ def get_versions_available(package, timeout, dists=None, http_proxy=None, arch='
if len(l) != 4:
continue
# map suites name (returned by madison) to dist name
dist = utils.SUITES2DISTS.get(l[2], l[2])
dist = utils.CODENAME2SUITE.get(l[2], l[2])
versions[dist] = l[1]

return versions


def get_newqueue_available(package, timeout, dists=None, http_proxy=None, arch='i386'):
if dists is None:
dists = ('unstable (new queue)', )
dists = ('unstable (new queue)',)
try:
page = open_url(NEWQUEUE_URL, http_proxy, timeout)
except NoNetwork:
@@ -150,12 +158,13 @@ def get_newqueue_available(package, timeout, dists=None, http_proxy=None, arch='
# iter over the entries, one paragraph at a time
for para in Deb822.iter_paragraphs(page):
if para['Source'] == package:
k = para['Distribution'] + ' (' + para['Queue'] + ')'
k = para['Distribution'] + ' (' + para['Queue'] + ')'
# in case of multiple versions, choose the bigger
versions[k] = max(para['Version'].split())

return versions


def get_incoming_version(package, timeout, http_proxy=None, arch='i386'):
try:
page = open_url(INCOMING_URL, http_proxy, timeout)
@@ -186,6 +195,7 @@ def get_incoming_version(package, timeout, http_proxy=None, arch='i386'):
del parser
return None


def check_available(package, version, timeout, dists=None,
check_incoming=True, check_newqueue=True,
http_proxy=None, arch='i386'):
@@ -199,11 +209,11 @@ def check_available(package, version, timeout, dists=None,
avail.update(stuff)
if check_newqueue:
srcpackage = utils.get_source_name(package)
if srcpackage is None:
srcpackage = package
if srcpackage is None:
srcpackage = package
stuff = get_newqueue_available(srcpackage, timeout, dists, http_proxy, arch)
avail.update(stuff)
#print gc.garbage, stuff
# print gc.garbage, stuff

new = {}
newer = 0


+ 354
- 299
reportbug/debbugs.py
File diff suppressed because it is too large
View File


+ 29
- 14
reportbug/exceptions.py View File

@@ -1,59 +1,74 @@
# Exceptions for reportbug
# Written by Chris Lawrence <lawrencc@debian.org>
# (C) 2002-04 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# 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.
# 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.


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


# There was a problem accessing BTS
class QuertBTSError(reportbug_exception):
pass

+ 7
- 5
reportbug/hiermatch.py View File

@@ -1,11 +1,12 @@
# hiermatch - Doing match on a list of string or a hierarchy.
# Written by Chris Lawrence <lawrencc@debian.org>
# Copyright (C) 1999-2008 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>

import re
import 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."""
@@ -14,10 +15,10 @@ def egrep_list(strlist, pattern_str, subindex=None):
return None

try:
pat = re.compile(pattern_str, re.I|re.M)
pat = re.compile(pattern_str, re.I | re.M)
except:
raise exceptions.InvalidRegex
resultlist = []
if subindex is None:
subindex = range(len(strlist))
@@ -26,6 +27,7 @@ def egrep_list(strlist, pattern_str, subindex=None):
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."""
@@ -33,7 +35,7 @@ def egrep_hierarchy(hier, pattern_str, subhier=None, nth=1):

for i in range(len(hier)):
if subhier:
if subhier[i]: # Only if have something to match.
if subhier[i]: # Only if have something to match.
resultlist = egrep_list(hier[i][nth], pattern_str, subhier[i])
else:
resultlist = []
@@ -43,6 +45,7 @@ def egrep_hierarchy(hier, pattern_str, subhier=None, nth=1):
resulthier.append(resultlist)
return resulthier


def matched_hierarchy(hier, pattern_str):
"""Actually create a new hierarchy from a pattern matching."""
mhier = []
@@ -54,4 +57,3 @@ def matched_hierarchy(hier, pattern_str):
return mhier

# vim:ts=8:sw=4:expandtab:


+ 77
- 45
reportbug/submit.py View File

@@ -1,23 +1,23 @@
# reportbug_submit module - email and GnuPG functions
# Written by Chris Lawrence <lawrencc@debian.org>
# Copyright (C) 1999-2006 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# 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.
# 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.

import sys
import os
@@ -34,22 +34,24 @@ 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
from email.Header import Header, decode_header
import mimetypes

from __init__ import VERSION, VERSION_NUMBER
from tempfiles import TempFile, open_write_safe, tempfile_prefix
from exceptions import (
NoMessage,
)
)
import ui.text_ui as ui
from utils import get_email_addr
import errno

quietly = False

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]')
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):
@@ -60,6 +62,7 @@ class BetterMIMEText(MIMEText):
if notascii2.search(_text):
self.set_param('charset', _charset)


def encode_if_needed(text, charset, encoding='q'):
needed = False

@@ -72,28 +75,39 @@ def encode_if_needed(text, charset, encoding='q'):
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(
newlist.append(email.Utils.formataddr(
(str(rfc2047_encode_header(realname, charset, mua)), address)))
else:
newlist.append( address )
newlist.append(address)
return ', '.join(newlist)


def rfc2047_encode_header(header, charset, mua=None):
if mua: return header
#print repr(header), repr(charset)
if mua:
return header
# print repr(header), repr(charset)

return encode_if_needed(header, charset)


def decode_email_header(header):
# returns a list of 2-items tuples
decoded = decode_header(header)
return ' '.join([x[0] for x in decoded]).strip()


# 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', draftpath=None):
'''Sign message with pgp key.'''
''' Return: a signed body.
@@ -118,10 +132,10 @@ def sign_message(body, fromaddr, package='x', pgp_addr=None, sign='gpg', draftpa
signcmd = "gpg --local-user '%s' --clearsign " % pgp_addr
else:
signcmd = "gpg --local-user '%s' --use-agent --clearsign " % pgp_addr
signcmd += '--output '+commands.mkarg(file2)+ ' ' + commands.mkarg(file1)
signcmd += '--output ' + commands.mkarg(file2) + ' ' + commands.mkarg(file1)
else:
signcmd = "pgp -u '%s' -fast" % pgp_addr
signcmd += '<'+commands.mkarg(file1)+' >'+commands.mkarg(file2)
signcmd += '<' + commands.mkarg(file1) + ' >' + commands.mkarg(file2)

try:
os.system(signcmd)
@@ -145,6 +159,7 @@ def sign_message(body, fromaddr, package='x', pgp_addr=None, sign='gpg', draftpa
body = None
return body


def mime_attach(body, attachments, charset, body_charset=None):
mimetypes.init()

@@ -166,8 +181,8 @@ def mime_attach(body, attachments, charset, body_charset=None):
continue
ctype = None
cset = charset
info = Popen(['file','--mime', '--brief', attachment],
stdout=PIPE, stderr=STDOUT).communicate()[0]
info = Popen(['file', '--mime', '--brief', attachment],
stdout=PIPE, stderr=STDOUT).communicate()[0]
if info:
match = re.match(r'([^;, ]*)(,[^;]+)?(?:; )?(.*)', info)
if match:
@@ -215,6 +230,7 @@ def mime_attach(body, attachments, charset, body_charset=None):
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,
@@ -302,7 +318,13 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
message = message.as_string()
if paranoid and not (template or printonly):
pager = os.environ.get('PAGER', 'sensible-pager')
os.popen(pager, 'w').write(message)
try:
os.popen(pager, 'w').write(message)
except Exception, e:
# if the PAGER exits before all the text has been sent,
# it'd send a SIGPIPE, so crash only if that's not the case
if e.errno != errno.EPIPE:
raise e
if not ui.yes_no('Does your report seem satisfactory', 'Yes, send it.',
'No, don\'t send it.'):
smtphost = mta = None
@@ -312,11 +334,15 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
pipe = sys.stdout
elif mua:
pipe, filename = TempFile(prefix=tfprefix, dir=draftpath)
elif outfile or not ((mta and os.path.exists(mta)) or smtphost):
msgname = os.path.expanduser(outfile) or ('/var/tmp/%s.bug' % package)
elif outfile or not ((mta and os.path.exists(mta)) and not smtphost):
# outfile can be None at this point
if outfile:
msgname = os.path.expanduser(outfile)
else:
msgname = '/var/tmp/%s.bug' % package
if os.path.exists(msgname):
try:
os.rename(msgname, msgname+'~')
os.rename(msgname, msgname + '~')
except OSError:
ewrite('Unable to rename existing %s as %s~\n',
msgname, msgname)
@@ -331,7 +357,7 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
# we just need a place where to write() and a file handler
# is here just for that
pipe = fh
elif mta and not smtphost:
elif (mta and os.path.exists(mta)) and not smtphost:
try:
x = os.getcwd()
except OSError:
@@ -347,12 +373,17 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
envfrom = faddr
ewrite("Sending message via %s...\n", mta)
pipe = os.popen('%s -f %s -oi -oem %s' % (
mta, commands.mkarg(envfrom), jalist), 'w')
mta, commands.mkarg(envfrom), jalist), 'w')
using_sendmail = True

# saving a backup of the report
backupfh, backupname = TempFile(prefix=tempfile_prefix(package, 'backup'), dir=draftpath)
ewrite('Saving a backup of the report at %s\n', backupname)
backupfh.write(message)
backupfh.close()

if smtphost:
toaddrs = [x[1] for x in alist]
smtp_message = re.sub(r'(?m)^[.]', '..', message)

tryagain = True
refused = None
@@ -365,8 +396,8 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
# if we're using reportbug.debian.org, send mail to
# submit
if smtphost.lower() == 'reportbug.debian.org':
conn = smtplib.SMTP(smtphost,587)
else:
conn = smtplib.SMTP(smtphost, 587)
else:
conn = smtplib.SMTP(smtphost)
response = conn.ehlo()
if not (200 <= response[0] <= 299):
@@ -382,7 +413,7 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
'Enter SMTP password for %s@%s: ' %
(smtpuser, smtphost))
conn.login(smtpuser, smtppasswd)
refused = conn.sendmail(fromaddr, toaddrs, smtp_message)
refused = conn.sendmail(fromaddr, toaddrs, message)
conn.quit()
except (socket.error, smtplib.SMTPException), x:
# If wrong password, try again...
@@ -397,8 +428,9 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
tryagain = False

# In case of failure, ask to retry or to save & exit
if ui.yes_no('SMTP send failure: %s. Do you want to retry (or else save the report and exit)?' % x, 'Yes, please retry.',
'No, save and exit.'):
if ui.yes_no('SMTP send failure: %s. Do you want to retry (or else save the report and exit)?' % x,
'Yes, please retry.',
'No, save and exit.'):
tryagain = True
continue
else:
@@ -447,8 +479,8 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
ewrite("Mutt users should be aware it is mandatory to edit the draft before sending.\n")
mtitle = 'Report has not been sent yet; what do you want to do now?'
mopts = 'Eq'
moptsdesc = {'e' : 'Edit the message.',
'q' : 'Quit reportbug; will save the draft for future use.'}
moptsdesc = {'e': 'Edit the message.',
'q': 'Quit reportbug; will save the draft for future use.'}
x = ui.select_options(mtitle, mopts, moptsdesc)
if x == 'q':
failed = True
@@ -474,17 +506,17 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
if len(addresses):
ewrite("Copies sent to:\n")
for address in addrs:
ewrite(' %s\n', address)
ewrite(' %s\n', decode_email_header(address))

if debbugs_cc and rtype == 'debbugs':
ewrite("Copies will be sent after processing to:\n")
for address in cclist:
ewrite(' %s\n', address)
ewrite(' %s\n', decode_email_header(address))

if not (exinfo or kudos) and rtype == 'debbugs' and sysinfo and not failed \
if not (exinfo or kudos) and rtype == 'debbugs' and sysinfo and 'email' in sysinfo and not failed \
and mailing:
ewrite('\n')
ui.long_message(
ui.final_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


+ 19
- 14
reportbug/tempfiles.py View File

@@ -2,28 +2,29 @@
# tempfiles module - Temporary file handling for reportbug
# Written by Chris Lawrence <lawrencc@debian.org>
# (C) 1999-2008 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# 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.
# 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.

import os
import tempfile
import time


def tempfile_prefix(package=None, extra=None):
if extra:
if package:
@@ -36,6 +37,7 @@ def tempfile_prefix(package=None, extra=None):
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
@@ -49,6 +51,7 @@ _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):
@@ -63,6 +66,7 @@ def open_write_safe(filename, mode='w+b', bufsize=-1):
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,
@@ -71,6 +75,7 @@ def TempFile(suffix="", prefix=template, dir=None, text=True,
fd = os.fdopen(fh, mode, bufsize)
return (fd, filename)


def cleanup_temp_file(temp_filename):
""" Clean up a temporary file.



+ 18
- 17
reportbug/ui/__init__.py View File

@@ -3,23 +3,23 @@
# reportbug - Report a bug in the Debian distribution.
# Written by Chris Lawrence <lawrencc@debian.org>
# Copyright (C) 1999-2008 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# 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.
# 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.


__all__ = ['text_ui', 'urwid_ui', 'gtk2_ui']
@@ -37,9 +37,9 @@ __LOADED_UIS = {}
for uis in UIS.keys():
try:
# let's try to import the ui...
ui_module = __import__('reportbug.ui', fromlist=[uis+'_ui'])
ui_module = __import__('reportbug.ui', fromlist=[uis + '_ui'])
# ... and check if it's really imported
ui = getattr(ui_module, uis+'_ui')
ui = getattr(ui_module, uis + '_ui')
# then we can finally add it to AVAILABLE_UIS
AVAILABLE_UIS[uis] = UIS[uis]
__LOADED_UIS[uis] = ui
@@ -47,10 +47,11 @@ for uis in UIS.keys():
# we can't import uis, so just skip it
pass


def getUI(ui):
"""Returns the requested UI, or default to text if not available"""

if __LOADED_UIS.has_key(ui):
if ui in __LOADED_UIS:
print "loading %s" % ui
return __LOADED_UIS[ui]
else:


+ 946
- 903
reportbug/ui/gtk2_ui.py
File diff suppressed because it is too large
View File


+ 233
- 215
reportbug/ui/text_ui.py
File diff suppressed because it is too large
View File


+ 120
- 134
reportbug/ui/urwid_ui.py View File

@@ -1,23 +1,23 @@
# urwid user interface for reportbug
# Written by Chris Lawrence <lawrencc@debian.org>
# (C) 2006-08 Chris Lawrence
# Copyright (C) 2008-2014 Sandro Tosi <morph@debian.org>
# Copyright (C) 2008-2016 Sandro Tosi <morph@debian.org>
#
# 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.
# 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
@@ -29,25 +29,26 @@ import getpass
from reportbug.exceptions import (
UINotImportable,
NoPackage, NoBugs, NoNetwork, NoReport,
)
)
from reportbug.urlutils import launch_browser
from text_ui import (
ewrite,
spawn_editor,
system
)
)
from reportbug import VERSION

try:
import urwid.raw_display
import urwid
except ImportError:
raise UINotImportable, 'Please install the python-urwid package to use this interface.'
raise UINotImportable('Please install the python-urwid package to use this interface.')

ISATTY = sys.stdin.isatty()

log_message = ewrite


# Start a urwid session
def initialize_urwid_ui():
ui = urwid.raw_display.Screen()
@@ -56,33 +57,38 @@ def initialize_urwid_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
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
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
self.scrollmode = True
else:
self.body = body = urwid.Filler(urwid.Divider(), 'top')

@@ -99,21 +105,21 @@ class dialog(object):

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.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) )
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'))
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' )
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):
@@ -122,12 +128,12 @@ class dialog(object):
if exitcode == '---':
# Separator is just a text label
b = urwid.Text(name)
b = urwid.AttrWrap( b, 'scrolllabel' )
b = urwid.AttrWrap(b, 'scrolllabel')
else:
b = urwid.Button( name, self.button_press )
b = urwid.Button(name, self.button_press)
b.exitcode = exitcode
b = urwid.AttrWrap( b, 'selectable','focus' )
l.append( b )
b = urwid.AttrWrap(b, 'selectable', 'focus')
l.append(b)

if vertical:
box = urwid.ListBox(l)
@@ -137,35 +143,35 @@ class dialog(object):
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 )
self.frame.footer = urwid.Pile([urwid.Divider(), self.buttons],
focus_item=1)

def button_press(self, button):
raise buttonpush, button.exitcode
raise buttonpush(button.exitcode)

def run(self):
#self.ui.set_mouse_tracking()
# 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 )
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.util.is_mouse_event(k):
event, button, col, row = k
self.view.mouse_event( size,
event, button, col, row,
focus=True)
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 )
k = self.view.keypress(size, k)
if k:
self.unhandled_key( size, k)
self.unhandled_key(size, k)
except buttonpush, e:
return self.on_exit( e.args[0] )
return self.on_exit(e.args[0])

def on_exit(self, exitcode):
return exitcode
@@ -178,7 +184,7 @@ class dialog(object):
else:
self.frame.set_focus('footer')

if k in ('up','page up', 'down', 'page down'):
if k in ('up', 'page up', 'down', 'page down'):
if self.scrollmode:
self.frame.set_focus('body')
self.body.keypress(size, k)
@@ -190,7 +196,7 @@ class dialog(object):
if k == 'enter':
# pass enter to the "ok" button
self.frame.set_focus('footer')
self.view.keypress( size, k )
self.view.keypress(size, k)

def main(self, ui=None):
if ui:
@@ -199,6 +205,7 @@ class dialog(object):
self.ui = initialize_urwid_ui()
return self.ui.run_wrapper(self.run)


class displaybox(dialog):
def show(self, ui=None):
if ui:
@@ -206,11 +213,12 @@ class displaybox(dialog):
else:
self.ui = initialize_urwid_ui()
size = self.ui.get_cols_rows()
canvas = self.view.render( size, focus=True )
canvas = self.view.render(size, focus=True)
self.ui.start()
self.ui.draw_screen( size, canvas )
self.ui.draw_screen(size, canvas)
self.ui.stop()


class textentry(dialog):
def __init__(self, text, width=None, height=None, multiline=False,
title='', edit_text=''):
@@ -218,7 +226,7 @@ class textentry(dialog):
body = urwid.ListBox([self.edit])
body = urwid.AttrWrap(body, 'selectable', 'focustext')
if not multiline:
body = urwid.Pile( [('fixed', 1, body), urwid.Divider()] )
body = urwid.Pile([('fixed', 1, body), urwid.Divider()])
body = urwid.Filler(body)

dialog.__init__(self, text, body, width, height, title)
@@ -228,6 +236,7 @@ class textentry(dialog):
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):
@@ -236,13 +245,13 @@ class listdialog(dialog):
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')
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" )
lb = urwid.AttrWrap(lb, "selectable")
dialog.__init__(self, text, height=height, width=width, body=lb,
title=title)

@@ -258,6 +267,7 @@ class listdialog(dialog):
return exitcode, i.get_label()
return exitcode, None


class checklistdialog(listdialog):
def on_exit(self, exitcode):
"""
@@ -273,6 +283,7 @@ class checklistdialog(listdialog):
l.append(i.get_label())
return exitcode, l


def display_message(message, *args, **kwargs):
if args:
message = message % tuple(args)
@@ -295,6 +306,7 @@ def display_message(message, *args, **kwargs):
box = displaybox('', long_message=message, title=title or VERSION)
box.show(ui)


def long_message(message, *args, **kwargs):
if args:
message = message % tuple(args)
@@ -315,13 +327,15 @@ def long_message(message, *args, **kwargs):
message = '\n\n'.join(chunks).strip()

box = dialog('', long_message=message, title=title or VERSION)
box.add_buttons([ ("OK", 0) ])
box.add_buttons([("OK", 0)])
box.main(ui)


final_message = long_message
display_report = long_message
display_failure = 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),
@@ -335,18 +349,20 @@ def select_options(msg, ok, help=None, allow_numbers=False, nowrap=False,
if option.isupper():
default = i
option = option.lower()
buttons.append( (help.get(option, option), option) )
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=VERSION)
box.add_buttons([ ('Yes', True), ('No', False) ], default=1-int(default))
box = dialog('', long_message=msg + "?", title=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, empty_ok=False, force_prompt=False,
default='', ui=None):
if title:
@@ -355,10 +371,11 @@ def get_string(prompt, options=None, title=None, empty_ok=False, force_prompt=Fa
title = VERSION

box = textentry(prompt, title=title, edit_text=default)
box.add_buttons([ ("OK", 0) ])
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:
@@ -367,14 +384,16 @@ def get_multiline(prompt, options=None, title=None, force_prompt=False,
title = VERSION

box = textentry(prompt, multiline=True)
box.add_buttons([ ("OK", 0) ])
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):
@@ -397,15 +416,15 @@ def menu(par, options, prompt, default=None, title=None, any_ok=False,
if order:
olist = []
for key in order:
if options.has_key(key):
olist.append( (key, options[key]) )
if key in options:
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 )
olist.append(option)
options = olist
else:
options = options.items()
@@ -425,13 +444,14 @@ def menu(par, options, prompt, default=None, title=None, any_ok=False,
box = checklistdialog(par, widgets, height=('relative', 80),
title=title)
if quitlabel:
box.add_buttons( [(oklabel, 0), (cancellabel, -1),
(quitlabel, -2)] )
box.add_buttons([(oklabel, 0), (cancellabel, -1),
(quitlabel, -2)])
else:
box.add_buttons( [(oklabel, 0), (cancellabel, -1)] )
box.add_buttons([(oklabel, 0), (cancellabel, -1)])
result, chosen = box.main(ui)
if result < 0:
return []
# We return None to differentiate a Cancel/Quit from no selection, []
return None
return chosen

# Single menu option only
@@ -444,28 +464,20 @@ def menu(par, options, prompt, default=None, title=None, any_ok=False,
if option == '---':
# Separator is just a text label
b = urwid.Text(desc)
b = urwid.AttrWrap( b, 'scrolllabel' )
b = urwid.AttrWrap(b, 'scrolllabel')
desc = ''
else:
b = urwid.RadioButton( rlist, label_button(option, desc), state=(option == default) )
b = urwid.RadioButton(rlist, label_button(option, desc), state=(option == default))
b.exitcode = option
b = urwid.AttrWrap( b, 'selectable','focus' )
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)] )
box.add_buttons([(oklabel, 0), (cancellabel, -1), (quitlabel, -2)])
else:
box.add_buttons( [(oklabel, 0), (cancellabel, -1)] )
box.add_buttons([(oklabel, 0), (cancellabel, -1)])
focus = 0
if default:
for i, opt in enumerate(options):
@@ -479,15 +491,18 @@ def menu(par, options, prompt, default=None, title=None, any_ok=False,

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, timeout, screen=None, queryonly=False, title='',
@@ -500,10 +515,10 @@ def show_report(number, system, mirrors,

sysinfo = debbugs.SYSTEMS[system]
display_message('Retrieving report #%d from %s bug tracking system...',
number, sysinfo['name'], title=title, ui=ui)
number, sysinfo['name'], title=title, ui=ui)

info = debbugs.get_report(number, timeout, system, mirrors=mirrors,
http_proxy=http_proxy, archived=archived)
http_proxy=http_proxy, archived=archived)
if not info:
long_message('Bug report #%d not found.', number, title=title, ui=ui)
return
@@ -528,6 +543,7 @@ def show_report(number, system, mirrors,
launch_browser(debbugs.get_report_url(system, number, archived))
return


def handle_bts_query(package, bts, timeout, mirrors=None, http_proxy="",
queryonly=False, screen=None, title="", archived='no',
source=False, version=None, mbox=False, buglist=None,
@@ -556,17 +572,23 @@ def handle_bts_query(package, bts, timeout, mirrors=None, http_proxy="",
else:
display_message('Querying %s bug tracking system for reports %s',
debbugs.SYSTEMS[bts]['name'],
' '.join([str(x) for x in package]), ui=ui,title=title)
' '.join([str(x) for x in package]), ui=ui, title=title)

result = None
try:
(count, sectitle, hierarchy) = debbugs.get_reports(
package, timeout, bts, mirrors=mirrors, version=version,
http_proxy=http_proxy, archived=archived, source=source)
except Exception, e:
ui.run_wrapper(nullfunc)
long_message('Unable to connect to %s BTS.', sysinfo['name'],
title=title)
raise NoBugs

try:
if not count:
ui.run_wrapper(nullfunc)
if hierarchy == None:
if hierarchy is None:
raise NoPackage
else:
raise NoBugs
@@ -579,25 +601,25 @@ def handle_bts_query(package, bts, timeout, mirrors=None, http_proxy="",
buglist = []
for (t, bugs) in hierarchy:
bcount = len(bugs)
buglist.append( ('---', t) )
buglist.append(('---', t))
buglist_tmp = {}
for bug in bugs:
# show if the bugs is already resolved
done = ''
if bug.pending == 'done':
done = ' [RESOLVED]'
buglist_tmp[bug.bug_num] = bug.subject+done
buglist_tmp[bug.bug_num] = bug.subject + done
# append the sorted list of bugs for this severity
map(buglist.append, [(str(k), buglist_tmp[k]) for k in sorted(buglist_tmp, reverse=latest_first)])