Add code to start up a Python console during Py module init, but disabled by default.

If a python console is wanted, change the last section
of src/python/init.py to "if True:".

From: Andy Clayton <q3aiml@gmail.com>

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@20471 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Christian Stimming
2011-03-24 20:10:45 +00:00
parent 1874219672
commit a3443e4f90
11 changed files with 1315 additions and 1 deletions

View File

@@ -1388,6 +1388,7 @@ AC_CONFIG_FILES(
src/optional/python-bindings/tests/Makefile
src/pixmaps/Makefile
src/python/Makefile
src/python/pycons/Makefile
src/quotes/Makefile
src/register/Makefile
src/register/ledger-core/Makefile

View File

@@ -1,4 +1,4 @@
SUBDIRS = .
SUBDIRS = . pycons
#test
pkglib_LTLIBRARIES = libgncmod-python.la

View File

@@ -2,6 +2,12 @@ import sys
import _sw_app_utils
from gnucash import *
import gtk
import os
sys.path.append(os.path.dirname(__file__))
print "woop", os.path.dirname(__file__)
import pycons.console as cons
print "Hello from python!"
print "test", sys.modules.keys()
@@ -19,5 +25,66 @@ print "test3", dir(acct)
#print acct.GetBalance()
#print acct.GetSplitList()
#print "test2", dir(gnucash.gnucash_core_c)
class Console (cons.Console):
""" GTK python console """
def __init__(self, argv=[], shelltype='python', banner=[],
filename=None, size=100):
cons.Console.__init__(self, argv, shelltype, banner, filename, size)
self.buffer.create_tag('center',
justification=gtk.JUSTIFY_CENTER,
font='Mono 4')
self.figures = []
self.callbacks = []
self.last_figure = None
self.active_canvas = None
self.view.connect ('key-press-event', self.key_press_event)
self.view.connect ('button-press-event', self.button_press_event)
self.view.connect ('scroll-event', self.scroll_event)
def key_press_event (self, widget, event):
""" Handle key press event """
if self.active_canvas:
self.active_canvas.emit ('key-press-event', event)
return True
return cons.Console.key_press_event (self, widget, event)
def scroll_event (self, widget, event):
""" Scroll event """
if self.active_canvas:
return True
return False
def button_press_event (self, widget, event):
""" Button press event """
return self.refresh()
def refresh (self):
""" Refresh drawing """
for fig in self.figures:
figure, canvas, anchor = fig
canvas.draw()
return False
# Change this to "if True:" to switch on a python console at gnucash
# startup:
if False:
console = Console(argv = [], shelltype = 'python', banner = [['woop', 'title']], size = 100)
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_position(gtk.WIN_POS_CENTER)
window.set_default_size(800,600)
window.set_border_width(0)
# Hm. gtk.main_quit will kill gnucash without closing the file
# properly. That's kinda bad.
window.connect('destroy-event', gtk.main_quit)
window.connect('delete-event', gtk.main_quit)
window.add (console)
window.show_all()
console.grab_focus()

View File

@@ -0,0 +1,14 @@
SUBDIRS = .
pyconsdir = ${GNC_SHAREDIR}/python/pycons/
pycons_DATA = \
console.py \
__init__.py \
ishell.py \
pycons \
setup.py \
shell.py \
simple_plot.py
EXTRA_DIST = ${pycons_DATA}

View File

@@ -0,0 +1,27 @@
#! /usr/bin/env python
#
# Copyright (c) 2008, Nicolas Rougier
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the University of California, Berkeley nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,443 @@
#! /usr/bin/env python
#
# Copyright (c) 2008, Nicolas Rougier
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the University of California, Berkeley nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
import sys
import re
import tempfile
import readline
import gobject
import gtk
import pango
from StringIO import StringIO
import shell
try: import ishell
except: pass
ansi_colors = {'0;30': '#2E3436',
'0;31': '#CC0000',
'0;32': '#4E9A06',
'0;33': '#C4A000',
'0;34': '#3465A4',
'0;35': '#75507B',
'0;36': '#06989A',
'0;37': '#D3D7CF',
'1;30': '#555753',
'1;31': '#EF2929',
'1;32': '#8AE234',
'1;33': '#FCE94F',
'1;34': '#729FCF',
'1;35': '#AD7FA8',
'1;36': '#34E2E2',
'1;37': '#EEEEEC'}
# ------------------------------------------------------------- class ConsoleOut
class ConsoleOut:
"""
A fake output file object. It sends output to the console widget,
and if asked for a file number, returns one set on instance creation
"""
def __init__(self, console, fn=-1, style=None):
self.fn = fn
self.console = console
self.style = style
def close(self): pass
flush = close
def fileno(self): return self.fn
def isatty(self): return False
def read(self, a): return ''
def readline(self): return ''
def readlines(self): return []
def write(self, s):
self.console.write (s, self.style)
def writelines(self, l):
for s in l:
self.console.write (s, self.style)
def seek(self, a): raise IOError, (29, 'Illegal seek')
def tell(self): raise IOError, (29, 'Illegal seek')
truncate = tell
# -------------------------------------------------------------- class ConsoleIn
class ConsoleIn:
"""
A fake input file object. It receives input from a GTK TextView widget,
and if asked for a file number, returns one set on instance creation
"""
def __init__(self, console, fn=-1):
self.fn = fn
self.console = console
def close(self): pass
flush = close
def fileno(self): return self.fn
def isatty(self): return False
def read(self, a): return self.readline()
def readline(self):
self.console.input_mode = True
buffer = self.console.buffer
#console.write('\n')
iter = buffer.get_iter_at_mark(buffer.get_insert())
buffer.move_mark (buffer.get_mark('linestart'), iter)
while self.console.input_mode:
#while gtk.events_pending():
gtk.main_iteration()
s = self.console.input
self.console.input = ''
return s+'\n'
def readlines(self): return []
def write(self, s): return None
def writelines(self, l): return None
def seek(self, a): raise IOError, (29, 'Illegal seek')
def tell(self): raise IOError, (29, 'Illegal seek')
truncate = tell
# ---------------------------------------------------------------- class Console
class Console (gtk.ScrolledWindow):
""" GTK python console """
def __init__(self, argv=[], shelltype='python', banner=[],
filename=None, size=100):
""" Console interface building + initialization"""
# GTK interface
self.do_quit = False
gtk.ScrolledWindow.__init__(self)
self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
self.set_shadow_type (gtk.SHADOW_NONE)
self.set_border_width(0)
self.view = gtk.TextView()
self.view.modify_font (pango.FontDescription("Mono 10"))
self.view.set_editable (True)
self.view.set_wrap_mode(True)
self.view.set_left_margin(0)
self.view.set_right_margin(0)
self.buffer = self.view.get_buffer()
self.buffer.create_tag ('title',
indent = 2,
weight=pango.WEIGHT_BOLD,
foreground='blue',
font='Mono 12')
self.buffer.create_tag ('subtitle',
indent = 2,
foreground='blue',
font='Mono 8')
self.buffer.create_tag ('output',
foreground = 'blue',
font='Mono 10')
self.buffer.create_tag ('error',
foreground='red',
style=pango.STYLE_OBLIQUE,
font='Mono 10')
self.buffer.create_tag ('prompt',
foreground='blue',
weight=pango.WEIGHT_BOLD,
font='Mono 10')
self.buffer.create_tag('0')
self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
for code in ansi_colors:
self.buffer.create_tag(code,
foreground=ansi_colors[code],
weight=700)
for text, style in banner:
self.write (text, style)
iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
self.buffer.create_mark ('linestart', iter, True)
self.view.add_events(gtk.gdk.KEY_PRESS_MASK)
self.view.connect ('key-press-event', self.key_press_event)
self.add(self.view)
self.show_all()
self.killbuffer = None
# Console stuff
self.argv = argv
self.history_init(filename, size)
self.cout = StringIO()
self.cout.truncate(0)
if shelltype=='ipython':
self.shell = ishell.Shell(argv,locals(),globals(),
cout=self.cout, cerr=self.cout,
input_func=self.raw_input)
else:
self.shell = shell.Shell(locals(),globals())
self.interrupt = False
self.input_mode = False
self.input = None
self.stdout = ConsoleOut (self, sys.stdout.fileno(), 'output')
self.stderr = ConsoleOut (self, sys.stderr.fileno(), 'error')
self.stdin = ConsoleIn (self, sys.stdin.fileno())
# Create a named pipe for system stdout/stderr redirection
self.fifoname = tempfile.mktemp()
if not os.path.exists (self.fifoname):
os.mkfifo (self.fifoname)
self.piperead = os.open (self.fifoname, os.O_RDONLY | os.O_NONBLOCK)
self.pipewrite = os.open (self.fifoname, os.O_WRONLY | os.O_NONBLOCK)
self.shell.eval(self)
self.cout.truncate(0)
def history_init(self, filename, size):
self.history_file = filename
self.history_size = size
if filename and os.path.exists(filename):
readline.read_history_file(filename)
readline.set_history_length(size)
self.history_reset()
def history_save(self):
if self.history_file:
readline.write_history_file(self.history_file)
def history_add(self, item):
if len(item):
readline.add_history (item)
self.history_reset()
def history_reset(self):
self.history_index = readline.get_current_history_length()+1
def history_next(self):
self.history_index += 1
if self.history_index <= readline.get_current_history_length():
return '' or readline.get_history_item (self.history_index)
self.history_index = readline.get_current_history_length()+1
return ''
def history_prev(self):
if self.history_index > 1:
self.history_index -= 1
else:
self.history_index = 1
return '' or readline.get_history_item (self.history_index)
def raw_input(self, prompt=''):
if self.interrupt:
self.interrupt = False
raise KeyboardInterrupt
return self.last_line()
def grab_focus (self):
""" Give focus to the TextView """
self.view.grab_focus()
def write (self, text, style=None):
""" Write text using given style (if any) """
segments = self.color_pat.split(text)
segment = segments.pop(0)
start,end = self.buffer.get_bounds()
if style==None:
self.buffer.insert(end, segment)
else:
self.buffer.insert_with_tags_by_name(end, segment, style)
if segments:
ansi_tags = self.color_pat.findall(text)
for tag in ansi_tags:
i = segments.index(tag)
self.buffer.insert_with_tags_by_name(self.buffer.get_end_iter(),
segments[i+1], tag)
segments.pop(i)
self.view.scroll_mark_onscreen(self.buffer.get_insert())
def overwrite (self, text, style=None):
""" Overwrite text after prompt with text """
mark = self.buffer.get_mark('linestart')
start = self.buffer.get_iter_at_mark(mark)
end = self.buffer.get_end_iter()
self.buffer.delete (start,end)
self.write (text, style)
def last_line (self):
""" Get last line (without prompt) """
mark = self.buffer.get_mark('linestart')
start = self.buffer.get_iter_at_mark(mark)
end = self.buffer.get_end_iter()
return self.buffer.get_text (start,end,True)
def prompt (self, style=None):
""" Display prompt """
iter = self.buffer.get_end_iter()
self.buffer.place_cursor (iter)
self.write (self.shell.prompt, style)
iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
self.buffer.move_mark (self.buffer.get_mark('linestart'), iter)
self.history_reset()
self.view.scroll_mark_onscreen(self.buffer.get_insert())
while gtk.events_pending():
gtk.main_iteration()
def key_press_event (self, widget, event):
""" Handle key press event """
keyname = gtk.gdk.keyval_name (event.keyval)
# New command
if keyname in ['Return', 'KP_Enter']:
line = self.last_line()
self.history_add (line)
if self.input_mode:
self.input_mode = False
self.input = self.last_line()
self.write('\n')
else:
self.execute()
return True
# Prevent cursor to go back past prompt
elif keyname in ['Left', 'BackSpace']:
mark = self.buffer.get_mark('linestart')
linestart = self.buffer.get_iter_at_mark(mark)
iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
if iter.compare(linestart) <= 0:
return True
elif keyname == 'Right':
return False
# Next history item
elif keyname == 'Down':
self.overwrite (self.history_next())
return True
# Previous history item
elif keyname == 'Up':
self.overwrite (self.history_prev())
return True
# Move cursor just after prompt
elif keyname == 'Home':
mark = self.buffer.get_mark('linestart')
linestart = self.buffer.get_iter_at_mark(mark)
self.buffer.place_cursor (linestart)
return True
# Completion if line not empty
elif keyname == 'Tab':
line = self.last_line()
if not line.strip():
return False
completed, possibilities = self.shell.complete(line)
if len(possibilities) > 1:
slice = line
self.write('\n')
for symbol in possibilities:
self.write(symbol+'\n')
self.prompt('prompt')
self.overwrite(completed or slice)
return True
# Controls
elif event.state & gtk.gdk.CONTROL_MASK:
if keyname in ['a','A']:
mark = self.buffer.get_mark('linestart')
linestart = self.buffer.get_iter_at_mark(mark)
self.buffer.place_cursor (linestart)
return True
elif keyname in ['e','E']:
end = self.buffer.get_end_iter()
self.buffer.place_cursor (end)
return True
elif keyname in ['k','K']:
start = self.buffer.get_iter_at_mark (self.buffer.get_insert())
end = self.buffer.get_end_iter()
self.killbuffer = self.buffer.get_text(start,end)
self.buffer.delete(start,end)
return True
elif keyname in ['y','Y']:
if self.killbuffer:
iter = self.buffer.get_iter_at_mark (self.buffer.get_insert())
self.buffer.insert(iter, self.killbuffer)
return True
elif keyname in ['l', 'L']:
start = self.buffer.get_start_iter()
end = self.buffer.get_end_iter()
end.backward_sentence_start()
self.buffer.delete (start,end)
elif keyname in ['d', 'D']:
if not len(self.last_line().strip()):
self.quit()
# Editing before prompt is forbidden
else:
mark = self.buffer.get_mark('linestart')
linestart = self.buffer.get_iter_at_mark(mark)
iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
if iter.compare(linestart) < 0:
iter = self.buffer.get_end_iter()
self.buffer.place_cursor (iter)
return False
def execute (self):
# Python stdout, stderr, stdin redirection
sys.stdout, self.stdout = self.stdout, sys.stdout
sys.stderr, self.stderr = self.stderr, sys.stderr
sys.stdin, self.stdin = self.stdin, sys.stdin
# System stdout, stderr redirection
sys_stdout = os.dup(1)
sys_stderr = os.dup(2)
os.dup2 (self.pipewrite, 1)
os.dup2 (self.pipewrite, 2)
self.shell.eval(self)
self.view.scroll_mark_onscreen(self.buffer.get_insert())
while gtk.events_pending():
gtk.main_iteration()
# Get system output and remove system redirection
os.dup2 (sys_stdout, 1)
os.dup2 (sys_stderr, 2)
os.close (sys_stdout)
os.close (sys_stderr)
# Remove python redirection
sys.stdout, self.stdout = self.stdout, sys.stdout
sys.stderr, self.stderr = self.stderr, sys.stderr
sys.stdin, self.stdin = self.stdin, sys.stdin
def quit(self):
""" Quit console """
gtk.main_quit()
self.history_save()
try:
os.close (self.piperead)
os.close (self.pipewrite)
except:
pass
if os.path.exists (self.fifoname):
os.remove (self.fifoname)
self.do_quit = True

134
src/python/pycons/ishell.py Normal file
View File

@@ -0,0 +1,134 @@
#! /usr/bin/env python
#
# Adapted from:
#
# Backend to the console plugin.
# @author: Eitan Isaacson
# @organization: IBM Corporation
# @copyright: Copyright (c) 2007 IBM Corporation
# @license: BSD
#
# All rights reserved. This program and the accompanying materials are made
# available under the terms of the BSD which accompanies this distribution, and
# is available at U{http://www.opensource.org/licenses/bsd-license.php}
#
import os
import sys
import re
from StringIO import StringIO
try:
import IPython
from IPython import ipapi
except Exception,e:
raise "Error importing IPython (%s)" % str(e)
# ------------------------------------------------------------------ class Shell
class Shell:
""" """
def __init__(self,argv=None,user_ns=None,user_global_ns=None,
cin=None, cout=None,cerr=None, input_func=None):
""" """
if input_func:
IPython.iplib.raw_input_original = input_func
if cin:
IPython.Shell.Term.cin = cin
if cout:
IPython.Shell.Term.cout = cout
if cerr:
IPython.Shell.Term.cerr = cerr
if argv is None:
argv=[]
IPython.iplib.raw_input = lambda x: None
self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr)
os.environ['TERM'] = 'dumb'
excepthook = sys.excepthook
self.IP = IPython.Shell.make_IPython(argv,
user_ns=user_ns,
user_global_ns=user_global_ns,
embedded=True,
shell_class=IPython.Shell.InteractiveShell)
self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd),
header='IPython system call: ',
verbose=self.IP.rc.system_verbose)
# Get a hold of the public IPython API object and use it
self.ip = ipapi.get()
self.ip.magic('colors LightBG')
sys.excepthook = excepthook
self.iter_more = 0
self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
def namespace(self):
return self.IP.user_ns
def eval(self, console):
console.write ('\n')
orig_stdout = sys.stdout
sys.stdout = IPython.Shell.Term.cout
try:
line = self.IP.raw_input(None, self.iter_more)
if self.IP.autoindent:
self.IP.readline_startup_hook(None)
except KeyboardInterrupt:
self.IP.write('\nKeyboardInterrupt\n')
self.IP.resetbuffer()
self.IP.outputcache.prompt_count -= 1
if self.IP.autoindent:
self.IP.indent_current_nsp = 0
self.iter_more = 0
except:
self.IP.showtraceback()
else:
self.iter_more = self.IP.push(line)
if (self.IP.SyntaxTB.last_syntax_error and self.IP.rc.autoedit_syntax):
self.IP.edit_syntax_error()
if self.iter_more:
self.prompt = str(self.IP.outputcache.prompt2).strip()
if self.IP.autoindent:
self.IP.readline_startup_hook(self.IP.pre_readline)
else:
self.prompt = str(self.IP.outputcache.prompt1).strip()
sys.stdout = orig_stdout
# System output (if any)
while True:
try:
buf = os.read(console.piperead, 256)
except:
break
else:
console.write (buf)
if len(buf) < 256: break
# Command output
rv = console.cout.getvalue()
if rv:
rv = rv.strip('\n')
console.write (rv)
if rv:
console.write ('\n')
console.cout.truncate(0)
console.prompt()
def complete(self, line):
split_line = self.complete_sep.split(line)
possibilities = self.IP.complete(split_line[-1])
if possibilities:
common_prefix = os.path.commonprefix (possibilities)
completed = line[:-len(split_line[-1])]+common_prefix
else:
completed = line
return completed, possibilities
def shell(self, cmd,verbose=0,debug=0,header=''):
stat = 0
if verbose or debug: print header+cmd
if not debug:
input, output = os.popen4(cmd)
print output.read()
output.close()
input.close()

