GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
console.py
1 #! /usr/bin/env python
2 #
3 # Copyright (c) 2008, Nicolas Rougier
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are met:
8 #
9 # * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # * Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
14 # * Neither the name of the University of California, Berkeley nor the
15 # names of its contributors may be used to endorse or promote products
16 # derived from this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
19 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS BE LIABLE FOR ANY
22 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 import os
30 import sys
31 import re
32 import tempfile
33 import readline
34 import gobject
35 import gtk
36 import pango
37 from StringIO import StringIO
38 import shell
39 try: import ishell
40 except: pass
41 
42 ansi_colors = {'0;30': '#2E3436',
43  '0;31': '#CC0000',
44  '0;32': '#4E9A06',
45  '0;33': '#C4A000',
46  '0;34': '#3465A4',
47  '0;35': '#75507B',
48  '0;36': '#06989A',
49  '0;37': '#D3D7CF',
50  '1;30': '#555753',
51  '1;31': '#EF2929',
52  '1;32': '#8AE234',
53  '1;33': '#FCE94F',
54  '1;34': '#729FCF',
55  '1;35': '#AD7FA8',
56  '1;36': '#34E2E2',
57  '1;37': '#EEEEEC'}
58 
59 # ------------------------------------------------------------- class ConsoleOut
60 class ConsoleOut:
61  """
62  A fake output file object. It sends output to the console widget,
63  and if asked for a file number, returns one set on instance creation
64  """
65 
66  def __init__(self, console, fn=-1, style=None):
67  self.fn = fn
68  self.console = console
69  self.style = style
70  def close(self): pass
71  flush = close
72  def fileno(self): return self.fn
73  def isatty(self): return False
74  def read(self, a): return ''
75  def readline(self): return ''
76  def readlines(self): return []
77  def write(self, s):
78  self.console.write (s, self.style)
79  def writelines(self, l):
80  for s in l:
81  self.console.write (s, self.style)
82  def seek(self, a): raise IOError, (29, 'Illegal seek')
83  def tell(self): raise IOError, (29, 'Illegal seek')
84  truncate = tell
85 
86 
87 # -------------------------------------------------------------- class ConsoleIn
88 class ConsoleIn:
89  """
90  A fake input file object. It receives input from a GTK TextView widget,
91  and if asked for a file number, returns one set on instance creation
92  """
93  def __init__(self, console, fn=-1):
94  self.fn = fn
95  self.console = console
96  def close(self): pass
97  flush = close
98  def fileno(self): return self.fn
99  def isatty(self): return False
100  def read(self, a): return self.readline()
101  def readline(self):
102  self.console.input_mode = True
103  buffer = self.console.buffer
104  #console.write('\n')
105  iter = buffer.get_iter_at_mark(buffer.get_insert())
106  buffer.move_mark (buffer.get_mark('linestart'), iter)
107  while self.console.input_mode:
108  #while gtk.events_pending():
109  gtk.main_iteration()
110  s = self.console.input
111  self.console.input = ''
112  return s+'\n'
113  def readlines(self): return []
114  def write(self, s): return None
115  def writelines(self, l): return None
116  def seek(self, a): raise IOError, (29, 'Illegal seek')
117  def tell(self): raise IOError, (29, 'Illegal seek')
118  truncate = tell
119 
120 
121 # ---------------------------------------------------------------- class Console
122 class Console (gtk.ScrolledWindow):
123  """ GTK python console """
124 
125  def __init__(self, argv=[], shelltype='python', banner=[],
126  filename=None, size=100):
127 
128  """ Console interface building + initialization"""
129 
130  # GTK interface
131  self.do_quit = False
132  gtk.ScrolledWindow.__init__(self)
133  self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
134  self.set_shadow_type (gtk.SHADOW_NONE)
135  self.set_border_width(0)
136  self.view = gtk.TextView()
137  self.view.modify_font (pango.FontDescription("Mono 10"))
138  self.view.set_editable (True)
139  self.view.set_wrap_mode(True)
140  self.view.set_left_margin(0)
141  self.view.set_right_margin(0)
142  self.buffer = self.view.get_buffer()
143  self.buffer.create_tag ('title',
144  indent = 2,
145  weight=pango.WEIGHT_BOLD,
146  foreground='blue',
147  font='Mono 12')
148  self.buffer.create_tag ('subtitle',
149  indent = 2,
150  foreground='blue',
151  font='Mono 8')
152  self.buffer.create_tag ('output',
153  foreground = 'blue',
154  font='Mono 10')
155  self.buffer.create_tag ('error',
156  foreground='red',
157  style=pango.STYLE_OBLIQUE,
158  font='Mono 10')
159  self.buffer.create_tag ('prompt',
160  foreground='blue',
161  weight=pango.WEIGHT_BOLD,
162  font='Mono 10')
163  self.buffer.create_tag('0')
164  self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
165  for code in ansi_colors:
166  self.buffer.create_tag(code,
167  foreground=ansi_colors[code],
168  weight=700)
169  for text, style in banner:
170  self.write (text, style)
171  iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
172  self.buffer.create_mark ('linestart', iter, True)
173  self.view.add_events(gtk.gdk.KEY_PRESS_MASK)
174  self.view.connect ('key-press-event', self.key_press_event)
175  self.add(self.view)
176  self.show_all()
177  self.killbuffer = None
178 
179  # Console stuff
180  self.argv = argv
181  self.history_init(filename, size)
182  self.cout = StringIO()
183  self.cout.truncate(0)
184  if shelltype=='ipython':
185  self.shell = ishell.Shell(argv,locals(),globals(),
186  cout=self.cout, cerr=self.cout,
187  input_func=self.raw_input)
188  else:
189  self.shell = shell.Shell(locals(),globals())
190  self.interrupt = False
191  self.input_mode = False
192  self.input = None
193  self.stdout = ConsoleOut (self, sys.stdout.fileno(), 'output')
194  self.stderr = ConsoleOut (self, sys.stderr.fileno(), 'error')
195  self.stdin = ConsoleIn (self, sys.stdin.fileno())
196 
197  # Create a named pipe for system stdout/stderr redirection
198  self.fifoname = tempfile.mktemp()
199  if not os.path.exists (self.fifoname):
200  os.mkfifo (self.fifoname)
201  self.piperead = os.open (self.fifoname, os.O_RDONLY | os.O_NONBLOCK)
202  self.pipewrite = os.open (self.fifoname, os.O_WRONLY | os.O_NONBLOCK)
203  self.shell.eval(self)
204  self.cout.truncate(0)
205 
206  def history_init(self, filename, size):
207  self.history_file = filename
208  self.history_size = size
209  if filename and os.path.exists(filename):
210  readline.read_history_file(filename)
211  readline.set_history_length(size)
212  self.history_reset()
213 
214  def history_save(self):
215  if self.history_file:
216  readline.write_history_file(self.history_file)
217 
218  def history_add(self, item):
219  if len(item):
220  readline.add_history (item)
221  self.history_reset()
222 
223  def history_reset(self):
224  self.history_index = readline.get_current_history_length()+1
225 
226  def history_next(self):
227  self.history_index += 1
228  if self.history_index <= readline.get_current_history_length():
229  return '' or readline.get_history_item (self.history_index)
230  self.history_index = readline.get_current_history_length()+1
231  return ''
232 
233  def history_prev(self):
234  if self.history_index > 1:
235  self.history_index -= 1
236  else:
237  self.history_index = 1
238  return '' or readline.get_history_item (self.history_index)
239 
240  def raw_input(self, prompt=''):
241  if self.interrupt:
242  self.interrupt = False
243  raise KeyboardInterrupt
244  return self.last_line()
245 
246  def grab_focus (self):
247  """ Give focus to the TextView """
248 
249  self.view.grab_focus()
250 
251  def write (self, text, style=None):
252  """ Write text using given style (if any) """
253  segments = self.color_pat.split(text)
254  segment = segments.pop(0)
255  start,end = self.buffer.get_bounds()
256  if style==None:
257  self.buffer.insert(end, segment)
258  else:
259  self.buffer.insert_with_tags_by_name(end, segment, style)
260  if segments:
261  ansi_tags = self.color_pat.findall(text)
262  for tag in ansi_tags:
263  i = segments.index(tag)
264  self.buffer.insert_with_tags_by_name(self.buffer.get_end_iter(),
265  segments[i+1], tag)
266  segments.pop(i)
267  self.view.scroll_mark_onscreen(self.buffer.get_insert())
268 
269  def overwrite (self, text, style=None):
270  """ Overwrite text after prompt with text """
271 
272  mark = self.buffer.get_mark('linestart')
273  start = self.buffer.get_iter_at_mark(mark)
274  end = self.buffer.get_end_iter()
275  self.buffer.delete (start,end)
276  self.write (text, style)
277 
278  def last_line (self):
279  """ Get last line (without prompt) """
280 
281  mark = self.buffer.get_mark('linestart')
282  start = self.buffer.get_iter_at_mark(mark)
283  end = self.buffer.get_end_iter()
284  return self.buffer.get_text (start,end,True)
285 
286 
287  def prompt (self, style=None):
288  """ Display prompt """
289 
290  iter = self.buffer.get_end_iter()
291  self.buffer.place_cursor (iter)
292  self.write (self.shell.prompt, style)
293  iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
294  self.buffer.move_mark (self.buffer.get_mark('linestart'), iter)
295  self.history_reset()
296  self.view.scroll_mark_onscreen(self.buffer.get_insert())
297  while gtk.events_pending():
298  gtk.main_iteration()
299 
300  def key_press_event (self, widget, event):
301  """ Handle key press event """
302 
303  keyname = gtk.gdk.keyval_name (event.keyval)
304 
305  # New command
306  if keyname in ['Return', 'KP_Enter']:
307  line = self.last_line()
308  self.history_add (line)
309  if self.input_mode:
310  self.input_mode = False
311  self.input = self.last_line()
312  self.write('\n')
313  else:
314  self.execute()
315  return True
316 
317  # Prevent cursor to go back past prompt
318  elif keyname in ['Left', 'BackSpace']:
319  mark = self.buffer.get_mark('linestart')
320  linestart = self.buffer.get_iter_at_mark(mark)
321  iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
322  if iter.compare(linestart) <= 0:
323  return True
324 
325  elif keyname == 'Right':
326  return False
327 
328  # Next history item
329  elif keyname == 'Down':
330  self.overwrite (self.history_next())
331  return True
332 
333  # Previous history item
334  elif keyname == 'Up':
335  self.overwrite (self.history_prev())
336  return True
337 
338  # Move cursor just after prompt
339  elif keyname == 'Home':
340  mark = self.buffer.get_mark('linestart')
341  linestart = self.buffer.get_iter_at_mark(mark)
342  self.buffer.place_cursor (linestart)
343  return True
344 
345  # Completion if line not empty
346  elif keyname == 'Tab':
347  line = self.last_line()
348  if not line.strip():
349  return False
350  completed, possibilities = self.shell.complete(line)
351  if len(possibilities) > 1:
352  slice = line
353  self.write('\n')
354  for symbol in possibilities:
355  self.write(symbol+'\n')
356  self.prompt('prompt')
357  self.overwrite(completed or slice)
358  return True
359 
360  # Controls
361  elif event.state & gtk.gdk.CONTROL_MASK:
362  if keyname in ['a','A']:
363  mark = self.buffer.get_mark('linestart')
364  linestart = self.buffer.get_iter_at_mark(mark)
365  self.buffer.place_cursor (linestart)
366  return True
367  elif keyname in ['e','E']:
368  end = self.buffer.get_end_iter()
369  self.buffer.place_cursor (end)
370  return True
371  elif keyname in ['k','K']:
372  start = self.buffer.get_iter_at_mark (self.buffer.get_insert())
373  end = self.buffer.get_end_iter()
374  self.killbuffer = self.buffer.get_text(start,end)
375  self.buffer.delete(start,end)
376  return True
377  elif keyname in ['y','Y']:
378  if self.killbuffer:
379  iter = self.buffer.get_iter_at_mark (self.buffer.get_insert())
380  self.buffer.insert(iter, self.killbuffer)
381  return True
382  elif keyname in ['l', 'L']:
383  start = self.buffer.get_start_iter()
384  end = self.buffer.get_end_iter()
385  end.backward_sentence_start()
386  self.buffer.delete (start,end)
387  elif keyname in ['d', 'D']:
388  if not len(self.last_line().strip()):
389  self.quit()
390 
391  # Editing before prompt is forbidden
392  else:
393  mark = self.buffer.get_mark('linestart')
394  linestart = self.buffer.get_iter_at_mark(mark)
395  iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
396  if iter.compare(linestart) < 0:
397  iter = self.buffer.get_end_iter()
398  self.buffer.place_cursor (iter)
399  return False
400 
401 
402  def execute (self):
403  # Python stdout, stderr, stdin redirection
404  sys.stdout, self.stdout = self.stdout, sys.stdout
405  sys.stderr, self.stderr = self.stderr, sys.stderr
406  sys.stdin, self.stdin = self.stdin, sys.stdin
407 
408  # System stdout, stderr redirection
409  sys_stdout = os.dup(1)
410  sys_stderr = os.dup(2)
411  os.dup2 (self.pipewrite, 1)
412  os.dup2 (self.pipewrite, 2)
413 
414  self.shell.eval(self)
415  self.view.scroll_mark_onscreen(self.buffer.get_insert())
416  while gtk.events_pending():
417  gtk.main_iteration()
418 
419  # Get system output and remove system redirection
420  os.dup2 (sys_stdout, 1)
421  os.dup2 (sys_stderr, 2)
422  os.close (sys_stdout)
423  os.close (sys_stderr)
424 
425  # Remove python redirection
426  sys.stdout, self.stdout = self.stdout, sys.stdout
427  sys.stderr, self.stderr = self.stderr, sys.stderr
428  sys.stdin, self.stdin = self.stdin, sys.stdin
429 
430 
431  def quit(self):
432  """ Quit console """
433 
434  gtk.main_quit()
435  self.history_save()
436  try:
437  os.close (self.piperead)
438  os.close (self.pipewrite)
439  except:
440  pass
441  if os.path.exists (self.fifoname):
442  os.remove (self.fifoname)
443  self.do_quit = True