[Reportbug-commits] r641 - in trunk (11 files)
morph-guest at users.alioth.debian.org
morph-guest at users.alioth.debian.org
Sun Aug 31 22:54:17 UTC 2008
Date: Sunday, August 31, 2008 @ 22:54:15
Author: morph-guest
Revision: 641
renamed ui files to let urwid work
Added:
trunk/reportbug/ui/newt_ui.py
(from rev 637, trunk/reportbug/ui/newt.py)
trunk/reportbug/ui/text_ui.py
(from rev 640, trunk/reportbug/ui/text.py)
trunk/reportbug/ui/urwid_ui.py
(from rev 640, trunk/reportbug/ui/urwid.py)
Modified:
trunk/bin/querybts
trunk/bin/reportbug
trunk/debian/changelog
trunk/reportbug/submit.py
trunk/reportbug/utils.py
Deleted:
trunk/reportbug/ui/newt.py
trunk/reportbug/ui/text.py
trunk/reportbug/ui/urwid.py
Modified: trunk/bin/querybts
===================================================================
--- trunk/bin/querybts 2008-08-31 20:45:18 UTC (rev 640)
+++ trunk/bin/querybts 2008-08-31 22:54:15 UTC (rev 641)
@@ -169,7 +169,7 @@
if interface:
global ui, ui_mode
- iface = '%(interface)s' % vars()
+ iface = '%(interface)s_ui' % vars()
try:
lib_package = __import__('reportbug.ui', fromlist=[iface])
ui = getattr(lib_package, iface)
Modified: trunk/bin/reportbug
===================================================================
--- trunk/bin/reportbug 2008-08-31 20:45:18 UTC (rev 640)
+++ trunk/bin/reportbug 2008-08-31 22:54:15 UTC (rev 641)
@@ -58,7 +58,7 @@
from reportbug import checkversions
from reportbug import debianbts
from reportbug import checkbuildd
-import reportbug.ui.text as ui
+import reportbug.ui.text_ui as ui
try:
gettext.install('reportbug')
@@ -830,7 +830,7 @@
"indicate problems with this interface.\n",
interface)
- iface = '%(interface)s' % vars()
+ iface = '%(interface)s_ui' % vars()
try:
lib_package = __import__('reportbug.ui', fromlist=[iface])
ui = getattr(lib_package, iface)
Modified: trunk/debian/changelog
===================================================================
--- trunk/debian/changelog 2008-08-31 20:45:18 UTC (rev 640)
+++ trunk/debian/changelog 2008-08-31 22:54:15 UTC (rev 641)
@@ -18,8 +18,16 @@
+ replaced relative imports with absolute ones
+ removed reportbug.utils import to avoid circular import (and because
it's not needed)
+ - reportbug/ui/{newt.py,text.py,urwid.py}
+ + renamed to <file>_ui.py because from <file>.py you cannot import <file>
+ (urwid UI had this problem)
+ - reportbug/{submit.py,utils.py}, bin/{querybts,reportbug}
+ + fixed for ui file rename
+ - reportbug/ui/text_ui.py
+ + disabled utils.NEWBIELINE check to avoid circular import between text
+ and utils ******** WE NEED TO FIND A SOLUTION TO THIS **********
- -- Sandro Tosi <matrixhasu at gmail.com> Sun, 31 Aug 2008 22:38:39 +0200
+ -- Sandro Tosi <matrixhasu at gmail.com> Mon, 01 Sep 2008 00:45:25 +0200
reportbug (3.45) unstable; urgency=low
Modified: trunk/reportbug/submit.py
===================================================================
--- trunk/reportbug/submit.py 2008-08-31 20:45:18 UTC (rev 640)
+++ trunk/reportbug/submit.py 2008-08-31 22:54:15 UTC (rev 641)
@@ -45,7 +45,7 @@
from exceptions import (
NoMessage,
)
-import ui.text as ui
+import ui.text_ui as ui
quietly = False
Deleted: trunk/reportbug/ui/newt.py
===================================================================
--- trunk/reportbug/ui/newt.py 2008-08-31 20:45:18 UTC (rev 640)
+++ trunk/reportbug/ui/newt.py 2008-08-31 22:54:15 UTC (rev 641)
@@ -1,300 +0,0 @@
-# Newt user interface for reportbug
-# Written by Chris Lawrence <lawrencc at debian.org>
-# (C) 2001-06 Chris Lawrence
-#
-# This program is freely distributable per the following license:
-#
-## Permission to use, copy, modify, and distribute this software and its
-## documentation for any purpose and without fee is hereby granted,
-## provided that the above copyright notice appears in all copies and that
-## both that copyright notice and this permission notice appear in
-## supporting documentation.
-##
-## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
-## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
-## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
-## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
-## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
-## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
-## SOFTWARE.
-#
-# $Id: reportbug_ui_newt.py,v 1.5.2.4 2007-04-17 19:42:56 lawrencc Exp $
-
-import sys
-import commands
-import string
-import debianbts
-from reportbug_exceptions import (
- UINotImportable,
- NoPackage, NoBugs, NoNetwork,
- )
-from urlutils import launch_browser
-
-try:
- import snack
-except ImportError:
- raise UINotImportable, 'Please install the python-newt package to use this interface.'
-
-ISATTY = sys.stdin.isatty()
-
-try:
- r, c = string.split(commands.getoutput('stty size'))
- rows, columns = int(r) or 24, int(c) or 79
-except:
- rows, columns = 24, 79
-
-def ewrite(message, *args):
- # ewrite shouldn't do anything on newt... maybe should log to a file
- # if specified.
- pass
-
-log_message = ewrite
-display_failure = ewrite
-
-# Utility functions for common newt dialogs
-def newt_screen():
- "Start a newt windowing session."
- return snack.SnackScreen()
-
-def newt_infobox(text, height=6, width=50, title="", screen=None):
- "Display a message and go on."
- if not screen:
- s = snack.SnackScreen()
- else:
- s = screen
-
- t = snack.TextboxReflowed(width, text, maxHeight = s.height - 12)
- g = snack.GridForm(s, title[:width], 1, 2)
- g.add(t, 0, 0, padding = (0, 0, 0, 1))
- g.draw()
- s.refresh()
- if not screen:
- s.finish()
-
-def newt_dialog(text, buttons=('Ok', 'Cancel'), width=50,
- title="", screen=None):
- "Display a message and wait for a response from the user."
- if not screen:
- s = snack.SnackScreen()
- else:
- s = screen
-
- selected = snack.ButtonChoiceWindow(s, title[:width], text, buttons,
- width=width)
-
- if not screen:
- s.finish()
- return selected
-
-def newt_msgbox(text, width=50, title="", screen=None):
- "Display a message and wait for an OK from the user."
- return newt_dialog(text, ['Ok'], width, title, screen)
-
-def long_message(message, *args):
- if args:
- message = message % tuple(args)
- newt_msgbox(message)
-
-final_message = long_message
-
-def newt_menu(text, height=20, width=60, menuheight=15, menu=None,
- title="", scroll=0, screen=None, startpos=1):
- "Display a menu of choices for the user."
- if not menu: return None
- if not screen:
- s = snack.SnackScreen()
- else:
- s = screen
- items = []
- for item in menu:
- if item[0]:
- items.append(('%6s: %s' % item)[:width])
- else:
- items.append(item[1])
-
- if len(items) > menuheight: scroll=1
- res = startpos
- while 1:
- button, res = snack.ListboxChoiceWindow(s, title[:width], text, items,
- width=width, height=menuheight,
- scroll=scroll, default=res,
- buttons=('View', 'Quit'))
- if button == 'quit':
- if not screen:
- s.finish()
- return None
- elif menu[res][0]:
- selected = menu[res][0]
- break
-
- if not screen:
- s.finish()
- return selected
-
-# XXX - From here on out needs to be rewritten for newt
-def select_options(msg, ok, help=None, allow_numbers=0):
- return None
-
-def get_string(prompt, options=None, title=None, force_prompt=0):
- return None
-
-def get_multiline(prompt, options=None, title=None, force_prompt=0):
- return None
-
-def menu(par, options, prompt, default=None, title=None, any_ok=0, order=None):
- return None
-
-# Things that are very UI dependent go here
-def show_report(number, system, mirrors, http_proxy, screen=None, queryonly=0,
- title='', archived='no'):
- s = screen
- if not s:
- s = newt_screen()
-
- sysinfo = debianbts.SYSTEMS[system]
- newt_infobox('Retrieving report #%d from %s bug tracking system...' % (
- number, sysinfo['name']), title=title, screen=s)
-
- width = columns-8
- info = debianbts.get_report(number, system, mirrors=mirrors,
- http_proxy=http_proxy, archived=archived)
- if not info:
- s.popWindow()
- newt_msgbox('Bug report #%d not found.' % number,
- screen=s, title=title)
- if not screen:
- s.finish()
- return
-
- buttons = ['Ok', 'More details (launch browser)', 'Quit']
- if not queryonly:
- buttons.append('Submit more information')
-
- s.popWindow()
- while 1:
- (bugtitle, bodies) = info
- body = bodies[0]
-
- lines = string.split(body, '\n')
- lines = map(lambda x, y=width: x[:y], lines)
- body = string.join(lines, '\n')
-
- r = newt_dialog(text=body, title=bugtitle, screen=s, width=width,
- buttons=buttons)
- if not r or (r == 'ok'):
- break
- elif r == 'quit':
- if not screen:
- s.finish()
- return -1
- elif r == 'submit more information':
- if not screen:
- s.finish()
- return number
-
- s.suspend()
- # print chr(27)+'c'
- # os.system('stty sane; clear')
- launch_browser(debianbts.get_report_url(system, number, archived))
- s.resume()
-
- if not screen:
- s.finish()
- return
-
-def handle_bts_query(package, bts, mirrors=None, http_proxy="",
- queryonly=0, screen=None, title="", archived='no',
- source=0):
- sysinfo = debianbts.SYSTEMS[bts]
- root = sysinfo.get('btsroot')
- if not root:
- ewrite("%s bug tracking system has no web URL; bypassing query.\n",
- sysinfo['name'])
- return
-
- scr = screen
- if not scr:
- scr = newt_screen()
-
- if isinstance(package, basestring):
- if source:
- newt_infobox('Querying %s bug tracking system for reports on'
- ' src:%s\n' % (debianbts.SYSTEMS[bts]['name'],
- package),
- screen=scr, title=title)
- else:
- newt_infobox('Querying %s bug tracking system for reports on %s\n'%
- (debianbts.SYSTEMS[bts]['name'], package),
- screen=scr, title=title)
- else:
- newt_infobox('Querying %s bug tracking system for reports %s\n' %
- (debianbts.SYSTEMS[bts]['name'], ' '.join([str(x) for x in
- package])),
- screen=scr, title=title)
-
- result = None
- try:
- (count, sectitle, hierarchy) = debianbts.get_reports(
- package, bts, mirrors=mirrors,
- http_proxy=http_proxy, archived=archived, source=source)
-
- if not count:
- scr.popWindow()
- if hierarchy == None:
- raise NoPackage
- else:
- raise NoBugs
- else:
- if count > 1:
- sectitle = '%d bug reports found' % (count)
- else:
- sectitle = '%d bug report found' % (count)
-
- list = []
- for (t, bugs) in hierarchy:
- bcount = len(bugs)
- list.append( ('', t) )
- for bug in bugs:
- bits = string.split(bug[1:], ':', 1)
- tag, info = bits
- info = string.strip(info)
- if not info:
- info = '(no subject)'
- list.append( (tag, info) )
-
- p = 1
- scr.popWindow()
- while True:
- info = newt_menu('Select a bug to read the report:', rows-6,
- columns-10, rows-15, list, sectitle,
- startpos=p, screen=scr)
- if not info:
- break
- else:
- p = i = 0
- for (number, subject) in list:
- if number == info: p = i
- i += 1
-
- if ' ' in info:
- info, blah = info.split(' ', 1)
-
- res = show_report(int(info), bts, mirrors,
- http_proxy, screen=scr,
- queryonly=queryonly, title=title)
- if res:
- result = res
- break
-
- except (IOError, NoNetwork):
- scr.popWindow()
- newt_msgbox('Unable to connect to %s BTS.' % sysinfo['name'],
- screen=scr, title=title)
- except NoPackage:
- #scr.popWindow()
- newt_msgbox('No record of this package found.',
- screen=scr, title=title)
-
- if not screen:
- scr.finish()
- return result
Copied: trunk/reportbug/ui/newt_ui.py (from rev 637, trunk/reportbug/ui/newt.py)
===================================================================
--- trunk/reportbug/ui/newt_ui.py (rev 0)
+++ trunk/reportbug/ui/newt_ui.py 2008-08-31 22:54:15 UTC (rev 641)
@@ -0,0 +1,300 @@
+# Newt user interface for reportbug
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 2001-06 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# $Id: reportbug_ui_newt.py,v 1.5.2.4 2007-04-17 19:42:56 lawrencc Exp $
+
+import sys
+import commands
+import string
+import debianbts
+from reportbug_exceptions import (
+ UINotImportable,
+ NoPackage, NoBugs, NoNetwork,
+ )
+from urlutils import launch_browser
+
+try:
+ import snack
+except ImportError:
+ raise UINotImportable, 'Please install the python-newt package to use this interface.'
+
+ISATTY = sys.stdin.isatty()
+
+try:
+ r, c = string.split(commands.getoutput('stty size'))
+ rows, columns = int(r) or 24, int(c) or 79
+except:
+ rows, columns = 24, 79
+
+def ewrite(message, *args):
+ # ewrite shouldn't do anything on newt... maybe should log to a file
+ # if specified.
+ pass
+
+log_message = ewrite
+display_failure = ewrite
+
+# Utility functions for common newt dialogs
+def newt_screen():
+ "Start a newt windowing session."
+ return snack.SnackScreen()
+
+def newt_infobox(text, height=6, width=50, title="", screen=None):
+ "Display a message and go on."
+ if not screen:
+ s = snack.SnackScreen()
+ else:
+ s = screen
+
+ t = snack.TextboxReflowed(width, text, maxHeight = s.height - 12)
+ g = snack.GridForm(s, title[:width], 1, 2)
+ g.add(t, 0, 0, padding = (0, 0, 0, 1))
+ g.draw()
+ s.refresh()
+ if not screen:
+ s.finish()
+
+def newt_dialog(text, buttons=('Ok', 'Cancel'), width=50,
+ title="", screen=None):
+ "Display a message and wait for a response from the user."
+ if not screen:
+ s = snack.SnackScreen()
+ else:
+ s = screen
+
+ selected = snack.ButtonChoiceWindow(s, title[:width], text, buttons,
+ width=width)
+
+ if not screen:
+ s.finish()
+ return selected
+
+def newt_msgbox(text, width=50, title="", screen=None):
+ "Display a message and wait for an OK from the user."
+ return newt_dialog(text, ['Ok'], width, title, screen)
+
+def long_message(message, *args):
+ if args:
+ message = message % tuple(args)
+ newt_msgbox(message)
+
+final_message = long_message
+
+def newt_menu(text, height=20, width=60, menuheight=15, menu=None,
+ title="", scroll=0, screen=None, startpos=1):
+ "Display a menu of choices for the user."
+ if not menu: return None
+ if not screen:
+ s = snack.SnackScreen()
+ else:
+ s = screen
+ items = []
+ for item in menu:
+ if item[0]:
+ items.append(('%6s: %s' % item)[:width])
+ else:
+ items.append(item[1])
+
+ if len(items) > menuheight: scroll=1
+ res = startpos
+ while 1:
+ button, res = snack.ListboxChoiceWindow(s, title[:width], text, items,
+ width=width, height=menuheight,
+ scroll=scroll, default=res,
+ buttons=('View', 'Quit'))
+ if button == 'quit':
+ if not screen:
+ s.finish()
+ return None
+ elif menu[res][0]:
+ selected = menu[res][0]
+ break
+
+ if not screen:
+ s.finish()
+ return selected
+
+# XXX - From here on out needs to be rewritten for newt
+def select_options(msg, ok, help=None, allow_numbers=0):
+ return None
+
+def get_string(prompt, options=None, title=None, force_prompt=0):
+ return None
+
+def get_multiline(prompt, options=None, title=None, force_prompt=0):
+ return None
+
+def menu(par, options, prompt, default=None, title=None, any_ok=0, order=None):
+ return None
+
+# Things that are very UI dependent go here
+def show_report(number, system, mirrors, http_proxy, screen=None, queryonly=0,
+ title='', archived='no'):
+ s = screen
+ if not s:
+ s = newt_screen()
+
+ sysinfo = debianbts.SYSTEMS[system]
+ newt_infobox('Retrieving report #%d from %s bug tracking system...' % (
+ number, sysinfo['name']), title=title, screen=s)
+
+ width = columns-8
+ info = debianbts.get_report(number, system, mirrors=mirrors,
+ http_proxy=http_proxy, archived=archived)
+ if not info:
+ s.popWindow()
+ newt_msgbox('Bug report #%d not found.' % number,
+ screen=s, title=title)
+ if not screen:
+ s.finish()
+ return
+
+ buttons = ['Ok', 'More details (launch browser)', 'Quit']
+ if not queryonly:
+ buttons.append('Submit more information')
+
+ s.popWindow()
+ while 1:
+ (bugtitle, bodies) = info
+ body = bodies[0]
+
+ lines = string.split(body, '\n')
+ lines = map(lambda x, y=width: x[:y], lines)
+ body = string.join(lines, '\n')
+
+ r = newt_dialog(text=body, title=bugtitle, screen=s, width=width,
+ buttons=buttons)
+ if not r or (r == 'ok'):
+ break
+ elif r == 'quit':
+ if not screen:
+ s.finish()
+ return -1
+ elif r == 'submit more information':
+ if not screen:
+ s.finish()
+ return number
+
+ s.suspend()
+ # print chr(27)+'c'
+ # os.system('stty sane; clear')
+ launch_browser(debianbts.get_report_url(system, number, archived))
+ s.resume()
+
+ if not screen:
+ s.finish()
+ return
+
+def handle_bts_query(package, bts, mirrors=None, http_proxy="",
+ queryonly=0, screen=None, title="", archived='no',
+ source=0):
+ sysinfo = debianbts.SYSTEMS[bts]
+ root = sysinfo.get('btsroot')
+ if not root:
+ ewrite("%s bug tracking system has no web URL; bypassing query.\n",
+ sysinfo['name'])
+ return
+
+ scr = screen
+ if not scr:
+ scr = newt_screen()
+
+ if isinstance(package, basestring):
+ if source:
+ newt_infobox('Querying %s bug tracking system for reports on'
+ ' src:%s\n' % (debianbts.SYSTEMS[bts]['name'],
+ package),
+ screen=scr, title=title)
+ else:
+ newt_infobox('Querying %s bug tracking system for reports on %s\n'%
+ (debianbts.SYSTEMS[bts]['name'], package),
+ screen=scr, title=title)
+ else:
+ newt_infobox('Querying %s bug tracking system for reports %s\n' %
+ (debianbts.SYSTEMS[bts]['name'], ' '.join([str(x) for x in
+ package])),
+ screen=scr, title=title)
+
+ result = None
+ try:
+ (count, sectitle, hierarchy) = debianbts.get_reports(
+ package, bts, mirrors=mirrors,
+ http_proxy=http_proxy, archived=archived, source=source)
+
+ if not count:
+ scr.popWindow()
+ if hierarchy == None:
+ raise NoPackage
+ else:
+ raise NoBugs
+ else:
+ if count > 1:
+ sectitle = '%d bug reports found' % (count)
+ else:
+ sectitle = '%d bug report found' % (count)
+
+ list = []
+ for (t, bugs) in hierarchy:
+ bcount = len(bugs)
+ list.append( ('', t) )
+ for bug in bugs:
+ bits = string.split(bug[1:], ':', 1)
+ tag, info = bits
+ info = string.strip(info)
+ if not info:
+ info = '(no subject)'
+ list.append( (tag, info) )
+
+ p = 1
+ scr.popWindow()
+ while True:
+ info = newt_menu('Select a bug to read the report:', rows-6,
+ columns-10, rows-15, list, sectitle,
+ startpos=p, screen=scr)
+ if not info:
+ break
+ else:
+ p = i = 0
+ for (number, subject) in list:
+ if number == info: p = i
+ i += 1
+
+ if ' ' in info:
+ info, blah = info.split(' ', 1)
+
+ res = show_report(int(info), bts, mirrors,
+ http_proxy, screen=scr,
+ queryonly=queryonly, title=title)
+ if res:
+ result = res
+ break
+
+ except (IOError, NoNetwork):
+ scr.popWindow()
+ newt_msgbox('Unable to connect to %s BTS.' % sysinfo['name'],
+ screen=scr, title=title)
+ except NoPackage:
+ #scr.popWindow()
+ newt_msgbox('No record of this package found.',
+ screen=scr, title=title)
+
+ if not screen:
+ scr.finish()
+ return result
Deleted: trunk/reportbug/ui/text.py
===================================================================
--- trunk/reportbug/ui/text.py 2008-08-31 20:45:18 UTC (rev 640)
+++ trunk/reportbug/ui/text.py 2008-08-31 22:54:15 UTC (rev 641)
@@ -1,937 +0,0 @@
-# Text user interface for reportbug
-# Written by Chris Lawrence <lawrencc at debian.org>
-# (C) 2001-08 Chris Lawrence
-#
-# This program is freely distributable per the following license:
-#
-## Permission to use, copy, modify, and distribute this software and its
-## documentation for any purpose and without fee is hereby granted,
-## provided that the above copyright notice appears in all copies and that
-## both that copyright notice and this permission notice appear in
-## supporting documentation.
-##
-## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
-## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
-## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
-## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
-## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
-## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
-## SOFTWARE.
-#
-# $Id: reportbug_ui_text.py,v 1.19.2.9 2008-04-18 05:38:28 lawrencc Exp $
-
-import sys
-import os
-import commands
-import re
-import math
-import string
-import errno
-import glob
-import getpass
-import textwrap
-try:
- import readline
-except ImportError:
- readline = None
-
-from reportbug import debianbts, hiermatch
-from reportbug.exceptions import (
- NoReport, NoPackage, NoBugs, NoNetwork,
- InvalidRegex,
- )
-from reportbug.urlutils import launch_browser
-
-ISATTY = sys.stdin.isatty()
-charset = 'us-ascii'
-
-try:
- r, c = commands.getoutput('stty size').split()
- rows, columns = int(r) or 24, int(c) or 79
-except:
- rows, columns = 24, 79
-
-def ewrite(message, *args):
- if not ISATTY:
- return
-
- if args:
- message = message % args
-
- if isinstance(message, unicode):
- message = message.encode(charset, 'replace')
-
- sys.stderr.write(message)
- sys.stderr.flush()
-
-log_message = ewrite
-display_failure = ewrite
-
-def indent_wrap_text(text, starttext='', indent=0, linelen=None):
- """Wrapper for textwrap.fill to the existing API."""
- if not linelen:
- linelen = columns-1
-
- if indent:
- si = ' '*indent
- else:
- si = ''
-
- text = ' '.join(text.split())
- if not text:
- return starttext+'\n'
-
- output = textwrap.fill(text, width=linelen, initial_indent=starttext,
- subsequent_indent=si)
- if output.endswith('\n'):
- return output
- return output + '\n'
-
-# Readline support, if available
-if readline is not None:
- readline.parse_and_bind("tab: complete")
- try:
- # minimize the word delimeter list if possible
- readline.set_completer_delims(' ')
- except:
- pass
-
-class our_completer(object):
- def __init__(self, completions=None):
- self.completions = None
- if completions:
- self.completions = tuple(map(str, completions))
-
- def complete(self, text, i):
- if not self.completions: return None
-
- matching = [x for x in self.completions if x.startswith(text)]
- if i < len(matching):
- return matching[i]
- else:
- return None
-
-def our_raw_input(prompt = None, completions=None, completer=None):
- istty = sys.stdout.isatty()
- if not istty:
- sys.stderr.write(prompt)
- sys.stderr.flush()
- if readline:
- if completions and not completer:
- completer = our_completer(completions).complete
- if completer:
- readline.set_completer(completer)
-
- try:
- if istty:
- ret = raw_input(prompt)
- else:
- ret = raw_input()
- except EOFError:
- ewrite('\nUser interrupt (^D).\n')
- raise SystemExit
-
- if readline:
- readline.set_completer(None)
- return ret.strip()
-
-def select_options(msg, ok, help, allow_numbers=None, nowrap=False):
- err_message = ''
- for option in ok:
- if option in string.ascii_uppercase:
- default=option
- break
-
- if not help: help = {}
-
- if '?' not in ok: ok = ok+'?'
-
- if nowrap:
- longmsg = msg+' ['+'|'.join(ok)+']?'+' '
- else:
- longmsg = indent_wrap_text(msg+' ['+'|'.join(ok)+']?').strip()+' '
- ch = our_raw_input(longmsg, allow_numbers)
- # Allow entry of a bug number here
- if allow_numbers:
- while ch and ch[0] == '#': ch = ch[1:]
- if type(allow_numbers) == type(1):
- try:
- return str(int(ch))
- except ValueError:
- pass
- else:
- try:
- number = int(ch)
- if number in allow_numbers:
- return str(number)
- else:
- nums = list(allow_numbers)
- nums.sort()
- err_message = 'Only the following entries are allowed: '+\
- ', '.join(map(str, nums))
- except (ValueError, TypeError):
- pass
-
- if not ch: ch = default
- ch = ch[0]
- if ch=='?':
- help['?'] = 'Display this help.'
- for ch in ok:
- if ch in string.ascii_uppercase:
- desc = '(default) '
- else:
- desc = ''
- desc += help.get(ch, help.get(ch.lower(),
- 'No help for this option.'))
- ewrite(indent_wrap_text(desc+'\n', '%s - '% ch, 4))
- return select_options(msg, ok, help, allow_numbers)
- elif (ch.lower() in ok) or (ch.upper() in ok):
- return ch.lower()
- elif err_message:
- ewrite(indent_wrap_text(err_message))
- else:
- ewrite('Invalid selection.\n')
-
- return select_options(msg, ok, help, allow_numbers, nowrap)
-
-def yes_no(msg, yeshelp, nohelp, default=True, nowrap=False):
- "Return True for yes, False for no."
- if default:
- ok = 'Ynq'
- else:
- ok = 'yNq'
-
- res = select_options(msg, ok, {'y': yeshelp, 'n': nohelp, 'q' : 'Quit.'},
- nowrap=nowrap)
- if res == 'q':
- raise SystemExit
- return (res == 'y')
-
-def long_message(text, *args):
- if args:
- ewrite(indent_wrap_text(text % args))
- else:
- ewrite(indent_wrap_text(text))
-
-final_message = long_message
-
-def get_string(prompt, options=None, title=None, force_prompt=False,
- default='', completer=None):
- if prompt and (len(prompt) < 2*columns/3) and not force_prompt:
- if default:
- prompt = '%s [%s]: ' % (prompt, default)
- response = our_raw_input(prompt, options, completer) or default
- else:
- response = our_raw_input(prompt, options, completer)
- else:
- if prompt:
- ewrite(indent_wrap_text(prompt))
- if default:
- response = our_raw_input('[%s]> ' % default, options, completer) or default
- else:
- response = our_raw_input('> ', options, completer)
-
- # Translate the response into a Unicode string
- if response is not None:
- response = unicode(response, charset, 'replace')
-
- return response
-
-def get_multiline(prompt):
- ewrite('\n')
- ewrite(indent_wrap_text(prompt + " Press ENTER on a blank line to continue.\n"))
- l = []
- while 1:
- entry = get_string('', force_prompt=True).strip()
- if not entry:
- break
- l.append(entry)
- ewrite('\n')
- return l
-
-def get_password(prompt=None):
- return getpass.getpass(prompt)
-
-def FilenameCompleter(text, i):
- paths = glob.glob(text+'*')
- if not paths: return None
-
- if i < len(paths):
- entry = paths[i]
- if os.path.isdir(entry):
- return entry+'/'
- return entry
- else:
- return None
-
-def get_filename(prompt, title=None, force_prompt=False, default=''):
- return get_string(prompt, title=title, force_prompt=force_prompt,
- default=default, completer=FilenameCompleter)
-
-def select_multiple(par, options, prompt, title=None, order=None, extras=None):
- return menu(par, options, prompt, title=title, order=order, extras=extras,
- multiple=True, empty_ok=False)
-
-def menu(par, options, prompt, default=None, title=None, any_ok=False,
- order=None, extras=None, multiple=False, empty_ok=False):
- selected = {}
-
- if not extras:
- extras = []
- else:
- extras = list(extras)
-
- if title:
- ewrite(title+'\n\n')
-
- ewrite(indent_wrap_text(par, linelen=columns)+'\n')
-
- if isinstance(options, dict):
- options = options.copy()
- # Convert to a list
- if order:
- olist = []
- for key in order:
- if options.has_key(key):
- olist.append( (key, options[key]) )
- del options[key]
-
- # Append anything out of order
- options = options.items()
- options.sort()
- for option in options:
- olist.append( option )
- options = olist
- else:
- options = options.items()
- options.sort()
-
- if multiple:
- options.append( ('none', '') )
- default = 'none'
- extras += ['done']
-
- allowed = map(lambda x: x[0], options)
- allowed = allowed + extras
-
- maxlen_name = min(max(map(len, allowed)), columns/3)
- digits = int(math.ceil(math.log10(len(options)+1)))
-
- i = 1
- for name, desc in options:
- text = indent_wrap_text(desc, indent=(maxlen_name+digits+3),
- starttext=('%*d %-*.*s ' % (
- digits, i, maxlen_name, maxlen_name, name)))
- ewrite(text)
- if len(options) < 5:
- ewrite('\n')
- i = i+1
- if len(options) >= 5:
- ewrite('\n')
-
- if multiple:
- prompt += '(one at a time) '
-
- while 1:
- if default:
- aprompt = prompt + '[%s] ' % default
- else:
- aprompt = prompt
-
- response = our_raw_input(aprompt, allowed)
- if not response: response = default
-
- try:
- num = int(response)
- if 1 <= num <= len(options):
- response = options[num-1][0]
- except (ValueError, TypeError):
- pass
-
- if response in allowed or (response == default and response):
- if multiple:
- if response == 'done':
- return selected.keys()
- elif response == 'none':
- return []
- elif selected.get(response):
- del selected[response]
- else:
- selected[response]=1
- ewrite('- selected: %s\n' % ', '.join(selected.keys()))
- if len(selected):
- default = 'done'
- else:
- default = 'none'
- continue
- else:
- return response
-
- if any_ok and response:
- return response
- elif empty_ok and not response:
- return
-
- ewrite('Invalid entry.\n')
- return
-
-# Things that are very UI dependent go here
-def show_report(number, system, mirrors,
- http_proxy, screen=None, queryonly=False, title='',
- archived='no'):
- sysinfo = debianbts.SYSTEMS[system]
- ewrite('Retrieving report #%d from %s bug tracking system...\n',
- number, sysinfo['name'])
-
- try:
- info = debianbts.get_report(number, system, mirrors=mirrors,
- followups=1,
- http_proxy=http_proxy, archived=archived)
- except:
- info = None
-
- if not info:
- ewrite('No report available: #%s\n', number)
- return
-
- (title, messages) = info
- current_message = 0
- skip_pager = False
-
- while 1:
- if current_message:
- text = 'Followup %d - %s\n\n%s' % (current_message, title,
- messages[current_message])
- else:
- text = 'Original report - %s\n\n%s' % (title, messages[0])
-
- if not skip_pager:
- fd = os.popen('sensible-pager', 'w')
- try:
- fd.write(text)
- fd.close()
- except IOError, x:
- if x.errno == errno.EPIPE:
- pass
- else:
- raise
- skip_pager = False
-
- options = 'xOrbq'
-
- if (current_message+1) < len(messages):
- options = 'N'+options.lower()
- if (current_message):
- options = 'p'+options
-
- x = select_options("What do you want to do now?", options,
- {'x' : 'Provide extra information.',
- 'o' : 'Show other bug reports (return to '
- 'bug listing).',
- 'n' : 'Show next message (followup).',
- 'p' : 'Show previous message (followup).',
- 'r' : 'Redisplay this message.',
- 'b' : 'Launch web browser to read '
- 'full log.',
- 'q' : "I'm bored; quit please."},
- allow_numbers = range(1, len(messages)+1))
- if x == 'x':
- return number
- elif x == 'q':
- raise NoReport
- elif x == 'b':
- launch_browser(debianbts.get_report_url(
- system, number, mirrors, archived))
- skip_pager = True
- elif x == 'o':
- break
- elif x == 'n':
- current_message += 1
- elif x == 'p':
- current_message -= 1
- return
-
-def handle_bts_query(package, bts, mirrors=None, http_proxy="",
- queryonly=False, title="", screen=None, archived='no',
- source=False, version=None):
- root = debianbts.SYSTEMS[bts].get('btsroot')
- if not root:
- ewrite('%s bug tracking system has no web URL; bypassing query\n',
- debianbts.SYSTEMS[bts]['name'])
- return
-
- srcstr = ""
- if source:
- srcstr = " (source)"
-
- if isinstance(package, basestring):
- long_message('Querying %s BTS for reports on %s%s...\n',
- debianbts.SYSTEMS[bts]['name'], package, srcstr)
- else:
- long_message('Querying %s BTS for reports on %s%s...\n',
- debianbts.SYSTEMS[bts]['name'],
- ' '.join([str(x) for x in package]), srcstr)
-
- bugs = []
- try:
- (count, title, hierarchy)=debianbts.get_reports(
- package, bts, mirrors=mirrors, version=version,
- source=source, http_proxy=http_proxy, archived=archived)
- if debianbts.SYSTEMS[bts].has_key('namefmt'):
- package2 = debianbts.SYSTEMS[bts]['namefmt'] % package
- (count2, title2, hierarchy2) = \
- debianbts.get_reports(package2, bts,
- mirrors=mirrors, source=source,
- http_proxy=http_proxy,
- version=version)
- count = count+count2
- for entry in hierarchy2:
- hierarchy.append( (package2+' '+entry[0], entry[1]) )
-
- exp = re.compile(r'#(\d+)[ :]')
- for entry in hierarchy or []:
- for bug in entry[1]:
- match = exp.match(bug)
- if match:
- bugs.append(int(match.group(1)))
-
- if not count:
- if hierarchy == None:
- raise NoPackage
- else:
- raise NoBugs
- elif count == 1:
- ewrite('%d bug report found:\n\n', count)
- else:
- ewrite('%d bug reports found:\n\n', count)
-
- return browse_bugs(hierarchy, count, bugs, bts, queryonly,
- mirrors, http_proxy, screen, title)
-
- except (IOError, NoNetwork):
- ewrite('Unable to connect to %s BTS; ', debianbts.SYSTEMS[bts]['name'])
- res = select_options('continue', 'yN',
- {'y': 'Keep going.',
- 'n': 'Abort.'})
- if res == 'n':
- raise NoNetwork
-
-def browse_bugs(hierarchy, count, bugs, bts, queryonly, mirrors,
- http_proxy, screen, title):
- endcount = catcount = 0
- scount = startcount = 1
- category = hierarchy[0]
- lastpage = []
- digits = len(str(len(bugs)))
- linefmt = ' %'+str(digits)+'d) %s\n'
- while category:
- scount = scount + 1
- catname, reports = category[0:2]
- while catname.endswith(':'): catname = catname[:-1]
- total = len(reports)
-
- while len(reports):
- these = reports[:rows-2]
- reports = reports[rows-2:]
- remain = len(reports)
-
- tplural = rplural = 's'
- if total == 1: tplural=''
- if remain != 1: rplural=''
-
- if remain:
- lastpage.append(' %s: %d remain%s\n' %
- (catname, remain, rplural))
- else:
- lastpage.append(catname+'\n')
-
- oldscount, oldecount = scount, endcount
- for report in these:
- scount = scount + 1
- endcount = endcount + 1
- lastpage.append(linefmt % (endcount,report[:columns-digits-5]))
-
- if category == hierarchy[-1] or \
- (scount >= (rows - len(hierarchy[catcount+1][1]) - 1)):
- skipmsg = ' (s to skip rest)'
- if endcount == count:
- skipmsg = ''
-
- options = 'yNmrqsf'
- if queryonly: options = 'Nmrqf'
-
- rstr = "(%d-%d/%d) " % (startcount, endcount, count)
- pstr = rstr + "Is the bug you found listed above"
- if queryonly:
- pstr = rstr + "What would you like to do next"
- allowed = bugs+range(1, count+1)
- helptext = {
- 'y' : 'Problem already reported; optionally '
- 'add extra information.',
- 'n' : 'Problem not listed above; possibly '
- 'check more.',
- 'm' : 'Get more information about a bug (you '
- 'can also enter a number\n'
- ' without selecting "m" first).',
- 'r' : 'Redisplay the last bugs shown.',
- 'q' : "I'm bored; quit please.",
- 's' : 'Skip remaining problems; file a new '
- 'report immediately.',
- 'f' : 'Filter bug list using a pattern.'}
- if skipmsg:
- helptext['n'] = helptext['n'][:-1]+' (skip to Next page).'
-
- while 1:
- sys.stderr.writelines(lastpage)
- x = select_options(pstr, options, helptext,
- allow_numbers=allowed)
- if x == 'n':
- lastpage = []
- break
- elif x == 'r':
- continue
- elif x == 'q':
- raise NoReport
- elif x == 's':
- return
- elif x == 'y':
- if queryonly:
- return
-
- if len(bugs) == 1:
- number = '1'
- else:
- number = our_raw_input(
- 'Enter the number of the bug report '
- 'you want to give more info on,\n'
- 'or press ENTER to exit: #', allowed)
- while number and number[0] == '#':
- number=number[1:]
- if number:
- try:
- number = int(number)
- if number not in bugs and 1 <= number <= len(bugs):
- number = bugs[number-1]
- return number
- except ValueError:
- ewrite('Invalid report number: %s\n',
- number)
- else:
- raise NoReport
- elif x == 'f':
- # Do filter. Recursive done.
- retval = search_bugs(hierarchy,bts, queryonly, mirrors,
- http_proxy, screen, title)
- if retval in ["FilterEnd", "Top"]:
- continue
- else:
- return retval
- else:
- if x == 'm' or x == 'i':
- if len(bugs) == 1:
- number = '1'
- else:
- number = our_raw_input(
- 'Please enter the number of the bug '
- 'you would like more info on: #',
- allowed)
- else:
- number = x
-
- while number and number[0] == '#':
- number=number[1:]
-
- if number:
- try:
- number = int(number)
- if number not in bugs and 1 <= number <= len(bugs):
- number = bugs[number-1]
- res = show_report(number, bts, mirrors,
- http_proxy,
- queryonly=queryonly,
- screen=screen,
- title=title)
- if res:
- return res
- except ValueError:
- ewrite('Invalid report number: %s\n',
- number)
-
- startcount = endcount+1
- scount = 0
-
- # these now empty
-
- if category == hierarchy[-1]: break
-
- catcount = catcount+1
- category = hierarchy[catcount]
- if scount:
- lastpage.append('\n')
- scount = scount + 1
-
-def proc_hierarchy(hierarchy):
- """Find out bug count and bug # in the hierarchy."""
-
- lenlist = [len(i[1]) for i in hierarchy]
- if lenlist:
- count = reduce(lambda x, y: x+y, lenlist)
- else:
- return 0, 0
-
- # Copy & paste from handle_bts_query()
- bugs = []
- exp = re.compile(r'\#(\d+)[ :]')
- for entry in hierarchy or []:
- for bug in entry[1]:
- match = exp.match(bug)
- if match:
- bugs.append(int(match.group(1)))
- return count, bugs
-
-def search_bugs(hierarchyfull, bts, queryonly, mirrors,
- http_proxy, screen, title):
- """Search for the bug list using a pattern."""
- """Return string "FilterEnd" when we are done with search."""
-
- pattern = our_raw_input(
- 'Enter the search pattern (a Perl-compatible regular expression)\n'
- 'or press ENTER to exit: ')
- if not pattern:
- return "FilterEnd"
-
- " Create new hierarchy match the pattern."
- try:
- hierarchy = hiermatch.matched_hierarchy(hierarchyfull, pattern)
- except InvalidRegex:
- our_raw_input('Invalid regular expression, press ENTER to continue.')
- return "FilterEnd"
-
- count, bugs = proc_hierarchy(hierarchy)
- exp = re.compile(r'\#(\d+):')
-
- if not count:
- our_raw_input('No match found, press ENTER to continue.')
- return "FilterEnd"
-
- endcount = catcount = 0
- scount = startcount = 1
- category = hierarchy[0]
- lastpage = []
- digits = len(str(len(bugs)))
- linefmt = ' %'+str(digits)+'d) %s\n'
- while category:
- scount = scount + 1
- catname, reports = category[0:2]
- while catname.endswith(':'): catname = catname[:-1]
- total = len(reports)
-
- while len(reports):
- these = reports[:rows-2]
- reports = reports[rows-2:]
- remain = len(reports)
-
- tplural = rplural = 's'
- if total == 1: tplural=''
- if remain != 1: rplural=''
-
- if remain:
- lastpage.append(' %s: %d report%s (%d remain%s)\n' %
- (catname, total, tplural, remain, rplural))
- else:
- lastpage.append(' %s: %d report%s\n' %
- (catname, total, tplural))
-
- oldscount, oldecount = scount, endcount
- for report in these:
- scount = scount + 1
- endcount = endcount + 1
- lastpage.append(linefmt % (endcount,report[:columns-digits-5]))
-
- if category == hierarchy[-1] or \
- (scount >= (rows - len(hierarchy[catcount+1][1]) - 1)):
- skipmsg = ' (s to skip rest)'
- if endcount == count:
- skipmsg = ''
-
- options = 'yNmrqsfut'
- if queryonly: options = 'Nmrqfut'
-
- rstr = "(%d-%d/%d) " % (startcount, endcount, count)
- pstr = rstr + "Is the bug you found listed above"
- if queryonly:
- pstr = rstr + "What would you like to do next"
- allowed = bugs+range(1, count+1)
- helptext = {
- 'y' : 'Problem already reported; optionally '
- 'add extra information.',
- 'n' : 'Problem not listed above; possibly '
- 'check more.',
- 'm' : 'Get more information about a bug (you '
- 'can also enter a number\n'
- ' without selecting "m" first).',
- 'r' : 'Redisplay the last bugs shown.',
- 'q' : "I'm bored; quit please.",
- 's' : 'Skip remaining problems; file a new '
- 'report immediately.',
- 'f' : 'Filter (search) bug list using a pattern.',
- 'u' : 'Up one level of filter.',
- 't' : 'Top of the bug list (remove all filters).'}
- if skipmsg:
- helptext['n'] = helptext['n'][:-1]+' (skip to Next page).'
-
- while 1:
- sys.stderr.writelines(lastpage)
- x = select_options(pstr, options, helptext,
- allow_numbers=allowed)
- if x == 'n':
- lastpage = []
- break
- elif x == 'r':
- continue
- elif x == 'q':
- raise NoReport
- elif x == 's':
- return
- elif x == 'y':
- if queryonly:
- return
-
- number = our_raw_input(
- 'Enter the number of the bug report '
- 'you want to give more info on,\n'
- 'or press ENTER to exit: #', allowed)
- while number and number[0] == '#':
- number=number[1:]
- if number:
- try:
- number = int(number)
- if number not in bugs and 1 <= number <= len(bugs):
- number = bugs[number-1]
- return number
- except ValueError:
- ewrite('Invalid report number: %s\n',
- number)
- else:
- raise NoReport
- elif x == 'f':
- # Do filter. Recursive done.
- retval = search_bugs(hierarchy, bts, queryonly, mirrors,
- http_proxy, screen, title)
- if retval == "FilterEnd":
- continue
- else:
- return retval
- elif x == 'u':
- # Up a level
- return "FilterEnd"
- elif x == 't':
- # go back to the Top level.
- return "Top"
- else:
- if x == 'm' or x == 'i':
- number = our_raw_input(
- 'Please enter the number of the bug '
- 'you would like more info on: #',
- allowed)
- else:
- number = x
-
- while number and number[0] == '#':
- number=number[1:]
-
- if number:
- try:
- number = int(number)
- if number not in bugs and 1 <= number <= len(bugs):
- number = bugs[number-1]
- res = show_report(number, bts, mirrors,
- http_proxy,
- queryonly=queryonly,
- screen=screen,
- title=title)
- if res:
- return res
- except ValueError:
- ewrite('Invalid report number: %s\n',
- number)
-
- startcount = endcount+1
- scount = 0
-
- # these now empty
-
- if category == hierarchy[-1]: break
-
- catcount = catcount+1
- category = hierarchy[catcount]
- if scount:
- lastpage.append('\n')
- scount = scount + 1
- return "FilterEnd"
-
-def display_report(text, use_pager=True):
- if not use_pager:
- ewrite(text)
- return
-
- pager = os.environ.get('PAGER', 'sensible-pager')
- try:
- os.popen(pager, 'w').write(text)
- except IOError:
- pass
-
-def spawn_editor(message, filename, editor, charset='utf-8'):
- if not editor:
- ewrite('No editor found!\n')
- return (message, 0)
-
- edname = os.path.basename(editor.split()[0])
-
- # Move the cursor for lazy buggers like me; add your editor here...
- ourline = 0
- for (lineno, line) in enumerate(file(filename)):
- if line == '\n' and not ourline:
- ourline = lineno + 2
- elif line.strip() == utils.NEWBIELINE:
- ourline = lineno + 2
-
- opts = ''
- if 'vim' in edname:
- # Force *vim to edit the file in the foreground, instead of forking
- opts = '-f '
-
- if ourline:
- if edname in ('vi', 'nvi', 'vim', 'elvis', 'gvim', 'kvim'):
- opts = '-c :%d' % ourline
- elif (edname in ('elvis-tiny', 'gnuclient', 'ee', 'pico', 'nano', 'zile') or
- 'emacs' in edname):
- opts = '+%d' % ourline
- elif edname in ('jed', 'xjed'):
- opts = '-g %d' % ourline
- elif edname == 'kate':
- opts = '--line %d' % ourline
-
- if '&' in editor or edname == 'kate':
- ewrite("Spawning %s in background; please press Enter when done "
- "editing.\n", edname)
- else:
- ewrite("Spawning %s...\n", edname)
-
- result = os.system("%s %s '%s'" % (editor, opts, filename))
-
- if result:
- ewrite('Warning: possible error exit from %s: %d\n', edname, result)
-
- if not os.path.exists(filename):
- ewrite('Bug report file %s removed!', filename)
- sys.exit(1)
-
- if '&' in editor: return (None, 1)
-
- newmessage = file(filename).read().decode(charset, 'replace')
-
- if newmessage == message:
- ewrite('No changes were made in the editor.\n')
-
- return (newmessage, newmessage != message)
Copied: trunk/reportbug/ui/text_ui.py (from rev 640, trunk/reportbug/ui/text.py)
===================================================================
--- trunk/reportbug/ui/text_ui.py (rev 0)
+++ trunk/reportbug/ui/text_ui.py 2008-08-31 22:54:15 UTC (rev 641)
@@ -0,0 +1,938 @@
+# Text user interface for reportbug
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 2001-08 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# $Id: reportbug_ui_text.py,v 1.19.2.9 2008-04-18 05:38:28 lawrencc Exp $
+
+import sys
+import os
+import commands
+import re
+import math
+import string
+import errno
+import glob
+import getpass
+import textwrap
+try:
+ import readline
+except ImportError:
+ readline = None
+
+from reportbug import debianbts, hiermatch
+from reportbug.exceptions import (
+ NoReport, NoPackage, NoBugs, NoNetwork,
+ InvalidRegex,
+ )
+from reportbug.urlutils import launch_browser
+
+ISATTY = sys.stdin.isatty()
+charset = 'us-ascii'
+
+try:
+ r, c = commands.getoutput('stty size').split()
+ rows, columns = int(r) or 24, int(c) or 79
+except:
+ rows, columns = 24, 79
+
+def ewrite(message, *args):
+ if not ISATTY:
+ return
+
+ if args:
+ message = message % args
+
+ if isinstance(message, unicode):
+ message = message.encode(charset, 'replace')
+
+ sys.stderr.write(message)
+ sys.stderr.flush()
+
+log_message = ewrite
+display_failure = ewrite
+
+def indent_wrap_text(text, starttext='', indent=0, linelen=None):
+ """Wrapper for textwrap.fill to the existing API."""
+ if not linelen:
+ linelen = columns-1
+
+ if indent:
+ si = ' '*indent
+ else:
+ si = ''
+
+ text = ' '.join(text.split())
+ if not text:
+ return starttext+'\n'
+
+ output = textwrap.fill(text, width=linelen, initial_indent=starttext,
+ subsequent_indent=si)
+ if output.endswith('\n'):
+ return output
+ return output + '\n'
+
+# Readline support, if available
+if readline is not None:
+ readline.parse_and_bind("tab: complete")
+ try:
+ # minimize the word delimeter list if possible
+ readline.set_completer_delims(' ')
+ except:
+ pass
+
+class our_completer(object):
+ def __init__(self, completions=None):
+ self.completions = None
+ if completions:
+ self.completions = tuple(map(str, completions))
+
+ def complete(self, text, i):
+ if not self.completions: return None
+
+ matching = [x for x in self.completions if x.startswith(text)]
+ if i < len(matching):
+ return matching[i]
+ else:
+ return None
+
+def our_raw_input(prompt = None, completions=None, completer=None):
+ istty = sys.stdout.isatty()
+ if not istty:
+ sys.stderr.write(prompt)
+ sys.stderr.flush()
+ if readline:
+ if completions and not completer:
+ completer = our_completer(completions).complete
+ if completer:
+ readline.set_completer(completer)
+
+ try:
+ if istty:
+ ret = raw_input(prompt)
+ else:
+ ret = raw_input()
+ except EOFError:
+ ewrite('\nUser interrupt (^D).\n')
+ raise SystemExit
+
+ if readline:
+ readline.set_completer(None)
+ return ret.strip()
+
+def select_options(msg, ok, help, allow_numbers=None, nowrap=False):
+ err_message = ''
+ for option in ok:
+ if option in string.ascii_uppercase:
+ default=option
+ break
+
+ if not help: help = {}
+
+ if '?' not in ok: ok = ok+'?'
+
+ if nowrap:
+ longmsg = msg+' ['+'|'.join(ok)+']?'+' '
+ else:
+ longmsg = indent_wrap_text(msg+' ['+'|'.join(ok)+']?').strip()+' '
+ ch = our_raw_input(longmsg, allow_numbers)
+ # Allow entry of a bug number here
+ if allow_numbers:
+ while ch and ch[0] == '#': ch = ch[1:]
+ if type(allow_numbers) == type(1):
+ try:
+ return str(int(ch))
+ except ValueError:
+ pass
+ else:
+ try:
+ number = int(ch)
+ if number in allow_numbers:
+ return str(number)
+ else:
+ nums = list(allow_numbers)
+ nums.sort()
+ err_message = 'Only the following entries are allowed: '+\
+ ', '.join(map(str, nums))
+ except (ValueError, TypeError):
+ pass
+
+ if not ch: ch = default
+ ch = ch[0]
+ if ch=='?':
+ help['?'] = 'Display this help.'
+ for ch in ok:
+ if ch in string.ascii_uppercase:
+ desc = '(default) '
+ else:
+ desc = ''
+ desc += help.get(ch, help.get(ch.lower(),
+ 'No help for this option.'))
+ ewrite(indent_wrap_text(desc+'\n', '%s - '% ch, 4))
+ return select_options(msg, ok, help, allow_numbers)
+ elif (ch.lower() in ok) or (ch.upper() in ok):
+ return ch.lower()
+ elif err_message:
+ ewrite(indent_wrap_text(err_message))
+ else:
+ ewrite('Invalid selection.\n')
+
+ return select_options(msg, ok, help, allow_numbers, nowrap)
+
+def yes_no(msg, yeshelp, nohelp, default=True, nowrap=False):
+ "Return True for yes, False for no."
+ if default:
+ ok = 'Ynq'
+ else:
+ ok = 'yNq'
+
+ res = select_options(msg, ok, {'y': yeshelp, 'n': nohelp, 'q' : 'Quit.'},
+ nowrap=nowrap)
+ if res == 'q':
+ raise SystemExit
+ return (res == 'y')
+
+def long_message(text, *args):
+ if args:
+ ewrite(indent_wrap_text(text % args))
+ else:
+ ewrite(indent_wrap_text(text))
+
+final_message = long_message
+
+def get_string(prompt, options=None, title=None, force_prompt=False,
+ default='', completer=None):
+ if prompt and (len(prompt) < 2*columns/3) and not force_prompt:
+ if default:
+ prompt = '%s [%s]: ' % (prompt, default)
+ response = our_raw_input(prompt, options, completer) or default
+ else:
+ response = our_raw_input(prompt, options, completer)
+ else:
+ if prompt:
+ ewrite(indent_wrap_text(prompt))
+ if default:
+ response = our_raw_input('[%s]> ' % default, options, completer) or default
+ else:
+ response = our_raw_input('> ', options, completer)
+
+ # Translate the response into a Unicode string
+ if response is not None:
+ response = unicode(response, charset, 'replace')
+
+ return response
+
+def get_multiline(prompt):
+ ewrite('\n')
+ ewrite(indent_wrap_text(prompt + " Press ENTER on a blank line to continue.\n"))
+ l = []
+ while 1:
+ entry = get_string('', force_prompt=True).strip()
+ if not entry:
+ break
+ l.append(entry)
+ ewrite('\n')
+ return l
+
+def get_password(prompt=None):
+ return getpass.getpass(prompt)
+
+def FilenameCompleter(text, i):
+ paths = glob.glob(text+'*')
+ if not paths: return None
+
+ if i < len(paths):
+ entry = paths[i]
+ if os.path.isdir(entry):
+ return entry+'/'
+ return entry
+ else:
+ return None
+
+def get_filename(prompt, title=None, force_prompt=False, default=''):
+ return get_string(prompt, title=title, force_prompt=force_prompt,
+ default=default, completer=FilenameCompleter)
+
+def select_multiple(par, options, prompt, title=None, order=None, extras=None):
+ return menu(par, options, prompt, title=title, order=order, extras=extras,
+ multiple=True, empty_ok=False)
+
+def menu(par, options, prompt, default=None, title=None, any_ok=False,
+ order=None, extras=None, multiple=False, empty_ok=False):
+ selected = {}
+
+ if not extras:
+ extras = []
+ else:
+ extras = list(extras)
+
+ if title:
+ ewrite(title+'\n\n')
+
+ ewrite(indent_wrap_text(par, linelen=columns)+'\n')
+
+ if isinstance(options, dict):
+ options = options.copy()
+ # Convert to a list
+ if order:
+ olist = []
+ for key in order:
+ if options.has_key(key):
+ olist.append( (key, options[key]) )
+ del options[key]
+
+ # Append anything out of order
+ options = options.items()
+ options.sort()
+ for option in options:
+ olist.append( option )
+ options = olist
+ else:
+ options = options.items()
+ options.sort()
+
+ if multiple:
+ options.append( ('none', '') )
+ default = 'none'
+ extras += ['done']
+
+ allowed = map(lambda x: x[0], options)
+ allowed = allowed + extras
+
+ maxlen_name = min(max(map(len, allowed)), columns/3)
+ digits = int(math.ceil(math.log10(len(options)+1)))
+
+ i = 1
+ for name, desc in options:
+ text = indent_wrap_text(desc, indent=(maxlen_name+digits+3),
+ starttext=('%*d %-*.*s ' % (
+ digits, i, maxlen_name, maxlen_name, name)))
+ ewrite(text)
+ if len(options) < 5:
+ ewrite('\n')
+ i = i+1
+ if len(options) >= 5:
+ ewrite('\n')
+
+ if multiple:
+ prompt += '(one at a time) '
+
+ while 1:
+ if default:
+ aprompt = prompt + '[%s] ' % default
+ else:
+ aprompt = prompt
+
+ response = our_raw_input(aprompt, allowed)
+ if not response: response = default
+
+ try:
+ num = int(response)
+ if 1 <= num <= len(options):
+ response = options[num-1][0]
+ except (ValueError, TypeError):
+ pass
+
+ if response in allowed or (response == default and response):
+ if multiple:
+ if response == 'done':
+ return selected.keys()
+ elif response == 'none':
+ return []
+ elif selected.get(response):
+ del selected[response]
+ else:
+ selected[response]=1
+ ewrite('- selected: %s\n' % ', '.join(selected.keys()))
+ if len(selected):
+ default = 'done'
+ else:
+ default = 'none'
+ continue
+ else:
+ return response
+
+ if any_ok and response:
+ return response
+ elif empty_ok and not response:
+ return
+
+ ewrite('Invalid entry.\n')
+ return
+
+# Things that are very UI dependent go here
+def show_report(number, system, mirrors,
+ http_proxy, screen=None, queryonly=False, title='',
+ archived='no'):
+ sysinfo = debianbts.SYSTEMS[system]
+ ewrite('Retrieving report #%d from %s bug tracking system...\n',
+ number, sysinfo['name'])
+
+ try:
+ info = debianbts.get_report(number, system, mirrors=mirrors,
+ followups=1,
+ http_proxy=http_proxy, archived=archived)
+ except:
+ info = None
+
+ if not info:
+ ewrite('No report available: #%s\n', number)
+ return
+
+ (title, messages) = info
+ current_message = 0
+ skip_pager = False
+
+ while 1:
+ if current_message:
+ text = 'Followup %d - %s\n\n%s' % (current_message, title,
+ messages[current_message])
+ else:
+ text = 'Original report - %s\n\n%s' % (title, messages[0])
+
+ if not skip_pager:
+ fd = os.popen('sensible-pager', 'w')
+ try:
+ fd.write(text)
+ fd.close()
+ except IOError, x:
+ if x.errno == errno.EPIPE:
+ pass
+ else:
+ raise
+ skip_pager = False
+
+ options = 'xOrbq'
+
+ if (current_message+1) < len(messages):
+ options = 'N'+options.lower()
+ if (current_message):
+ options = 'p'+options
+
+ x = select_options("What do you want to do now?", options,
+ {'x' : 'Provide extra information.',
+ 'o' : 'Show other bug reports (return to '
+ 'bug listing).',
+ 'n' : 'Show next message (followup).',
+ 'p' : 'Show previous message (followup).',
+ 'r' : 'Redisplay this message.',
+ 'b' : 'Launch web browser to read '
+ 'full log.',
+ 'q' : "I'm bored; quit please."},
+ allow_numbers = range(1, len(messages)+1))
+ if x == 'x':
+ return number
+ elif x == 'q':
+ raise NoReport
+ elif x == 'b':
+ launch_browser(debianbts.get_report_url(
+ system, number, mirrors, archived))
+ skip_pager = True
+ elif x == 'o':
+ break
+ elif x == 'n':
+ current_message += 1
+ elif x == 'p':
+ current_message -= 1
+ return
+
+def handle_bts_query(package, bts, mirrors=None, http_proxy="",
+ queryonly=False, title="", screen=None, archived='no',
+ source=False, version=None):
+ root = debianbts.SYSTEMS[bts].get('btsroot')
+ if not root:
+ ewrite('%s bug tracking system has no web URL; bypassing query\n',
+ debianbts.SYSTEMS[bts]['name'])
+ return
+
+ srcstr = ""
+ if source:
+ srcstr = " (source)"
+
+ if isinstance(package, basestring):
+ long_message('Querying %s BTS for reports on %s%s...\n',
+ debianbts.SYSTEMS[bts]['name'], package, srcstr)
+ else:
+ long_message('Querying %s BTS for reports on %s%s...\n',
+ debianbts.SYSTEMS[bts]['name'],
+ ' '.join([str(x) for x in package]), srcstr)
+
+ bugs = []
+ try:
+ (count, title, hierarchy)=debianbts.get_reports(
+ package, bts, mirrors=mirrors, version=version,
+ source=source, http_proxy=http_proxy, archived=archived)
+ if debianbts.SYSTEMS[bts].has_key('namefmt'):
+ package2 = debianbts.SYSTEMS[bts]['namefmt'] % package
+ (count2, title2, hierarchy2) = \
+ debianbts.get_reports(package2, bts,
+ mirrors=mirrors, source=source,
+ http_proxy=http_proxy,
+ version=version)
+ count = count+count2
+ for entry in hierarchy2:
+ hierarchy.append( (package2+' '+entry[0], entry[1]) )
+
+ exp = re.compile(r'#(\d+)[ :]')
+ for entry in hierarchy or []:
+ for bug in entry[1]:
+ match = exp.match(bug)
+ if match:
+ bugs.append(int(match.group(1)))
+
+ if not count:
+ if hierarchy == None:
+ raise NoPackage
+ else:
+ raise NoBugs
+ elif count == 1:
+ ewrite('%d bug report found:\n\n', count)
+ else:
+ ewrite('%d bug reports found:\n\n', count)
+
+ return browse_bugs(hierarchy, count, bugs, bts, queryonly,
+ mirrors, http_proxy, screen, title)
+
+ except (IOError, NoNetwork):
+ ewrite('Unable to connect to %s BTS; ', debianbts.SYSTEMS[bts]['name'])
+ res = select_options('continue', 'yN',
+ {'y': 'Keep going.',
+ 'n': 'Abort.'})
+ if res == 'n':
+ raise NoNetwork
+
+def browse_bugs(hierarchy, count, bugs, bts, queryonly, mirrors,
+ http_proxy, screen, title):
+ endcount = catcount = 0
+ scount = startcount = 1
+ category = hierarchy[0]
+ lastpage = []
+ digits = len(str(len(bugs)))
+ linefmt = ' %'+str(digits)+'d) %s\n'
+ while category:
+ scount = scount + 1
+ catname, reports = category[0:2]
+ while catname.endswith(':'): catname = catname[:-1]
+ total = len(reports)
+
+ while len(reports):
+ these = reports[:rows-2]
+ reports = reports[rows-2:]
+ remain = len(reports)
+
+ tplural = rplural = 's'
+ if total == 1: tplural=''
+ if remain != 1: rplural=''
+
+ if remain:
+ lastpage.append(' %s: %d remain%s\n' %
+ (catname, remain, rplural))
+ else:
+ lastpage.append(catname+'\n')
+
+ oldscount, oldecount = scount, endcount
+ for report in these:
+ scount = scount + 1
+ endcount = endcount + 1
+ lastpage.append(linefmt % (endcount,report[:columns-digits-5]))
+
+ if category == hierarchy[-1] or \
+ (scount >= (rows - len(hierarchy[catcount+1][1]) - 1)):
+ skipmsg = ' (s to skip rest)'
+ if endcount == count:
+ skipmsg = ''
+
+ options = 'yNmrqsf'
+ if queryonly: options = 'Nmrqf'
+
+ rstr = "(%d-%d/%d) " % (startcount, endcount, count)
+ pstr = rstr + "Is the bug you found listed above"
+ if queryonly:
+ pstr = rstr + "What would you like to do next"
+ allowed = bugs+range(1, count+1)
+ helptext = {
+ 'y' : 'Problem already reported; optionally '
+ 'add extra information.',
+ 'n' : 'Problem not listed above; possibly '
+ 'check more.',
+ 'm' : 'Get more information about a bug (you '
+ 'can also enter a number\n'
+ ' without selecting "m" first).',
+ 'r' : 'Redisplay the last bugs shown.',
+ 'q' : "I'm bored; quit please.",
+ 's' : 'Skip remaining problems; file a new '
+ 'report immediately.',
+ 'f' : 'Filter bug list using a pattern.'}
+ if skipmsg:
+ helptext['n'] = helptext['n'][:-1]+' (skip to Next page).'
+
+ while 1:
+ sys.stderr.writelines(lastpage)
+ x = select_options(pstr, options, helptext,
+ allow_numbers=allowed)
+ if x == 'n':
+ lastpage = []
+ break
+ elif x == 'r':
+ continue
+ elif x == 'q':
+ raise NoReport
+ elif x == 's':
+ return
+ elif x == 'y':
+ if queryonly:
+ return
+
+ if len(bugs) == 1:
+ number = '1'
+ else:
+ number = our_raw_input(
+ 'Enter the number of the bug report '
+ 'you want to give more info on,\n'
+ 'or press ENTER to exit: #', allowed)
+ while number and number[0] == '#':
+ number=number[1:]
+ if number:
+ try:
+ number = int(number)
+ if number not in bugs and 1 <= number <= len(bugs):
+ number = bugs[number-1]
+ return number
+ except ValueError:
+ ewrite('Invalid report number: %s\n',
+ number)
+ else:
+ raise NoReport
+ elif x == 'f':
+ # Do filter. Recursive done.
+ retval = search_bugs(hierarchy,bts, queryonly, mirrors,
+ http_proxy, screen, title)
+ if retval in ["FilterEnd", "Top"]:
+ continue
+ else:
+ return retval
+ else:
+ if x == 'm' or x == 'i':
+ if len(bugs) == 1:
+ number = '1'
+ else:
+ number = our_raw_input(
+ 'Please enter the number of the bug '
+ 'you would like more info on: #',
+ allowed)
+ else:
+ number = x
+
+ while number and number[0] == '#':
+ number=number[1:]
+
+ if number:
+ try:
+ number = int(number)
+ if number not in bugs and 1 <= number <= len(bugs):
+ number = bugs[number-1]
+ res = show_report(number, bts, mirrors,
+ http_proxy,
+ queryonly=queryonly,
+ screen=screen,
+ title=title)
+ if res:
+ return res
+ except ValueError:
+ ewrite('Invalid report number: %s\n',
+ number)
+
+ startcount = endcount+1
+ scount = 0
+
+ # these now empty
+
+ if category == hierarchy[-1]: break
+
+ catcount = catcount+1
+ category = hierarchy[catcount]
+ if scount:
+ lastpage.append('\n')
+ scount = scount + 1
+
+def proc_hierarchy(hierarchy):
+ """Find out bug count and bug # in the hierarchy."""
+
+ lenlist = [len(i[1]) for i in hierarchy]
+ if lenlist:
+ count = reduce(lambda x, y: x+y, lenlist)
+ else:
+ return 0, 0
+
+ # Copy & paste from handle_bts_query()
+ bugs = []
+ exp = re.compile(r'\#(\d+)[ :]')
+ for entry in hierarchy or []:
+ for bug in entry[1]:
+ match = exp.match(bug)
+ if match:
+ bugs.append(int(match.group(1)))
+ return count, bugs
+
+def search_bugs(hierarchyfull, bts, queryonly, mirrors,
+ http_proxy, screen, title):
+ """Search for the bug list using a pattern."""
+ """Return string "FilterEnd" when we are done with search."""
+
+ pattern = our_raw_input(
+ 'Enter the search pattern (a Perl-compatible regular expression)\n'
+ 'or press ENTER to exit: ')
+ if not pattern:
+ return "FilterEnd"
+
+ " Create new hierarchy match the pattern."
+ try:
+ hierarchy = hiermatch.matched_hierarchy(hierarchyfull, pattern)
+ except InvalidRegex:
+ our_raw_input('Invalid regular expression, press ENTER to continue.')
+ return "FilterEnd"
+
+ count, bugs = proc_hierarchy(hierarchy)
+ exp = re.compile(r'\#(\d+):')
+
+ if not count:
+ our_raw_input('No match found, press ENTER to continue.')
+ return "FilterEnd"
+
+ endcount = catcount = 0
+ scount = startcount = 1
+ category = hierarchy[0]
+ lastpage = []
+ digits = len(str(len(bugs)))
+ linefmt = ' %'+str(digits)+'d) %s\n'
+ while category:
+ scount = scount + 1
+ catname, reports = category[0:2]
+ while catname.endswith(':'): catname = catname[:-1]
+ total = len(reports)
+
+ while len(reports):
+ these = reports[:rows-2]
+ reports = reports[rows-2:]
+ remain = len(reports)
+
+ tplural = rplural = 's'
+ if total == 1: tplural=''
+ if remain != 1: rplural=''
+
+ if remain:
+ lastpage.append(' %s: %d report%s (%d remain%s)\n' %
+ (catname, total, tplural, remain, rplural))
+ else:
+ lastpage.append(' %s: %d report%s\n' %
+ (catname, total, tplural))
+
+ oldscount, oldecount = scount, endcount
+ for report in these:
+ scount = scount + 1
+ endcount = endcount + 1
+ lastpage.append(linefmt % (endcount,report[:columns-digits-5]))
+
+ if category == hierarchy[-1] or \
+ (scount >= (rows - len(hierarchy[catcount+1][1]) - 1)):
+ skipmsg = ' (s to skip rest)'
+ if endcount == count:
+ skipmsg = ''
+
+ options = 'yNmrqsfut'
+ if queryonly: options = 'Nmrqfut'
+
+ rstr = "(%d-%d/%d) " % (startcount, endcount, count)
+ pstr = rstr + "Is the bug you found listed above"
+ if queryonly:
+ pstr = rstr + "What would you like to do next"
+ allowed = bugs+range(1, count+1)
+ helptext = {
+ 'y' : 'Problem already reported; optionally '
+ 'add extra information.',
+ 'n' : 'Problem not listed above; possibly '
+ 'check more.',
+ 'm' : 'Get more information about a bug (you '
+ 'can also enter a number\n'
+ ' without selecting "m" first).',
+ 'r' : 'Redisplay the last bugs shown.',
+ 'q' : "I'm bored; quit please.",
+ 's' : 'Skip remaining problems; file a new '
+ 'report immediately.',
+ 'f' : 'Filter (search) bug list using a pattern.',
+ 'u' : 'Up one level of filter.',
+ 't' : 'Top of the bug list (remove all filters).'}
+ if skipmsg:
+ helptext['n'] = helptext['n'][:-1]+' (skip to Next page).'
+
+ while 1:
+ sys.stderr.writelines(lastpage)
+ x = select_options(pstr, options, helptext,
+ allow_numbers=allowed)
+ if x == 'n':
+ lastpage = []
+ break
+ elif x == 'r':
+ continue
+ elif x == 'q':
+ raise NoReport
+ elif x == 's':
+ return
+ elif x == 'y':
+ if queryonly:
+ return
+
+ number = our_raw_input(
+ 'Enter the number of the bug report '
+ 'you want to give more info on,\n'
+ 'or press ENTER to exit: #', allowed)
+ while number and number[0] == '#':
+ number=number[1:]
+ if number:
+ try:
+ number = int(number)
+ if number not in bugs and 1 <= number <= len(bugs):
+ number = bugs[number-1]
+ return number
+ except ValueError:
+ ewrite('Invalid report number: %s\n',
+ number)
+ else:
+ raise NoReport
+ elif x == 'f':
+ # Do filter. Recursive done.
+ retval = search_bugs(hierarchy, bts, queryonly, mirrors,
+ http_proxy, screen, title)
+ if retval == "FilterEnd":
+ continue
+ else:
+ return retval
+ elif x == 'u':
+ # Up a level
+ return "FilterEnd"
+ elif x == 't':
+ # go back to the Top level.
+ return "Top"
+ else:
+ if x == 'm' or x == 'i':
+ number = our_raw_input(
+ 'Please enter the number of the bug '
+ 'you would like more info on: #',
+ allowed)
+ else:
+ number = x
+
+ while number and number[0] == '#':
+ number=number[1:]
+
+ if number:
+ try:
+ number = int(number)
+ if number not in bugs and 1 <= number <= len(bugs):
+ number = bugs[number-1]
+ res = show_report(number, bts, mirrors,
+ http_proxy,
+ queryonly=queryonly,
+ screen=screen,
+ title=title)
+ if res:
+ return res
+ except ValueError:
+ ewrite('Invalid report number: %s\n',
+ number)
+
+ startcount = endcount+1
+ scount = 0
+
+ # these now empty
+
+ if category == hierarchy[-1]: break
+
+ catcount = catcount+1
+ category = hierarchy[catcount]
+ if scount:
+ lastpage.append('\n')
+ scount = scount + 1
+ return "FilterEnd"
+
+def display_report(text, use_pager=True):
+ if not use_pager:
+ ewrite(text)
+ return
+
+ pager = os.environ.get('PAGER', 'sensible-pager')
+ try:
+ os.popen(pager, 'w').write(text)
+ except IOError:
+ pass
+
+def spawn_editor(message, filename, editor, charset='utf-8'):
+ if not editor:
+ ewrite('No editor found!\n')
+ return (message, 0)
+
+ edname = os.path.basename(editor.split()[0])
+
+ # Move the cursor for lazy buggers like me; add your editor here...
+ ourline = 0
+ for (lineno, line) in enumerate(file(filename)):
+ if line == '\n' and not ourline:
+ ourline = lineno + 2
+ # Morph @ 2008-08-31
+ #elif line.strip() == utils.NEWBIELINE:
+ # ourline = lineno + 2
+
+ opts = ''
+ if 'vim' in edname:
+ # Force *vim to edit the file in the foreground, instead of forking
+ opts = '-f '
+
+ if ourline:
+ if edname in ('vi', 'nvi', 'vim', 'elvis', 'gvim', 'kvim'):
+ opts = '-c :%d' % ourline
+ elif (edname in ('elvis-tiny', 'gnuclient', 'ee', 'pico', 'nano', 'zile') or
+ 'emacs' in edname):
+ opts = '+%d' % ourline
+ elif edname in ('jed', 'xjed'):
+ opts = '-g %d' % ourline
+ elif edname == 'kate':
+ opts = '--line %d' % ourline
+
+ if '&' in editor or edname == 'kate':
+ ewrite("Spawning %s in background; please press Enter when done "
+ "editing.\n", edname)
+ else:
+ ewrite("Spawning %s...\n", edname)
+
+ result = os.system("%s %s '%s'" % (editor, opts, filename))
+
+ if result:
+ ewrite('Warning: possible error exit from %s: %d\n', edname, result)
+
+ if not os.path.exists(filename):
+ ewrite('Bug report file %s removed!', filename)
+ sys.exit(1)
+
+ if '&' in editor: return (None, 1)
+
+ newmessage = file(filename).read().decode(charset, 'replace')
+
+ if newmessage == message:
+ ewrite('No changes were made in the editor.\n')
+
+ return (newmessage, newmessage != message)
Deleted: trunk/reportbug/ui/urwid.py
===================================================================
--- trunk/reportbug/ui/urwid.py 2008-08-31 20:45:18 UTC (rev 640)
+++ trunk/reportbug/ui/urwid.py 2008-08-31 22:54:15 UTC (rev 641)
@@ -1,685 +0,0 @@
-# urwid user interface for reportbug
-# Written by Chris Lawrence <lawrencc at debian.org>
-# (C) 2006-08 Chris Lawrence
-#
-# This program is freely distributable per the following license:
-#
-## Permission to use, copy, modify, and distribute this software and its
-## documentation for any purpose and without fee is hereby granted,
-## provided that the above copyright notice appears in all copies and that
-## both that copyright notice and this permission notice appear in
-## supporting documentation.
-##
-## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
-## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
-## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
-## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
-## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
-## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
-## SOFTWARE.
-#
-# Portions of this file are licensed under the Lesser GNU Public License
-# (LGPL) Version 2.1 or later. On Debian systems, this license is available
-# in /usr/share/common-licenses/LGPL
-#
-# $Id: reportbug_ui_urwid.py,v 1.3.2.19 2007-04-17 20:02:41 lawrencc Exp $
-
-import sys
-import re
-import getpass
-from reportbug.exceptions import (
- UINotImportable,
- NoPackage, NoBugs, NoNetwork, NoReport,
- )
-from reportbug.urlutils import launch_browser
-from text import (
- ewrite,
- spawn_editor,
- )
-from reportbug import VERSION
-
-try:
- import urwid.raw_display
- import urwid
-except ImportError:
- raise UINotImportable, 'Please install the python-urwid package to use this interface.'
-
-ISATTY = sys.stdin.isatty()
-
-log_message = ewrite
-display_failure = ewrite
-
-# Start a urwid session
-def initialize_urwid_ui():
- ui = urwid.raw_display.Screen()
- ui.register_palette(palette)
- # Improve responsiveness of UI
- ui.set_input_timeouts(max_wait=0.1)
- return ui
-
-# Empty function to satisfy ui.run_wrapper()
-def nullfunc():
- pass
-
-# Widgets ripped mercilessly from urwid examples (dialog.py)
-class buttonpush(Exception):
- pass
-
-def button_press(button):
- raise buttonpush, button.exitcode
-
-class SelectableText(urwid.Edit):
- def valid_char(self, ch):
- return False
-
-class dialog(object):
- def __init__(self, message, body=None, width=None, height=None,
- title='', long_message=''):
- self.body = body
-
- self.scrollmode=False
- if not body:
- if long_message:
- box = SelectableText(edit_text=long_message)
- box.set_edit_pos(0)
- self.body = body = urwid.ListBox([box])
- self.scrollmode=True
- else:
- self.body = body = urwid.Filler(urwid.Divider(), 'top')
-
- if not width:
- width = ('relative', 80)
-
- if not height:
- height = ('relative', 80)
-
- self.frame = urwid.Frame(body, focus_part='footer')
- if message:
- self.frame.header = urwid.Pile([urwid.Text(message),
- urwid.Divider()])
-
- w = self.frame
- # pad area around listbox
- w = urwid.Padding(w, ('fixed left',2), ('fixed right',2))
- w = urwid.Filler(w, ('fixed top',1), ('fixed bottom',1))
- w = urwid.AttrWrap(w, 'body')
-
- if title:
- w = urwid.Frame(w)
- w.header = urwid.Text( ('title', title) )
-
- # "shadow" effect
- w = urwid.Columns( [w, ('fixed', 1, urwid.AttrWrap( urwid.Filler(urwid.Text(('border',' ')), "top") ,'shadow'))])
- w = urwid.Frame( w, footer = urwid.AttrWrap(urwid.Text(('border',' ')),'shadow'))
- # outermost border area
- w = urwid.Padding(w, 'center', width )
- w = urwid.Filler(w, 'middle', height )
- w = urwid.AttrWrap( w, 'border' )
- self.view = w
-
- def add_buttons(self, buttons, default=0, vertical=False):
- l = []
- for name, exitcode in buttons:
- if exitcode == '---':
- # Separator is just a text label
- b = urwid.Text(name)
- b = urwid.AttrWrap( b, 'scrolllabel' )
- else:
- b = urwid.Button( name, self.button_press )
- b.exitcode = exitcode
- b = urwid.AttrWrap( b, 'selectable','focus' )
- l.append( b )
-
- if vertical:
- box = urwid.ListBox(l)
- box.set_focus(default or 0)
- self.buttons = urwid.Frame(urwid.AttrWrap(box, 'selectable'))
- self.frame.footer = urwid.BoxAdapter(self.buttons, min(len(l), 10))
- else:
- self.buttons = urwid.GridFlow(l, 12, 3, 1, 'center')
- self.buttons.set_focus(default or 0)
- self.frame.footer = urwid.Pile( [ urwid.Divider(), self.buttons ],
- focus_item = 1 )
-
- def button_press(self, button):
- raise buttonpush, button.exitcode
-
- def run(self):
- #self.ui.set_mouse_tracking()
- size = self.ui.get_cols_rows()
- try:
- while True:
- canvas = self.view.render( size, focus=True )
- self.ui.draw_screen( size, canvas )
- keys = None
- while not keys:
- keys = self.ui.get_input()
- for k in keys:
- if urwid.is_mouse_event(k):
- event, button, col, row = k
- self.view.mouse_event( size,
- event, button, col, row,
- focus=True)
- if k == 'window resize':
- size = self.ui.get_cols_rows()
- k = self.view.keypress( size, k )
- if k:
- self.unhandled_key( size, k)
- except buttonpush, e:
- return self.on_exit( e.args[0] )
-
- def on_exit(self, exitcode):
- return exitcode
-
- def unhandled_key(self, size, k):
- if k in ('tab', 'shift tab'):
- focus = self.frame.focus_part
- if focus == 'footer':
- self.frame.set_focus('body')
- else:
- self.frame.set_focus('footer')
-
- if k in ('up','page up', 'down', 'page down'):
- if self.scrollmode:
- self.frame.set_focus('body')
- self.body.keypress(size, k)
- elif k in ('up', 'page up'):
- self.frame.set_focus('body')
- else:
- self.frame.set_focus('footer')
-
- if k == 'enter':
- # pass enter to the "ok" button
- self.frame.set_focus('footer')
- self.view.keypress( size, k )
-
- def main(self, ui=None):
- if ui:
- self.ui = ui
- else:
- self.ui = initialize_urwid_ui()
- return self.ui.run_wrapper(self.run)
-
-class displaybox(dialog):
- def show(self, ui=None):
- if ui:
- self.ui = ui
- else:
- self.ui = initialize_urwid_ui()
- size = self.ui.get_cols_rows()
- canvas = self.view.render( size, focus=True )
- self.ui.start()
- self.ui.draw_screen( size, canvas )
- self.ui.stop()
-
-class textentry(dialog):
- def __init__(self, text, width=None, height=None, multiline=False,
- title=''):
- self.edit = urwid.Edit(multiline=multiline)
- body = urwid.ListBox([self.edit])
- body = urwid.AttrWrap(body, 'selectable', 'focustext')
- if not multiline:
- body = urwid.Pile( [('fixed', 1, body), urwid.Divider()] )
- body = urwid.Filler(body)
-
- dialog.__init__(self, text, body, width, height, title)
-
- self.frame.set_focus('body')
-
- def on_exit(self, exitcode):
- return exitcode, self.edit.get_edit_text()
-
-class listdialog(dialog):
- def __init__(self, text, widgets, has_default=False, width=None,
- height=None, title='', buttonwidth=12):
- l = []
- self.items = []
- for (w, label) in widgets:
- self.items.append(w)
- if label:
- w = urwid.Columns( [('fixed', buttonwidth, w),
- urwid.Text(label)], 2 )
- w = urwid.AttrWrap(w, 'selectable','focus')
- l.append(w)
-
- lb = urwid.ListBox(l)
- lb = urwid.AttrWrap( lb, "selectable" )
- dialog.__init__(self, text, height=height, width=width, body=lb,
- title=title)
-
- self.frame.set_focus('body')
-
- def on_exit(self, exitcode):
- """Print the tag of the item selected."""
- if exitcode:
- return exitcode, None
-
- for i in self.items:
- if hasattr(i, 'get_state') and i.get_state():
- return exitcode, i.get_label()
- return exitcode, None
-
-class checklistdialog(listdialog):
- def on_exit(self, exitcode):
- """
- Mimick dialog(1)'s --checklist exit.
- Put each checked item in double quotes with a trailing space.
- """
- if exitcode:
- return exitcode, []
-
- l = []
- for i in self.items:
- if i.get_state():
- l.append(i.get_label())
- return exitcode, l
-
-def display_message(message, *args, **kwargs):
- if args:
- message = message % tuple(args)
-
- if 'title' in kwargs:
- title = kwargs['title']
- else:
- title = ''
-
- if 'ui' in kwargs:
- ui = kwargs['ui']
- else:
- ui = None
-
- # Rewrap the message
- chunks = re.split('\n\n+', message)
- chunks = [re.sub(r'\s+', ' ', x).strip() for x in chunks]
- message = '\n\n'.join(chunks).strip()
-
- box = displaybox('', long_message=message, title=title or VERSION)
- box.show(ui)
-
-def long_message(message, *args, **kwargs):
- if args:
- message = message % tuple(args)
-
- if 'title' in kwargs:
- title = kwargs['title']
- else:
- title = ''
-
- if 'ui' in kwargs:
- ui = kwargs['ui']
- else:
- ui = None
-
- # Rewrap the message
- chunks = re.split('\n\n+', message)
- chunks = [re.sub(r'\s+', ' ', x).strip() for x in chunks]
- message = '\n\n'.join(chunks).strip()
-
- box = dialog('', long_message=message, title=title or VERSION)
- box.add_buttons([ ("OK", 0) ])
- box.main(ui)
-
-final_message = long_message
-display_report = long_message
-
-def select_options(msg, ok, help=None, allow_numbers=False, nowrap=False,
- ui=None, title=None):
- box = dialog('', long_message=msg, height=('relative', 80),
- title=title or VERSION)
- if not help:
- help = {}
-
- buttons = []
- default = None
- for i, option in enumerate(ok):
- if option.isupper():
- default = i
- option = option.lower()
- buttons.append( (help.get(option, option), option) )
-
- box.add_buttons(buttons, default, vertical=True)
- result = box.main(ui)
- return result
-
-def yes_no(msg, yeshelp, nohelp, default=True, nowrap=False, ui=None):
- box = dialog('', long_message=msg+"?", title=VERSION)
- box.add_buttons([ ('Yes', True), ('No', False) ], default=1-int(default))
- result = box.main(ui)
- return result
-
-def get_string(prompt, options=None, title=None, force_prompt=False,
- default='', ui=None):
- if title:
- title = '%s: %s' % (VERSION, title)
- else:
- title = VERSION
-
- box = textentry(prompt, title=title)
- box.add_buttons([ ("OK", 0) ])
- code, text = box.main(ui)
- return text or default
-
-def get_multiline(prompt, options=None, title=None, force_prompt=False,
- ui=None):
- if title:
- title = '%s: %s' % (VERSION, title)
- else:
- title = VERSION
-
- box = textentry(prompt, multiline=True)
- box.add_buttons([ ("OK", 0) ])
- code, text = box.main(ui)
- l = text.split('\n')
- return l
-
-def get_password(prompt=None):
- return getpass.getpass(prompt)
-
-def menu(par, options, prompt, default=None, title=None, any_ok=False,
- order=None, extras=None, multiple=False, empty_ok=False, ui=None,
- oklabel='Ok', cancellabel='Cancel', quitlabel=None):
- if not extras:
- extras = []
- else:
- extras = list(extras)
-
- if not default:
- default = ''
-
- if title:
- title = '%s: %s' % (VERSION, title)
- else:
- title = VERSION
-
- if isinstance(options, dict):
- options = options.copy()
- # Convert to a list
- if order:
- olist = []
- for key in order:
- if options.has_key(key):
- olist.append( (key, options[key]) )
- del options[key]
-
- # Append anything out of order
- options = options.items()
- options.sort()
- for option in options:
- olist.append( option )
- options = olist
- else:
- options = options.items()
- options.sort()
-
- opts = []
- for option, desc in options:
- if desc:
- opts.append((option, re.sub(r'\s+', ' ', desc)))
- else:
- opts.append((option, desc))
- options = opts
-
- if multiple:
- widgets = [(urwid.CheckBox(option, state=(option == default)),
- desc or '') for (option, desc) in options]
- box = checklistdialog(par, widgets, height=('relative', 80),
- title=title)
- if quitlabel:
- box.add_buttons( [(oklabel, 0), (cancellabel, -1),
- (quitlabel, -2)] )
- else:
- box.add_buttons( [(oklabel, 0), (cancellabel, -1)] )
- result, chosen = box.main(ui)
- if result < 0:
- return []
- return chosen
-
- # Single menu option only
- def label_button(option, desc):
- return option
-
- widgets = []
- rlist = []
- for option, desc in options:
- if option == '---':
- # Separator is just a text label
- b = urwid.Text(desc)
- b = urwid.AttrWrap( b, 'scrolllabel' )
- desc = ''
- else:
- b = urwid.RadioButton( rlist, label_button(option, desc) )
- b.exitcode = option
- b = urwid.AttrWrap( b, 'selectable','focus' )
- widgets.append((b, desc))
-
-## if any_ok:
-## editbox = urwid.Edit(multiline=False)
-## e = urwid.ListBox([editbox])
-## e = urwid.AttrWrap(e, 'selectable', 'focustext')
-## e = urwid.Pile( [('fixed', 1, e)] )
-## e = urwid.Filler(e)
-## widgets.append((e, None))
-
- box = listdialog(par, widgets, height=('relative', 80),
- title=title, buttonwidth=12)
- if quitlabel:
- box.add_buttons( [(oklabel, 0), (cancellabel, -1), (quitlabel, -2)] )
- else:
- box.add_buttons( [(oklabel, 0), (cancellabel, -1)] )
- focus = 0
- if default:
- for i, opt in enumerate(options):
- if opt[0] == default:
- focus = i
- break
-
- result, chosen = box.main(ui)
- if result < 0:
- return result
-
- return chosen
-
-# A real file dialog would be nice here
-def get_filename(prompt, title=None, force_prompt=False, default=''):
- return get_string(prompt, title=title, force_prompt=force_prompt,
- default=default)
-
-def select_multiple(par, options, prompt, title=None, order=None, extras=None):
- return menu(par, options, prompt, title=title, order=order, extras=extras,
- multiple=True, empty_ok=False)
-
-# Things that are very UI dependent go here
-def show_report(number, system, mirrors,
- http_proxy, screen=None, queryonly=False, title='',
- archived='no'):
- import debianbts
-
- ui = screen
- if not ui:
- ui = initialize_urwid_ui()
-
- sysinfo = debianbts.SYSTEMS[system]
- display_message('Retrieving report #%d from %s bug tracking system...',
- number, sysinfo['name'], title=title, ui=ui)
-
- info = debianbts.get_report(number, system, mirrors=mirrors,
- http_proxy=http_proxy, archived=archived)
- if not info:
- long_message('Bug report #%d not found.', number, title=title, ui=ui)
- return
-
- options = dict(o='Ok', d='More details (launch browser)',
- m='Submit more information', q='Quit')
- valid = 'Odmq'
-
- while 1:
- (bugtitle, bodies) = info
- body = bodies[0]
-
- r = select_options(body, valid, title=bugtitle, ui=ui, help=options)
- ui = None
- if not r or (r == 'o'):
- break
- elif r == 'q':
- return -1
- elif r == 'm':
- return number
-
- launch_browser(debianbts.get_report_url(system, number, archived))
- return
-
-def handle_bts_query(package, bts, mirrors=None, http_proxy="",
- queryonly=False, screen=None, title="", archived='no',
- source=False, version=None):
- import debianbts
-
- sysinfo = debianbts.SYSTEMS[bts]
- root = sysinfo.get('btsroot')
- if not root:
- ewrite("%s bug tracking system has no web URL; bypassing query.\n",
- sysinfo['name'])
- return
-
- ui = screen
- if not ui:
- ui = initialize_urwid_ui()
-
- if isinstance(package, basestring):
- pkgname = package
- if source:
- pkgname += ' (source)'
-
- display_message('Querying %s bug tracking system for reports on %s',
- debianbts.SYSTEMS[bts]['name'], pkgname,
- ui=ui, title=title)
- else:
- display_message('Querying %s bug tracking system for reports %s',
- debianbts.SYSTEMS[bts]['name'],
- ' '.join([str(x) for x in package]), ui=ui,title=title)
-
- result = None
- try:
- (count, sectitle, hierarchy) = debianbts.get_reports(
- package, bts, mirrors=mirrors, version=version,
- http_proxy=http_proxy, archived=archived, source=source)
-
- if not count:
- ui.run_wrapper(nullfunc)
- if hierarchy == None:
- raise NoPackage
- else:
- raise NoBugs
- else:
- if count > 1:
- sectitle = '%d bug reports found' % (count,)
- else:
- sectitle = '%d bug report found' % (count,)
-
- buglist = []
- for (t, bugs) in hierarchy:
- bcount = len(bugs)
- buglist.append( ('---', t) )
- for bug in bugs:
- bits = re.split(r'[: ]', bug[1:], 1)
- if len(bits) > 1:
- tag, info = bits
- info = info.strip()
- if not info:
- info = '(no subject)'
- else:
- tag = bug[1:]
- info = '(no subject)'
- buglist.append( (tag, info) )
-
- p = buglist[1][0]
- #scr.popWindow()
- if queryonly:
- cancellabel = 'Exit'
- quitlabel = None
- else:
- cancellabel = 'Continue'
- quitlabel='Quit'
-
- while True:
- info = menu('Select a bug to read the report:', buglist,
- '', ui=ui, title=sectitle, default=p,
- oklabel='Get info',
- cancellabel=cancellabel,
- quitlabel=quitlabel)
- ui = None
- if info < 0:
- if info == -1:
- result = None
- else:
- result = info
- break
- else:
- p = info
- res = show_report(int(p), bts, mirrors, http_proxy,
- queryonly=queryonly)
- if res:
- result = res
- break
-
- except (IOError, NoNetwork):
- ui.run_wrapper(nullfunc)
- long_message('Unable to connect to %s BTS.', sysinfo['name'],
- title=title)
- except NoPackage:
- ui.run_wrapper(nullfunc)
- long_message('No record of this package found.', title=title)
- raise NoPackage
-
- if result and result < 0:
- raise NoReport
-
- return result
-
-palette = [
- ('body','black','light gray', 'standout'),
- ('border','black','dark blue'),
- ('shadow','white','black'),
- ('selectable','black', 'dark cyan'),
- ('focus','white','dark blue','bold'),
- ('focustext','light gray','dark blue'),
- ('title', 'dark red', 'light gray'),
- ('scrolllabel', 'white', 'dark cyan'),
- ]
-
-def test():
- import time
-
- fp = sys.stdout
-
- long_message('This is a test. This is only a test.\nPlease do not adjust your set.')
- time.sleep(1)
-## output = get_string('Tell me your name, biatch.')
-## print >> fp, output
-## output = get_multiline('List all of your aliases now.')
-## print >> fp, output
-## result = select_options('This is really lame', 'ynM', {
-## 'y' : 'You bet', 'n' : 'Never!', 'm' : 'Maybe'})
-## print >> fp, result
-## yn = yes_no('Do you like green eggs and ham?', 'Yes sireee', 'No way!')
-## print >> fp, yn
-
- mailers = [(x, '') for x in reportbug.MUA.keys()]
- mailers.sort()
- mailer = menu('Choose a mailer for your report', mailers,
- 'Select mailer: ', default='mutt', empty_ok=True)
- print >> fp, mailer
-
- import debianbts
-
- tags = debianbts.TAGS.copy()
- tags.update(debianbts.CRITICAL_TAGS)
- tagorder = debianbts.TAGLIST + debianbts.CRITICAL_TAGLIST
-
- taglist = select_multiple(
- 'Do any of the following apply to this report?', tags,
- 'Please select tags: ', order=tagorder,
- extras=debianbts.EXTRA_TAGS)
- print >> fp, taglist
-
-if __name__ == '__main__':
- test()
Copied: trunk/reportbug/ui/urwid_ui.py (from rev 640, trunk/reportbug/ui/urwid.py)
===================================================================
--- trunk/reportbug/ui/urwid_ui.py (rev 0)
+++ trunk/reportbug/ui/urwid_ui.py 2008-08-31 22:54:15 UTC (rev 641)
@@ -0,0 +1,685 @@
+# urwid user interface for reportbug
+# Written by Chris Lawrence <lawrencc at debian.org>
+# (C) 2006-08 Chris Lawrence
+#
+# This program is freely distributable per the following license:
+#
+## Permission to use, copy, modify, and distribute this software and its
+## documentation for any purpose and without fee is hereby granted,
+## provided that the above copyright notice appears in all copies and that
+## both that copyright notice and this permission notice appear in
+## supporting documentation.
+##
+## I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+## BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+## DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+## WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+## ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+#
+# Portions of this file are licensed under the Lesser GNU Public License
+# (LGPL) Version 2.1 or later. On Debian systems, this license is available
+# in /usr/share/common-licenses/LGPL
+#
+# $Id: reportbug_ui_urwid.py,v 1.3.2.19 2007-04-17 20:02:41 lawrencc Exp $
+
+import sys
+import re
+import getpass
+from reportbug.exceptions import (
+ UINotImportable,
+ NoPackage, NoBugs, NoNetwork, NoReport,
+ )
+from reportbug.urlutils import launch_browser
+from text_ui import (
+ ewrite,
+ spawn_editor,
+ )
+from reportbug import VERSION
+
+try:
+ import urwid.raw_display
+ import urwid
+except ImportError:
+ raise UINotImportable, 'Please install the python-urwid package to use this interface.'
+
+ISATTY = sys.stdin.isatty()
+
+log_message = ewrite
+display_failure = ewrite
+
+# Start a urwid session
+def initialize_urwid_ui():
+ ui = urwid.raw_display.Screen()
+ ui.register_palette(palette)
+ # Improve responsiveness of UI
+ ui.set_input_timeouts(max_wait=0.1)
+ return ui
+
+# Empty function to satisfy ui.run_wrapper()
+def nullfunc():
+ pass
+
+# Widgets ripped mercilessly from urwid examples (dialog.py)
+class buttonpush(Exception):
+ pass
+
+def button_press(button):
+ raise buttonpush, button.exitcode
+
+class SelectableText(urwid.Edit):
+ def valid_char(self, ch):
+ return False
+
+class dialog(object):
+ def __init__(self, message, body=None, width=None, height=None,
+ title='', long_message=''):
+ self.body = body
+
+ self.scrollmode=False
+ if not body:
+ if long_message:
+ box = SelectableText(edit_text=long_message)
+ box.set_edit_pos(0)
+ self.body = body = urwid.ListBox([box])
+ self.scrollmode=True
+ else:
+ self.body = body = urwid.Filler(urwid.Divider(), 'top')
+
+ if not width:
+ width = ('relative', 80)
+
+ if not height:
+ height = ('relative', 80)
+
+ self.frame = urwid.Frame(body, focus_part='footer')
+ if message:
+ self.frame.header = urwid.Pile([urwid.Text(message),
+ urwid.Divider()])
+
+ w = self.frame
+ # pad area around listbox
+ w = urwid.Padding(w, ('fixed left',2), ('fixed right',2))
+ w = urwid.Filler(w, ('fixed top',1), ('fixed bottom',1))
+ w = urwid.AttrWrap(w, 'body')
+
+ if title:
+ w = urwid.Frame(w)
+ w.header = urwid.Text( ('title', title) )
+
+ # "shadow" effect
+ w = urwid.Columns( [w, ('fixed', 1, urwid.AttrWrap( urwid.Filler(urwid.Text(('border',' ')), "top") ,'shadow'))])
+ w = urwid.Frame( w, footer = urwid.AttrWrap(urwid.Text(('border',' ')),'shadow'))
+ # outermost border area
+ w = urwid.Padding(w, 'center', width )
+ w = urwid.Filler(w, 'middle', height )
+ w = urwid.AttrWrap( w, 'border' )
+ self.view = w
+
+ def add_buttons(self, buttons, default=0, vertical=False):
+ l = []
+ for name, exitcode in buttons:
+ if exitcode == '---':
+ # Separator is just a text label
+ b = urwid.Text(name)
+ b = urwid.AttrWrap( b, 'scrolllabel' )
+ else:
+ b = urwid.Button( name, self.button_press )
+ b.exitcode = exitcode
+ b = urwid.AttrWrap( b, 'selectable','focus' )
+ l.append( b )
+
+ if vertical:
+ box = urwid.ListBox(l)
+ box.set_focus(default or 0)
+ self.buttons = urwid.Frame(urwid.AttrWrap(box, 'selectable'))
+ self.frame.footer = urwid.BoxAdapter(self.buttons, min(len(l), 10))
+ else:
+ self.buttons = urwid.GridFlow(l, 12, 3, 1, 'center')
+ self.buttons.set_focus(default or 0)
+ self.frame.footer = urwid.Pile( [ urwid.Divider(), self.buttons ],
+ focus_item = 1 )
+
+ def button_press(self, button):
+ raise buttonpush, button.exitcode
+
+ def run(self):
+ #self.ui.set_mouse_tracking()
+ size = self.ui.get_cols_rows()
+ try:
+ while True:
+ canvas = self.view.render( size, focus=True )
+ self.ui.draw_screen( size, canvas )
+ keys = None
+ while not keys:
+ keys = self.ui.get_input()
+ for k in keys:
+ if urwid.is_mouse_event(k):
+ event, button, col, row = k
+ self.view.mouse_event( size,
+ event, button, col, row,
+ focus=True)
+ if k == 'window resize':
+ size = self.ui.get_cols_rows()
+ k = self.view.keypress( size, k )
+ if k:
+ self.unhandled_key( size, k)
+ except buttonpush, e:
+ return self.on_exit( e.args[0] )
+
+ def on_exit(self, exitcode):
+ return exitcode
+
+ def unhandled_key(self, size, k):
+ if k in ('tab', 'shift tab'):
+ focus = self.frame.focus_part
+ if focus == 'footer':
+ self.frame.set_focus('body')
+ else:
+ self.frame.set_focus('footer')
+
+ if k in ('up','page up', 'down', 'page down'):
+ if self.scrollmode:
+ self.frame.set_focus('body')
+ self.body.keypress(size, k)
+ elif k in ('up', 'page up'):
+ self.frame.set_focus('body')
+ else:
+ self.frame.set_focus('footer')
+
+ if k == 'enter':
+ # pass enter to the "ok" button
+ self.frame.set_focus('footer')
+ self.view.keypress( size, k )
+
+ def main(self, ui=None):
+ if ui:
+ self.ui = ui
+ else:
+ self.ui = initialize_urwid_ui()
+ return self.ui.run_wrapper(self.run)
+
+class displaybox(dialog):
+ def show(self, ui=None):
+ if ui:
+ self.ui = ui
+ else:
+ self.ui = initialize_urwid_ui()
+ size = self.ui.get_cols_rows()
+ canvas = self.view.render( size, focus=True )
+ self.ui.start()
+ self.ui.draw_screen( size, canvas )
+ self.ui.stop()
+
+class textentry(dialog):
+ def __init__(self, text, width=None, height=None, multiline=False,
+ title=''):
+ self.edit = urwid.Edit(multiline=multiline)
+ body = urwid.ListBox([self.edit])
+ body = urwid.AttrWrap(body, 'selectable', 'focustext')
+ if not multiline:
+ body = urwid.Pile( [('fixed', 1, body), urwid.Divider()] )
+ body = urwid.Filler(body)
+
+ dialog.__init__(self, text, body, width, height, title)
+
+ self.frame.set_focus('body')
+
+ def on_exit(self, exitcode):
+ return exitcode, self.edit.get_edit_text()
+
+class listdialog(dialog):
+ def __init__(self, text, widgets, has_default=False, width=None,
+ height=None, title='', buttonwidth=12):
+ l = []
+ self.items = []
+ for (w, label) in widgets:
+ self.items.append(w)
+ if label:
+ w = urwid.Columns( [('fixed', buttonwidth, w),
+ urwid.Text(label)], 2 )
+ w = urwid.AttrWrap(w, 'selectable','focus')
+ l.append(w)
+
+ lb = urwid.ListBox(l)
+ lb = urwid.AttrWrap( lb, "selectable" )
+ dialog.__init__(self, text, height=height, width=width, body=lb,
+ title=title)
+
+ self.frame.set_focus('body')
+
+ def on_exit(self, exitcode):
+ """Print the tag of the item selected."""
+ if exitcode:
+ return exitcode, None
+
+ for i in self.items:
+ if hasattr(i, 'get_state') and i.get_state():
+ return exitcode, i.get_label()
+ return exitcode, None
+
+class checklistdialog(listdialog):
+ def on_exit(self, exitcode):
+ """
+ Mimick dialog(1)'s --checklist exit.
+ Put each checked item in double quotes with a trailing space.
+ """
+ if exitcode:
+ return exitcode, []
+
+ l = []
+ for i in self.items:
+ if i.get_state():
+ l.append(i.get_label())
+ return exitcode, l
+
+def display_message(message, *args, **kwargs):
+ if args:
+ message = message % tuple(args)
+
+ if 'title' in kwargs:
+ title = kwargs['title']
+ else:
+ title = ''
+
+ if 'ui' in kwargs:
+ ui = kwargs['ui']
+ else:
+ ui = None
+
+ # Rewrap the message
+ chunks = re.split('\n\n+', message)
+ chunks = [re.sub(r'\s+', ' ', x).strip() for x in chunks]
+ message = '\n\n'.join(chunks).strip()
+
+ box = displaybox('', long_message=message, title=title or VERSION)
+ box.show(ui)
+
+def long_message(message, *args, **kwargs):
+ if args:
+ message = message % tuple(args)
+
+ if 'title' in kwargs:
+ title = kwargs['title']
+ else:
+ title = ''
+
+ if 'ui' in kwargs:
+ ui = kwargs['ui']
+ else:
+ ui = None
+
+ # Rewrap the message
+ chunks = re.split('\n\n+', message)
+ chunks = [re.sub(r'\s+', ' ', x).strip() for x in chunks]
+ message = '\n\n'.join(chunks).strip()
+
+ box = dialog('', long_message=message, title=title or VERSION)
+ box.add_buttons([ ("OK", 0) ])
+ box.main(ui)
+
+final_message = long_message
+display_report = long_message
+
+def select_options(msg, ok, help=None, allow_numbers=False, nowrap=False,
+ ui=None, title=None):
+ box = dialog('', long_message=msg, height=('relative', 80),
+ title=title or VERSION)
+ if not help:
+ help = {}
+
+ buttons = []
+ default = None
+ for i, option in enumerate(ok):
+ if option.isupper():
+ default = i
+ option = option.lower()
+ buttons.append( (help.get(option, option), option) )
+
+ box.add_buttons(buttons, default, vertical=True)
+ result = box.main(ui)
+ return result
+
+def yes_no(msg, yeshelp, nohelp, default=True, nowrap=False, ui=None):
+ box = dialog('', long_message=msg+"?", title=VERSION)
+ box.add_buttons([ ('Yes', True), ('No', False) ], default=1-int(default))
+ result = box.main(ui)
+ return result
+
+def get_string(prompt, options=None, title=None, force_prompt=False,
+ default='', ui=None):
+ if title:
+ title = '%s: %s' % (VERSION, title)
+ else:
+ title = VERSION
+
+ box = textentry(prompt, title=title)
+ box.add_buttons([ ("OK", 0) ])
+ code, text = box.main(ui)
+ return text or default
+
+def get_multiline(prompt, options=None, title=None, force_prompt=False,
+ ui=None):
+ if title:
+ title = '%s: %s' % (VERSION, title)
+ else:
+ title = VERSION
+
+ box = textentry(prompt, multiline=True)
+ box.add_buttons([ ("OK", 0) ])
+ code, text = box.main(ui)
+ l = text.split('\n')
+ return l
+
+def get_password(prompt=None):
+ return getpass.getpass(prompt)
+
+def menu(par, options, prompt, default=None, title=None, any_ok=False,
+ order=None, extras=None, multiple=False, empty_ok=False, ui=None,
+ oklabel='Ok', cancellabel='Cancel', quitlabel=None):
+ if not extras:
+ extras = []
+ else:
+ extras = list(extras)
+
+ if not default:
+ default = ''
+
+ if title:
+ title = '%s: %s' % (VERSION, title)
+ else:
+ title = VERSION
+
+ if isinstance(options, dict):
+ options = options.copy()
+ # Convert to a list
+ if order:
+ olist = []
+ for key in order:
+ if options.has_key(key):
+ olist.append( (key, options[key]) )
+ del options[key]
+
+ # Append anything out of order
+ options = options.items()
+ options.sort()
+ for option in options:
+ olist.append( option )
+ options = olist
+ else:
+ options = options.items()
+ options.sort()
+
+ opts = []
+ for option, desc in options:
+ if desc:
+ opts.append((option, re.sub(r'\s+', ' ', desc)))
+ else:
+ opts.append((option, desc))
+ options = opts
+
+ if multiple:
+ widgets = [(urwid.CheckBox(option, state=(option == default)),
+ desc or '') for (option, desc) in options]
+ box = checklistdialog(par, widgets, height=('relative', 80),
+ title=title)
+ if quitlabel:
+ box.add_buttons( [(oklabel, 0), (cancellabel, -1),
+ (quitlabel, -2)] )
+ else:
+ box.add_buttons( [(oklabel, 0), (cancellabel, -1)] )
+ result, chosen = box.main(ui)
+ if result < 0:
+ return []
+ return chosen
+
+ # Single menu option only
+ def label_button(option, desc):
+ return option
+
+ widgets = []
+ rlist = []
+ for option, desc in options:
+ if option == '---':
+ # Separator is just a text label
+ b = urwid.Text(desc)
+ b = urwid.AttrWrap( b, 'scrolllabel' )
+ desc = ''
+ else:
+ b = urwid.RadioButton( rlist, label_button(option, desc) )
+ b.exitcode = option
+ b = urwid.AttrWrap( b, 'selectable','focus' )
+ widgets.append((b, desc))
+
+## if any_ok:
+## editbox = urwid.Edit(multiline=False)
+## e = urwid.ListBox([editbox])
+## e = urwid.AttrWrap(e, 'selectable', 'focustext')
+## e = urwid.Pile( [('fixed', 1, e)] )
+## e = urwid.Filler(e)
+## widgets.append((e, None))
+
+ box = listdialog(par, widgets, height=('relative', 80),
+ title=title, buttonwidth=12)
+ if quitlabel:
+ box.add_buttons( [(oklabel, 0), (cancellabel, -1), (quitlabel, -2)] )
+ else:
+ box.add_buttons( [(oklabel, 0), (cancellabel, -1)] )
+ focus = 0
+ if default:
+ for i, opt in enumerate(options):
+ if opt[0] == default:
+ focus = i
+ break
+
+ result, chosen = box.main(ui)
+ if result < 0:
+ return result
+
+ return chosen
+
+# A real file dialog would be nice here
+def get_filename(prompt, title=None, force_prompt=False, default=''):
+ return get_string(prompt, title=title, force_prompt=force_prompt,
+ default=default)
+
+def select_multiple(par, options, prompt, title=None, order=None, extras=None):
+ return menu(par, options, prompt, title=title, order=order, extras=extras,
+ multiple=True, empty_ok=False)
+
+# Things that are very UI dependent go here
+def show_report(number, system, mirrors,
+ http_proxy, screen=None, queryonly=False, title='',
+ archived='no'):
+ import debianbts
+
+ ui = screen
+ if not ui:
+ ui = initialize_urwid_ui()
+
+ sysinfo = debianbts.SYSTEMS[system]
+ display_message('Retrieving report #%d from %s bug tracking system...',
+ number, sysinfo['name'], title=title, ui=ui)
+
+ info = debianbts.get_report(number, system, mirrors=mirrors,
+ http_proxy=http_proxy, archived=archived)
+ if not info:
+ long_message('Bug report #%d not found.', number, title=title, ui=ui)
+ return
+
+ options = dict(o='Ok', d='More details (launch browser)',
+ m='Submit more information', q='Quit')
+ valid = 'Odmq'
+
+ while 1:
+ (bugtitle, bodies) = info
+ body = bodies[0]
+
+ r = select_options(body, valid, title=bugtitle, ui=ui, help=options)
+ ui = None
+ if not r or (r == 'o'):
+ break
+ elif r == 'q':
+ return -1
+ elif r == 'm':
+ return number
+
+ launch_browser(debianbts.get_report_url(system, number, archived))
+ return
+
+def handle_bts_query(package, bts, mirrors=None, http_proxy="",
+ queryonly=False, screen=None, title="", archived='no',
+ source=False, version=None):
+ import debianbts
+
+ sysinfo = debianbts.SYSTEMS[bts]
+ root = sysinfo.get('btsroot')
+ if not root:
+ ewrite("%s bug tracking system has no web URL; bypassing query.\n",
+ sysinfo['name'])
+ return
+
+ ui = screen
+ if not ui:
+ ui = initialize_urwid_ui()
+
+ if isinstance(package, basestring):
+ pkgname = package
+ if source:
+ pkgname += ' (source)'
+
+ display_message('Querying %s bug tracking system for reports on %s',
+ debianbts.SYSTEMS[bts]['name'], pkgname,
+ ui=ui, title=title)
+ else:
+ display_message('Querying %s bug tracking system for reports %s',
+ debianbts.SYSTEMS[bts]['name'],
+ ' '.join([str(x) for x in package]), ui=ui,title=title)
+
+ result = None
+ try:
+ (count, sectitle, hierarchy) = debianbts.get_reports(
+ package, bts, mirrors=mirrors, version=version,
+ http_proxy=http_proxy, archived=archived, source=source)
+
+ if not count:
+ ui.run_wrapper(nullfunc)
+ if hierarchy == None:
+ raise NoPackage
+ else:
+ raise NoBugs
+ else:
+ if count > 1:
+ sectitle = '%d bug reports found' % (count,)
+ else:
+ sectitle = '%d bug report found' % (count,)
+
+ buglist = []
+ for (t, bugs) in hierarchy:
+ bcount = len(bugs)
+ buglist.append( ('---', t) )
+ for bug in bugs:
+ bits = re.split(r'[: ]', bug[1:], 1)
+ if len(bits) > 1:
+ tag, info = bits
+ info = info.strip()
+ if not info:
+ info = '(no subject)'
+ else:
+ tag = bug[1:]
+ info = '(no subject)'
+ buglist.append( (tag, info) )
+
+ p = buglist[1][0]
+ #scr.popWindow()
+ if queryonly:
+ cancellabel = 'Exit'
+ quitlabel = None
+ else:
+ cancellabel = 'Continue'
+ quitlabel='Quit'
+
+ while True:
+ info = menu('Select a bug to read the report:', buglist,
+ '', ui=ui, title=sectitle, default=p,
+ oklabel='Get info',
+ cancellabel=cancellabel,
+ quitlabel=quitlabel)
+ ui = None
+ if info < 0:
+ if info == -1:
+ result = None
+ else:
+ result = info
+ break
+ else:
+ p = info
+ res = show_report(int(p), bts, mirrors, http_proxy,
+ queryonly=queryonly)
+ if res:
+ result = res
+ break
+
+ except (IOError, NoNetwork):
+ ui.run_wrapper(nullfunc)
+ long_message('Unable to connect to %s BTS.', sysinfo['name'],
+ title=title)
+ except NoPackage:
+ ui.run_wrapper(nullfunc)
+ long_message('No record of this package found.', title=title)
+ raise NoPackage
+
+ if result and result < 0:
+ raise NoReport
+
+ return result
+
+palette = [
+ ('body','black','light gray', 'standout'),
+ ('border','black','dark blue'),
+ ('shadow','white','black'),
+ ('selectable','black', 'dark cyan'),
+ ('focus','white','dark blue','bold'),
+ ('focustext','light gray','dark blue'),
+ ('title', 'dark red', 'light gray'),
+ ('scrolllabel', 'white', 'dark cyan'),
+ ]
+
+def test():
+ import time
+
+ fp = sys.stdout
+
+ long_message('This is a test. This is only a test.\nPlease do not adjust your set.')
+ time.sleep(1)
+## output = get_string('Tell me your name, biatch.')
+## print >> fp, output
+## output = get_multiline('List all of your aliases now.')
+## print >> fp, output
+## result = select_options('This is really lame', 'ynM', {
+## 'y' : 'You bet', 'n' : 'Never!', 'm' : 'Maybe'})
+## print >> fp, result
+## yn = yes_no('Do you like green eggs and ham?', 'Yes sireee', 'No way!')
+## print >> fp, yn
+
+ mailers = [(x, '') for x in reportbug.MUA.keys()]
+ mailers.sort()
+ mailer = menu('Choose a mailer for your report', mailers,
+ 'Select mailer: ', default='mutt', empty_ok=True)
+ print >> fp, mailer
+
+ import debianbts
+
+ tags = debianbts.TAGS.copy()
+ tags.update(debianbts.CRITICAL_TAGS)
+ tagorder = debianbts.TAGLIST + debianbts.CRITICAL_TAGLIST
+
+ taglist = select_multiple(
+ 'Do any of the following apply to this report?', tags,
+ 'Please select tags: ', order=tagorder,
+ extras=debianbts.EXTRA_TAGS)
+ print >> fp, taglist
+
+if __name__ == '__main__':
+ test()
Modified: trunk/reportbug/utils.py
===================================================================
--- trunk/reportbug/utils.py 2008-08-31 20:45:18 UTC (rev 640)
+++ trunk/reportbug/utils.py 2008-08-31 22:54:15 UTC (rev 641)
@@ -49,7 +49,7 @@
AVAILABLE_UIS = ['text']
try:
- import ui.urwid
+ import ui.urwid_ui
AVAILABLE_UIS.append('urwid')
except:
pass
More information about the Reportbug-commits
mailing list