398
src/python/pycons/pycons Normal file
View File

@@ -0,0 +1,398 @@
#! /usr/bin/env python
#
# Copyright (c) 2008, Nicolas Rougier
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the University of California, Berkeley nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" Interactive Python/IPython/Pylab console
This console implements a python (or ipython) shell within a GTK window and
handles python stdin/stderr/stdout redirection and system wide stdout/stderr
redirection (using a pipe), provides history based on the GNU readline package
and automatic completion. It is also able to display matplotlib figures
inline. Each call to the show functions actually produces a FigureCanvasGTKAgg
that is inserted within the console.
Usage: ./pycons [--ipython] [--pylab] [--toolbar] file
You can refresh all active figures by calling the refresh() method and replot
the last figures using the replot() method. You can also zoom(), pan(), home(),
back(), forward() and save() active figure.
"""
import os
import sys
import gc
import gobject
import gtk
import pango
pylab_available = True
try:
import matplotlib
matplotlib.use('GtkAgg')
from matplotlib.backends.backend_gtkagg \
import FigureCanvasGTKAgg as Canvas
from matplotlib.backends.backend_gtkagg \
import NavigationToolbar2GTKAgg as NavigationToolbar
import matplotlib.backends.backend_gtkagg as backend_gtkagg
def draw_if_interactive():
""" Is called after every pylab drawing command """
show(console)
backend_gtkagg.draw_if_interactive = draw_if_interactive
import pylab
import matplotlib.pylab
from matplotlib._pylab_helpers import Gcf
except:
pylab_available = False
ipython_available = True
try:
import IPython
except:
ipython_available = False
try:
from functools import partial
except ImportError:
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
import pycons.console as cons
# ---------------------------------------------------------------------- replace
def replace (console, canvas, anchor):
""" Replaces a given canvas with a static image replica """
figures = console.figures
view = console.view
canvas.draw()
w, h = canvas.get_size_request()
pixbuf = gtk.gdk.pixbuf_new_from_data (
canvas.buffer_rgba(0,0), gtk.gdk.COLORSPACE_RGB, True,8,w,h,w*4)
image = gtk.Image()
image.set_from_pixbuf (pixbuf)
widget = anchor.get_widgets()
for widget in anchor.get_widgets():
for child in widget.get_children():
widget.remove (child)
view.add_child_at_anchor(image, anchor)
image.show()
#gc.collect()
return widget
# ----------------------------------------------------------------------- refresh
def refresh(console):
""" Refreshs all active canvas """
figures = console.figures
for fig in figures:
figure, canvas, anchor = fig
canvas.draw()
while gtk.events_pending():
gtk.main_iteration()
# ----------------------------------------------------------------------- refresh
def figure_enter(widget, event, console):
""" Change cursor to an arrow """
watch = gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW)
widget.window.set_cursor (watch)
widget.grab_focus()
console.active_canvas = widget.get_child()
# ----------------------------------------------------------------------- refresh
def figure_leave(widget, event, console):
""" Change cursor to text cursor """
cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
widget.window.set_cursor (cursor)
console.grab_focus()
console.active_canvas = None
# ----------------------------------------------------------------------- insert
def insert (console, figure):
""" Inserts a new canvas for the given figure """
figures = console.figures
last_figure = console.last_figure
figure.set_facecolor ('w')
view = console.view
buffer = console.buffer
# Compute size of the canvas according to current console visible area
x,y,width,height = console.get_allocation()
dpi = figure.get_dpi()
figwidth = figure.get_figwidth() * dpi
figheight = figure.get_figheight() * dpi
w = int (width*.75)
h = int ( (w/figwidth)*figheight)
if h > height*.75:
h = int (height*.75)
w = int ( (h/figheight)*figwidth)
figure.set_figwidth (w/dpi)
figure.set_figheight (h/dpi)
canvas = Canvas(figure)
for s,func in console.callbacks:
canvas.mpl_connect(s,func)
canvas.set_size_request (w,h)
canvas.show_all()
console.write ('\n')
console.write (' ', 'center')
iter = buffer.get_iter_at_mark(buffer.get_mark('insert'))
anchor = buffer.create_child_anchor(iter)
console.write (' ', 'center')
if console.use_toolbar:
boxout = gtk.EventBox()
boxout.set_border_width(0)
boxin = gtk.EventBox()
boxin.set_border_width(1)
boxout.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cccccc"))
boxout.add (boxin)
vbox = gtk.VBox()
box = gtk.EventBox()
box.add(canvas)
box.connect ('enter-notify-event', figure_enter, console)
box.connect ('leave-notify-event', figure_leave, console)
vbox.add(box)
toolbar = NavigationToolbar(canvas, None)
vbox.add(toolbar)
if console.use_toolbar:
boxin.add(vbox)
boxout.show_all()
vbox.show()
box.show()
view.add_child_at_anchor(boxout, anchor)
else:
vbox.show_all()
toolbar.hide()
view.add_child_at_anchor(vbox, anchor)
console.shell.namespace()['pan'] = toolbar.pan
console.shell.namespace()['zoom'] = toolbar.zoom
console.shell.namespace()['back'] = toolbar.back
console.shell.namespace()['forward'] = toolbar.forward
console.shell.namespace()['home'] = toolbar.forward
console.shell.namespace()['save'] = partial(toolbar.save_figure,1)
console.write ('\n')
figures.append ( (figure, canvas, anchor) )
console.last_figure = figure
# ----------------------------------------------------------------------- refresh
def refresh(console):
""" Refreshs all active canvas """
figures = console.figures
for fig in figures:
figure, canvas, anchor = fig
canvas.draw()
# ----------------------------------------------------------------------- replot
def replot (console):
"""
Produces a replot of the last figure and insert it within console. Previous
figure, if it exists, is transformed into a static image replica and
inserted in place of the previous figure.
"""
figures = console.figures
last_figure = console.last_figure
if not figures:
if last_figure:
insert (console, last_figure)
return
else:
return
figure, canvas, anchor = figures[-1]
if not anchor.get_deleted():
replace (console, canvas, anchor)
figures.remove ( (figure, canvas, anchor) )
# insert (console, figure, canvas)
insert (console, figure)
else:
console.figures = console.figures[0:-1]
insert (console, figure)
console.view.scroll_mark_onscreen(console.buffer.get_insert())
while gtk.events_pending():
gtk.main_iteration()
# ---------------------------------------------------------------------- connect
def connect (console, s, func):
""" Append callback to the list of callbacks (to be connected later) """
console.callbacks.append([s,func])
# ------------------------------------------------------------------------- show
def show (console):
""" Insert pending figures within console """
figures = console.figures
last_figure = console.last_figure
for manager in Gcf.get_all_fig_managers():
found = False
for fig in figures:
figure, canvas, anchor = fig
if figure == manager.canvas.figure:
canvas.draw()
found = True
break
if not found:
insert (console, manager.canvas.figure)
# ---------------------------------------------------------------- class Console
class Console (cons.Console):
""" GTK python console """
def __init__(self, argv=[], shelltype='python', banner=[],
filename=None, size=100):
cons.Console.__init__(self, argv, shelltype, banner, filename, size)
self.buffer.create_tag('center',
justification=gtk.JUSTIFY_CENTER,
font='Mono 4')
self.figures = []
self.callbacks = []
self.last_figure = None
self.active_canvas = None
self.view.connect ('key-press-event', self.key_press_event)
self.view.connect ('button-press-event', self.button_press_event)
self.view.connect ('scroll-event', self.scroll_event)
def key_press_event (self, widget, event):
""" Handle key press event """
if self.active_canvas:
self.active_canvas.emit ('key-press-event', event)
return True
return cons.Console.key_press_event (self, widget, event)
def scroll_event (self, widget, event):
""" Scroll event """
if self.active_canvas:
return True
return False
def button_press_event (self, widget, event):
""" Button press event """
return self.refresh()
def refresh (self):
""" Refresh drawing """
for fig in self.figures:
figure, canvas, anchor = fig
canvas.draw()
return False
if __name__ == "__main__":
from optparse import OptionParser
try:
import IPython
except:
pass
usage = "Usage: %pycons [options] file"
parser = OptionParser(usage=usage, version="%prog 1.0")
parser.add_option("", "--ipython", action="store_true", dest="ipython",
help="Use IPython as shell (if available)")
parser.add_option("", "--pylab", action="store_true", dest="pylab",
help="Use Pylab integration (if available)")
parser.add_option("", "--toolbar", action="store_true", dest="toolbar",
help="Use Pylab toolbar")
(options, args) = parser.parse_args()
filename = os.path.expanduser("~/.pyhistory")
p = 'Python %s' % sys.version.split(' ')[0]
l = 'matplotlib %s' % matplotlib.__version__
shelltype = 'python'
if (options.ipython and ipython_available) is True:
p = 'IPython %s' % IPython.__version__
shelltype = 'ipython'
if not pylab_available or not options.pylab:
banner = [
['GTK Python console\n', 'title'],
[' Using %s\n' % p,'subtitle'],
[' Type "help", "copyright", "credits" or "license" for more information.\n',
'subtitle']
]
else:
banner = [
['GTK Pylab console\n', 'title'],
[' Using %s and %s\n' % (p,l),'subtitle'],
[' Extra commands: "replot", "refresh", "pan", "zoom", "back", "forward", "home"\n',
'subtitle']
]
console = Console(argv=args, shelltype=shelltype,
banner=banner, filename=filename, size=100)
if options.toolbar:
console.use_toolbar = True
else:
console.use_toolbar = False
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_position(gtk.WIN_POS_CENTER)
window.set_default_size(800,600)
window.set_border_width(0)
window.connect('destroy-event', gtk.main_quit)
window.connect('delete-event', gtk.main_quit)
window.add (console)
window.show_all()
console.grab_focus()
if pylab_available and options.pylab:
console.write ("from pylab import *")
console.execute()
pylab.show = partial (show, console)
matplotlib.pylab.show = pylab.show
matplotlib.pyplot.show = pylab.show
pylab.connect = partial (connect, console)
matplotlib.pylab.connect = pylab.connect
console.shell.namespace()['replot'] = partial (replot, console)
console.shell.namespace()['refresh'] = partial (refresh, console)
def execfiles(console):
console.write ('execfile("%s")' % args[0])
# execfile(console.argv[0], console.shell.namespace())
console.execute()
return False
if len(args):
gobject.timeout_add(50, execfiles, console)
# Prevent external commands/scripts to quit
while console.do_quit == False:
gtk.main()

View File

@@ -0,0 +1,40 @@
#! /usr/bin/env python
#
# Copyright (c) 2008, Nicolas Rougier
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the University of California, Berkeley nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from distutils.core import setup
setup(name='PyCons',
version='1.0.1',
description='IPython/Python/Pylab GTK Console',
author='Nicolas Rougier',
author_email='Nicolas.Rougier@loria.fr',
url='http://www.loria.fr/~rougier/pycons.html',
packages=['pycons'],
package_dir = {'pycons': '.'},
scripts=['pycons']
)

173
src/python/pycons/shell.py Normal file
View File

@@ -0,0 +1,173 @@
#! /usr/bin/env python
#
# Copyright (c) 2008, Nicolas Rougier
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the University of California, Berkeley nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
import sys
import re
import rlcompleter
import traceback
import tempfile
if not hasattr(sys, 'ps1'): sys.ps1 = '>>> '
if not hasattr(sys, 'ps2'): sys.ps2 = '... '
class Shell:
""" """
def __init__(self, ns_globals={}, ns_locals={}):
""" """
self.completer = rlcompleter.Completer (ns_globals)
self.command = ''
self.globals = ns_globals
self.locals = ns_locals
self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
self.prompt = sys.ps1
def namespace(self):
return self.globals
def is_balanced (self, line):
""" Checks line balance for brace, bracket, parenthese and string quote
This helper function checks for the balance of brace, bracket,
parenthese and string quote. Any unbalanced line means to wait until
some other lines are fed to the console.
"""
s = line
s = filter(lambda x: x in '()[]{}"\'', s)
s = s.replace ("'''", "'")
s = s.replace ('"""', '"')
instring = False
brackets = {'(':')', '[':']', '{':'}', '"':'"', '\'':'\''}
stack = []
while len(s):
if not instring:
if s[0] in ')]}':
if stack and brackets[stack[-1]] == s[0]:
del stack[-1]
else:
return False
elif s[0] in '"\'':
if stack and brackets[stack[-1]] == s[0]:
del stack[-1]
instring = False
else:
stack.append(s[0])
instring = True
else:
stack.append(s[0])
else:
if s[0] in '"\'' and stack and brackets[stack[-1]] == s[0]:
del stack[-1]
instring = False
s = s[1:]
return len(stack) == 0
def complete(self, line):
split_line = self.complete_sep.split(line)
possibilities = []
i = 0
c = self.completer.complete (split_line[-1], i)
while c:
possibilities.append(c)
i = i+1
c = self.completer.complete (split_line[-1], i)
if possibilities:
common_prefix = os.path.commonprefix (possibilities)
completed = line[:-len(split_line[-1])]+common_prefix
else:
completed = line
return completed, possibilities
def eval (self, console):
line = console.last_line()
console.write ('\n')
if line == '':
self.execute (console)
self.command = ''
self.prompt = sys.ps1
console.prompt('prompt')
return
self.command = self.command + line + '\n'
if not self.is_balanced (self.command):
self.prompt = sys.ps2
console.prompt('prompt')
return
line = line.rstrip()
if len(line) > 0:
if line[-1] == ':' or line[-1] == '\\' or line[0] in ' \11':
self.prompt = sys.ps2
console.prompt('prompt')
return
self.execute (console)
self.command = ''
self.prompt = sys.ps1
console.prompt('prompt')
def execute (self, console):
if not self.command:
return
try:
try:
r = eval (self.command, self.globals, self.locals)
if r is not None:
# System output (if any)
while True:
try:
buf = os.read(console.piperead, 256)
except:
break
else:
console.write (buf, 'output')
if len(buf) < 256: break
# Command output
print `r`
except SyntaxError:
exec self.command in self.globals
except:
if hasattr (sys, 'last_type') and sys.last_type == SystemExit:
console.quit()
elif hasattr (sys, 'exc_type') and sys.exc_type == SystemExit:
console.quit()
else:
try:
tb = sys.exc_traceback
if tb:
tb=tb.tb_next
traceback.print_exception (sys.exc_type, sys.exc_value, tb)
except:
sys.stderr, console.stderr = console.stderr, sys.stderr
traceback.print_exc()

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env python
"""
Example: simple line plot.
Show how to make and save a simple line plot with labels, title and grid
"""
from pylab import *
figure()
t = arange(0.0, 1.0+0.01, 0.01)
s = cos(2*2*pi*t)
plot(t, s, '-', lw=2)
xlabel('time (s)')
ylabel('voltage (mV)')
title('About as simple as it gets, folks')
grid(True)
show()