diff options
Diffstat (limited to 'MAFH2/fortuneengine/fortuneengine/pyconsole/pyconsole.py')
-rw-r--r-- | MAFH2/fortuneengine/fortuneengine/pyconsole/pyconsole.py | 637 |
1 files changed, 637 insertions, 0 deletions
diff --git a/MAFH2/fortuneengine/fortuneengine/pyconsole/pyconsole.py b/MAFH2/fortuneengine/fortuneengine/pyconsole/pyconsole.py new file mode 100644 index 0000000..7bb68e4 --- /dev/null +++ b/MAFH2/fortuneengine/fortuneengine/pyconsole/pyconsole.py @@ -0,0 +1,637 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# pyconsole - a simple console for pygame based applications +# +# Copyright (C) 2006 John Schanck +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +import pygame, os, sys +from pygame.locals import * + +import re # Python's Regexp library. Used in pyconsole for parsing +import textwrap # Used for proper word wrapping +from string import ascii_letters +from code import InteractiveConsole # Gives us access to the python interpreter + + +__version__ = "0.7" + +WIDTH=0 +HEIGHT=1 + +OUT = 0 +IN = 1 +ERR = 2 + +PYCONSOLE = 1 +PYTHON = 2 + +path = os.path.abspath(os.path.dirname(__file__)) +font_path = os.path.join(path, "fonts") +cfg_path = os.path.join(path, "pyconsole.cfg") + + +re_token = re.compile(r"""[\"].*?[\"]|[\{].*?[\}]|[\(].*?[\)]|[\[].*?[\]]|\S+""") +re_is_list = re.compile(r'^[{\[(]') +re_is_number = re.compile(r""" + (?x) + [-]?[0][x][0-9a-fA-F]+[lLjJ]? | # Hexadecimal + [-]?[0][0-7]+[lLjJ]? | # Octal + [-]?[\d]+(?:[.][\d]*)?[lLjJ]? # Decimal (Int or float) + """) +re_is_assign = re.compile(r'[$](?P<name>[a-zA-Z_]+\S*)\s*[=]\s*(?P<value>.+)') +re_is_comment = re.compile(r'\s*#.*') +re_is_var = re.compile(r'^[$][a-zA-Z_]+\w*\Z') + + + +class Writable(list): + line_pointer = -1 + def write(self, line): + self.append(str(line)) + def reset(self): + self.__init__() + def readline(self, size=-1): + # Python's interactive help likes to try and call this, which causes the program to crash + # I see no reason to implement interactive help. + raise NotImplementedError + +class ParseError(Exception): + def __init__(self, token): + self.token = token + def at_token(self): + return self.token + +def balanced(t): + stack = [] + pairs = {"\'":"\'", '\"':'\"', "{":"}", "[":"]", "(":")"} + for char in t: + if stack and char == pairs[stack[-1]]: + stack.pop() + elif char in pairs: + stack.append(char) + return not bool(stack) + +class Console: + def __init__(self, screen, rect, functions={}, key_calls={}, vars={}, syntax={}): + if not pygame.display.get_init(): + raise pygame.error, "Display not initialized. Initialize the display before creating a Console" + + if not pygame.font.get_init(): + pygame.font.init() + + self.parent_screen = screen + self.rect = pygame.Rect(rect) + self.size = self.rect.size + + self.user_vars = vars + self.user_syntax = syntax + self.user_namespace = {} + + self.variables = {\ + "bg_alpha":int,\ + "bg_color": list,\ + "txt_color_i": list,\ + "txt_color_o": list,\ + "ps1": str,\ + "ps2": str,\ + "ps3": str,\ + "active": bool,\ + "repeat_rate": list,\ + "preserve_events":bool,\ + "python_mode":bool,\ + "motd":list + } + + self.load_cfg() + + self.set_interpreter() + + #pygame.key.set_repeat(*self.repeat_rate) + + self.bg_layer = pygame.Surface(self.size) + self.bg_layer.set_alpha(self.bg_alpha) + + self.txt_layer = pygame.Surface(self.size) + self.txt_layer.set_colorkey(self.bg_color) + + try: + self.font = pygame.font.Font(os.path.join(font_path,"default.ttf"), 14) + except IOError: + self.font = pygame.font.SysFont("monospace", 14) + + self.font_height = self.font.get_linesize() + self.max_lines = (self.size[HEIGHT] / self.font_height) - 1 + + self.max_chars = (self.size[WIDTH]/(self.font.size(ascii_letters)[WIDTH]/len(ascii_letters))) - 1 + self.txt_wrapper = textwrap.TextWrapper() + + self.c_out = self.motd + self.c_hist = [""] + self.c_hist_pos = 0 + self.c_in = "" + self.c_pos = 0 + self.c_draw_pos = 0 + self.c_scroll = 0 + + + self.changed = True + + self.func_calls = {} + self.key_calls = {} + + self.add_func_calls({"echo":self.output, "clear": self.clear, "help":self.help}) + self.add_func_calls(functions) + + self.add_key_calls({"l":self.clear, "c":self.clear_input, "w":self.set_active}) + self.add_key_calls(key_calls) + + + ################## + #-Initialization-# + def load_cfg(self): + '''\ + Loads the config file path/pygame-console.cfg\ + All variables are initialized to their defaults,\ + then new values will be loaded from the config file if it exists. + ''' + self.init_default_cfg() + if os.path.exists(cfg_path): + for line in file(cfg_path): + tokens = self.tokenize(line) + if re_is_comment.match(line): + continue + elif len(tokens) != 2: + continue + self.safe_set_attr(tokens[0],tokens[1]) + + def init_default_cfg(self): + self.bg_alpha = 255 + self.bg_color = [0x0,0x0,0x0] + self.txt_color_i = [0xFF,0xFF,0xFF] + self.txt_color_o = [0xCC,0xCC,0xCC] + self.ps1 = "] " + self.ps2 = ">>> " + self.ps3 = "... " + self.active = False + self.repeat_rate = [500,30] + self.python_mode = False + self.preserve_events = False + self.motd = ["[PyConsole 0.5]"] + + def safe_set_attr(self, name, value): + '''\ + Safely set the console variables + ''' + if name in self.variables: + if isinstance(value, self.variables[name]) or not self.variables[name]: + self.__dict__[name] = value + + def add_func_calls(self, functions): + '''\ + Add functions to the func_calls dictionary. + Arguments: + functions -- dictionary of functions to add. + ''' + if isinstance(functions,dict): + self.func_calls.update(functions) + self.user_namespace.update(self.func_calls) + + def add_key_calls(self, functions): + '''\ + Add functions to the key_calls dictionary. + Arguments: + functions -- dictionary of key_calls to add. + ''' + if isinstance(functions,dict): + self.key_calls.update(functions) + + def output(self, text): + '''\ + Prepare text to be displayed + Arguments: + text -- Text to be displayed + ''' + if not str(text): + return; + + try: + self.changed = True + if not isinstance(text,str): + text = str(text) + text = text.expandtabs() + text = text.splitlines() + self.txt_wrapper.width = self.max_chars + for line in text: + for w in self.txt_wrapper.wrap(line): + self.c_out.append(w) + except: + pass + + def set_active(self,b=None): + '''\ + Activate or Deactivate the console + Arguments: + b -- Optional boolean argument, True=Activate False=Deactivate + ''' + if not b: + self.active = not self.active + else: + self.active = b + + + def format_input_line(self): + '''\ + Format input line to be displayed + ''' + # The \v here is sort of a hack, it's just a character that isn't recognized by the font engine + text = self.c_in[:self.c_pos] + "\v" + self.c_in[self.c_pos+1:] + n_max = self.max_chars - len(self.c_ps) + vis_range = self.c_draw_pos, self.c_draw_pos + n_max + return self.c_ps + text[vis_range[0]:vis_range[1]] + + def draw(self): + '''\ + Draw the console to the parent screen + ''' + if not self.active: + return; + + if self.changed: + self.changed = False + # Draw Output + self.txt_layer.fill(self.bg_color) + lines = self.c_out[-(self.max_lines+self.c_scroll):len(self.c_out)-self.c_scroll] + y_pos = self.size[HEIGHT]-(self.font_height*(len(lines)+1)) + + for line in lines: + tmp_surf = self.font.render(line, True, self.txt_color_o) + self.txt_layer.blit(tmp_surf, (1, y_pos, 0, 0)) + y_pos += self.font_height + # Draw Input + tmp_surf = self.font.render(self.format_input_line(), True, self.txt_color_i) + self.txt_layer.blit(tmp_surf, (1,self.size[HEIGHT]-self.font_height,0,0)) + # Clear background and blit text to it + self.bg_layer.fill(self.bg_color) + self.bg_layer.blit(self.txt_layer,(0,0,0,0)) + + # Draw console to parent screen + # self.parent_screen.fill(self.txt_color_i, (self.rect.x-1, self.rect.y-1, self.size[WIDTH]+2, self.size[HEIGHT]+2)) + pygame.draw.rect(self.parent_screen, self.txt_color_i, (self.rect.x-1, self.rect.y-1, self.size[WIDTH]+2, self.size[HEIGHT]+2), 1) + self.parent_screen.blit(self.bg_layer,self.rect) + + ####################################################################### + # Functions to communicate with the console and the python interpreter# + def set_interpreter(self): + if self.python_mode: + self.output("Entering Python mode") + self.python_mode = True + self.python_interpreter = InteractiveConsole() + self.tmp_fds = [] + self.py_fds = [Writable() for i in range(3)] + self.c_ps = self.ps2 + else: + self.output("Entering Pyconsole mode") + self.python_mode = False + self.c_ps = self.ps1 + + def catch_output(self): + if not self.tmp_fds: + self.tmp_fds = [sys.stdout, sys.stdin, sys.stderr] + sys.stdout, sys.stdin, sys.stderr = self.py_fds + + def release_output(self): + if self.tmp_fds: + sys.stdout, sys.stdin, sys.stderr = self.tmp_fds + self.tmp_fds = [] + [fd.reset() for fd in self.py_fds] + + def submit_input(self, text): + '''\ + Submit input + 1) Move input to output + 2) Evaluate input + 3) Clear input line + ''' + + self.clear_input() + self.output(self.c_ps + text) + self.c_scroll = 0 + if self.python_mode: + self.send_python(text) + else: + self.send_pyconsole(text) + + def send_python(self, text): + '''\ + Sends input the the python interpreter in effect giving the user the ability to do anything python can. + ''' + self.c_ps = self.ps2 + self.catch_output() + if text: + self.add_to_history(text) + r = self.python_interpreter.push(text) + if r: + self.c_ps = self.ps3 + else: + code = "".join(self.py_fds[OUT]) + self.python_interpreter.push("\n") + self.python_interpreter.runsource(code) + for i in self.py_fds[OUT]+self.py_fds[ERR]: + self.output(i) + self.release_output() + + def send_pyconsole(self, text): + '''\ + Sends input to pyconsole to be interpreted + ''' + if not text: # Output a blank row if nothing is entered + self.output("") + return; + + self.add_to_history(text) + + #Determine if the statement is an assignment + assign = re_is_assign.match(text) + try: + #If it is tokenize only the "value" part of $name = value + if assign: + tokens = self.tokenize(assign.group('value')) + else: + tokens = self.tokenize(text) + except ParseError, e: + self.output(r'At Token: "%s"' % e.at_token()) + return; + + if tokens == None: + return + + #Evaluate + try: + out = None + # A variable alone on a line + if (len(tokens) is 1) and re_is_var.match(text) and not assign: + out = tokens[0] + # Statement in the form $name = value + elif (len(tokens) is 1) and assign: + self.setvar(assign.group('name'), tokens[0]) + else: + # Function + out = self.func_calls[tokens[0]](*tokens[1:]) + # Assignment from function's return value + if assign: + self.setvar(assign.group('name'), out) + + if not out == None: + self.output(out) + except (KeyError,TypeError): + self.output("Unknown Command: " + str(tokens[0])) + self.output(r'Type "help" for a list of commands.') + + + + #################################################### + #-Functions for sharing variables with the console-# + def setvar(self, name, value): + '''\ + Sets the value of a variable + ''' + if self.user_vars.has_key(name) or not self.__dict__.has_key(name): + self.user_vars.update({name:value}) + self.user_namespace.update(self.user_vars) + elif self.__dict__.has_key(name): + self.__dict__.update({name:value}) + + def getvar(self, name): + '''\ + Gets the value of a variable, this is useful for people that want to access console variables from within their game + ''' + if self.user_vars.has_key(name) or not self.__dict__.has_key(name): + return self.user_vars[name] + elif self.__dict__.has_key(name): + return self.__dict__[name] + + def setvars(self, vars): + try: + self.user_vars.update(vars) + self.user_namespace.update(self.user_vars) + except TypeError: + self.output("setvars requires a dictionary") + + def getvars(self, opt_dict=None): + if opt_dict: + opt_dict.update(self.user_vars) + else: + return self.user_vars + + + def add_to_history(self, text): + '''\ + Add specified text to the history + ''' + self.c_hist.insert(-1,text) + self.c_hist_pos = len(self.c_hist)-1 + + def clear_input(self): + '''\ + Clear input line and reset cursor position + ''' + self.c_in = "" + self.c_pos = 0 + self.c_draw_pos = 0 + + def set_pos(self, newpos): + '''\ + Moves cursor safely + ''' + self.c_pos = newpos + if (self.c_pos - self.c_draw_pos) >= (self.max_chars - len(self.c_ps)): + self.c_draw_pos = max(0, self.c_pos - (self.max_chars - len(self.c_ps))) + elif self.c_draw_pos > self.c_pos: + self.c_draw_pos = self.c_pos - (self.max_chars/2) + if self.c_draw_pos < 0: + self.c_draw_pos = 0 + self.c_pos = 0 + + def str_insert(self, text, strn): + '''\ + Insert characters at the current cursor position + ''' + foo = text[:self.c_pos] + strn + text[self.c_pos:] + self.set_pos(self.c_pos + len(strn)) + return foo + + def process_input(self, event): + '''\ + Loop through pygame events and evaluate them + ''' + if not self.active: + return False; + + if event.type == KEYDOWN: + self.changed = True + ## Special Character Manipulation + if event.key == K_TAB: + self.c_in = self.str_insert(self.c_in, " ") + elif event.key == K_BACKSPACE: + if self.c_pos > 0: + self.c_in = self.c_in[:self.c_pos-1] + self.c_in[self.c_pos:] + self.set_pos(self.c_pos-1) + elif event.key == K_DELETE: + if self.c_pos < len(self.c_in): + self.c_in = self.c_in[:self.c_pos] + self.c_in[self.c_pos+1:] + elif event.key == K_RETURN or event.key == 271: + self.submit_input(self.c_in) + ## Changing Cursor Position + elif event.key == K_LEFT: + if self.c_pos > 0: + self.set_pos(self.c_pos-1) + elif event.key == K_RIGHT: + if self.c_pos < len(self.c_in): + self.set_pos(self.c_pos+1) + elif event.key == K_HOME: + self.set_pos(0) + elif event.key == K_END: + self.set_pos(len(self.c_in)) + ## History Navigation + elif event.key == K_UP: + if len(self.c_out): + if self.c_hist_pos > 0: + self.c_hist_pos -= 1 + self.c_in = self.c_hist[self.c_hist_pos] + self.set_pos(len(self.c_in)) + elif event.key == K_DOWN: + if len(self.c_out): + if self.c_hist_pos < len(self.c_hist)-1: + self.c_hist_pos += 1 + self.c_in = self.c_hist[self.c_hist_pos] + self.set_pos(len(self.c_in)) + ## Scrolling + elif event.key == K_PAGEUP: + if self.c_scroll < len(self.c_out)-1: + self.c_scroll += 1 + elif event.key == K_PAGEDOWN: + if self.c_scroll > 0: + self.c_scroll -= 1 + ## Normal character printing + elif event.key >= 32: + mods = pygame.key.get_mods() + if mods & KMOD_CTRL: + if event.key in range(256) and chr(event.key) in self.key_calls: + self.key_calls[chr(event.key)]() + else: + char = str(event.unicode) + self.c_in = self.str_insert(self.c_in, char) + return True + + def convert_token(self, tok): + '''\ + Convert a token to its proper type + ''' + tok = tok.strip("$") + try: + tmp = eval(tok, self.__dict__, self.user_namespace) + except SyntaxError, strerror: + self.output("SyntaxError: " + str(strerror)) + raise ParseError, tok + except TypeError, strerror: + self.output("TypeError: " + str(strerror)) + raise ParseError, tok + except NameError, strerror: + self.output("NameError: " + str(strerror)) + except: + self.output("Error:") + raise ParseError, tok + else: + return tmp + + def tokenize(self, s): + '''\ + Tokenize input line, convert tokens to proper types + ''' + if re_is_comment.match(s): + return [s] + + for re in self.user_syntax: + group = re.match(s) + if group: + self.user_syntax[re](self, group) + return + + tokens = re_token.findall(s) + tokens = [i.strip("\"") for i in tokens] + cmd = [] + i = 0 + while i < len(tokens): + t_count = 0 + val = tokens[i] + + if re_is_number.match(val): + cmd.append(self.convert_token(val)) + elif re_is_var.match(val): + cmd.append(self.convert_token(val)) + elif val == "True": + cmd.append(True) + elif val == "False": + cmd.append(False) + elif re_is_list.match(val): + while not balanced(val) and (i + t_count) < len(tokens)-1: + t_count += 1 + val += tokens[i+t_count] + else: + if (i + t_count) < len(tokens): + cmd.append(self.convert_token(val)) + else: + raise ParseError, val + else: + cmd.append(val) + i += t_count + 1 + return cmd + + + ########################## + #-Some Builtin functions-# + def clear(self): + '''\ + Clear the Screen + ''' + self.c_out = ["[Screen Cleared]"] + self.c_scroll = 0 + + def help(self, *args): + '''\ + Output information about functions + Arguments: + args -- arbitrary argument list of function names + |- No Args - A list of available functions will be displayed + |- One or more Args - Docstring of each function will be displayed + ''' + if args: + items = [(i,self.func_calls[i]) for i in args if i in self.func_calls] + for i,v in items: + out = i + ": Takes %d arguments. " % (v.func_code.co_argcount - (v.func_code.co_varnames[0] is "self")) + doc = v.func_doc + if doc: + out += textwrap.dedent(doc) + tmp_indent = self.txt_wrapper.subsequent_indent + self.txt_wrapper.subsequent_indent = " "*(len(i)+2) + self.output(out) + self.txt_wrapper.subsequent_indent = tmp_indent + else: + out = "Available commands: " + str(self.func_calls.keys()).strip("[]") + self.output(out) + self.output(r'Type "help command-name" for more information on that command') |