A Discrete-Event Network Simulator
API
ipython_view.py
Go to the documentation of this file.
1 """
2 Backend to the console plugin.
3 
4 @author: Eitan Isaacson
5 @organization: IBM Corporation
6 @copyright: Copyright (c) 2007 IBM Corporation
7 @license: BSD
8 
9 All rights reserved. This program and the accompanying materials are made
10 available under the terms of the BSD which accompanies this distribution, and
11 is available at U{http://www.opensource.org/licenses/bsd-license.php}
12 """
13 # this file is a modified version of source code from the Accerciser project
14 # http://live.gnome.org/accerciser
15 
16 import gtk, gobject
17 import re
18 import sys
19 import os
20 import pango
21 from StringIO import StringIO
22 import IPython
23 
24 from pkg_resources import parse_version
25 
26 try:
27  import IPython
28 except ImportError:
29  IPython = None
30 
32  def __init__(self,argv=None,user_ns=None,user_global_ns=None,
33  cin=None, cout=None,cerr=None, input_func=None):
34  '''
35 
36 
37  @param argv: Command line options for IPython
38  @type argv: list
39  @param user_ns: User namespace.
40  @type user_ns: dictionary
41  @param user_global_ns: User global namespace.
42  @type user_global_ns: dictionary.
43  @param cin: Console standard input.
44  @type cin: IO stream
45  @param cout: Console standard output.
46  @type cout: IO stream
47  @param cerr: Console standard error.
48  @type cerr: IO stream
49  @param input_func: Replacement for builtin raw_input()
50  @type input_func: function
51  '''
52  io = IPython.utils.io
53  if input_func:
54  if parse_version(IPython.release.version) >= parse_version("1.2.1"):
55  IPython.terminal.interactiveshell.raw_input_original = input_func
56  else:
57  IPython.frontend.terminal.interactiveshell.raw_input_original = input_func
58  if cin:
59  io.stdin = io.IOStream(cin)
60  if cout:
61  io.stdout = io.IOStream(cout)
62  if cerr:
63  io.stderr = io.IOStream(cerr)
64 
65  # This is to get rid of the blockage that occurs during
66  # IPython.Shell.InteractiveShell.user_setup()
67 
68  io.raw_input = lambda x: None
69 
70  os.environ['TERM'] = 'dumb'
71  excepthook = sys.excepthook
72 
73  from IPython.config.loader import Config
74  cfg = Config()
75  cfg.InteractiveShell.colors = "Linux"
76 
77  # InteractiveShell's __init__ overwrites io.stdout,io.stderr with
78  # sys.stdout, sys.stderr, this makes sure they are right
79  #
80  old_stdout, old_stderr = sys.stdout, sys.stderr
81  sys.stdout, sys.stderr = io.stdout.stream, io.stderr.stream
82 
83  # InteractiveShell inherits from SingletonConfigurable, so use instance()
84  #
85  if parse_version(IPython.release.version) >= parse_version("1.2.1"):
86  self.IP = IPython.terminal.embed.InteractiveShellEmbed.instance(\
87  config=cfg, user_ns=user_ns)
88  else:
89  self.IP = IPython.frontend.terminal.embed.InteractiveShellEmbed.instance(\
90  config=cfg, user_ns=user_ns)
91 
92  sys.stdout, sys.stderr = old_stdout, old_stderr
93 
94  self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd),
95  header='IPython system call: ')
96 # local_ns=user_ns)
97  #global_ns=user_global_ns)
98  #verbose=self.IP.rc.system_verbose)
99 
100  self.IP.raw_input = input_func
101  sys.excepthook = excepthook
102  self.iter_more = 0
103  self.history_level = 0
104  self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
105  self.updateNamespace({'exit':lambda:None})
106  self.updateNamespace({'quit':lambda:None})
107  self.IP.readline_startup_hook(self.IP.pre_readline)
108  # Workaround for updating namespace with sys.modules
109  #
110  self.__update_namespace()
111 
113  '''
114  Update self.IP namespace for autocompletion with sys.modules
115  '''
116  for k, v in list(sys.modules.items()):
117  if not '.' in k:
118  self.IP.user_ns.update({k:v})
119 
120  def execute(self):
121  '''
122  Executes the current line provided by the shell object.
123  '''
124  self.history_level = 0
125  orig_stdout = sys.stdout
126  sys.stdout = IPython.utils.io.stdout
127 
128  orig_stdin = sys.stdin
129  sys.stdin = IPython.utils.io.stdin;
130  self.prompt = self.generatePrompt(self.iter_more)
131 
132  self.IP.hooks.pre_prompt_hook()
133  if self.iter_more:
134  try:
135  self.prompt = self.generatePrompt(True)
136  except:
137  self.IP.showtraceback()
138  if self.IP.autoindent:
139  self.IP.rl_do_indent = True
140 
141  try:
142  line = self.IP.raw_input(self.prompt)
143  except KeyboardInterrupt:
144  self.IP.write('\nKeyboardInterrupt\n')
145  self.IP.input_splitter.reset()
146  except:
147  self.IP.showtraceback()
148  else:
149  self.IP.input_splitter.push(line)
150  self.iter_more = self.IP.input_splitter.push_accepts_more()
151  self.prompt = self.generatePrompt(self.iter_more)
152  if (self.IP.SyntaxTB.last_syntax_error and
153  self.IP.autoedit_syntax):
154  self.IP.edit_syntax_error()
155  if not self.iter_more:
156  if parse_version(IPython.release.version) >= parse_version("2.0.0-dev"):
157  source_raw = self.IP.input_splitter.raw_reset()
158  else:
159  source_raw = self.IP.input_splitter.source_raw_reset()[1]
160  self.IP.run_cell(source_raw, store_history=True)
161  self.IP.rl_do_indent = False
162  else:
163  # TODO: Auto-indent
164  #
165  self.IP.rl_do_indent = True
166  pass
167 
168  sys.stdout = orig_stdout
169  sys.stdin = orig_stdin
170 
171  def generatePrompt(self, is_continuation):
172  '''
173  Generate prompt depending on is_continuation value
174 
175  @param is_continuation
176  @type is_continuation: boolean
177 
178  @return: The prompt string representation
179  @rtype: string
180 
181  '''
182 
183  # Backwards compatibility with ipyton-0.11
184  #
185  ver = IPython.__version__
186  if '0.11' in ver:
187  prompt = self.IP.hooks.generate_prompt(is_continuation)
188  else:
189  if is_continuation:
190  prompt = self.IP.prompt_manager.render('in2')
191  else:
192  prompt = self.IP.prompt_manager.render('in')
193 
194  return prompt
195 
196 
197  def historyBack(self):
198  '''
199  Provides one history command back.
200 
201  @return: The command string.
202  @rtype: string
203  '''
204  self.history_level -= 1
205  if not self._getHistory():
206  self.history_level +=1
207  return self._getHistory()
208 
209  def historyForward(self):
210  '''
211  Provides one history command forward.
212 
213  @return: The command string.
214  @rtype: string
215  '''
216  if self.history_level < 0:
217  self.history_level += 1
218  return self._getHistory()
219 
220  def _getHistory(self):
221  '''
222  Get's the command string of the current history level.
223 
224  @return: Historic command string.
225  @rtype: string
226  '''
227  try:
228  rv = self.IP.user_ns['In'][self.history_level].strip('\n')
229  except IndexError:
230  rv = ''
231  return rv
232 
233  def updateNamespace(self, ns_dict):
234  '''
235  Add the current dictionary to the shell namespace.
236 
237  @param ns_dict: A dictionary of symbol-values.
238  @type ns_dict: dictionary
239  '''
240  self.IP.user_ns.update(ns_dict)
241 
242  def complete(self, line):
243  '''
244  Returns an auto completed line and/or posibilities for completion.
245 
246  @param line: Given line so far.
247  @type line: string
248 
249  @return: Line completed as for as possible,
250  and possible further completions.
251  @rtype: tuple
252  '''
253  split_line = self.complete_sep.split(line)
254  if split_line[-1]:
255  possibilities = self.IP.complete(split_line[-1])
256  else:
257  completed = line
258  possibilities = ['', []]
259  if possibilities:
260  def _commonPrefix(str1, str2):
261  '''
262  Reduction function. returns common prefix of two given strings.
263 
264  @param str1: First string.
265  @type str1: string
266  @param str2: Second string
267  @type str2: string
268 
269  @return: Common prefix to both strings.
270  @rtype: string
271  '''
272  for i in range(len(str1)):
273  if not str2.startswith(str1[:i+1]):
274  return str1[:i]
275  return str1
276  if possibilities[1]:
277  common_prefix = reduce(_commonPrefix, possibilities[1]) or line[-1]
278  completed = line[:-len(split_line[-1])]+common_prefix
279  else:
280  completed = line
281  else:
282  completed = line
283  return completed, possibilities[1]
284 
285 
286  def shell(self, cmd,verbose=0,debug=0,header=''):
287  '''
288  Replacement method to allow shell commands without them blocking.
289 
290  @param cmd: Shell command to execute.
291  @type cmd: string
292  @param verbose: Verbosity
293  @type verbose: integer
294  @param debug: Debug level
295  @type debug: integer
296  @param header: Header to be printed before output
297  @type header: string
298  '''
299  stat = 0
300  if verbose or debug: print header+cmd
301  # flush stdout so we don't mangle python's buffering
302  if not debug:
303  input, output = os.popen4(cmd)
304  print output.read()
305  output.close()
306  input.close()
307 
308 class ConsoleView(gtk.TextView):
309  '''
310  Specialized text view for console-like workflow.
311 
312  @cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
313  @type ANSI_COLORS: dictionary
314 
315  @ivar text_buffer: Widget's text buffer.
316  @type text_buffer: gtk.TextBuffer
317  @ivar color_pat: Regex of terminal color pattern
318  @type color_pat: _sre.SRE_Pattern
319  @ivar mark: Scroll mark for automatic scrolling on input.
320  @type mark: gtk.TextMark
321  @ivar line_start: Start of command line mark.
322  @type line_start: gtk.TextMark
323  '''
324  ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red',
325  '0;32': 'Green', '0;33': 'Brown',
326  '0;34': 'Blue', '0;35': 'Purple',
327  '0;36': 'Cyan', '0;37': 'LightGray',
328  '1;30': 'DarkGray', '1;31': 'DarkRed',
329  '1;32': 'SeaGreen', '1;33': 'Yellow',
330  '1;34': 'LightBlue', '1;35': 'MediumPurple',
331  '1;36': 'LightCyan', '1;37': 'White'}
332 
333  def __init__(self):
334  '''
335  Initialize console view.
336  '''
337  gtk.TextView.__init__(self)
338  self.modify_font(pango.FontDescription('Mono'))
339  self.set_cursor_visible(True)
340  self.text_buffer = self.get_buffer()
341  self.mark = self.text_buffer.create_mark('scroll_mark',
342  self.text_buffer.get_end_iter(),
343  False)
344  for code in self.ANSI_COLORS:
345  self.text_buffer.create_tag(code,
346  foreground=self.ANSI_COLORS[code],
347  weight=700)
348  self.text_buffer.create_tag('0')
349  self.text_buffer.create_tag('notouch', editable=False)
350  self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
351  self.line_start = \
352  self.text_buffer.create_mark('line_start',
353  self.text_buffer.get_end_iter(), True)
354  self.connect('key-press-event', self.onKeyPress)
355 
356  def write(self, text, editable=False):
357  gobject.idle_add(self._write, text, editable)
358 
359  def _write(self, text, editable=False):
360  '''
361  Write given text to buffer.
362 
363  @param text: Text to append.
364  @type text: string
365  @param editable: If true, added text is editable.
366  @type editable: boolean
367  '''
368  segments = self.color_pat.split(text)
369  segment = segments.pop(0)
370  start_mark = self.text_buffer.create_mark(None,
371  self.text_buffer.get_end_iter(),
372  True)
373  self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)
374 
375  if segments:
376  ansi_tags = self.color_pat.findall(text)
377  for tag in ansi_tags:
378  i = segments.index(tag)
379  self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(),
380  segments[i+1], str(tag))
381  segments.pop(i)
382  if not editable:
383  self.text_buffer.apply_tag_by_name('notouch',
384  self.text_buffer.get_iter_at_mark(start_mark),
385  self.text_buffer.get_end_iter())
386  self.text_buffer.delete_mark(start_mark)
387  self.scroll_mark_onscreen(self.mark)
388 
389  def showPrompt(self, prompt):
390  gobject.idle_add(self._showPrompt, prompt)
391 
392  def _showPrompt(self, prompt):
393  '''
394  Prints prompt at start of line.
395 
396  @param prompt: Prompt to print.
397  @type prompt: string
398  '''
399  self._write(prompt)
400  self.text_buffer.move_mark(self.line_start,
401  self.text_buffer.get_end_iter())
402 
403  def changeLine(self, text):
404  gobject.idle_add(self._changeLine, text)
405 
406  def _changeLine(self, text):
407  '''
408  Replace currently entered command line with given text.
409 
410  @param text: Text to use as replacement.
411  @type text: string
412  '''
413  iter = self.text_buffer.get_iter_at_mark(self.line_start)
414  iter.forward_to_line_end()
415  self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter)
416  self._write(text, True)
417 
418  def getCurrentLine(self):
419  '''
420  Get text in current command line.
421 
422  @return: Text of current command line.
423  @rtype: string
424  '''
425  rv = self.text_buffer.get_slice(
426  self.text_buffer.get_iter_at_mark(self.line_start),
427  self.text_buffer.get_end_iter(), False)
428  return rv
429 
430  def showReturned(self, text):
431  gobject.idle_add(self._showReturned, text)
432 
433  def _showReturned(self, text):
434  '''
435  Show returned text from last command and print new prompt.
436 
437  @param text: Text to show.
438  @type text: string
439  '''
440  iter = self.text_buffer.get_iter_at_mark(self.line_start)
441  iter.forward_to_line_end()
442  self.text_buffer.apply_tag_by_name(
443  'notouch',
444  self.text_buffer.get_iter_at_mark(self.line_start),
445  iter)
446  self._write('\n'+text)
447  if text:
448  self._write('\n')
449  self._showPrompt(self.prompt)
450  self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter())
451  self.text_buffer.place_cursor(self.text_buffer.get_end_iter())
452 
453  if self.IP.rl_do_indent:
454  indentation = self.IP.input_splitter.indent_spaces * ' '
455  self.text_buffer.insert_at_cursor(indentation)
456 
457  def onKeyPress(self, widget, event):
458  '''
459  Key press callback used for correcting behavior for console-like
460  interfaces. For example 'home' should go to prompt, not to begining of
461  line.
462 
463  @param widget: Widget that key press accored in.
464  @type widget: gtk.Widget
465  @param event: Event object
466  @type event: gtk.gdk.Event
467 
468  @return: Return True if event should not trickle.
469  @rtype: boolean
470  '''
471  insert_mark = self.text_buffer.get_insert()
472  insert_iter = self.text_buffer.get_iter_at_mark(insert_mark)
473  selection_mark = self.text_buffer.get_selection_bound()
474  selection_iter = self.text_buffer.get_iter_at_mark(selection_mark)
475  start_iter = self.text_buffer.get_iter_at_mark(self.line_start)
476  if event.keyval == gtk.keysyms.Home:
477  if event.state & gtk.gdk.CONTROL_MASK or event.state & gtk.gdk.MOD1_MASK:
478  pass
479  elif event.state & gtk.gdk.SHIFT_MASK:
480  self.text_buffer.move_mark(insert_mark, start_iter)
481  return True
482  else:
483  self.text_buffer.place_cursor(start_iter)
484  return True
485  elif event.keyval == gtk.keysyms.Left:
486  insert_iter.backward_cursor_position()
487  if not insert_iter.editable(True):
488  return True
489  elif not event.string:
490  pass
491  elif start_iter.compare(insert_iter) <= 0 and \
492  start_iter.compare(selection_iter) <= 0:
493  pass
494  elif start_iter.compare(insert_iter) > 0 and \
495  start_iter.compare(selection_iter) > 0:
496  self.text_buffer.place_cursor(start_iter)
497  elif insert_iter.compare(selection_iter) < 0:
498  self.text_buffer.move_mark(insert_mark, start_iter)
499  elif insert_iter.compare(selection_iter) > 0:
500  self.text_buffer.move_mark(selection_mark, start_iter)
501 
502  return self.onKeyPressExtend(event)
503 
504  def onKeyPressExtend(self, event):
505  '''
506  For some reason we can't extend onKeyPress directly (bug #500900).
507  '''
508  pass
509 
510 class IPythonView(ConsoleView, IterableIPShell):
511  '''
512  Sub-class of both modified IPython shell and L{ConsoleView} this makes
513  a GTK+ IPython console.
514  '''
515  def __init__(self):
516  '''
517  Initialize. Redirect I/O to console.
518  '''
519  ConsoleView.__init__(self)
520  self.cout = StringIO()
521  IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout,
522  input_func=self.raw_input)
523  self.interrupt = False
524  self.execute()
525  self.prompt = self.generatePrompt(False)
526  self.cout.truncate(0)
527  self.showPrompt(self.prompt)
528 
529  def raw_input(self, prompt=''):
530  '''
531  Custom raw_input() replacement. Get's current line from console buffer.
532 
533  @param prompt: Prompt to print. Here for compatability as replacement.
534  @type prompt: string
535 
536  @return: The current command line text.
537  @rtype: string
538  '''
539  if self.interrupt:
540  self.interrupt = False
541  raise KeyboardInterrupt
542  return self.getCurrentLine()
543 
544  def onKeyPressExtend(self, event):
545  '''
546  Key press callback with plenty of shell goodness, like history,
547  autocompletions, etc.
548 
549  @param widget: Widget that key press occured in.
550  @type widget: gtk.Widget
551  @param event: Event object.
552  @type event: gtk.gdk.Event
553 
554  @return: True if event should not trickle.
555  @rtype: boolean
556  '''
557 
558  if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99:
559  self.interrupt = True
560  self._processLine()
561  return True
562  elif event.keyval == gtk.keysyms.Return:
563  self._processLine()
564  return True
565  elif event.keyval == gtk.keysyms.Up:
566  self.changeLine(self.historyBack())
567  return True
568  elif event.keyval == gtk.keysyms.Down:
569  self.changeLine(self.historyForward())
570  return True
571  elif event.keyval == gtk.keysyms.Tab:
572  if not self.getCurrentLine().strip():
573  return False
574  completed, possibilities = self.complete(self.getCurrentLine())
575  if len(possibilities) > 1:
576  slice = self.getCurrentLine()
577  self.write('\n')
578  for symbol in possibilities:
579  self.write(symbol+'\n')
580  self.showPrompt(self.prompt)
581  self.changeLine(completed or slice)
582  return True
583 
584  def _processLine(self):
585  '''
586  Process current command line.
587  '''
588  self.history_pos = 0
589  self.execute()
590  rv = self.cout.getvalue()
591  if rv: rv = rv.strip('\n')
592  self.showReturned(rv)
593  self.cout.truncate(0)
594  self.cout.seek(0)
595 
596 if __name__ == "__main__":
597  window = gtk.Window()
598  window.set_default_size(640, 320)
599  window.connect('delete-event', lambda x, y: gtk.main_quit())
600  window.add(IPythonView())
601  window.show_all()
602  gtk.main()
603 
#define list
def onKeyPress(self, widget, event)
def generatePrompt(self, is_continuation)