Browse Source

PEP8-fied the source code (except for "line too long", which we ignore)

tags/6.6.5
Sandro Tosi 6 years ago
parent
commit
b86543ed9c
31 changed files with 2384 additions and 2170 deletions
  1. +1
    -1
      Makefile
  2. +37
    -36
      bin/querybts
  3. +234
    -224
      bin/reportbug
  4. +2
    -1
      debian/changelog
  5. +2
    -2
      reportbug/__init__.py
  6. +19
    -18
      reportbug/bugreport.py
  7. +19
    -17
      reportbug/checkbuildd.py
  8. +33
    -23
      reportbug/checkversions.py
  9. +323
    -296
      reportbug/debbugs.py
  10. +25
    -14
      reportbug/exceptions.py
  11. +6
    -4
      reportbug/hiermatch.py
  12. +43
    -33
      reportbug/submit.py
  13. +18
    -13
      reportbug/tempfiles.py
  14. +17
    -16
      reportbug/ui/__init__.py
  15. +943
    -904
      reportbug/ui/gtk2_ui.py
  16. +220
    -205
      reportbug/ui/text_ui.py
  17. +112
    -94
      reportbug/ui/urwid_ui.py
  18. +43
    -35
      reportbug/urlutils.py
  19. +143
    -85
      reportbug/utils.py
  20. +1
    -1
      setup.py
  21. +3
    -3
      test/test_bugreport.py
  22. +2
    -2
      test/test_checkbuildd.py
  23. +9
    -10
      test/test_checkversions.py
  24. +90
    -67
      test/test_debbugs.py
  25. +1
    -0
      test/test_exception.py
  26. +1
    -0
      test/test_hiermatch.py
  27. +1
    -4
      test/test_tempfiles.py
  28. +1
    -0
      test/test_ui.py
  29. +3
    -3
      test/test_ui_gtk2.py
  30. +2
    -1
      test/test_urlutils.py
  31. +30
    -58
      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/*


+ 37
- 36
bin/querybts View File

@@ -7,19 +7,19 @@
#
# 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:


+ 234
- 224
bin/reportbug
File diff suppressed because it is too large
View File


+ 2
- 1
debian/changelog View File

@@ -16,8 +16,9 @@ reportbug (6.6.5) UNRELEASED; urgency=medium
* 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> Sun, 30 Aug 2015 02:39:06 +0100
-- Sandro Tosi <morph@debian.org> Mon, 31 Aug 2015 16:22:42 +0100

reportbug (6.6.4) unstable; urgency=medium



+ 2
- 2
reportbug/__init__.py View File

@@ -7,7 +7,7 @@
#
# 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
@@ -27,6 +27,6 @@ __all__ = ['bugreport', 'utils', 'urlutils', 'checkbuildd', 'checkversions',

VERSION_NUMBER = "6.6.4"

VERSION = "reportbug "+VERSION_NUMBER
VERSION = "reportbug " + VERSION_NUMBER
COPYRIGHT = VERSION + '\nCopyright (C) 1999-2008 Chris Lawrence <lawrencc@debian.org>' + \
'\nCopyright (C) 2008-2015 Sandro Tosi <morph@debian.org>'

+ 19
- 18
reportbug/bugreport.py View File

@@ -6,19 +6,19 @@
#
# 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)

+ 19
- 17
reportbug/checkbuildd.py View File

@@ -7,19 +7,19 @@
#
# 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)"""


+ 33
- 23
reportbug/checkversions.py View File

@@ -7,19 +7,19 @@
#
# 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')
@@ -132,9 +139,10 @@ def get_versions_available(package, timeout, dists=None, http_proxy=None, arch='

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


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


+ 25
- 14
reportbug/exceptions.py View File

@@ -5,59 +5,70 @@
#
# 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
pass

+ 6
- 4
reportbug/hiermatch.py View File

@@ -6,6 +6,7 @@
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:


+ 43
- 33
reportbug/submit.py View File

@@ -5,19 +5,19 @@
#
# 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
@@ -41,15 +41,16 @@ 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

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 +61,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 +74,33 @@ 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)


# 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 +125,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 +152,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 +174,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 +223,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,
@@ -320,7 +329,7 @@ def send_report(body, attachments, mua, fromaddr, sendto, ccaddr, bccaddr,
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)
@@ -351,7 +360,7 @@ 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

if smtphost:
@@ -369,8 +378,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):
@@ -401,8 +410,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:
@@ -451,8 +461,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


+ 18
- 13
reportbug/tempfiles.py View File

@@ -6,24 +6,25 @@
#
# 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.



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

@@ -7,19 +7,19 @@
#
# 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:


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


+ 220
- 205
reportbug/ui/text_ui.py View File

@@ -5,19 +5,19 @@
#
# 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
@@ -39,7 +39,7 @@ from reportbug import debbugs, hiermatch
from reportbug.exceptions import (
NoReport, NoPackage, NoBugs, NoNetwork,
InvalidRegex,
)
)
from reportbug.urlutils import launch_browser
import reportbug.utils

@@ -52,6 +52,7 @@ try:
except:
rows, columns = 24, 79


def ewrite(message, *args):
if not ISATTY:
return
@@ -68,6 +69,7 @@ def ewrite(message, *args):
log_message = ewrite
display_failure = ewrite


def system(cmdline):
try:
x = os.getcwd()
@@ -75,19 +77,20 @@ def system(cmdline):
os.chdir('/')
return os.system(cmdline)


def indent_wrap_text(text, starttext='', indent=0, linelen=None):
"""Wrapper for textwrap.fill to the existing API."""
if not linelen:
linelen = columns-1
linelen = columns - 1

if indent:
si = ' '*indent
si = ' ' * indent
else:
si = ''

text = ' '.join(text.split())
if not text:
return starttext+'\n'
return starttext + '\n'

output = textwrap.fill(text, width=linelen, initial_indent=starttext,
subsequent_indent=si)
@@ -104,20 +107,21 @@ if readline is not None:
except:
pass


def _launch_mbox_reader(mbox_reader_cmd, bts, bugs, number, mirrors, archived,
mbox, http_proxy, timeout):
try:
number = int(number)
if number not in bugs and 1 <= number <= len(bugs):
number = bugs[number-1]
number = bugs[number - 1]
reportbug.utils.launch_mbox_reader(mbox_reader_cmd,
debbugs.get_report_url(
bts, number, mirrors, archived, mbox), http_proxy,
timeout)
debbugs.get_report_url(bts, number, mirrors, archived, mbox),
http_proxy, timeout)
except ValueError:
ewrite('Invalid report number: %s\n',
number)


class our_completer(object):
def __init__(self, completions=None):
self.completions = None
@@ -125,7 +129,8 @@ class our_completer(object):
self.completions = tuple(map(str, completions))

def complete(self, text, i):
if not self.completions: return None
if not self.completions:
return None

matching = [x for x in self.completions if x.startswith(text)]
if i < len(matching):
@@ -133,7 +138,8 @@ class our_completer(object):
else:
return None

def our_raw_input(prompt = None, completions=None, completer=None):

def our_raw_input(prompt=None, completions=None, completer=None):
istty = sys.stdout.isatty()
if not istty:
sys.stderr.write(prompt)
@@ -157,26 +163,30 @@ def our_raw_input(prompt = None, completions=None, completer=None):
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
default = option
break

if not help: help = {}
if not help:
help = {}

if '?' not in ok: ok = ok+'?'
if '?' not in ok:
ok = ok + '?'

if nowrap:
longmsg = msg+' ['+'|'.join(ok)+']?'+' '
longmsg = msg + ' [' + '|'.join(ok) + ']?' + ' '
else:
longmsg = indent_wrap_text(msg+' ['+'|'.join(ok)+']?').strip()+' '
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):
while ch and ch[0] == '#':
ch = ch[1:]
if isinstance(allow_numbers, int):
try:
return str(int(ch))
except ValueError:
@@ -189,14 +199,15 @@ def select_options(msg, ok, help, allow_numbers=None, nowrap=False):
else:
nums = list(allow_numbers)
nums.sort()
err_message = 'Only the following entries are allowed: '+\
err_message = 'Only the following entries are allowed: ' + \
', '.join(map(str, nums))
except (ValueError, TypeError):
pass

if not ch: ch = default
if not ch:
ch = default
ch = ch[0]
if ch=='?':
if ch == '?':
help['?'] = 'Display this help.'
for ch in ok:
if ch in string.ascii_uppercase:
@@ -205,7 +216,7 @@ def select_options(msg, ok, help, allow_numbers=None, nowrap=False):
desc = ''
desc += help.get(ch, help.get(ch.lower(),
'No help for this option.'))
ewrite(indent_wrap_text(desc+'\n', '%s - '% ch, 4))
ewrite(indent_wrap_text(desc + '\n', '%s - ' % ch, 4))
return select_options(msg, ok, help, allow_numbers, nowrap)
elif (ch.lower() in ok) or (ch.upper() in ok):
return ch.lower()
@@ -216,6 +227,7 @@ def select_options(msg, ok, help, allow_numbers=None, nowrap=False):

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:
@@ -223,12 +235,13 @@ def yes_no(msg, yeshelp, nohelp, default=True, nowrap=False):
else:
ok = 'yNq'

res = select_options(msg, ok, {'y': yeshelp, 'n': nohelp, 'q' : 'Quit.'},
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))
@@ -237,9 +250,10 @@ def long_message(text, *args):

final_message = long_message


def get_string(prompt, options=None, title=None, empty_ok=False, force_prompt=False,
default='', completer=None):
if prompt and (len(prompt) < 2*columns/3) and not force_prompt:
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
@@ -259,6 +273,7 @@ def get_string(prompt, options=None, title=None, empty_ok=False, force_prompt=Fa

return response


def get_multiline(prompt):
ewrite('\n')
ewrite(indent_wrap_text(prompt + " Press ENTER on a blank line to continue.\n"))
@@ -271,31 +286,37 @@ def get_multiline(prompt):
ewrite('\n')
return l


def get_password(prompt=None):
return getpass.getpass(prompt)


def FilenameCompleter(text, i):
text = os.path.expanduser(text)
text = os.path.expandvars(text)
paths = glob.glob(text+'*')
if not paths: return None
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 + '/'
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 = {}
@@ -306,9 +327,9 @@ def menu(par, options, prompt, default=None, title=None, any_ok=False,
extras = list(extras)

if title:
ewrite(title+'\n\n')
ewrite(title + '\n\n')

ewrite(indent_wrap_text(par, linelen=columns)+'\n')
ewrite(indent_wrap_text(par, linelen=columns) + '\n')

if isinstance(options, dict):
options = options.copy()
@@ -316,40 +337,39 @@ 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()
options.sort()

if multiple:
options.append( ('none', '') )
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)))
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)))
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
i += 1
if len(options) >= 5:
ewrite('\n')

@@ -363,12 +383,13 @@ def menu(par, options, prompt, default=None, title=None, any_ok=False,
aprompt = prompt

response = our_raw_input(aprompt, allowed)
if not response: response = default
if not response:
response = default

try:
num = int(response)
if 1 <= num <= len(options):
response = options[num-1][0]
response = options[num - 1][0]
except (ValueError, TypeError):
pass

@@ -381,7 +402,7 @@ def menu(par, options, prompt, default=None, title=None, any_ok=False,
elif selected.get(response):
del selected[response]
else:
selected[response]=1
selected[response] = 1
ewrite('- selected: %s\n' % ', '.join(selected.keys()))
if len(selected):
default = 'done'
@@ -399,6 +420,7 @@ def menu(par, options, prompt, default=None, title=None, any_ok=False,
ewrite('Invalid entry.\n')
return


# Things that are very UI dependent go here
def show_report(number, system, mirrors,
http_proxy, timeout, screen=None, queryonly=False, title='',
@@ -409,8 +431,7 @@ def show_report(number, system, mirrors,

try:
info = debbugs.get_report(number, timeout, system, mirrors=mirrors,
followups=1,
http_proxy=http_proxy, archived=archived)
followups=1, http_proxy=http_proxy, archived=archived)
except:
info = None

@@ -431,7 +452,7 @@ def show_report(number, system, mirrors,
while 1:
if current_message:
text = 'Followup %d - %s\n\n%s' % (current_message, buginfo.subject,
messages[current_message])
messages[current_message])
else:
text = 'Original report - %s\n\n%s' % (buginfo.subject, messages[0])

@@ -449,23 +470,21 @@ def show_report(number, system, mirrors,

options = 'xOrbeq'

if (current_message+1) < len(messages):
options = 'N'+options.lower()
if (current_message + 1) < len(messages):
options = 'N' + options.lower()
if (current_message):
options = 'p'+options
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.',
'e' : 'Launch e-mail client to read full log.',
'b' : 'Launch web browser to read '
'full log.',
'q' : "I'm bored; quit please."},
allow_numbers = range(1, len(messages)+1))
{'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.',
'e': 'Launch e-mail client to read full log.',
'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 buginfo
elif x == 'q':
@@ -476,9 +495,8 @@ def show_report(number, system, mirrors,
skip_pager = True
elif x == 'e':
reportbug.utils.launch_mbox_reader(mbox_reader_cmd,
debbugs.get_report_url(
system, number, mirrors, archived, True), http_proxy,
timeout)
debbugs.get_report_url(system, number, mirrors, archived, True),
http_proxy, timeout)
skip_pager = True
elif x == 'o':
break
@@ -488,6 +506,7 @@ def show_report(number, system, mirrors,
current_message -= 1
return


def handle_bts_query(package, bts, timeout, mirrors=None, http_proxy="",
queryonly=False, title="", screen=None, archived='no',
source=False, version=None, mbox=False, buglist=None,
@@ -512,13 +531,13 @@ def handle_bts_query(package, bts, timeout, mirrors=None, http_proxy="",

bugs = []
try:
(count, title, hierarchy)=debbugs.get_reports(
(count, title, hierarchy) = debbugs.get_reports(
package, timeout, bts, mirrors=mirrors, version=version,
source=source, http_proxy=http_proxy, archived=archived)

# If there's no report, then skip all the rest
if not count:
if hierarchy == None:
if hierarchy is None:
raise NoPackage
else:
raise NoBugs
@@ -536,7 +555,7 @@ def handle_bts_query(package, bts, timeout, mirrors=None, http_proxy="",
for entry in hierarchy:
# second item is a list of bugs report
for bug in entry[1]:
msg = "#%d %s" %(bug.bug_num, bug.subject)
msg = "#%d %s" % (bug.bug_num, bug.subject)
msg = msg.encode(charset, 'replace')
print msg
sys.exit(0)
@@ -567,7 +586,7 @@ def handle_bts_query(package, bts, timeout, mirrors=None, http_proxy="",
hierarchy = hierarchy_new

if not count:
if hierarchy == None:
if hierarchy is None:
raise NoPackage
else:
raise NoBugs
@@ -591,6 +610,7 @@ def handle_bts_query(package, bts, timeout, mirrors=None, http_proxy="",
long_message('No record of this package found.')
raise NoPackage


def browse_bugs(hierarchy, count, bugs, bts, queryonly, mirrors,
http_proxy, timeout, screen, title, package, mbox_reader_cmd):
try:
@@ -603,65 +623,65 @@ def browse_bugs(hierarchy, count, bugs, bts, queryonly, mirrors,
category = hierarchy[0]
lastpage = []
digits = len(str(len(bugs)))
linefmt = ' %'+str(digits)+'d) %s\n'
linefmt = ' %' + str(digits) + 'd) %s\n'
while category:
scount = scount + 1
scount += 1
catname, reports = category[0:2]
while catname.endswith(':'): catname = catname[:-1]
while catname.endswith(':'):
catname = catname[:-1]
total = len(reports)

while len(reports):
these = reports[:rows-2]
reports = reports[rows-2:]
these = reports[:rows - 2]
reports = reports[rows - 2:]
remain = len(reports)

tplural = rplural = 's'
if total == 1: tplural=''
if remain != 1: rplural=''
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')
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]))
lastpage.append(linefmt % (endcount, report[:columns - digits - 5]))

if category == hierarchy[-1] or \
(scount >= (rows - len(hierarchy[catcount+1][1]) - 1)):
(scount >= (rows - len(hierarchy[catcount + 1][1]) - 1)):
skipmsg = ' (s to skip rest)'
if endcount == count:
skipmsg = ''

options = 'yNbmrqsfe'
if queryonly: options = 'Nbmrqfe'
if queryonly:
options = 'Nbmrqfe'

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)
allowed = bugs + range(1, count + 1)
helptext = {
'y' : 'Problem already reported; optionally '
'add extra information.',
'n' : 'Problem not listed above; possibly '
'check more.',
'b' : 'Open the complete bugs list in a web browser.',
'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.',
'e' : 'Open the report using an e-mail client.',
'f' : 'Filter bug list using a pattern.'}
'y': 'Problem already reported; optionally add extra information.',
'n': 'Problem not listed above; possibly check more.',
'b': 'Open the complete bugs list in a web browser.',
'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.',
'e': 'Open the report using an e-mail client.',
'f': 'Filter bug list using a pattern.'}
if skipmsg:
helptext['n'] = helptext['n'][:-1]+' (skip to Next page).'
helptext['n'] = helptext['n'][:-1] + ' (skip to Next page).'

while 1:
for line in lastpage:
@@ -692,32 +712,28 @@ def browse_bugs(hierarchy, count, bugs, bts, queryonly, mirrors,
'you want to give more info on,\n'
'or press ENTER to exit: #', allowed)
while number and number[0] == '#':
number=number[1:]
number = number[1:]
if number:
try:
number = int(number)
if number not in bugs and 1 <= number <= len(bugs):
number = bugs[number-1]
number = bugs[number - 1]
return debbugs.get_report(number, timeout)[0]
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, timeout, screen, title,
elif x == 'f':
# Do filter. Recursive done.
retval = search_bugs(hierarchy, bts, queryonly, mirrors, http_proxy, timeout, screen, title,
package, mbox_reader_cmd)
if isinstance(retval, basestring) and \
retval in ["FilterEnd", "Top"]:
continue
else:
return retval
if isinstance(retval, basestring) and retval in ["FilterEnd", "Top"]:
continue
else:
return retval
elif x == 'e':
number = our_raw_input('Please enter the number of the '
'bug you would like to view: #',
allowed)
number = our_raw_input('Please enter the number of the bug you would like to view: #', allowed)
_launch_mbox_reader(mbox_reader_cmd, bts, bugs, number,
mirrors, 'no', True, http_proxy,
timeout)
@@ -726,21 +742,19 @@ def browse_bugs(hierarchy, count, bugs, bts, queryonly, mirrors,
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)
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:]
number = number[1:]

if number:
try:
number = int(number)
if number not in bugs and 1 <= number <= len(bugs):
number = bugs[number-1]
number = bugs[number - 1]
res = show_report(number, bts, mirrors,
http_proxy, timeout,
queryonly=queryonly,
@@ -753,38 +767,41 @@ def browse_bugs(hierarchy, count, bugs, bts, queryonly, mirrors,
ewrite('Invalid report number: %s\n',
number)

startcount = endcount+1
scount = 0
startcount = endcount + 1
scount = 0

# these now empty

if category == hierarchy[-1]: break
if category == hierarchy[-1]:
break

catcount = catcount+1
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)
count = reduce(lambda x, y: x + y, lenlist)
else:
return 0, 0
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)))
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, timeout, screen, title, package, mbox_reader_cmd):
"""Search for the bug list using a pattern."""
@@ -796,48 +813,50 @@ def search_bugs(hierarchyfull, bts, queryonly, mirrors,
print msg
sys.exit(1)

pattern = our_raw_input(
'Enter the search pattern (a Perl-compatible regular expression)\n'
'or press ENTER to exit: ')
pattern = our_raw_input('Enter the search pattern (a Perl-compatible regular expression)\n'
'or press ENTER to exit: ')
if not pattern:
return "FilterEnd"
return "FilterEnd"

" Create new hierarchy match the pattern."
# 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"
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"
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'
linefmt = ' %' + str(digits) + 'd) %s\n'
# XXX: it's kinda non-sense to replicate all this code here!!! it's the same
# as of browse_report!
while category:
scount = scount + 1
catname, reports = category[0:2]
while catname.endswith(':'): catname = catname[:-1]
while catname.endswith(':'):
catname = catname[:-1]
total = len(reports)

while len(reports):
these = reports[:rows-2]
reports = reports[rows-2:]
these = reports[:rows - 2]
reports = reports[rows - 2:]
remain = len(reports)

tplural = rplural = 's'
if total == 1: tplural=''
if remain != 1: rplural=''
if total == 1:
tplural = ''
if remain != 1:
rplural = ''

if remain:
lastpage.append(' %s: %d report%s (%d remain%s)\n' %
@@ -850,41 +869,38 @@ def search_bugs(hierarchyfull, bts, queryonly, mirrors,
for report in these:
scount = scount + 1
endcount = endcount + 1
lastpage.append(linefmt % (endcount,report[:columns-digits-5]))
lastpage.append(linefmt % (endcount, report[:columns - digits - 5]))

if category == hierarchy[-1] or \
(scount >= (rows - len(hierarchy[catcount+1][1]) - 1)):
(scount >= (rows - len(hierarchy[catcount + 1][1]) - 1)):
skipmsg = ' (s to skip rest)'
if endcount == count:
skipmsg = ''

options = 'yNbmrqsfute'
if queryonly: options = 'Nmbrqfute'
if queryonly:
options = 'Nmbrqfute'

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)
allowed = bugs + range(1, count + 1)
helptext = {
'y' : 'Problem already reported; optionally '
'add extra information.',
'n' : 'Problem not listed above; possibly '
'check more.',
'b' : 'Open the complete bugs list in a web browser.',
'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.',
'e' : 'Open the report using an e-mail client.',
't' : 'Top of the bug list (remove all filters).'}
'y': 'Problem already reported; optionally add extra information.',
'n': 'Problem not listed above; possibly check more.',
'b': 'Open the complete bugs list in a web browser.',
'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.',
'e': 'Open the report using an e-mail client.',
't': 'Top of the bug list (remove all filters).'}
if skipmsg:
helptext['n'] = helptext['n'][:-1]+' (skip to Next page).'
helptext['n'] = helptext['n'][:-1] + ' (skip to Next page).'

while 1:
for line in lastpage:
@@ -914,40 +930,37 @@ def search_bugs(hierarchyfull, bts, queryonly, mirrors,
'you want to give more info on,\n'
'or press ENTER to exit: #', allowed)
while number and number[0] == '#':
number=number[1:]
number = number[1:]
if number:
try:
number = int(number)
if number not in bugs and 1 <= number <= len(bugs):
number = bugs[number-1]
number = bugs[number - 1]
return debbugs.get_report(number, timeout)[0]
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, timeout, screen, title, package,
mbox_reader_cmd)
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"
elif x == 'f':
# Do filter. Recursive done.
retval = search_bugs(hierarchy, bts, queryonly, mirrors, http_proxy, timeout, screen,
title, package, mbox_reader_cmd)
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"
elif x == 'e':
number = our_raw_input('Please enter the number of the '
'bug you would like to view: #',
allowed)
'bug you would like to view: #', allowed)
_launch_mbox_reader(mbox_reader_cmd, bts, bugs, number,
mirrors, 'no', True, http_proxy,
timeout)
mirrors, 'no', True, http_proxy, timeout)
else:
if x == 'm' or x == 'i':
number = our_raw_input(
@@ -958,13 +971,13 @@ def search_bugs(hierarchyfull, bts, queryonly, mirrors,
number = x

while number and number[0] == '#':
number=number[1:]
number = number[1:]

if number:
try:
number = int(number)
if number not in bugs and 1 <= number <= len(bugs):
number = bugs[number-1]
number = bugs[number - 1]
res = show_report(number, bts, mirrors,
http_proxy, timeout,
queryonly=queryonly,
@@ -973,23 +986,24 @@ def search_bugs(hierarchyfull, bts, queryonly, mirrors,
if res:
return res
except ValueError:
ewrite('Invalid report number: %s\n',
number)