1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import pygame, os, sys
23 from pygame.locals import *
24
25 import re
26 import textwrap
27 from string import ascii_letters
28 from code import InteractiveConsole
29
30
31 __version__ = "0.7"
32
33 WIDTH=0
34 HEIGHT=1
35
36 OUT = 0
37 IN = 1
38 ERR = 2
39
40 PYCONSOLE = 1
41 PYTHON = 2
42
43 path = os.path.abspath(os.path.dirname(__file__))
44 font_path = os.path.join(path, "fonts")
45 cfg_path = os.path.join(path, "pyconsole.cfg")
46
47
48 re_token = re.compile(r"""[\"].*?[\"]|[\{].*?[\}]|[\(].*?[\)]|[\[].*?[\]]|\S+""")
49 re_is_list = re.compile(r'^[{\[(]')
50 re_is_number = re.compile(r"""
51 (?x)
52 [-]?[0][x][0-9a-fA-F]+[lLjJ]? | # Hexadecimal
53 [-]?[0][0-7]+[lLjJ]? | # Octal
54 [-]?[\d]+(?:[.][\d]*)?[lLjJ]? # Decimal (Int or float)
55 """)
56 re_is_assign = re.compile(r'[$](?P<name>[a-zA-Z_]+\S*)\s*[=]\s*(?P<value>.+)')
57 re_is_comment = re.compile(r'\s*#.*')
58 re_is_var = re.compile(r'^[$][a-zA-Z_]+\w*\Z')
59
60
61
63 line_pointer = -1
65 self.append(str(line))
69
70
71 raise NotImplementedError
72
78
80 stack = []
81 pairs = {"\'":"\'", '\"':'\"', "{":"}", "[":"]", "(":")"}
82 for char in t:
83 if stack and char == pairs[stack[-1]]:
84 stack.pop()
85 elif char in pairs:
86 stack.append(char)
87 return not bool(stack)
88
90 - def __init__(self, screen, rect, functions={}, key_calls={}, vars={}, syntax={}):
91 if not pygame.display.get_init():
92 raise pygame.error, "Display not initialized. Initialize the display before creating a Console"
93
94 if not pygame.font.get_init():
95 pygame.font.init()
96
97 self.parent_screen = screen
98 self.rect = pygame.Rect(rect)
99 self.size = self.rect.size
100
101 self.user_vars = vars
102 self.user_syntax = syntax
103 self.user_namespace = {}
104
105 self.variables = {\
106 "bg_alpha":int,\
107 "bg_color": list,\
108 "txt_color_i": list,\
109 "txt_color_o": list,\
110 "ps1": str,\
111 "ps2": str,\
112 "ps3": str,\
113 "active": bool,\
114 "repeat_rate": list,\
115 "preserve_events":bool,\
116 "python_mode":bool,\
117 "motd":list
118 }
119
120 self.load_cfg()
121
122 self.set_interpreter()
123
124
125
126 self.bg_layer = pygame.Surface(self.size)
127 self.bg_layer.set_alpha(self.bg_alpha)
128
129 self.txt_layer = pygame.Surface(self.size)
130 self.txt_layer.set_colorkey(self.bg_color)
131
132 try:
133 self.font = pygame.font.Font(os.path.join(font_path,"default.ttf"), 14)
134 except IOError:
135 self.font = pygame.font.SysFont("monospace", 14)
136
137 self.font_height = self.font.get_linesize()
138 self.max_lines = (self.size[HEIGHT] / self.font_height) - 1
139
140 self.max_chars = (self.size[WIDTH]/(self.font.size(ascii_letters)[WIDTH]/len(ascii_letters))) - 1
141 self.txt_wrapper = textwrap.TextWrapper()
142
143 self.c_out = self.motd
144 self.c_hist = [""]
145 self.c_hist_pos = 0
146 self.c_in = ""
147 self.c_pos = 0
148 self.c_draw_pos = 0
149 self.c_scroll = 0
150
151
152 self.changed = True
153
154 self.func_calls = {}
155 self.key_calls = {}
156
157 self.add_func_calls({"echo":self.output, "clear": self.clear, "help":self.help})
158 self.add_func_calls(functions)
159
160 self.add_key_calls({"l":self.clear, "c":self.clear_input, "w":self.set_active})
161 self.add_key_calls(key_calls)
162
163
164
165
167 '''\
168 Loads the config file path/pygame-console.cfg\
169 All variables are initialized to their defaults,\
170 then new values will be loaded from the config file if it exists.
171 '''
172 self.init_default_cfg()
173 if os.path.exists(cfg_path):
174 for line in file(cfg_path):
175 tokens = self.tokenize(line)
176 if re_is_comment.match(line):
177 continue
178 elif len(tokens) != 2:
179 continue
180 self.safe_set_attr(tokens[0],tokens[1])
181
183 self.bg_alpha = 255
184 self.bg_color = [0x0,0x0,0x0]
185 self.txt_color_i = [0xFF,0xFF,0xFF]
186 self.txt_color_o = [0xCC,0xCC,0xCC]
187 self.ps1 = "] "
188 self.ps2 = ">>> "
189 self.ps3 = "... "
190 self.active = False
191 self.repeat_rate = [500,30]
192 self.python_mode = False
193 self.preserve_events = False
194 self.motd = ["[PyConsole 0.5]"]
195
197 '''\
198 Safely set the console variables
199 '''
200 if name in self.variables:
201 if isinstance(value, self.variables[name]) or not self.variables[name]:
202 self.__dict__[name] = value
203
205 '''\
206 Add functions to the func_calls dictionary.
207 Arguments:
208 functions -- dictionary of functions to add.
209 '''
210 if isinstance(functions,dict):
211 self.func_calls.update(functions)
212 self.user_namespace.update(self.func_calls)
213
215 '''\
216 Add functions to the key_calls dictionary.
217 Arguments:
218 functions -- dictionary of key_calls to add.
219 '''
220 if isinstance(functions,dict):
221 self.key_calls.update(functions)
222
224 '''\
225 Prepare text to be displayed
226 Arguments:
227 text -- Text to be displayed
228 '''
229 if not str(text):
230 return;
231
232 try:
233 self.changed = True
234 if not isinstance(text,str):
235 text = str(text)
236 text = text.expandtabs()
237 text = text.splitlines()
238 self.txt_wrapper.width = self.max_chars
239 for line in text:
240 for w in self.txt_wrapper.wrap(line):
241 self.c_out.append(w)
242 except:
243 pass
244
246 '''\
247 Activate or Deactivate the console
248 Arguments:
249 b -- Optional boolean argument, True=Activate False=Deactivate
250 '''
251 if not b:
252 self.active = not self.active
253 else:
254 self.active = b
255
256
266
268 '''\
269 Draw the console to the parent screen
270 '''
271 if not self.active:
272 return;
273
274 if self.changed:
275 self.changed = False
276
277 self.txt_layer.fill(self.bg_color)
278 lines = self.c_out[-(self.max_lines+self.c_scroll):len(self.c_out)-self.c_scroll]
279 y_pos = self.size[HEIGHT]-(self.font_height*(len(lines)+1))
280
281 for line in lines:
282 tmp_surf = self.font.render(line, True, self.txt_color_o)
283 self.txt_layer.blit(tmp_surf, (1, y_pos, 0, 0))
284 y_pos += self.font_height
285
286 tmp_surf = self.font.render(self.format_input_line(), True, self.txt_color_i)
287 self.txt_layer.blit(tmp_surf, (1,self.size[HEIGHT]-self.font_height,0,0))
288
289 self.bg_layer.fill(self.bg_color)
290 self.bg_layer.blit(self.txt_layer,(0,0,0,0))
291
292
293
294 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)
295 self.parent_screen.blit(self.bg_layer,self.rect)
296
297
298
300 if self.python_mode:
301 self.output("Entering Python mode")
302 self.python_mode = True
303 self.python_interpreter = InteractiveConsole()
304 self.tmp_fds = []
305 self.py_fds = [Writable() for i in range(3)]
306 self.c_ps = self.ps2
307 else:
308 self.output("Entering Pyconsole mode")
309 self.python_mode = False
310 self.c_ps = self.ps1
311
313 if not self.tmp_fds:
314 self.tmp_fds = [sys.stdout, sys.stdin, sys.stderr]
315 sys.stdout, sys.stdin, sys.stderr = self.py_fds
316
318 if self.tmp_fds:
319 sys.stdout, sys.stdin, sys.stderr = self.tmp_fds
320 self.tmp_fds = []
321 [fd.reset() for fd in self.py_fds]
322
338
340 '''\
341 Sends input the the python interpreter in effect giving the user the ability to do anything python can.
342 '''
343 self.c_ps = self.ps2
344 self.catch_output()
345 if text:
346 self.add_to_history(text)
347 r = self.python_interpreter.push(text)
348 if r:
349 self.c_ps = self.ps3
350 else:
351 code = "".join(self.py_fds[OUT])
352 self.python_interpreter.push("\n")
353 self.python_interpreter.runsource(code)
354 for i in self.py_fds[OUT]+self.py_fds[ERR]:
355 self.output(i)
356 self.release_output()
357
359 '''\
360 Sends input to pyconsole to be interpreted
361 '''
362 if not text:
363 self.output("")
364 return;
365
366 self.add_to_history(text)
367
368
369 assign = re_is_assign.match(text)
370 try:
371
372 if assign:
373 tokens = self.tokenize(assign.group('value'))
374 else:
375 tokens = self.tokenize(text)
376 except ParseError, e:
377 self.output(r'At Token: "%s"' % e.at_token())
378 return;
379
380 if tokens == None:
381 return
382
383
384 try:
385 out = None
386
387 if (len(tokens) is 1) and re_is_var.match(text) and not assign:
388 out = tokens[0]
389
390 elif (len(tokens) is 1) and assign:
391 self.setvar(assign.group('name'), tokens[0])
392 else:
393
394 out = self.func_calls[tokens[0]](*tokens[1:])
395
396 if assign:
397 self.setvar(assign.group('name'), out)
398
399 if not out == None:
400 self.output(out)
401 except (KeyError,TypeError):
402 self.output("Unknown Command: " + str(tokens[0]))
403 self.output(r'Type "help" for a list of commands.')
404
405
406
407
408
409 - def setvar(self, name, value):
410 '''\
411 Sets the value of a variable
412 '''
413 if self.user_vars.has_key(name) or not self.__dict__.has_key(name):
414 self.user_vars.update({name:value})
415 self.user_namespace.update(self.user_vars)
416 elif self.__dict__.has_key(name):
417 self.__dict__.update({name:value})
418
420 '''\
421 Gets the value of a variable, this is useful for people that want to access console variables from within their game
422 '''
423 if self.user_vars.has_key(name) or not self.__dict__.has_key(name):
424 return self.user_vars[name]
425 elif self.__dict__.has_key(name):
426 return self.__dict__[name]
427
429 try:
430 self.user_vars.update(vars)
431 self.user_namespace.update(self.user_vars)
432 except TypeError:
433 self.output("setvars requires a dictionary")
434
436 if opt_dict:
437 opt_dict.update(self.user_vars)
438 else:
439 return self.user_vars
440
441
442 - def add_to_history(self, text):
443 '''\
444 Add specified text to the history
445 '''
446 self.c_hist.insert(-1,text)
447 self.c_hist_pos = len(self.c_hist)-1
448
456
458 '''\
459 Moves cursor safely
460 '''
461 self.c_pos = newpos
462 if (self.c_pos - self.c_draw_pos) >= (self.max_chars - len(self.c_ps)):
463 self.c_draw_pos = max(0, self.c_pos - (self.max_chars - len(self.c_ps)))
464 elif self.c_draw_pos > self.c_pos:
465 self.c_draw_pos = self.c_pos - (self.max_chars/2)
466 if self.c_draw_pos < 0:
467 self.c_draw_pos = 0
468 self.c_pos = 0
469
471 '''\
472 Insert characters at the current cursor position
473 '''
474 foo = text[:self.c_pos] + strn + text[self.c_pos:]
475 self.set_pos(self.c_pos + len(strn))
476 return foo
477
540
542 '''\
543 Convert a token to its proper type
544 '''
545 tok = tok.strip("$")
546 try:
547 tmp = eval(tok, self.__dict__, self.user_namespace)
548 except SyntaxError, strerror:
549 self.output("SyntaxError: " + str(strerror))
550 raise ParseError, tok
551 except TypeError, strerror:
552 self.output("TypeError: " + str(strerror))
553 raise ParseError, tok
554 except NameError, strerror:
555 self.output("NameError: " + str(strerror))
556 except:
557 self.output("Error:")
558 raise ParseError, tok
559 else:
560 return tmp
561
563 '''\
564 Tokenize input line, convert tokens to proper types
565 '''
566 if re_is_comment.match(s):
567 return [s]
568
569 for re in self.user_syntax:
570 group = re.match(s)
571 if group:
572 self.user_syntax[re](self, group)
573 return
574
575 tokens = re_token.findall(s)
576 tokens = [i.strip("\"") for i in tokens]
577 cmd = []
578 i = 0
579 while i < len(tokens):
580 t_count = 0
581 val = tokens[i]
582
583 if re_is_number.match(val):
584 cmd.append(self.convert_token(val))
585 elif re_is_var.match(val):
586 cmd.append(self.convert_token(val))
587 elif val == "True":
588 cmd.append(True)
589 elif val == "False":
590 cmd.append(False)
591 elif re_is_list.match(val):
592 while not balanced(val) and (i + t_count) < len(tokens)-1:
593 t_count += 1
594 val += tokens[i+t_count]
595 else:
596 if (i + t_count) < len(tokens):
597 cmd.append(self.convert_token(val))
598 else:
599 raise ParseError, val
600 else:
601 cmd.append(val)
602 i += t_count + 1
603 return cmd
604
605
606
607
609 '''\
610 Clear the Screen
611 '''
612 self.c_out = ["[Screen Cleared]"]
613 self.c_scroll = 0
614
615 - def help(self, *args):
616 '''\
617 Output information about functions
618 Arguments:
619 args -- arbitrary argument list of function names
620 |- No Args - A list of available functions will be displayed
621 |- One or more Args - Docstring of each function will be displayed
622 '''
623 if args:
624 items = [(i,self.func_calls[i]) for i in args if i in self.func_calls]
625 for i,v in items:
626 out = i + ": Takes %d arguments. " % (v.func_code.co_argcount - (v.func_code.co_varnames[0] is "self"))
627 doc = v.func_doc
628 if doc:
629 out += textwrap.dedent(doc)
630 tmp_indent = self.txt_wrapper.subsequent_indent
631 self.txt_wrapper.subsequent_indent = " "*(len(i)+2)
632 self.output(out)
633 self.txt_wrapper.subsequent_indent = tmp_indent
634 else:
635 out = "Available commands: " + str(self.func_calls.keys()).strip("[]")
636 self.output(out)
637 self.output(r'Type "help command-name" for more information on that command')
638