Browse Source

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

master
Sandro Tosi 7 years ago
parent
commit
b86543ed9c
  1. 2
      Makefile
  2. 73
      bin/querybts
  3. 458
      bin/reportbug
  4. 3
      debian/changelog
  5. 4
      reportbug/__init__.py
  6. 37
      reportbug/bugreport.py
  7. 36
      reportbug/checkbuildd.py
  8. 56
      reportbug/checkversions.py
  9. 619
      reportbug/debbugs.py
  10. 39
      reportbug/exceptions.py
  11. 10
      reportbug/hiermatch.py
  12. 76
      reportbug/submit.py
  13. 31
      reportbug/tempfiles.py
  14. 33
      reportbug/ui/__init__.py
  15. 1847
      reportbug/ui/gtk2_ui.py
  16. 425
      reportbug/ui/text_ui.py
  17. 206
      reportbug/ui/urwid_ui.py
  18. 78
      reportbug/urlutils.py
  19. 228
      reportbug/utils.py
  20. 2
      setup.py
  21. 6
      test/test_bugreport.py
  22. 4
      test/test_checkbuildd.py
  23. 19
      test/test_checkversions.py
  24. 157
      test/test_debbugs.py
  25. 1
      test/test_exception.py
  26. 1
      test/test_hiermatch.py
  27. 5
      test/test_tempfiles.py
  28. 1
      test/test_ui.py
  29. 6
      test/test_ui_gtk2.py
  30. 3
      test/test_urlutils.py
  31. 88
      test/test_utils.py

2
Makefile

@ -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/*

73
bin/querybts

@ -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:

458
bin/reportbug

File diff suppressed because it is too large

3
debian/changelog

@ -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

4
reportbug/__init__.py

@ -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>'

37
reportbug/bugreport.py

@ -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)

36
reportbug/checkbuildd.py

@ -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)"""

56
reportbug/checkversions.py

@ -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

619
reportbug/debbugs.py

File diff suppressed because it is too large

39
reportbug/exceptions.py

@ -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

10
reportbug/hiermatch.py

@ -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:

76
reportbug/submit.py

@ -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

31
reportbug/tempfiles.py

@ -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.

33
reportbug/ui/__init__.py

@ -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:

1847
reportbug/ui/gtk2_ui.py

File diff suppressed because it is too large

425
reportbug/ui/text_ui.py

@ -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 '