GnuCash  5.6-150-g038405b370+
console.py
1 #! /usr/bin/env python3
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 gi
35 gi.require_version('Gtk', '3.0')
36 from gi.repository import GObject
37 from gi.repository import Gtk
38 from gi.repository import Gdk
39 from gi.repository import Pango
40 import io
41 import pycons.shell as shell
42 try: import pycons.ishell as ishell
43 except: pass
44 
45 ansi_colors = {'0;30': '#2E3436',
46  '0;31': '#CC0000',
47  '0;32': '#4E9A06',
48  '0;33': '#C4A000',
49  '0;34': '#3465A4',
50  '0;35': '#75507B',
51  '0;36': '#06989A',
52  '0;37': '#D3D7CF',
53  '1;30': '#555753',
54  '1;31': '#EF2929',
55  '1;32': '#8AE234',
56  '1;33': '#FCE94F',
57  '1;34': '#729FCF',
58  '1;35': '#AD7FA8',
59  '1;36': '#34E2E2',
60  '1;37': '#EEEEEC'}
61 
62 # ------------------------------------------------------------- class ConsoleOut
63 class ConsoleOut:
64  """
65  A fake output file object. It sends output to the console widget,
66  and if asked for a file number, returns one set on instance creation
67  """
68 
69  def __init__(self, console, fn=-1, style=None):
70  self.fn = fn
71  self.console = console
72  self.style = style
73  def close(self): pass
74  flush = close
75  def fileno(self): return self.fn
76  def isatty(self): return False
77  def read(self, a): return ''
78  def readline(self): return ''
79  def readlines(self): return []
80  def write(self, s):
81  self.console.write (s, self.style)
82  def writelines(self, l):
83  for s in l:
84  self.console.write (s, self.style)
85  def seek(self, a): raise IOError(29, 'Illegal seek')
86  def tell(self): raise IOError(29, 'Illegal seek')
87  truncate = tell
88 
89 
90 # -------------------------------------------------------------- class ConsoleIn
91 class ConsoleIn:
92  """
93  A fake input file object. It receives input from a GTK TextView widget,
94  and if asked for a file number, returns one set on instance creation
95  """
96  def __init__(self, console, fn=-1):
97  self.fn = fn
98  self.console = console
99  def close(self): pass
100  flush = close
101  def fileno(self): return self.fn
102  def isatty(self): return False
103  def read(self, a): return self.readline()
104  def readline(self):
105  self.console.input_mode = True
106  buffer = self.console.buffer
107  #console.write('\n')
108  iter = buffer.get_iter_at_mark(buffer.get_insert())
109  buffer.move_mark (buffer.get_mark('linestart'), iter)
110  while self.console.input_mode:
111  #while Gtk.events_pending():
112  Gtk.main_iteration()
113  s = self.console.input
114  self.console.input = ''
115  return s+'\n'
116  def readlines(self): return []
117  def write(self, s): return None
118  def writelines(self, l): return None
119  def seek(self, a): raise IOError(29, 'Illegal seek')
120  def tell(self): raise IOError(29, 'Illegal seek')
121  truncate = tell
122 
123 
124 # ---------------------------------------------------------------- class Console
125 class Console (Gtk.ScrolledWindow):
126  """ GTK python console """
127 
128  def __init__(self, argv=[], shelltype='python', banner=[],
129  filename=None, size=100, user_local_ns=None, user_global_ns=None):
130 
131  """ Console interface building + initialization"""
132 
133  # GTK interface
134  self.do_quit = False
135  GObject.GObject.__init__(self)
136  self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
137  self.set_shadow_type (Gtk.ShadowType.NONE)
138  self.set_border_width(0)
139  self.view = Gtk.TextView()
140  self.view.modify_font (Pango.FontDescription("Mono 10"))
141  self.view.set_editable (True)
142  self.view.set_wrap_mode(True)
143  self.view.set_left_margin(0)
144  self.view.set_right_margin(0)
145  self.buffer = self.view.get_buffer()
146  self.buffer.create_tag ('title',
147  indent = 2,
148  weight=Pango.Weight.BOLD,
149  foreground='blue',
150  font='Mono 12')
151  self.buffer.create_tag ('subtitle',
152  indent = 2,
153  foreground='blue',
154  font='Mono 8')
155  self.buffer.create_tag ('output',
156  foreground = 'blue',
157  font='Mono 10')
158  self.buffer.create_tag ('error',
159  foreground='red',
160  style=Pango.Style.OBLIQUE,
161  font='Mono 10')
162  self.buffer.create_tag ('prompt',
163  foreground='blue',
164  weight=Pango.Weight.BOLD,
165  font='Mono 10')
166  self.buffer.create_tag('0')
167  self.color_pat = re.compile(r'\x01?\x1b\[(.*?)m\x02?')
168  for code in ansi_colors:
169  self.buffer.create_tag(code,
170  foreground=ansi_colors[code],
171  weight=700)
172  for text, style in banner:
173  self.write (text, style)
174  iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
175  self.buffer.create_mark ('linestart', iter, True)
176  self.view.add_events(Gdk.EventMask.KEY_PRESS_MASK)
177  self.view.connect ('key-press-event', self.key_press_event)
178  self.add(self.view)
179  self.show_all()
180  self.killbuffer = None
181 
182  # Console stuff
183  self.argv = argv
184  self.history_init(filename, size)
185  self.cout = io.StringIO()
186  self.cout.truncate(0)
187 
188  if not user_local_ns:
189  user_local_ns = locals()
190  if not user_global_ns:
191  user_global_ns = globals()
192 
193  if shelltype=='ipython':
194  self.shell = ishell.Shell(argv,user_local_ns, user_global_ns,
195  cout=self.cout, cerr=self.cout,
196  input_func=self.raw_input)
197  else:
198  self.shell = shell.Shell(user_local_ns,user_global_ns)
199  self.interrupt = False
200  self.input_mode = False
201  self.input = None
202  self.stdout = ConsoleOut (self, sys.stdout.fileno(), 'output')
203  self.stderr = ConsoleOut (self, sys.stderr.fileno(), 'error')
204  self.stdin = ConsoleIn (self, sys.stdin.fileno())
205 
206  # Create a named pipe for system stdout/stderr redirection
207  self.fifoname = tempfile.mktemp()
208  if not os.path.exists (self.fifoname):
209  os.mkfifo (self.fifoname)
210  self.piperead = os.open (self.fifoname, os.O_RDONLY | os.O_NONBLOCK)
211  self.pipewrite = os.open (self.fifoname, os.O_WRONLY | os.O_NONBLOCK)
212  self.shell.eval(self)
213  self.cout.truncate(0)
214 
215  def history_init(self, filename, size):
216  self.history_file = filename
217  self.history_size = size
218  if filename and os.path.exists(filename):
219  readline.read_history_file(filename)
220  readline.set_history_length(size)
221  self.history_reset()
222 
223  def history_save(self):
224  if self.history_file:
225  readline.write_history_file(self.history_file)
226 
227  def history_add(self, item):
228  if len(item):
229  readline.add_history (item)
230  self.history_reset()
231 
232  def history_reset(self):
233  self.history_index = readline.get_current_history_length()+1
234 
235  def history_next(self):
236  self.history_index += 1
237  if self.history_index <= readline.get_current_history_length():
238  return '' or readline.get_history_item (self.history_index)
239  self.history_index = readline.get_current_history_length()+1
240  return ''
241 
242  def history_prev(self):
243  if self.history_index > 1:
244  self.history_index -= 1
245  else:
246  self.history_index = 1
247  return '' or readline.get_history_item (self.history_index)
248 
249  def raw_input(self, prompt=''):
250  if self.interrupt:
251  self.interrupt = False
252  raise KeyboardInterrupt
253  return self.last_line()
254 
255  def grab_focus (self):
256  """ Give focus to the TextView """
257 
258  self.view.grab_focus()
259 
260  def write (self, text, style=None):
261  """ Write text using given style (if any) """
262  segments = self.color_pat.split(text)
263  segment = segments.pop(0)
264  start,end = self.buffer.get_bounds()
265  if style==None:
266  self.buffer.insert(end, segment)
267  else:
268  self.buffer.insert_with_tags_by_name(end, segment, style)
269  if segments:
270  ansi_tags = self.color_pat.findall(text)
271  for tag in ansi_tags:
272  i = segments.index(tag)
273  self.buffer.insert_with_tags_by_name(self.buffer.get_end_iter(),
274  segments[i+1], tag)
275  segments.pop(i)
276  self.view.scroll_mark_onscreen(self.buffer.get_insert())
277 
278  def overwrite (self, text, style=None):
279  """ Overwrite text after prompt with text """
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  self.buffer.delete (start,end)
285  self.write (text, style)
286 
287  def last_line (self):
288  """ Get last line (without prompt) """
289 
290  mark = self.buffer.get_mark('linestart')
291  start = self.buffer.get_iter_at_mark(mark)
292  end = self.buffer.get_end_iter()
293  return self.buffer.get_text (start,end,True)
294 
295 
296  def prompt (self, style=None):
297  """ Display prompt """
298 
299  iter = self.buffer.get_end_iter()
300  self.buffer.place_cursor (iter)
301  self.write (self.shell.prompt, style)
302  iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
303  self.buffer.move_mark (self.buffer.get_mark('linestart'), iter)
304  self.history_reset()
305  self.view.scroll_mark_onscreen(self.buffer.get_insert())
306  while Gtk.events_pending():
307  Gtk.main_iteration()
308 
309  def key_press_event (self, widget, event):
310  """ Handle key press event """
311 
312  keyname = Gdk.keyval_name (event.keyval)
313 
314  # New command
315  if keyname in ['Return', 'KP_Enter']:
316  line = self.last_line()
317  self.history_add (line)
318  if self.input_mode:
319  self.input_mode = False
320  self.input = self.last_line()
321  self.write('\n')
322  else:
323  self.execute()
324  return True
325 
326  # Prevent cursor to go back past prompt
327  elif keyname in ['Left', 'BackSpace']:
328  mark = self.buffer.get_mark('linestart')
329  linestart = self.buffer.get_iter_at_mark(mark)
330  iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
331  if iter.compare(linestart) <= 0:
332  return True
333 
334  elif keyname == 'Right':
335  return False
336 
337  # Next history item
338  elif keyname == 'Down':
339  self.overwrite (self.history_next())
340  return True
341 
342  # Previous history item
343  elif keyname == 'Up':
344  self.overwrite (self.history_prev())
345  return True
346 
347  # Move cursor just after prompt
348  elif keyname == 'Home':
349  mark = self.buffer.get_mark('linestart')
350  linestart = self.buffer.get_iter_at_mark(mark)
351  self.buffer.place_cursor (linestart)
352  return True
353 
354  # Completion if line not empty
355  elif keyname == 'Tab':
356  line = self.last_line()
357  if not line.strip():
358  return False
359  completed, possibilities = self.shell.complete(line)
360  if len(possibilities) > 1:
361  slice = line
362  self.write('\n')
363  for symbol in possibilities:
364  self.write(symbol+'\n')
365  self.prompt('prompt')
366  self.overwrite(completed or slice)
367  return True
368 
369  # Controls
370  elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
371  if keyname in ['a','A']:
372  mark = self.buffer.get_mark('linestart')
373  linestart = self.buffer.get_iter_at_mark(mark)
374  self.buffer.place_cursor (linestart)
375  return True
376  elif keyname in ['e','E']:
377  end = self.buffer.get_end_iter()
378  self.buffer.place_cursor (end)
379  return True
380  elif keyname in ['k','K']:
381  start = self.buffer.get_iter_at_mark (self.buffer.get_insert())
382  end = self.buffer.get_end_iter()
383  self.killbuffer = self.buffer.get_text(start,end)
384  self.buffer.delete(start,end)
385  return True
386  elif keyname in ['y','Y']:
387  if self.killbuffer:
388  iter = self.buffer.get_iter_at_mark (self.buffer.get_insert())
389  self.buffer.insert(iter, self.killbuffer)
390  return True
391  elif keyname in ['l', 'L']:
392  start = self.buffer.get_start_iter()
393  end = self.buffer.get_end_iter()
394  end.backward_sentence_start()
395  self.buffer.delete (start,end)
396  elif keyname in ['d', 'D']:
397  if not len(self.last_line().strip()):
398  self.quit()
399 
400  # Editing before prompt is forbidden
401  else:
402  mark = self.buffer.get_mark('linestart')
403  linestart = self.buffer.get_iter_at_mark(mark)
404  iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
405  if iter.compare(linestart) < 0:
406  iter = self.buffer.get_end_iter()
407  self.buffer.place_cursor (iter)
408  return False
409 
410 
411  def execute (self):
412  # Python stdout, stderr, stdin redirection
413  sys.stdout, self.stdout = self.stdout, sys.stdout
414  sys.stderr, self.stderr = self.stderr, sys.stderr
415  sys.stdin, self.stdin = self.stdin, sys.stdin
416 
417  # System stdout, stderr redirection
418  sys_stdout = os.dup(1)
419  sys_stderr = os.dup(2)
420  os.dup2 (self.pipewrite, 1)
421  os.dup2 (self.pipewrite, 2)
422 
423  self.shell.eval(self)
424  self.view.scroll_mark_onscreen(self.buffer.get_insert())
425  while Gtk.events_pending():
426  Gtk.main_iteration()
427 
428  # Get system output and remove system redirection
429  os.dup2 (sys_stdout, 1)
430  os.dup2 (sys_stderr, 2)
431  os.close (sys_stdout)
432  os.close (sys_stderr)
433 
434  # Remove python redirection
435  sys.stdout, self.stdout = self.stdout, sys.stdout
436  sys.stderr, self.stderr = self.stderr, sys.stderr
437  sys.stdin, self.stdin = self.stdin, sys.stdin
438 
439 
440  def quit(self):
441  """ Quit console """
442 
443  self.history_save()
444  try:
445  os.close (self.piperead)
446  os.close (self.pipewrite)
447  except:
448  pass
449  if os.path.exists (self.fifoname):
450  os.remove (self.fifoname)
451  self.do_quit = True
def history_reset(self)
Definition: console.py:232
def overwrite(self, text, style=None)
Definition: console.py:278
def write(self, text, style=None)
Definition: console.py:260
def raw_input(self, prompt='')
Definition: console.py:249
def history_save(self)
Definition: console.py:223
def history_add(self, item)
Definition: console.py:227
def last_line(self)
Definition: console.py:287
def history_prev(self)
Definition: console.py:242
def __init__(self, argv=[], shelltype='python', banner=[], filename=None, size=100, user_local_ns=None, user_global_ns=None)
Definition: console.py:129
def grab_focus(self)
Definition: console.py:255
def history_init(self, filename, size)
Definition: console.py:215
def history_next(self)
Definition: console.py:235
def key_press_event(self, widget, event)
Definition: console.py:309
def prompt(self, style=None)
Definition: console.py:296