http://www.mediawiki.org/wiki/Special:Code/pywikipedia/10043
Revision: 10043 Author: valhallasw Date: 2012-03-26 00:01:16 +0000 (Mon, 26 Mar 2012) Log Message: ----------- Reworked terminal_interface.
Architectural change: cleaner separation between generic code, unix-specific and windows-specific (in two varieties) code
Behavioural change: windows now either inputs/outputs ascii (without ctypes) or full unicode (with ctypes). Yes, no more chcp crap!
Modified Paths: -------------- trunk/pywikipedia/userinterfaces/terminal_interface.py
Added Paths: ----------- trunk/pywikipedia/userinterfaces/terminal_interface_base.py trunk/pywikipedia/userinterfaces/terminal_interface_unix.py trunk/pywikipedia/userinterfaces/terminal_interface_win32.py
Modified: trunk/pywikipedia/userinterfaces/terminal_interface.py =================================================================== --- trunk/pywikipedia/userinterfaces/terminal_interface.py 2012-03-25 23:58:52 UTC (rev 10042) +++ trunk/pywikipedia/userinterfaces/terminal_interface.py 2012-03-26 00:01:16 UTC (rev 10043) @@ -1,255 +1,14 @@ # -*- coding: utf-8 -*- # -# (C) Pywikipedia bot team, 2003-2007 +# (C) Pywikipedia bot team, 2003-2012 # # Distributed under the terms of the MIT license. # __version__ = '$Id$'
-import config, transliteration -import traceback, re, sys -import wikipedia +import sys
-try: - import ctypes - ctypes_found = True -except ImportError: - ctypes_found = False - -transliterator = transliteration.transliterator() - -# TODO: other colors: - #0 = Black - #1 = Blue - #2 = Green - #3 = Aqua - #4 = Red - #5 = Purple - #6 = Yellow - #7 = White - #8 = Gray - #9 = Light Blue - #10 = Light Green - #11 = Light Aqua - #12 = Light Red - #13 = Light Purple - #14 = Light Yellow - #15 = Bright White - -unixColors = { - 'default': chr(27) + '[0m', # Unix end tag to switch back to default - 'lightblue': chr(27) + '[94;1m', # Light Blue start tag - 'lightgreen': chr(27) + '[92;1m', # Light Green start tag - 'lightaqua': chr(27) + '[36;1m', # Light Aqua start tag - 'lightred': chr(27) + '[91;1m', # Light Red start tag - 'lightpurple': chr(27) + '[35;1m', # Light Purple start tag - 'lightyellow': chr(27) + '[33;1m', # Light Yellow start tag -} - -windowsColors = { - 'default': 7, - 'black': 0, - 'blue': 1, - 'green': 2, - 'aqua': 3, - 'red': 4, - 'purple': 5, - 'yellow': 6, - 'lightgray': 7, - 'gray': 8, - 'lightblue': 9, - 'lightgreen': 10, - 'lightaqua': 11, - 'lightred': 12, - 'lightpurple': 13, - 'lightyellow': 14, - 'white': 15, -} - -colorTagR = re.compile('\03{(?P<name>%s)}' % '|'.join(windowsColors.keys())) - -class UI: - def __init__(self): - pass - - def printColorizedInUnix(self, text, targetStream): - lastColor = None - for key, value in unixColors.iteritems(): - text = text.replace('\03{%s}' % key, value) - # just to be sure, reset the color - text += unixColors['default'] - - targetStream.write(text.encode(config.console_encoding, 'replace')) - - def printColorizedInWindows(self, text, targetStream): - """ - This only works in Python 2.5 or higher. - """ - if ctypes_found: - std_out_handle = ctypes.windll.kernel32.GetStdHandle(-11) - # Color tags might be cascaded, e.g. because of transliteration. - # Therefore we need this stack. - colorStack = [] - tagM = True - while tagM: - tagM = colorTagR.search(text) - if tagM: - # print the text up to the tag. - targetStream.write(text[:tagM.start()].encode(config.console_encoding, 'replace')) - newColor = tagM.group('name') - if newColor == 'default': - if len(colorStack) > 0: - colorStack.pop() - if len(colorStack) > 0: - lastColor = colorStack[-1] - else: - lastColor = 'default' - ctypes.windll.kernel32.SetConsoleTextAttribute(std_out_handle, windowsColors[lastColor]) - else: - colorStack.append(newColor) - # set the new color - ctypes.windll.kernel32.SetConsoleTextAttribute(std_out_handle, windowsColors[newColor]) - text = text[tagM.end():] - # print the rest of the text - targetStream.write(text.encode(config.console_encoding, 'replace')) - # just to be sure, reset the color - ctypes.windll.kernel32.SetConsoleTextAttribute(std_out_handle, windowsColors['default']) - else: - # ctypes is only available since Python 2.5, and we won't - # try to colorize without it. Instead we add *** after the text as a whole - # if anything needed to be colorized. - lines = text.split('\n') - for line in lines: - line, count = colorTagR.subn('', line) - if count > 0: - line += '***' - line += '\n' - targetStream.write(line.encode(config.console_encoding, 'replace')) - - def printColorized(self, text, targetStream): - if config.colorized_output: - if sys.platform == 'win32': - self.printColorizedInWindows(text, targetStream) - else: - self.printColorizedInUnix(text, targetStream) - else: - targetStream.write(text.encode(config.console_encoding, 'replace')) - - def output(self, text, toStdout = False): - """ - If a character can't be displayed in the encoding used by the user's - terminal, it will be replaced with a question mark or by a - transliteration. - """ - if config.transliterate: - # Encode our unicode string in the encoding used by the user's console, - # and decode it back to unicode. Then we can see which characters - # can't be represented in the console encoding. - codecedText = text.encode(config.console_encoding, 'replace').decode(config.console_encoding) - transliteratedText = '' - # Note: A transliteration replacement might be longer than the original - # character, e.g. ч is transliterated to ch. - prev = "-" - for i in xrange(len(codecedText)): - # work on characters that couldn't be encoded, but not on - # original question marks. - if codecedText[i] == '?' and text[i] != u'?': - try: - transliterated = transliterator.transliterate(text[i], default = '?', prev = prev, next = text[i+1]) - except IndexError: - transliterated = transliterator.transliterate(text[i], default = '?', prev = prev, next = ' ') - # transliteration was successful. The replacement - # could consist of multiple letters. - # mark the transliterated letters in yellow. - transliteratedText += '\03{lightyellow}%s\03{default}' % transliterated - transLength = len(transliterated) - # memorize if we replaced a single letter by multiple letters. - if len(transliterated) > 0: - prev = transliterated[-1] - else: - # no need to try to transliterate. - transliteratedText += codecedText[i] - prev = codecedText[i] - text = transliteratedText - - if toStdout: - targetStream = sys.stdout - else: - targetStream = sys.stderr - self.printColorized(text, targetStream) - - def input(self, question, password = False): - """ - Works like raw_input(), but returns a unicode string instead of ASCII. - - Unlike raw_input, this function automatically adds a space after the - question. - """ - - # sound the terminal bell to notify the user - if config.ring_bell: - sys.stdout.write('\07') - # TODO: make sure this is logged as well - self.output(question + ' ') - if password: - import getpass - text = getpass.getpass('') - else: - text = raw_input() - text = unicode(text, config.console_encoding) - return text - - def inputChoice(self, question, options, hotkeys, default = None): - options = options[:] # we don't want to edit the passed parameter - for i in range(len(options)): - option = options[i] - hotkey = hotkeys[i] - # try to mark a part of the option name as the hotkey - m = re.search('[%s%s]' % (hotkey.lower(), hotkey.upper()), option) - if hotkey == default: - caseHotkey = hotkey.upper() - else: - caseHotkey = hotkey - if m: - pos = m.start() - options[i] = '%s[%s]%s' % (option[:pos], caseHotkey, option[pos+1:]) - else: - options[i] = '%s [%s]' % (option, caseHotkey) - # loop until the user entered a valid choice - while True: - prompt = '%s (%s)' % (question, ', '.join(options)) - answer = self.input(prompt) - if answer.lower() in hotkeys or answer.upper() in hotkeys: - return answer - elif default and answer=='': # empty string entered - return default - - def editText(self, text, jumpIndex = None, highlight = None): - """ - Uses a Tkinter edit box because we don't have a console editor - - Parameters: - * text - a Unicode string - * jumpIndex - an integer: position at which to put the caret - * highlight - a substring; each occurence will be highlighted - """ - try: - import gui - except ImportError, e: - print 'Could not load GUI modules: %s' % e - return text - editor = gui.EditBoxWindow() - return editor.edit(text, jumpIndex = jumpIndex, highlight = highlight) - - def askForCaptcha(self, url): - try: - import webbrowser - wikipedia.output(u'Opening CAPTCHA in your web browser...') - if webbrowser.open(url): - return wikipedia.input(u'What is the solution of the CAPTCHA that is shown in your web browser?') - else: - raise - except: - wikipedia.output(u'Error in opening web browser: %s' % sys.exc_info()[0]) - wikipedia.output(u'Please copy this url to your web browser and open it:\n %s' % url) - return wikipedia.input(u'What is the solution of the CAPTCHA at this url ?') +if sys.platform == 'win32': + from terminal_interface_win32 import Win32UI as UI +else: + from terminal_interface_unix import UnixUI as UI \ No newline at end of file
Added: trunk/pywikipedia/userinterfaces/terminal_interface_base.py =================================================================== --- trunk/pywikipedia/userinterfaces/terminal_interface_base.py (rev 0) +++ trunk/pywikipedia/userinterfaces/terminal_interface_base.py 2012-03-26 00:01:16 UTC (rev 10043) @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- +# +# (C) Pywikipedia bot team, 2003-2012 +# +# Distributed under the terms of the MIT license. +# +__version__ = '$Id: terminal_interface.py 10040 2012-03-25 21:26:29Z valhallasw $' + +import config, transliteration +import traceback, re, sys +import wikipedia + +transliterator = transliteration.transliterator() + +colors = [ + 'default', + 'black', + 'blue', + 'green', + 'aqua', + 'red', + 'purple', + 'yellow', + 'lightgray', + 'gray', + 'lightblue', + 'lightgreen', + 'lightaqua', + 'lightred', + 'lightpurple', + 'lightyellow', + 'white', +] + +colorTagR = re.compile('\03{(?P<name>%s)}' % '|'.join(colors)) + +class UI: + def __init__(self): + self.stdin = sys.stdin + self.stdout = sys.stdout + self.stderr = sys.stderr + + def printNonColorized(self, text, targetStream): + # We add *** after the text as a whole if anything needed to be colorized. + lines = text.split('\n') + for line in lines: + line, count = colorTagR.subn('', line) + if count > 0: + line += '***' + line += '\n' + targetStream.write(line.encode(self.encoding, 'replace')) + + printColorized = printNonColorized + + def _print(self, text, targetStream): + if config.colorized_output: + self.printColorized(text, targetStream) + else: + self.printNonColorized(text, targetStream) + + def output(self, text, toStdout = False): + """ + If a character can't be displayed in the encoding used by the user's + terminal, it will be replaced with a question mark or by a + transliteration. + """ + if config.transliterate: + # Encode our unicode string in the encoding used by the user's console, + # and decode it back to unicode. Then we can see which characters + # can't be represented in the console encoding. + codecedText = text.encode(config.console_encoding, 'replace').decode(config.console_encoding) + transliteratedText = '' + # Note: A transliteration replacement might be longer than the original + # character, e.g. ч is transliterated to ch. + prev = "-" + for i in xrange(len(codecedText)): + # work on characters that couldn't be encoded, but not on + # original question marks. + if codecedText[i] == '?' and text[i] != u'?': + try: + transliterated = transliterator.transliterate(text[i], default = '?', prev = prev, next = text[i+1]) + except IndexError: + transliterated = transliterator.transliterate(text[i], default = '?', prev = prev, next = ' ') + # transliteration was successful. The replacement + # could consist of multiple letters. + # mark the transliterated letters in yellow. + transliteratedText += '\03{lightyellow}%s\03{default}' % transliterated + transLength = len(transliterated) + # memorize if we replaced a single letter by multiple letters. + if len(transliterated) > 0: + prev = transliterated[-1] + else: + # no need to try to transliterate. + transliteratedText += codecedText[i] + prev = codecedText[i] + text = transliteratedText + + if toStdout: + targetStream = self.stdout + else: + targetStream = self.stderr + self._print(text, targetStream) + + def _raw_input(self): + return raw_input() + + def input(self, question, password = False): + """ + Works like raw_input(), but returns a unicode string instead of ASCII. + + Unlike raw_input, this function automatically adds a space after the + question. + """ + + # sound the terminal bell to notify the user + if config.ring_bell: + sys.stdout.write('\07') + # TODO: make sure this is logged as well + self.output(question + ' ') + if password: + import getpass + text = getpass.getpass('') + else: + text = self._raw_input() + text = unicode(text, config.console_encoding) + return text + + def inputChoice(self, question, options, hotkeys, default = None): + options = options[:] # we don't want to edit the passed parameter + for i in range(len(options)): + option = options[i] + hotkey = hotkeys[i] + # try to mark a part of the option name as the hotkey + m = re.search('[%s%s]' % (hotkey.lower(), hotkey.upper()), option) + if hotkey == default: + caseHotkey = hotkey.upper() + else: + caseHotkey = hotkey + if m: + pos = m.start() + options[i] = '%s[%s]%s' % (option[:pos], caseHotkey, option[pos+1:]) + else: + options[i] = '%s [%s]' % (option, caseHotkey) + # loop until the user entered a valid choice + while True: + prompt = '%s (%s)' % (question, ', '.join(options)) + answer = self.input(prompt) + if answer.lower() in hotkeys or answer.upper() in hotkeys: + return answer + elif default and answer=='': # empty string entered + return default + + def editText(self, text, jumpIndex = None, highlight = None): + """ + Uses a Tkinter edit box because we don't have a console editor + + Parameters: + * text - a Unicode string + * jumpIndex - an integer: position at which to put the caret + * highlight - a substring; each occurence will be highlighted + """ + try: + import gui + except ImportError, e: + print 'Could not load GUI modules: %s' % e + return text + editor = gui.EditBoxWindow() + return editor.edit(text, jumpIndex = jumpIndex, highlight = highlight) + + def askForCaptcha(self, url): + try: + import webbrowser + wikipedia.output(u'Opening CAPTCHA in your web browser...') + if webbrowser.open(url): + return wikipedia.input(u'What is the solution of the CAPTCHA that is shown in your web browser?') + else: + raise + except: + wikipedia.output(u'Error in opening web browser: %s' % sys.exc_info()[0]) + wikipedia.output(u'Please copy this url to your web browser and open it:\n %s' % url) + return wikipedia.input(u'What is the solution of the CAPTCHA at this url ?')
Property changes on: trunk/pywikipedia/userinterfaces/terminal_interface_base.py ___________________________________________________________________ Added: svn:eol-style + native
Added: trunk/pywikipedia/userinterfaces/terminal_interface_unix.py =================================================================== --- trunk/pywikipedia/userinterfaces/terminal_interface_unix.py (rev 0) +++ trunk/pywikipedia/userinterfaces/terminal_interface_unix.py 2012-03-26 00:01:16 UTC (rev 10043) @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# (C) Pywikipedia bot team, 2003-2012 +# +# Distributed under the terms of the MIT license. +# +__version__ = '$Id: terminal_interface.py 10040 2012-03-25 21:26:29Z valhallasw $' + +from terminal_interface_base import colors + +# TODO: other colors: + #0 = Black + #1 = Blue + #2 = Green + #3 = Aqua + #4 = Red + #5 = Purple + #6 = Yellow + #7 = White + #8 = Gray + #9 = Light Blue + #10 = Light Green + #11 = Light Aqua + #12 = Light Red + #13 = Light Purple + #14 = Light Yellow + #15 = Bright White + +unixColors = { + 'default': chr(27) + '[0m', # Unix end tag to switch back to default + 'lightblue': chr(27) + '[94;1m', # Light Blue start tag + 'lightgreen': chr(27) + '[92;1m', # Light Green start tag + 'lightaqua': chr(27) + '[36;1m', # Light Aqua start tag + 'lightred': chr(27) + '[91;1m', # Light Red start tag + 'lightpurple': chr(27) + '[35;1m', # Light Purple start tag + 'lightyellow': chr(27) + '[33;1m', # Light Yellow start tag +} + +class UnixUI(terminal_interface_base.UI): + def printColorized(self, text, targetStream): + lastColor = None + for key, value in unixColors.iteritems(): + text = text.replace('\03{%s}' % key, value) + # just to be sure, reset the color + text += unixColors['default'] + + targetStream.write(text.encode(config.console_encoding, 'replace')) \ No newline at end of file
Property changes on: trunk/pywikipedia/userinterfaces/terminal_interface_unix.py ___________________________________________________________________ Added: svn:eol-style + native
Added: trunk/pywikipedia/userinterfaces/terminal_interface_win32.py =================================================================== --- trunk/pywikipedia/userinterfaces/terminal_interface_win32.py (rev 0) +++ trunk/pywikipedia/userinterfaces/terminal_interface_win32.py 2012-03-26 00:01:16 UTC (rev 10043) @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# +# (C) Pywikipedia bot team, 2003-2012 +# +# Distributed under the terms of the MIT license. +# +__version__ = '$Id: terminal_interface.py 10040 2012-03-25 21:26:29Z valhallasw $' + +import re +import terminal_interface_base + +try: + import ctypes + ctypes_found = True +except ImportError: + ctypes_found = False + +windowsColors = { + 'default': 7, + 'black': 0, + 'blue': 1, + 'green': 2, + 'aqua': 3, + 'red': 4, + 'purple': 5, + 'yellow': 6, + 'lightgray': 7, + 'gray': 8, + 'lightblue': 9, + 'lightgreen': 10, + 'lightaqua': 11, + 'lightred': 12, + 'lightpurple': 13, + 'lightyellow': 14, + 'white': 15, +} + +colorTagR = re.compile('\03{(?P<name>%s)}' % '|'.join(windowsColors.keys())) + +# Compat for python <= 2.5 +class Win32BaseUI(terminal_interface_base.UI): + def __init__(self): + terminal_interface_base.UI.__init__(self) + self.encoding = 'ascii' + + +class Win32CtypesUI(Win32BaseUI): + def __init__(self): + Win32BaseUI.__init__(self) + from win32_unicode import stdin, stdout, stderr + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.encoding = 'utf-8' + + def printColorized(self, text, targetStream): + std_out_handle = ctypes.windll.kernel32.GetStdHandle(-11) + # Color tags might be cascaded, e.g. because of transliteration. + # Therefore we need this stack. + colorStack = [] + tagM = True + while tagM: + tagM = colorTagR.search(text) + if tagM: + # print the text up to the tag. + targetStream.write(text[:tagM.start()].encode(self.encoding, 'replace')) + newColor = tagM.group('name') + if newColor == 'default': + if len(colorStack) > 0: + colorStack.pop() + if len(colorStack) > 0: + lastColor = colorStack[-1] + else: + lastColor = 'default' + ctypes.windll.kernel32.SetConsoleTextAttribute(std_out_handle, windowsColors[lastColor]) + else: + colorStack.append(newColor) + # set the new color + ctypes.windll.kernel32.SetConsoleTextAttribute(std_out_handle, windowsColors[newColor]) + text = text[tagM.end():] + # print the rest of the text + targetStream.write(text.encode(self.encoding, 'replace')) + # just to be sure, reset the color + ctypes.windll.kernel32.SetConsoleTextAttribute(std_out_handle, windowsColors['default']) + + def _raw_input(self): + data = self.stdin.readline() + if '\x1a' in data: + raise EOFError() + return self.stdin.readline().strip() + +if ctypes_found: + Win32UI = Win32CtypesUI +else: + Win32UI = Win32BaseUI
Property changes on: trunk/pywikipedia/userinterfaces/terminal_interface_win32.py ___________________________________________________________________ Added: svn:eol-style + native
pywikipedia-svn@lists.wikimedia.org