Switch to tabs for indentation.

Instead of a mix of 2- and 4-space tabs just use actual tabs.  ;-P
This commit is contained in:
Simon Forman 2020-04-24 12:48:15 -07:00
parent 2fb610e733
commit 078f29830d
26 changed files with 4080 additions and 4080 deletions

View File

@ -16,17 +16,17 @@ import base64, os, io, zipfile
def initialize(joy_home):
Z.extractall(joy_home)
Z.extractall(joy_home)
def create_data(from_dir='./default_joy_home'):
f = io.StringIO()
z = zipfile.ZipFile(f, mode='w')
for fn in os.listdir(from_dir):
from_fn = os.path.join(from_dir, fn)
z.write(from_fn, fn)
z.close()
return base64.encodestring(f.getvalue())
f = io.StringIO()
z = zipfile.ZipFile(f, mode='w')
for fn in os.listdir(from_dir):
from_fn = os.path.join(from_dir, fn)
z.write(from_fn, fn)
z.close()
return base64.encodestring(f.getvalue())
Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\
@ -103,4 +103,4 @@ AAAAAAAAAAAAtIH2BgAAZGVmaW5pdGlvbnMudHh0UEsFBgAAAAAFAAUAHgEAAF0OAAAAAA==''')))
if __name__ == '__main__':
print(create_data())
print(create_data())

View File

@ -23,10 +23,10 @@ repo = init_home(JOY_HOME)
_log = logging.getLogger(__name__)
logging.basicConfig(
format='%(asctime)-15s %(levelname)s %(name)s %(message)s',
filename=os.path.join(JOY_HOME, 'thun.log'),
level=logging.INFO,
)
format='%(asctime)-15s %(levelname)s %(name)s %(message)s',
filename=os.path.join(JOY_HOME, 'thun.log'),
level=logging.INFO,
)
_log.info('Starting with JOY_HOME=%s', JOY_HOME)
@ -39,73 +39,73 @@ from joy.utils.stack import stack_to_string
cp = RawConfigParser()
cp.optionxform = str # Don't mess with uppercase.
with open(os.path.join(args.joy_home, 'thun.config')) as f:
cp.readfp(f)
cp.readfp(f)
GLOBAL_COMMANDS = dict(cp.items('key bindings'))
def repo_relative_path(path):
return os.path.relpath(
path,
os.path.commonprefix((repo.controldir(), path))
)
return os.path.relpath(
path,
os.path.commonprefix((repo.controldir(), path))
)
def commands():
# pylint: disable=unused-variable
# pylint: disable=unused-variable
def key_bindings(*args):
commands = [ # These are bound in the TextViewerWidget.
'Control-Enter - Run the selection as Joy code, or if there\'s no selection the line containing the cursor.',
'F3 - Copy selection to stack.',
'Shift-F3 - Cut selection to stack.',
'F4 - Paste item on top of stack to insertion cursor.',
'Shift-F4 - Pop and paste top of stack to insertion cursor.',
]
for key, command in GLOBAL_COMMANDS.items():
commands.append('%s - %s' % (key.lstrip('<').rstrip('>'), command))
print('\n'.join([''] + sorted(commands)))
return args
def key_bindings(*args):
commands = [ # These are bound in the TextViewerWidget.
'Control-Enter - Run the selection as Joy code, or if there\'s no selection the line containing the cursor.',
'F3 - Copy selection to stack.',
'Shift-F3 - Cut selection to stack.',
'F4 - Paste item on top of stack to insertion cursor.',
'Shift-F4 - Pop and paste top of stack to insertion cursor.',
]
for key, command in GLOBAL_COMMANDS.items():
commands.append('%s - %s' % (key.lstrip('<').rstrip('>'), command))
print('\n'.join([''] + sorted(commands)))
return args
def mouse_bindings(*args):
print(dedent('''
Mouse button chords (to cancel a chord, click the third mouse button.)
def mouse_bindings(*args):
print(dedent('''
Mouse button chords (to cancel a chord, click the third mouse button.)
Left - Point, sweep selection
Left-Middle - Copy the selection, place text on stack
Left-Right - Run the selection as Joy code
Left - Point, sweep selection
Left-Middle - Copy the selection, place text on stack
Left-Right - Run the selection as Joy code
Middle - Paste selection (bypass stack); click and drag to scroll.
Middle-Left - Paste from top of stack, preserve
Middle-Right - Paste from top of stack, pop
Middle - Paste selection (bypass stack); click and drag to scroll.
Middle-Left - Paste from top of stack, preserve
Middle-Right - Paste from top of stack, pop
Right - Execute command word under mouse cursor
Right-Left - Print docs of command word under mouse cursor
Right-Middle - Lookup word (kinda useless now)
'''))
return args
Right - Execute command word under mouse cursor
Right-Left - Print docs of command word under mouse cursor
Right-Middle - Lookup word (kinda useless now)
'''))
return args
def reset_log(*args):
log.delete('0.0', tk.END)
print(__doc__)
return args
def reset_log(*args):
log.delete('0.0', tk.END)
print(__doc__)
return args
def show_log(*args):
log_window.wm_deiconify()
log_window.update()
return args
def show_log(*args):
log_window.wm_deiconify()
log_window.update()
return args
def grand_reset(s, e, d):
stack = world.load_stack() or ()
log.reset()
t.reset()
return stack, e, d
def grand_reset(s, e, d):
stack = world.load_stack() or ()
log.reset()
t.reset()
return stack, e, d
return locals()
return locals()
STACK_FN = os.path.join(JOY_HOME, 'stack.pickle')
@ -125,19 +125,19 @@ FONT = get_font('Iosevka', size=14) # Requires Tk root already set up.
log.init('Log', LOG_FN, repo_relative_path(LOG_FN), repo, FONT)
t.init('Joy - ' + JOY_HOME, JOY_FN, repo_relative_path(JOY_FN), repo, FONT)
for event, command in GLOBAL_COMMANDS.items():
callback = lambda _, _command=command: world.interpret(_command)
t.bind(event, callback)
log.bind(event, callback)
callback = lambda _, _command=command: world.interpret(_command)
t.bind(event, callback)
log.bind(event, callback)
def main():
sys.stdout, old_stdout = FileFaker(log), sys.stdout
try:
t.mainloop()
finally:
sys.stdout = old_stdout
return 0
sys.stdout, old_stdout = FileFaker(log), sys.stdout
try:
t.mainloop()
finally:
sys.stdout = old_stdout
return 0
if __name__ == '__main__':
main()
main()

View File

@ -25,187 +25,187 @@ nothing = lambda event: None
class MouseBindingsMixin(object):
"""TextViewerWidget mixin class to provide mouse bindings."""
"""TextViewerWidget mixin class to provide mouse bindings."""
def __init__(self):
def __init__(self):
#Remember our mouse button state
self.B1_DOWN = False
self.B2_DOWN = False
self.B3_DOWN = False
#Remember our mouse button state
self.B1_DOWN = False
self.B2_DOWN = False
self.B3_DOWN = False
#Remember our pending action.
self.dothis = nothing
#Remember our pending action.
self.dothis = nothing
#We'll need to remember whether or not we've been moving B2.
self.beenMovingB2 = False
#We'll need to remember whether or not we've been moving B2.
self.beenMovingB2 = False
#Unbind the events we're interested in.
for sequence in (
"<Button-1>", "<B1-Motion>", "<ButtonRelease-1>",
"<Button-2>", "<B2-Motion>", "<ButtonRelease-2>",
"<Button-3>", "<B3-Motion>", "<ButtonRelease-3>",
"<B1-Leave>", "<B2-Leave>", "<B3-Leave>", "<Any-Leave>", "<Leave>"
):
self.unbind(sequence)
self.unbind_all(sequence)
#Unbind the events we're interested in.
for sequence in (
"<Button-1>", "<B1-Motion>", "<ButtonRelease-1>",
"<Button-2>", "<B2-Motion>", "<ButtonRelease-2>",
"<Button-3>", "<B3-Motion>", "<ButtonRelease-3>",
"<B1-Leave>", "<B2-Leave>", "<B3-Leave>", "<Any-Leave>", "<Leave>"
):
self.unbind(sequence)
self.unbind_all(sequence)
self.event_delete('<<PasteSelection>>') #I forgot what this was for! :-P D'oh!
self.event_delete('<<PasteSelection>>') #I forgot what this was for! :-P D'oh!
#Bind our event handlers to their events.
self.bind("<Button-1>", self.B1d)
self.bind("<B1-Motion>", self.B1m)
self.bind("<ButtonRelease-1>", self.B1r)
#Bind our event handlers to their events.
self.bind("<Button-1>", self.B1d)
self.bind("<B1-Motion>", self.B1m)
self.bind("<ButtonRelease-1>", self.B1r)
self.bind("<Button-2>", self.B2d)
self.bind("<B2-Motion>", self.B2m)
self.bind("<ButtonRelease-2>", self.B2r)
self.bind("<Button-2>", self.B2d)
self.bind("<B2-Motion>", self.B2m)
self.bind("<ButtonRelease-2>", self.B2r)
self.bind("<Button-3>", self.B3d)
self.bind("<B3-Motion>", self.B3m)
self.bind("<ButtonRelease-3>", self.B3r)
self.bind("<Button-3>", self.B3d)
self.bind("<B3-Motion>", self.B3m)
self.bind("<ButtonRelease-3>", self.B3r)
self.bind("<Any-Leave>", self.leave)
self.bind("<Motion>", self.scan_command)
self.bind("<Any-Leave>", self.leave)
self.bind("<Motion>", self.scan_command)
def B1d(self, event):
'''button one pressed'''
self.B1_DOWN = True
def B1d(self, event):
'''button one pressed'''
self.B1_DOWN = True
if self.B2_DOWN:
if self.B2_DOWN:
self.unhighlight_command()
self.unhighlight_command()
if self.B3_DOWN :
self.dothis = self.cancel
if self.B3_DOWN :
self.dothis = self.cancel
else:
#copy TOS to the mouse (instead of system selection.)
self.dothis = self.copyto #middle-left-interclick
else:
#copy TOS to the mouse (instead of system selection.)
self.dothis = self.copyto #middle-left-interclick
elif self.B3_DOWN :
self.unhighlight_command()
self.dothis = self.opendoc #right-left-interclick
elif self.B3_DOWN :
self.unhighlight_command()
self.dothis = self.opendoc #right-left-interclick
else:
##button 1 down, set insertion and begin selection.
##Actually, do nothing. Tk Text widget defaults take care of it.
self.dothis = nothing
return
else:
##button 1 down, set insertion and begin selection.
##Actually, do nothing. Tk Text widget defaults take care of it.
self.dothis = nothing
return
#Prevent further event handling by returning "break".
return "break"
#Prevent further event handling by returning "break".
return "break"
def B2d(self, event):
'''button two pressed'''
self.B2_DOWN = 1
def B2d(self, event):
'''button two pressed'''
self.B2_DOWN = 1
if self.B1_DOWN :
if self.B1_DOWN :
if self.B3_DOWN :
self.dothis = self.cancel
if self.B3_DOWN :
self.dothis = self.cancel
else:
#left-middle-interclick - copy selection to stack
self.dothis = self.copy_selection_to_stack
else:
#left-middle-interclick - copy selection to stack
self.dothis = self.copy_selection_to_stack
elif self.B3_DOWN :
self.unhighlight_command()
self.dothis = self.lookup #right-middle-interclick - lookup
elif self.B3_DOWN :
self.unhighlight_command()
self.dothis = self.lookup #right-middle-interclick - lookup
else:
#middle-click - paste X selection to mouse pointer
self.set_insertion_point(event)
self.dothis = self.paste_X_selection_to_mouse_pointer
return
else:
#middle-click - paste X selection to mouse pointer
self.set_insertion_point(event)
self.dothis = self.paste_X_selection_to_mouse_pointer
return
return "break"
return "break"
def B3d(self, event):
'''button three pressed'''
self.B3_DOWN = 1
def B3d(self, event):
'''button three pressed'''
self.B3_DOWN = 1
if self.B1_DOWN :
if self.B1_DOWN :
if self.B2_DOWN :
self.dothis = self.cancel
if self.B2_DOWN :
self.dothis = self.cancel
else:
#left-right-interclick - run selection
self.dothis = self.run_selection
else:
#left-right-interclick - run selection
self.dothis = self.run_selection
elif self.B2_DOWN :
#middle-right-interclick - Pop/Cut from TOS to insertion cursor
self.unhighlight_command()
self.dothis = self.pastecut
elif self.B2_DOWN :
#middle-right-interclick - Pop/Cut from TOS to insertion cursor
self.unhighlight_command()
self.dothis = self.pastecut
else:
#right-click
self.CommandFirstDown(event)
else:
#right-click
self.CommandFirstDown(event)
return "break"
return "break"
def B1m(self, event):
'''button one moved'''
if self.B2_DOWN or self.B3_DOWN:
return "break"
def B1m(self, event):
'''button one moved'''
if self.B2_DOWN or self.B3_DOWN:
return "break"
def B2m(self, event):
'''button two moved'''
if self.dothis == self.paste_X_selection_to_mouse_pointer and \
not (self.B1_DOWN or self.B3_DOWN):
def B2m(self, event):
'''button two moved'''
if self.dothis == self.paste_X_selection_to_mouse_pointer and \
not (self.B1_DOWN or self.B3_DOWN):
self.beenMovingB2 = True
return
self.beenMovingB2 = True
return
return "break"
return "break"
def B3m(self, event):
'''button three moved'''
if self.dothis == self.do_command and \
not (self.B1_DOWN or self.B2_DOWN):
def B3m(self, event):
'''button three moved'''
if self.dothis == self.do_command and \
not (self.B1_DOWN or self.B2_DOWN):
self.update_command_word(event)
self.update_command_word(event)
return "break"
return "break"
def scan_command(self, event):
self.update_command_word(event)
def scan_command(self, event):
self.update_command_word(event)
def B1r(self, event):
'''button one released'''
self.B1_DOWN = False
def B1r(self, event):
'''button one released'''
self.B1_DOWN = False
if not (self.B2_DOWN or self.B3_DOWN):
self.dothis(event)
if not (self.B2_DOWN or self.B3_DOWN):
self.dothis(event)
return "break"
return "break"
def B2r(self, event):
'''button two released'''
self.B2_DOWN = False
def B2r(self, event):
'''button two released'''
self.B2_DOWN = False
if not (self.B1_DOWN or self.B3_DOWN or self.beenMovingB2):
self.dothis(event)
if not (self.B1_DOWN or self.B3_DOWN or self.beenMovingB2):
self.dothis(event)
self.beenMovingB2 = False
self.beenMovingB2 = False
return "break"
return "break"
def B3r(self, event):
'''button three released'''
self.B3_DOWN = False
def B3r(self, event):
'''button three released'''
self.B3_DOWN = False
if not (self.B1_DOWN or self.B2_DOWN) :
self.dothis(event)
if not (self.B1_DOWN or self.B2_DOWN) :
self.dothis(event)
return "break"
return "break"
def InsertFirstDown(self, event):
self.focus()
self.dothis = nothing
self.set_insertion_point(event)
def InsertFirstDown(self, event):
self.focus()
self.dothis = nothing
self.set_insertion_point(event)
def CommandFirstDown(self, event):
self.dothis = self.do_command
self.update_command_word(event)
def CommandFirstDown(self, event):
self.dothis = self.do_command
self.update_command_word(event)

View File

@ -25,12 +25,12 @@ A Graphical User Interface for a dialect of Joy in Python.
The GUI
History
Structure
Commands
Mouse Chords
Keyboard
Output from Joy
History
Structure
Commands
Mouse Chords
Keyboard
Output from Joy
'''
@ -40,11 +40,11 @@ standard_library.install_aliases()
from builtins import str, map, object
from past.builtins import basestring
try:
import tkinter as tk
from tkinter.font import families, Font
import tkinter as tk
from tkinter.font import families, Font
except ImportError:
import Tkinter as tk
from tkFont import families, Font
import Tkinter as tk
from tkFont import families, Font
from re import compile as regular_expression
from traceback import format_exc
@ -58,17 +58,17 @@ from .world import World
def make_gui(dictionary):
t = TextViewerWidget(World(dictionary=dictionary))
t['font'] = get_font()
t._root().title('Joy')
t.pack(expand=True, fill=tk.BOTH)
return t
t = TextViewerWidget(World(dictionary=dictionary))
t['font'] = get_font()
t._root().title('Joy')
t.pack(expand=True, fill=tk.BOTH)
return t
def get_font(family='EB Garamond', size=14):
if family not in families():
family = 'Times'
return Font(family=family, size=size)
if family not in families():
family = 'Times'
return Font(family=family, size=size)
#: Define mapping between Tkinter events and functions or methods. The
@ -77,85 +77,85 @@ def get_font(family='EB Garamond', size=14):
#: must return the actual callable to which to bind the event sequence.
TEXT_BINDINGS = {
#I want to ensure that these keyboard shortcuts work.
'<Control-Return>': lambda tv: tv._control_enter,
'<Control-v>': lambda tv: tv._paste,
'<Control-V>': lambda tv: tv._paste,
'<F3>': lambda tv: tv.copy_selection_to_stack,
'<F4>': lambda tv: tv.copyto,
'<Shift-F3>': lambda tv: tv.cut,
'<Shift-F4>': lambda tv: tv.pastecut,
'<Shift-Insert>': lambda tv: tv._paste,
}
#I want to ensure that these keyboard shortcuts work.
'<Control-Return>': lambda tv: tv._control_enter,
'<Control-v>': lambda tv: tv._paste,
'<Control-V>': lambda tv: tv._paste,
'<F3>': lambda tv: tv.copy_selection_to_stack,
'<F4>': lambda tv: tv.copyto,
'<Shift-F3>': lambda tv: tv.cut,
'<Shift-F4>': lambda tv: tv.pastecut,
'<Shift-Insert>': lambda tv: tv._paste,
}
class SavingMixin(object):
def __init__(self, saver=None, filename=None, save_delay=2000):
self.saver = self._saver if saver is None else saver
self.filename = filename
self._save_delay = save_delay
self.tk.call(self._w, 'edit', 'modified', 0)
self.bind('<<Modified>>', self._beenModified)
self._resetting_modified_flag = False
self._save = None
def __init__(self, saver=None, filename=None, save_delay=2000):
self.saver = self._saver if saver is None else saver
self.filename = filename
self._save_delay = save_delay
self.tk.call(self._w, 'edit', 'modified', 0)
self.bind('<<Modified>>', self._beenModified)
self._resetting_modified_flag = False
self._save = None
def save(self):
'''
Call _saveFunc() after a certain amount of idle time.
def save(self):
'''
Call _saveFunc() after a certain amount of idle time.
Called by _beenModified().
'''
self._cancelSave()
if self.saver:
self._saveAfter(self._save_delay)
Called by _beenModified().
'''
self._cancelSave()
if self.saver:
self._saveAfter(self._save_delay)
def _saveAfter(self, delay):
'''
Trigger a cancel-able call to _saveFunc() after delay milliseconds.
'''
self._save = self.after(delay, self._saveFunc)
def _saveAfter(self, delay):
'''
Trigger a cancel-able call to _saveFunc() after delay milliseconds.
'''
self._save = self.after(delay, self._saveFunc)
def _saveFunc(self):
self._save = None
self.saver(self._get_contents())
def _saveFunc(self):
self._save = None
self.saver(self._get_contents())
def _saver(self, text):
if not self.filename:
return
with open(self.filename, 'wb') as f:
os.chmod(self.filename, 0o600)
f.write(text.encode('UTF_8'))
f.flush()
os.fsync(f.fileno())
if hasattr(self, 'repo'):
self.repo.stage([self.repo_relative_filename])
self.world.save()
def _saver(self, text):
if not self.filename:
return
with open(self.filename, 'wb') as f:
os.chmod(self.filename, 0o600)
f.write(text.encode('UTF_8'))
f.flush()
os.fsync(f.fileno())
if hasattr(self, 'repo'):
self.repo.stage([self.repo_relative_filename])
self.world.save()
def _cancelSave(self):
if self._save is not None:
self.after_cancel(self._save)
self._save = None
def _cancelSave(self):
if self._save is not None:
self.after_cancel(self._save)
self._save = None
def _get_contents(self):
self['state'] = 'disabled'
try:
return self.get('0.0', 'end')[:-1]
finally:
self['state'] = 'normal'
def _get_contents(self):
self['state'] = 'disabled'
try:
return self.get('0.0', 'end')[:-1]
finally:
self['state'] = 'normal'
def _beenModified(self, event):
if self._resetting_modified_flag:
return
self._clearModifiedFlag()
self.save()
def _beenModified(self, event):
if self._resetting_modified_flag:
return
self._clearModifiedFlag()
self.save()
def _clearModifiedFlag(self):
self._resetting_modified_flag = True
try:
self.tk.call(self._w, 'edit', 'modified', 0)
finally:
self._resetting_modified_flag = False
def _clearModifiedFlag(self):
self._resetting_modified_flag = True
try:
self.tk.call(self._w, 'edit', 'modified', 0)
finally:
self._resetting_modified_flag = False
## tags = self._saveTags()
## chunks = self.DUMP()
@ -163,309 +163,309 @@ class SavingMixin(object):
class TextViewerWidget(tk.Text, MouseBindingsMixin, SavingMixin):
"""
This class is a Tkinter Text with special mousebindings to make
it act as a Xerblin Text Viewer.
"""
"""
This class is a Tkinter Text with special mousebindings to make
it act as a Xerblin Text Viewer.
"""
#This is a regular expression for finding commands in the text.
command_re = regular_expression(r'[-a-zA-Z0-9_\\~/.:!@#$%&*?=+<>]+')
#This is a regular expression for finding commands in the text.
command_re = regular_expression(r'[-a-zA-Z0-9_\\~/.:!@#$%&*?=+<>]+')
#These are the config tags for command text when it's highlighted.
command_tags = dict(
#underline = 1,
#bgstipple = "gray50",
borderwidth = 2,
relief=tk.RIDGE,
foreground = "green"
)
#These are the config tags for command text when it's highlighted.
command_tags = dict(
#underline = 1,
#bgstipple = "gray50",
borderwidth = 2,
relief=tk.RIDGE,
foreground = "green"
)
def __init__(self, world, master=None, **kw):
def __init__(self, world, master=None, **kw):
self.world = world
if self.world.text_widget is None:
self.world.text_widget = self
self.world = world
if self.world.text_widget is None:
self.world.text_widget = self
#Turn on undo, but don't override a passed-in setting.
kw.setdefault('undo', True)
#Turn on undo, but don't override a passed-in setting.
kw.setdefault('undo', True)
# kw.setdefault('bg', 'white')
kw.setdefault('wrap', 'word')
kw.setdefault('font', 'arial 12')
kw.setdefault('wrap', 'word')
kw.setdefault('font', 'arial 12')
text_bindings = kw.pop('text_bindings', TEXT_BINDINGS)
text_bindings = kw.pop('text_bindings', TEXT_BINDINGS)
#Create ourselves as a Tkinter Text
tk.Text.__init__(self, master, **kw)
#Create ourselves as a Tkinter Text
tk.Text.__init__(self, master, **kw)
#Initialize our mouse mixin.
MouseBindingsMixin.__init__(self)
#Initialize our mouse mixin.
MouseBindingsMixin.__init__(self)
#Initialize our saver mixin.
SavingMixin.__init__(self)
#Initialize our saver mixin.
SavingMixin.__init__(self)
#Add tag config for command highlighting.
self.tag_config('command', **self.command_tags)
self.tag_config('bzzt', foreground = "orange")
self.tag_config('huh', foreground = "grey")
self.tag_config('number', foreground = "blue")
#Add tag config for command highlighting.
self.tag_config('command', **self.command_tags)
self.tag_config('bzzt', foreground = "orange")
self.tag_config('huh', foreground = "grey")
self.tag_config('number', foreground = "blue")
#Create us a command instance variable
self.command = ''
#Create us a command instance variable
self.command = ''
#Activate event bindings. Modify text_bindings in your config
#file to affect the key bindings and whatnot here.
for event_sequence, callback_finder in text_bindings.items():
callback = callback_finder(self)
self.bind(event_sequence, callback)
#Activate event bindings. Modify text_bindings in your config
#file to affect the key bindings and whatnot here.
for event_sequence, callback_finder in text_bindings.items():
callback = callback_finder(self)
self.bind(event_sequence, callback)
## T.protocol("WM_DELETE_WINDOW", self.on_close)
def find_command_in_line(self, line, index):
'''
Return the command at index in line and its begin and end indices.
find_command_in_line(line, index) => command, begin, end
'''
for match in self.command_re.finditer(line):
b, e = match.span()
if b <= index <= e:
return match.group(), b, e
def find_command_in_line(self, line, index):
'''
Return the command at index in line and its begin and end indices.
find_command_in_line(line, index) => command, begin, end
'''
for match in self.command_re.finditer(line):
b, e = match.span()
if b <= index <= e:
return match.group(), b, e
def paste_X_selection_to_mouse_pointer(self, event):
'''Paste the X selection to the mouse pointer.'''
try:
text = self.selection_get()
except tk.TclError:
return 'break'
self.insert_it(text)
def paste_X_selection_to_mouse_pointer(self, event):
'''Paste the X selection to the mouse pointer.'''
try:
text = self.selection_get()
except tk.TclError:
return 'break'
self.insert_it(text)
def update_command_word(self, event):
'''Highlight the command under the mouse.'''
self.unhighlight_command()
self.command = ''
index = '@%d,%d' % (event.x, event.y)
linestart = self.index(index + 'linestart')
lineend = self.index(index + 'lineend')
line = self.get(linestart, lineend)
row, offset = self._get_index(index)
def update_command_word(self, event):
'''Highlight the command under the mouse.'''
self.unhighlight_command()
self.command = ''
index = '@%d,%d' % (event.x, event.y)
linestart = self.index(index + 'linestart')
lineend = self.index(index + 'lineend')
line = self.get(linestart, lineend)
row, offset = self._get_index(index)
if offset >= len(line) or line[offset].isspace():
# The mouse is off the end of the line or on a space so there's no
# command, we're done.
return
if offset >= len(line) or line[offset].isspace():
# The mouse is off the end of the line or on a space so there's no
# command, we're done.
return
cmd = self.find_command_in_line(line, offset)
if cmd is None:
return
cmd = self.find_command_in_line(line, offset)
if cmd is None:
return
cmd, b, e = cmd
if is_numerical(cmd):
extra_tags = 'number',
elif self.world.has(cmd):
check = self.world.check(cmd)
if check: extra_tags = ()
elif check is None: extra_tags = 'huh',
else: extra_tags = 'bzzt',
else:
return
self.command = cmd
self.highlight_command(
'%d.%d' % (row, b),
'%d.%d' % (row, e),
*extra_tags)
cmd, b, e = cmd
if is_numerical(cmd):
extra_tags = 'number',
elif self.world.has(cmd):
check = self.world.check(cmd)
if check: extra_tags = ()
elif check is None: extra_tags = 'huh',
else: extra_tags = 'bzzt',
else:
return
self.command = cmd
self.highlight_command(
'%d.%d' % (row, b),
'%d.%d' % (row, e),
*extra_tags)
def highlight_command(self, from_, to, *extra_tags):
'''Apply command style from from_ to to.'''
cmdstart = self.index(from_)
cmdend = self.index(to)
self.tag_add('command', cmdstart, cmdend)
for tag in extra_tags:
self.tag_add(tag, cmdstart, cmdend)
def highlight_command(self, from_, to, *extra_tags):
'''Apply command style from from_ to to.'''
cmdstart = self.index(from_)
cmdend = self.index(to)
self.tag_add('command', cmdstart, cmdend)
for tag in extra_tags:
self.tag_add(tag, cmdstart, cmdend)
def do_command(self, event):
'''Do the currently highlighted command.'''
self.unhighlight_command()
if self.command:
self.run_command(self.command)
def do_command(self, event):
'''Do the currently highlighted command.'''
self.unhighlight_command()
if self.command:
self.run_command(self.command)
def _control_enter(self, event):
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
command = self.get(select_indices[0], select_indices[1])
else:
linestart = self.index(tk.INSERT + ' linestart')
lineend = self.index(tk.INSERT + ' lineend')
command = self.get(linestart, lineend)
if command and not command.isspace():
self.run_command(command)
return 'break'
def _control_enter(self, event):
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
command = self.get(select_indices[0], select_indices[1])
else:
linestart = self.index(tk.INSERT + ' linestart')
lineend = self.index(tk.INSERT + ' lineend')
command = self.get(linestart, lineend)
if command and not command.isspace():
self.run_command(command)
return 'break'
def run_command(self, command):
'''Given a string run it on the stack, report errors.'''
try:
self.world.interpret(command)
except SystemExit:
raise
except:
self.popupTB(format_exc().rstrip())
def run_command(self, command):
'''Given a string run it on the stack, report errors.'''
try:
self.world.interpret(command)
except SystemExit:
raise
except:
self.popupTB(format_exc().rstrip())
def unhighlight_command(self):
'''Remove any command highlighting.'''
self.tag_remove('number', 1.0, tk.END)
self.tag_remove('huh', 1.0, tk.END)
self.tag_remove('bzzt', 1.0, tk.END)
self.tag_remove('command', 1.0, tk.END)
def unhighlight_command(self):
'''Remove any command highlighting.'''
self.tag_remove('number', 1.0, tk.END)
self.tag_remove('huh', 1.0, tk.END)
self.tag_remove('bzzt', 1.0, tk.END)
self.tag_remove('command', 1.0, tk.END)
def set_insertion_point(self, event):
'''Set the insertion cursor to the current mouse location.'''
self.focus()
self.mark_set(tk.INSERT, '@%d,%d' % (event.x, event.y))
def set_insertion_point(self, event):
'''Set the insertion cursor to the current mouse location.'''
self.focus()
self.mark_set(tk.INSERT, '@%d,%d' % (event.x, event.y))
def copy_selection_to_stack(self, event):
'''Copy selection to stack.'''
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
s = self.get(select_indices[0], select_indices[1])
self.world.push(s)
def copy_selection_to_stack(self, event):
'''Copy selection to stack.'''
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
s = self.get(select_indices[0], select_indices[1])
self.world.push(s)
def cut(self, event):
'''Cut selection to stack.'''
self.copy_selection_to_stack(event)
# Let the pre-existing machinery take care of cutting the selection.
self.event_generate("<<Cut>>")
def cut(self, event):
'''Cut selection to stack.'''
self.copy_selection_to_stack(event)
# Let the pre-existing machinery take care of cutting the selection.
self.event_generate("<<Cut>>")
def copyto(self, event):
'''Actually "paste" from TOS'''
s = self.world.peek()
if s is not None:
self.insert_it(s)
def copyto(self, event):
'''Actually "paste" from TOS'''
s = self.world.peek()
if s is not None:
self.insert_it(s)
def insert_it(self, s):
if not isinstance(s, basestring):
s = stack_to_string(s)
def insert_it(self, s):
if not isinstance(s, basestring):
s = stack_to_string(s)
# When pasting from the mouse we have to remove the current selection
# to prevent destroying it by the paste operation.
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
# Set two marks to remember the selection.
self.mark_set('_sel_start', select_indices[0])
self.mark_set('_sel_end', select_indices[1])
self.tag_remove(tk.SEL, 1.0, tk.END)
# When pasting from the mouse we have to remove the current selection
# to prevent destroying it by the paste operation.
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
# Set two marks to remember the selection.
self.mark_set('_sel_start', select_indices[0])
self.mark_set('_sel_end', select_indices[1])
self.tag_remove(tk.SEL, 1.0, tk.END)
self.insert(tk.INSERT, s)
self.insert(tk.INSERT, s)
if select_indices:
self.tag_add(tk.SEL, '_sel_start', '_sel_end')
self.mark_unset('_sel_start')
self.mark_unset('_sel_end')
if select_indices:
self.tag_add(tk.SEL, '_sel_start', '_sel_end')
self.mark_unset('_sel_start')
self.mark_unset('_sel_end')
def run_selection(self, event):
'''Run the current selection if any on the stack.'''
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
selection = self.get(select_indices[0], select_indices[1])
self.tag_remove(tk.SEL, 1.0, tk.END)
self.run_command(selection)
def run_selection(self, event):
'''Run the current selection if any on the stack.'''
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
selection = self.get(select_indices[0], select_indices[1])
self.tag_remove(tk.SEL, 1.0, tk.END)
self.run_command(selection)
def pastecut(self, event):
'''Cut the TOS item to the mouse.'''
self.copyto(event)
self.world.pop()
def pastecut(self, event):
'''Cut the TOS item to the mouse.'''
self.copyto(event)
self.world.pop()
def opendoc(self, event):
'''OpenDoc the current command.'''
if self.command:
self.world.do_opendoc(self.command)
def opendoc(self, event):
'''OpenDoc the current command.'''
if self.command:
self.world.do_opendoc(self.command)
def lookup(self, event):
'''Look up the current command.'''
if self.command:
self.world.do_lookup(self.command)
def lookup(self, event):
'''Look up the current command.'''
if self.command:
self.world.do_lookup(self.command)
def cancel(self, event):
'''Cancel whatever we're doing.'''
self.leave(None)
self.tag_remove(tk.SEL, 1.0, tk.END)
self._sel_anchor = '0.0'
self.mark_unset(tk.INSERT)
def cancel(self, event):
'''Cancel whatever we're doing.'''
self.leave(None)
self.tag_remove(tk.SEL, 1.0, tk.END)
self._sel_anchor = '0.0'
self.mark_unset(tk.INSERT)
def leave(self, event):
'''Called when mouse leaves the Text window.'''
self.unhighlight_command()
self.command = ''
def leave(self, event):
'''Called when mouse leaves the Text window.'''
self.unhighlight_command()
self.command = ''
def _get_index(self, index):
'''Get the index in (int, int) form of index.'''
return tuple(map(int, self.index(index).split('.')))
def _get_index(self, index):
'''Get the index in (int, int) form of index.'''
return tuple(map(int, self.index(index).split('.')))
def _paste(self, event):
'''Paste the system selection to the current selection, replacing it.'''
def _paste(self, event):
'''Paste the system selection to the current selection, replacing it.'''
# If we're "key" pasting, we have to move the insertion point
# to the selection so the pasted text gets inserted at the
# location of the deleted selection.
# If we're "key" pasting, we have to move the insertion point
# to the selection so the pasted text gets inserted at the
# location of the deleted selection.
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
# Mark the location of the current insertion cursor
self.mark_set('tmark', tk.INSERT)
# Put the insertion cursor at the selection
self.mark_set(tk.INSERT, select_indices[1])
select_indices = self.tag_ranges(tk.SEL)
if select_indices:
# Mark the location of the current insertion cursor
self.mark_set('tmark', tk.INSERT)
# Put the insertion cursor at the selection
self.mark_set(tk.INSERT, select_indices[1])
# Paste to the current selection, or if none, to the insertion cursor.
self.event_generate("<<Paste>>")
# Paste to the current selection, or if none, to the insertion cursor.
self.event_generate("<<Paste>>")
# If we mess with the insertion cursor above, fix it now.
if select_indices:
# Put the insertion cursor back where it was.
self.mark_set(tk.INSERT, 'tmark')
# And get rid of our unneeded mark.
self.mark_unset('tmark')
# If we mess with the insertion cursor above, fix it now.
if select_indices:
# Put the insertion cursor back where it was.
self.mark_set(tk.INSERT, 'tmark')
# And get rid of our unneeded mark.
self.mark_unset('tmark')
return 'break'
return 'break'
def init(self, title, filename, repo_relative_filename, repo, font):
self.winfo_toplevel().title(title)
if os.path.exists(filename):
with open(filename) as f:
data = f.read()
self.insert(tk.END, data)
# Prevent this from triggering a git commit.
self.update()
self._cancelSave()
self.pack(expand=True, fill=tk.BOTH)
self.filename = filename
self.repo_relative_filename = repo_relative_filename
self.repo = repo
self['font'] = font # See below.
def init(self, title, filename, repo_relative_filename, repo, font):
self.winfo_toplevel().title(title)
if os.path.exists(filename):
with open(filename) as f:
data = f.read()
self.insert(tk.END, data)
# Prevent this from triggering a git commit.
self.update()
self._cancelSave()
self.pack(expand=True, fill=tk.BOTH)
self.filename = filename
self.repo_relative_filename = repo_relative_filename
self.repo = repo
self['font'] = font # See below.
def reset(self):
if os.path.exists(self.filename):
with open(self.filename) as f:
data = f.read()
if data:
self.delete('0.0', tk.END)
self.insert(tk.END, data)
def reset(self):
if os.path.exists(self.filename):
with open(self.filename) as f:
data = f.read()
if data:
self.delete('0.0', tk.END)
self.insert(tk.END, data)
def popupTB(self, tb):
top = tk.Toplevel()
T = TextViewerWidget(
self.world,
top,
width=max(len(s) for s in tb.splitlines()) + 3,
)
def popupTB(self, tb):
top = tk.Toplevel()
T = TextViewerWidget(
self.world,
top,
width=max(len(s) for s in tb.splitlines()) + 3,
)
T['background'] = 'darkgrey'
T['foreground'] = 'darkblue'
T.tag_config('err', foreground='yellow')
T['background'] = 'darkgrey'
T['foreground'] = 'darkblue'
T.tag_config('err', foreground='yellow')
T.insert(tk.END, tb)
last_line = str(int(T.index(tk.END).split('.')[0]) - 1) + '.0'
T.tag_add('err', last_line, tk.END)
T['state'] = tk.DISABLED
T.insert(tk.END, tb)
last_line = str(int(T.index(tk.END).split('.')[0]) - 1) + '.0'
T.tag_add('err', last_line, tk.END)
T['state'] = tk.DISABLED
top.title(T.get(last_line, tk.END).strip())
top.title(T.get(last_line, tk.END).strip())
T.pack(expand=1, fill=tk.BOTH)
T.see(tk.END)
T.pack(expand=1, fill=tk.BOTH)
T.see(tk.END)

View File

@ -13,85 +13,85 @@ DEFAULT_JOY_HOME = expanduser(join('~', '.joypy'))
def is_numerical(s):
try:
float(s)
except ValueError:
return False
return True
try:
float(s)
except ValueError:
return False
return True
def home_dir(path):
'''Return the absolute path of an existing directory.'''
'''Return the absolute path of an existing directory.'''
fullpath = expanduser(path) if path.startswith('~') else abspath(path)
fullpath = expanduser(path) if path.startswith('~') else abspath(path)
if not exists(fullpath):
if path == DEFAULT_JOY_HOME:
print('Creating JOY_HOME', repr(fullpath))
mkdir(fullpath, 0o700)
else:
print(repr(fullpath), "doesn't exist.", file=sys.stderr)
raise ValueError(path)
if not exists(fullpath):
if path == DEFAULT_JOY_HOME:
print('Creating JOY_HOME', repr(fullpath))
mkdir(fullpath, 0o700)
else:
print(repr(fullpath), "doesn't exist.", file=sys.stderr)
raise ValueError(path)
return fullpath
return fullpath
def init_home(fullpath):
'''
Open or create the Repo.
If there are contents in the dir but it's not a git repo, quit.
'''
try:
repo = Repo(fullpath)
except NotGitRepository:
print(repr(fullpath), "no repository", file=sys.stderr)
'''
Open or create the Repo.
If there are contents in the dir but it's not a git repo, quit.
'''
try:
repo = Repo(fullpath)
except NotGitRepository:
print(repr(fullpath), "no repository", file=sys.stderr)
if listdir(fullpath):
print(repr(fullpath), "has contents\nQUIT.", file=sys.stderr)
sys.exit(2)
if listdir(fullpath):
print(repr(fullpath), "has contents\nQUIT.", file=sys.stderr)
sys.exit(2)
print('Initializing repository in', fullpath)
repo = init_repo(fullpath)
print('Initializing repository in', fullpath)
repo = init_repo(fullpath)
print('Using repository in', fullpath)
return repo
print('Using repository in', fullpath)
return repo
def init_repo(repo_dir):
'''
Create a repo, load the initial content, and make the first commit.
Return the Repo object.
'''
repo = Repo.init(repo_dir)
import joy.gui.init_joy_home
joy.gui.init_joy_home.initialize(repo_dir)
repo.stage([fn for fn in listdir(repo_dir) if isfile(join(repo_dir, fn))])
repo.do_commit('Initial commit.', committer=COMMITTER)
return repo
'''
Create a repo, load the initial content, and make the first commit.
Return the Repo object.
'''
repo = Repo.init(repo_dir)
import joy.gui.init_joy_home
joy.gui.init_joy_home.initialize(repo_dir)
repo.stage([fn for fn in listdir(repo_dir) if isfile(join(repo_dir, fn))])
repo.do_commit('Initial commit.', committer=COMMITTER)
return repo
argparser = argparse.ArgumentParser(
description='Experimental Brutalist UI for Joy.',
)
description='Experimental Brutalist UI for Joy.',
)
argparser.add_argument(
'-j', '--joy-home',
help='Use a directory other than %s as JOY_HOME' % DEFAULT_JOY_HOME,
default=DEFAULT_JOY_HOME,
dest='joy_home',
type=home_dir,
)
'-j', '--joy-home',
help='Use a directory other than %s as JOY_HOME' % DEFAULT_JOY_HOME,
default=DEFAULT_JOY_HOME,
dest='joy_home',
type=home_dir,
)
class FileFaker(object):
def __init__(self, T):
self.T = T
def __init__(self, T):
self.T = T
def write(self, text):
self.T.insert('end', text)
self.T.see('end')
def write(self, text):
self.T.insert('end', text)
self.T.see('end')
def flush(self):
pass
def flush(self):
pass

View File

@ -35,118 +35,118 @@ from .utils import is_numerical
class World(object):
def __init__(self, stack=(), dictionary=None, text_widget=None):
self.stack = stack
self.dictionary = dictionary or {}
self.text_widget = text_widget
self.check_cache = {}
def __init__(self, stack=(), dictionary=None, text_widget=None):
self.stack = stack
self.dictionary = dictionary or {}
self.text_widget = text_widget
self.check_cache = {}
def check(self, name):
try:
res = self.check_cache[name]
except KeyError:
res = self.check_cache[name] = type_check(name, self.stack)
return res
def check(self, name):
try:
res = self.check_cache[name]
except KeyError:
res = self.check_cache[name] = type_check(name, self.stack)
return res
def do_lookup(self, name):
if name in self.dictionary:
self.stack = (Symbol(name), ()), self.stack
self.print_stack()
self.check_cache.clear()
else:
assert is_numerical(name)
self.interpret(name)
def do_lookup(self, name):
if name in self.dictionary:
self.stack = (Symbol(name), ()), self.stack
self.print_stack()
self.check_cache.clear()
else:
assert is_numerical(name)
self.interpret(name)
def do_opendoc(self, name):
if is_numerical(name):
print('The number', name)
else:
try:
word = self.dictionary[name]
except KeyError:
print(repr(name), '???')
else:
print(getdoc(word))
self.print_stack()
def do_opendoc(self, name):
if is_numerical(name):
print('The number', name)
else:
try:
word = self.dictionary[name]
except KeyError:
print(repr(name), '???')
else:
print(getdoc(word))
self.print_stack()
def pop(self):
if self.stack:
self.stack = self.stack[1]
self.print_stack()
self.check_cache.clear()
def pop(self):
if self.stack:
self.stack = self.stack[1]
self.print_stack()
self.check_cache.clear()
def push(self, it):
it = it.encode('utf8')
self.stack = it, self.stack
self.print_stack()
self.check_cache.clear()
def push(self, it):
it = it.encode('utf8')
self.stack = it, self.stack
self.print_stack()
self.check_cache.clear()
def peek(self):
if self.stack:
return self.stack[0]
def peek(self):
if self.stack:
return self.stack[0]
def interpret(self, command):
if self.has(command) and self.check(command) == False: # not in {True, None}:
return
old_stack = self.stack
try:
self.stack, _, self.dictionary = run(
command,
self.stack,
self.dictionary,
)
finally:
self.print_stack()
if old_stack != self.stack:
self.check_cache.clear()
def interpret(self, command):
if self.has(command) and self.check(command) == False: # not in {True, None}:
return
old_stack = self.stack
try:
self.stack, _, self.dictionary = run(
command,
self.stack,
self.dictionary,
)
finally:
self.print_stack()
if old_stack != self.stack:
self.check_cache.clear()
def has(self, name):
return name in self.dictionary
def has(self, name):
return name in self.dictionary
def save(self):
pass
def save(self):
pass
def print_stack(self):
stack_out_index = self.text_widget.search('<' 'STACK', 1.0)
if stack_out_index:
self.text_widget.see(stack_out_index)
s = stack_to_string(self.stack) + '\n'
self.text_widget.insert(stack_out_index, s)
def print_stack(self):
stack_out_index = self.text_widget.search('<' 'STACK', 1.0)
if stack_out_index:
self.text_widget.see(stack_out_index)
s = stack_to_string(self.stack) + '\n'
self.text_widget.insert(stack_out_index, s)
class StackDisplayWorld(World):
def __init__(self, repo, filename, rel_filename, dictionary=None, text_widget=None):
self.filename = filename
stack = self.load_stack() or ()
World.__init__(self, stack, dictionary, text_widget)
self.repo = repo
self.relative_STACK_FN = rel_filename
def __init__(self, repo, filename, rel_filename, dictionary=None, text_widget=None):
self.filename = filename
stack = self.load_stack() or ()
World.__init__(self, stack, dictionary, text_widget)
self.repo = repo
self.relative_STACK_FN = rel_filename
def interpret(self, command):
command = command.strip()
if self.has(command) and self.check(command) == False: # not in {True, None}:
return
print('\njoy?', command)
super(StackDisplayWorld, self).interpret(command)
def interpret(self, command):
command = command.strip()
if self.has(command) and self.check(command) == False: # not in {True, None}:
return
print('\njoy?', command)
super(StackDisplayWorld, self).interpret(command)
def print_stack(self):
print('\n%s <-' % stack_to_string(self.stack))
def print_stack(self):
print('\n%s <-' % stack_to_string(self.stack))
def save(self):
with open(self.filename, 'wb') as f:
os.chmod(self.filename, 0o600)
pickle.dump(self.stack, f, protocol=2)
f.flush()
os.fsync(f.fileno())
self.repo.stage([self.relative_STACK_FN])
commit_id = self.repo.do_commit(
b'auto-save',
committer=b'thun-auto-save <nobody@example.com>',
)
_log.info('commit %s', commit_id)
def save(self):
with open(self.filename, 'wb') as f:
os.chmod(self.filename, 0o600)
pickle.dump(self.stack, f, protocol=2)
f.flush()
os.fsync(f.fileno())
self.repo.stage([self.relative_STACK_FN])
commit_id = self.repo.do_commit(
b'auto-save',
committer=b'thun-auto-save <nobody@example.com>',
)
_log.info('commit %s', commit_id)
def load_stack(self):
if os.path.exists(self.filename):
with open(self.filename, 'rb') as f:
return pickle.load(f)
def load_stack(self):
if os.path.exists(self.filename):
with open(self.filename, 'rb') as f:
return pickle.load(f)

View File

@ -32,86 +32,86 @@ from .utils.pretty_print import TracePrinter
def joy(stack, expression, dictionary, viewer=None):
'''Evaluate a Joy expression on a stack.
'''Evaluate a Joy expression on a stack.
This function iterates through a sequence of terms which are either
literals (strings, numbers, sequences of terms) or function symbols.
Literals are put onto the stack and functions are looked up in the
disctionary and executed.
This function iterates through a sequence of terms which are either
literals (strings, numbers, sequences of terms) or function symbols.
Literals are put onto the stack and functions are looked up in the
disctionary and executed.
The viewer is a function that is called with the stack and expression
on every iteration, its return value is ignored.
The viewer is a function that is called with the stack and expression
on every iteration, its return value is ignored.
:param stack stack: The stack.
:param stack expression: The expression to evaluate.
:param dict dictionary: A ``dict`` mapping names to Joy functions.
:param function viewer: Optional viewer function.
:rtype: (stack, (), dictionary)
:param stack stack: The stack.
:param stack expression: The expression to evaluate.
:param dict dictionary: A ``dict`` mapping names to Joy functions.
:param function viewer: Optional viewer function.
:rtype: (stack, (), dictionary)
'''
while expression:
'''
while expression:
if viewer: viewer(stack, expression)
if viewer: viewer(stack, expression)
term, expression = expression
if isinstance(term, Symbol):
term = dictionary[term]
stack, expression, dictionary = term(stack, expression, dictionary)
else:
stack = term, stack
term, expression = expression
if isinstance(term, Symbol):
term = dictionary[term]
stack, expression, dictionary = term(stack, expression, dictionary)
else:
stack = term, stack
if viewer: viewer(stack, expression)
return stack, expression, dictionary
if viewer: viewer(stack, expression)
return stack, expression, dictionary
def run(text, stack, dictionary, viewer=None):
'''
Return the stack resulting from running the Joy code text on the stack.
'''
Return the stack resulting from running the Joy code text on the stack.
:param str text: Joy code.
:param stack stack: The stack.
:param dict dictionary: A ``dict`` mapping names to Joy functions.
:param function viewer: Optional viewer function.
:rtype: (stack, (), dictionary)
:param str text: Joy code.
:param stack stack: The stack.
:param dict dictionary: A ``dict`` mapping names to Joy functions.
:param function viewer: Optional viewer function.
:rtype: (stack, (), dictionary)
'''
expression = text_to_expression(text)
return joy(stack, expression, dictionary, viewer)
'''
expression = text_to_expression(text)
return joy(stack, expression, dictionary, viewer)
def repl(stack=(), dictionary=None):
'''
Read-Evaluate-Print Loop
'''
Read-Evaluate-Print Loop
Accept input and run it on the stack, loop.
Accept input and run it on the stack, loop.
:param stack stack: The stack.
:param dict dictionary: A ``dict`` mapping names to Joy functions.
:rtype: stack
:param stack stack: The stack.
:param dict dictionary: A ``dict`` mapping names to Joy functions.
:rtype: stack
'''
if dictionary is None:
dictionary = {}
try:
while True:
print()
print(stack_to_string(stack), '<-top')
print()
try:
text = input('joy? ')
except (EOFError, KeyboardInterrupt):
break
viewer = TracePrinter()
try:
stack, _, dictionary = run(text, stack, dictionary, viewer.viewer)
except:
exc = format_exc() # Capture the exception.
viewer.print_() # Print the Joy trace.
print('-' * 73)
print(exc) # Print the original exception.
else:
viewer.print_()
except:
print_exc()
print()
return stack
'''
if dictionary is None:
dictionary = {}
try:
while True:
print()
print(stack_to_string(stack), '<-top')
print()
try:
text = input('joy? ')
except (EOFError, KeyboardInterrupt):
break
viewer = TracePrinter()
try:
stack, _, dictionary = run(text, stack, dictionary, viewer.viewer)
except:
exc = format_exc() # Capture the exception.
viewer.print_() # Print the Joy trace.
print('-' * 73)
print(exc) # Print the original exception.
else:
viewer.print_()
except:
print_exc()
print()
return stack

File diff suppressed because it is too large Load Diff

View File

@ -51,74 +51,74 @@ BLANKS = r'\s+'
class Symbol(str):
'''A string class that represents Joy function names.'''
__repr__ = str.__str__
'''A string class that represents Joy function names.'''
__repr__ = str.__str__
def text_to_expression(text):
'''Convert a string to a Joy expression.
'''Convert a string to a Joy expression.
When supplied with a string this function returns a Python datastructure
that represents the Joy datastructure described by the text expression.
Any unbalanced square brackets will raise a ParseError.
When supplied with a string this function returns a Python datastructure
that represents the Joy datastructure described by the text expression.
Any unbalanced square brackets will raise a ParseError.
:param str text: Text to convert.
:rtype: stack
:raises ParseError: if the parse fails.
'''
return _parse(_tokenize(text))
:param str text: Text to convert.
:rtype: stack
:raises ParseError: if the parse fails.
'''
return _parse(_tokenize(text))
class ParseError(ValueError):
'''Raised when there is a error while parsing text.'''
'''Raised when there is a error while parsing text.'''
def _tokenize(text):
'''Convert a text into a stream of tokens.
'''Convert a text into a stream of tokens.
Converts function names to Symbols.
Converts function names to Symbols.
Raise ParseError (with some of the failing text) if the scan fails.
'''
tokens, rest = _scanner.scan(text)
if rest:
raise ParseError(
'Scan failed at position %i, %r'
% (len(text) - len(rest), rest[:10])
)
return tokens
Raise ParseError (with some of the failing text) if the scan fails.
'''
tokens, rest = _scanner.scan(text)
if rest:
raise ParseError(
'Scan failed at position %i, %r'
% (len(text) - len(rest), rest[:10])
)
return tokens
def _parse(tokens):
'''
Return a stack/list expression of the tokens.
'''
frame = []
stack = []
for tok in tokens:
if tok == '[':
stack.append(frame)
frame = []
stack[-1].append(frame)
elif tok == ']':
try:
frame = stack.pop()
except IndexError:
raise ParseError('Extra closing bracket.')
frame[-1] = list_to_stack(frame[-1])
else:
frame.append(tok)
if stack:
raise ParseError('Unclosed bracket.')
return list_to_stack(frame)
'''
Return a stack/list expression of the tokens.
'''
frame = []
stack = []
for tok in tokens:
if tok == '[':
stack.append(frame)
frame = []
stack[-1].append(frame)
elif tok == ']':
try:
frame = stack.pop()
except IndexError:
raise ParseError('Extra closing bracket.')
frame[-1] = list_to_stack(frame[-1])
else:
frame.append(tok)
if stack:
raise ParseError('Unclosed bracket.')
return list_to_stack(frame)
_scanner = Scanner([
(FLOAT, lambda _, token: float(token)),
(INT, lambda _, token: int(token)),
(SYMBOL, lambda _, token: Symbol(token)),
(BRACKETS, lambda _, token: token),
(STRING_DOUBLE_QUOTED, lambda _, token: token[1:-1].replace('\\"', '"')),
(STRING_SINGLE_QUOTED, lambda _, token: token[1:-1].replace("\\'", "'")),
(BLANKS, None),
])
(FLOAT, lambda _, token: float(token)),
(INT, lambda _, token: int(token)),
(SYMBOL, lambda _, token: Symbol(token)),
(BRACKETS, lambda _, token: token),
(STRING_DOUBLE_QUOTED, lambda _, token: token[1:-1].replace('\\"', '"')),
(STRING_SINGLE_QUOTED, lambda _, token: token[1:-1].replace("\\'", "'")),
(BLANKS, None),
])

View File

@ -54,43 +54,43 @@ function name! Hopefully they will discover this documentation.
def rename_code_object(new_name):
'''
If you want to wrap a function in another function and have the wrapped
function's name show up in the traceback when an exception occurs in
the wrapper function, you must do this brutal hackery to change the
func.__code__.co_name attribute. Just functools.wraps() is not enough.
'''
If you want to wrap a function in another function and have the wrapped
function's name show up in the traceback when an exception occurs in
the wrapper function, you must do this brutal hackery to change the
func.__code__.co_name attribute. Just functools.wraps() is not enough.
See:
See:
https://stackoverflow.com/questions/29919804/function-decorated-using-functools-wraps-raises-typeerror-with-the-name-of-the-w
https://stackoverflow.com/questions/29919804/function-decorated-using-functools-wraps-raises-typeerror-with-the-name-of-the-w
https://stackoverflow.com/questions/29488327/changing-the-name-of-a-generator/29488561#29488561
https://stackoverflow.com/questions/29488327/changing-the-name-of-a-generator/29488561#29488561
I'm just glad it's possible.
'''
def inner(func):
name = new_name + ':' + func.__name__
code_object = func.__code__
return type(func)(
type(code_object)(
code_object.co_argcount,
code_object.co_nlocals,
code_object.co_stacksize,
code_object.co_flags,
code_object.co_code,
code_object.co_consts,
code_object.co_names,
code_object.co_varnames,
code_object.co_filename,
name,
code_object.co_firstlineno,
code_object.co_lnotab,
code_object.co_freevars,
code_object.co_cellvars
),
func.__globals__,
name,
func.__defaults__,
func.__closure__
)
return inner
I'm just glad it's possible.
'''
def inner(func):
name = new_name + ':' + func.__name__
code_object = func.__code__
return type(func)(
type(code_object)(
code_object.co_argcount,
code_object.co_nlocals,
code_object.co_stacksize,
code_object.co_flags,
code_object.co_code,
code_object.co_consts,
code_object.co_names,
code_object.co_varnames,
code_object.co_filename,
name,
code_object.co_firstlineno,
code_object.co_lnotab,
code_object.co_freevars,
code_object.co_cellvars
),
func.__globals__,
name,
func.__defaults__,
func.__closure__
)
return inner

View File

@ -21,49 +21,49 @@ from functools import reduce
def import_yin():
from joy.utils.generated_library import *
return locals()
from joy.utils.generated_library import *
return locals()
class InfiniteStack(tuple):
def _names():
n = 0
while True:
m = yield Symbol('a' + str(n))
n = n + 1 if m is None else m
def _names():
n = 0
while True:
m = yield Symbol('a' + str(n))
n = n + 1 if m is None else m
_NAMES = _names()
next(_NAMES)
_NAMES = _names()
next(_NAMES)
names = lambda: next(_NAMES)
reset = lambda _self, _n=_NAMES: _n.send(-1)
names = lambda: next(_NAMES)
reset = lambda _self, _n=_NAMES: _n.send(-1)
def __init__(self, code):
self.reset()
self.code = code
def __init__(self, code):
self.reset()
self.code = code
def __iter__(self):
if not self:
new_var = self.names()
self.code.append(('pop', new_var))
return iter((new_var, self))
def __iter__(self):
if not self:
new_var = self.names()
self.code.append(('pop', new_var))
return iter((new_var, self))
def I(expression):
code = []
stack = InfiniteStack(code)
code = []
stack = InfiniteStack(code)
while expression:
term, expression = expression
if isinstance(term, Symbol):
func = D[term]
stack, expression, _ = func(stack, expression, code)
else:
stack = term, stack
while expression:
term, expression = expression
if isinstance(term, Symbol):
func = D[term]
stack, expression, _ = func(stack, expression, code)
else:
stack = term, stack
code.append(tuple(['ret'] + list(iter_stack(stack))))
return code
code.append(tuple(['ret'] + list(iter_stack(stack))))
return code
strtup = lambda a, b: '(%s, %s)' % (b, a)
@ -71,123 +71,123 @@ strstk = lambda rest: reduce(strtup, rest, 'stack')
def code_gen(code):
#for p in code: print p
coalesce_pops(code)
lines = []
emit = lines.append
for t in code:
tag, rest = t[0], t[1:]
if tag == 'pop': emit(strstk(rest) + ' = stack')
elif tag == 'call': emit('%s = %s%s' % rest)
elif tag == 'ret': emit('return ' + strstk(rest[::-1]))
else:
raise ValueError(tag)
return '\n'.join(' ' + line for line in lines)
#for p in code: print p
coalesce_pops(code)
lines = []
emit = lines.append
for t in code:
tag, rest = t[0], t[1:]
if tag == 'pop': emit(strstk(rest) + ' = stack')
elif tag == 'call': emit('%s = %s%s' % rest)
elif tag == 'ret': emit('return ' + strstk(rest[::-1]))
else:
raise ValueError(tag)
return '\n'.join(' ' + line for line in lines)
def coalesce_pops(code):
code.sort(key=lambda p: p[0] != 'pop') # All pops to the front.
try: index = next((i for i, t in enumerate(code) if t[0] != 'pop'))
except StopIteration: return
code[:index] = [tuple(['pop'] + [t for _, t in code[:index][::-1]])]
code.sort(key=lambda p: p[0] != 'pop') # All pops to the front.
try: index = next((i for i, t in enumerate(code) if t[0] != 'pop'))
except StopIteration: return
code[:index] = [tuple(['pop'] + [t for _, t in code[:index][::-1]])]
def compile_yinyang(name, text):
return '''
return '''
def %s(stack):
%s
''' % (name, code_gen(I(text_to_expression(text))))
def q():
memo = {}
def bar(type_var):
try:
res = memo[type_var]
except KeyError:
res = memo[type_var] = InfiniteStack.names()
return res
return bar
memo = {}
def bar(type_var):
try:
res = memo[type_var]
except KeyError:
res = memo[type_var] = InfiniteStack.names()
return res
return bar
def type_vars_to_labels(thing, map_):
if not thing:
return thing
if not isinstance(thing, tuple):
return map_(thing)
return tuple(type_vars_to_labels(inner, map_) for inner in thing)
if not thing:
return thing
if not isinstance(thing, tuple):
return map_(thing)
return tuple(type_vars_to_labels(inner, map_) for inner in thing)
def remap_inputs(in_, stack, code):
map_ = q()
while in_:
term, in_ = in_
arg0, stack = stack
term = type_vars_to_labels(term, map_)
code.append(('call', term, '', arg0))
return stack, map_
map_ = q()
while in_:
term, in_ = in_
arg0, stack = stack
term = type_vars_to_labels(term, map_)
code.append(('call', term, '', arg0))
return stack, map_
class BinaryBuiltin(object):
def __init__(self, name):
self.name = name
def __init__(self, name):
self.name = name
def __call__(self, stack, expression, code):
in1, (in0, stack) = stack
out = InfiniteStack.names()
code.append(('call', out, self.name, (in0, in1)))
return (out, stack), expression, code
def __call__(self, stack, expression, code):
in1, (in0, stack) = stack
out = InfiniteStack.names()
code.append(('call', out, self.name, (in0, in1)))
return (out, stack), expression, code
YIN = import_yin()
D = {
name: SimpleFunctionWrapper(YIN[name])
for name in '''
ccons
cons
dup
dupd
dupdd
over
pop
popd
popdd
popop
popopd
popopdd
rolldown
rollup
swap
swons
tuck
unit
'''.split()
}
name: SimpleFunctionWrapper(YIN[name])
for name in '''
ccons
cons
dup
dupd
dupdd
over
pop
popd
popdd
popop
popopd
popopdd
rolldown
rollup
swap
swons
tuck
unit
'''.split()
}
for name in '''
first
first_two
fourth
rest
rrest
second
third
uncons
unswons
'''.split():
first
first_two
fourth
rest
rrest
second
third
uncons
unswons
'''.split():
def foo(stack, expression, code, name=name):
in_, out = YIN_STACK_EFFECTS[name]
stack, map_ = remap_inputs(in_, stack, code)
out = type_vars_to_labels(out, map_)
return concat(out, stack), expression, code
def foo(stack, expression, code, name=name):
in_, out = YIN_STACK_EFFECTS[name]
stack, map_ = remap_inputs(in_, stack, code)
out = type_vars_to_labels(out, map_)
return concat(out, stack), expression, code
foo.__name__ = name
D[name] = foo
foo.__name__ = name
D[name] = foo
for name in '''
@ -210,18 +210,18 @@ for name in '''
sub
truediv
'''.split():
D[name.rstrip('-')] = BinaryBuiltin(name)
D[name.rstrip('-')] = BinaryBuiltin(name)
'''
stack
stuncons
stununcons
swaack
stack
stuncons
stununcons
swaack
'''
for name in sorted(D):
print(name, end=' ')
print(name, end=' ')
## print compile_yinyang(name, name)
print('-' * 100)

View File

@ -3,19 +3,19 @@ from joy.parser import Symbol
def _names():
n = 0
while True:
yield Symbol('a' + str(n))
n += 1
n = 0
while True:
yield Symbol('a' + str(n))
n += 1
class InfiniteStack(tuple):
names = lambda n=_names(): next(n)
names = lambda n=_names(): next(n)
def __iter__(self):
if not self:
return iter((self.names(), self))
def __iter__(self):
if not self:
return iter((self.names(), self))
i = InfiniteStack()
@ -23,9 +23,9 @@ i = InfiniteStack()
a, b = i
lambda u: (lambda fu, u: fu * fu * u)(
(lambda u: (lambda fu, u: fu * fu)(
(lambda u: (lambda fu, u: fu * fu * u)(
(lambda u: 1)(u), u))(u), u))(u),
u)
(lambda u: (lambda fu, u: fu * fu)(
(lambda u: (lambda fu, u: fu * fu * u)(
(lambda u: 1)(u), u))(u), u))(u),
u)
lambda u: (lambda fu, u: fu * fu * u)((lambda u: (lambda fu, u: fu * fu)((lambda u: (lambda fu, u: fu * fu * u)((lambda u: 1)(u), u))(u), u))(u), u)

View File

@ -46,54 +46,54 @@ from .stack import expression_to_string, stack_to_string
class TracePrinter(object):
'''
This is what does the formatting. You instantiate it and pass the ``viewer()``
method to the :py:func:`joy.joy.joy` function, then print it to see the
trace.
'''
'''
This is what does the formatting. You instantiate it and pass the ``viewer()``
method to the :py:func:`joy.joy.joy` function, then print it to see the
trace.
'''
def __init__(self):
self.history = []
def __init__(self):
self.history = []
def viewer(self, stack, expression):
'''
Record the current stack and expression in the TracePrinter's history.
Pass this method as the ``viewer`` argument to the :py:func:`joy.joy.joy` function.
def viewer(self, stack, expression):
'''
Record the current stack and expression in the TracePrinter's history.
Pass this method as the ``viewer`` argument to the :py:func:`joy.joy.joy` function.
:param stack quote: A stack.
:param stack expression: A stack.
'''
self.history.append((stack, expression))
:param stack quote: A stack.
:param stack expression: A stack.
'''
self.history.append((stack, expression))
def __str__(self):
return '\n'.join(self.go())
def __str__(self):
return '\n'.join(self.go())
def go(self):
'''
Return a list of strings, one for each entry in the history, prefixed
with enough spaces to align all the interpreter dots.
def go(self):
'''
Return a list of strings, one for each entry in the history, prefixed
with enough spaces to align all the interpreter dots.
This method is called internally by the ``__str__()`` method.
This method is called internally by the ``__str__()`` method.
:rtype: list(str)
'''
max_stack_length = 0
lines = []
for stack, expression in self.history:
stack = stack_to_string(stack)
expression = expression_to_string(expression)
n = len(stack)
if n > max_stack_length:
max_stack_length = n
lines.append((n, '%s . %s' % (stack, expression)))
return [ # Prefix spaces to line up '.'s.
(' ' * (max_stack_length - length) + line)
for length, line in lines
]
:rtype: list(str)
'''
max_stack_length = 0
lines = []
for stack, expression in self.history:
stack = stack_to_string(stack)
expression = expression_to_string(expression)
n = len(stack)
if n > max_stack_length:
max_stack_length = n
lines.append((n, '%s . %s' % (stack, expression)))
return [ # Prefix spaces to line up '.'s.
(' ' * (max_stack_length - length) + line)
for length, line in lines
]
def print_(self):
try:
print(self)
except:
print_exc()
print('Exception while printing viewer.')
def print_(self):
try:
print(self)
except:
print_exc()
print('Exception while printing viewer.')

View File

@ -43,8 +43,8 @@ means we can directly "unpack" the expected arguments to a Joy function.
For example::
def dup((head, tail)):
return head, (head, tail)
def dup((head, tail)):
return head, (head, tail)
We replace the argument "stack" by the expected structure of the stack,
in this case "(head, tail)", and Python takes care of unpacking the
@ -56,9 +56,9 @@ Unfortunately, the Sphinx documentation generator, which is used to generate thi
web page, doesn't handle tuples in the function parameters. And in Python 3, this
syntax was removed entirely. Instead you would have to write::
def dup(stack):
head, tail = stack
return head, (head, tail)
def dup(stack):
head, tail = stack
return head, (head, tail)
We have two very simple functions, one to build up a stack from a Python
@ -74,95 +74,95 @@ printed left-to-right. These functions are written to support :doc:`../pretty`.
from builtins import map
def list_to_stack(el, stack=()):
'''Convert a Python list (or other sequence) to a Joy stack::
'''Convert a Python list (or other sequence) to a Joy stack::
[1, 2, 3] -> (1, (2, (3, ())))
[1, 2, 3] -> (1, (2, (3, ())))
:param list el: A Python list or other sequence (iterators and generators
won't work because ``reverse()`` is called on ``el``.)
:param stack stack: A stack, optional, defaults to the empty stack.
:rtype: stack
:param list el: A Python list or other sequence (iterators and generators
won't work because ``reverse()`` is called on ``el``.)
:param stack stack: A stack, optional, defaults to the empty stack.
:rtype: stack
'''
for item in reversed(el):
stack = item, stack
return stack
'''
for item in reversed(el):
stack = item, stack
return stack
def iter_stack(stack):
'''Iterate through the items on the stack.
'''Iterate through the items on the stack.
:param stack stack: A stack.
:rtype: iterator
'''
while stack:
item, stack = stack
yield item
:param stack stack: A stack.
:rtype: iterator
'''
while stack:
item, stack = stack
yield item
def stack_to_string(stack):
'''
Return a "pretty print" string for a stack.
'''
Return a "pretty print" string for a stack.
The items are written right-to-left::
The items are written right-to-left::
(top, (second, ...)) -> '... second top'
(top, (second, ...)) -> '... second top'
:param stack stack: A stack.
:rtype: str
'''
f = lambda stack: reversed(list(iter_stack(stack)))
return _to_string(stack, f)
:param stack stack: A stack.
:rtype: str
'''
f = lambda stack: reversed(list(iter_stack(stack)))
return _to_string(stack, f)
def expression_to_string(expression):
'''
Return a "pretty print" string for a expression.
'''
Return a "pretty print" string for a expression.
The items are written left-to-right::
The items are written left-to-right::
(top, (second, ...)) -> 'top second ...'
(top, (second, ...)) -> 'top second ...'
:param stack expression: A stack.
:rtype: str
'''
return _to_string(expression, iter_stack)
:param stack expression: A stack.
:rtype: str
'''
return _to_string(expression, iter_stack)
def _to_string(stack, f):
if not isinstance(stack, tuple): return repr(stack)
if not stack: return '' # shortcut
return ' '.join(map(_s, f(stack)))
if not isinstance(stack, tuple): return repr(stack)
if not stack: return '' # shortcut
return ' '.join(map(_s, f(stack)))
_s = lambda s: (
'[%s]' % expression_to_string(s) if isinstance(s, tuple)
else repr(s)
)
'[%s]' % expression_to_string(s) if isinstance(s, tuple)
else repr(s)
)
def concat(quote, expression):
'''Concatinate quote onto expression.
'''Concatinate quote onto expression.
In joy [1 2] [3 4] would become [1 2 3 4].
In joy [1 2] [3 4] would become [1 2 3 4].
:param stack quote: A stack.
:param stack expression: A stack.
:raises RuntimeError: if quote is larger than sys.getrecursionlimit().
:rtype: stack
'''
# This is the fastest implementation, but will trigger
# RuntimeError: maximum recursion depth exceeded
# on quotes longer than sys.getrecursionlimit().
:param stack quote: A stack.
:param stack expression: A stack.
:raises RuntimeError: if quote is larger than sys.getrecursionlimit().
:rtype: stack
'''
# This is the fastest implementation, but will trigger
# RuntimeError: maximum recursion depth exceeded
# on quotes longer than sys.getrecursionlimit().
return (quote[0], concat(quote[1], expression)) if quote else expression
return (quote[0], concat(quote[1], expression)) if quote else expression
# Original implementation.
# Original implementation.
## return list_to_stack(list(iter_stack(quote)), expression)
# In-lining is slightly faster (and won't break the
# recursion limit on long quotes.)
# In-lining is slightly faster (and won't break the
# recursion limit on long quotes.)
## temp = []
## while quote:
@ -175,23 +175,23 @@ def concat(quote, expression):
def pick(stack, n):
'''
Return the nth item on the stack.
'''
Return the nth item on the stack.
:param stack stack: A stack.
:param int n: An index into the stack.
:raises ValueError: if ``n`` is less than zero.
:raises IndexError: if ``n`` is equal to or greater than the length of ``stack``.
:rtype: whatever
'''
if n < 0:
raise ValueError
while True:
try:
item, stack = stack
except ValueError:
raise IndexError
n -= 1
if n < 0:
break
return item
:param stack stack: A stack.
:param int n: An index into the stack.
:raises ValueError: if ``n`` is less than zero.
:raises IndexError: if ``n`` is equal to or greater than the length of ``stack``.
:rtype: whatever
'''
if n < 0:
raise ValueError
while True:
try:
item, stack = stack
except ValueError:
raise IndexError
n -= 1
if n < 0:
break
return item

File diff suppressed because it is too large Load Diff

View File

@ -48,18 +48,18 @@ GREEN = 70, 200, 70
MOUSE_EVENTS = frozenset({
pygame.MOUSEMOTION,
pygame.MOUSEBUTTONDOWN,
pygame.MOUSEBUTTONUP
})
pygame.MOUSEMOTION,
pygame.MOUSEBUTTONDOWN,
pygame.MOUSEBUTTONUP
})
'PyGame mouse events.'
ARROW_KEYS = frozenset({
pygame.K_UP,
pygame.K_DOWN,
pygame.K_LEFT,
pygame.K_RIGHT
})
pygame.K_UP,
pygame.K_DOWN,
pygame.K_LEFT,
pygame.K_RIGHT
})
'PyGame arrow key events.'
@ -82,201 +82,201 @@ SUCCESS = 1
class Message(object):
'''Message base class. Contains ``sender`` field.'''
def __init__(self, sender):
self.sender = sender
'''Message base class. Contains ``sender`` field.'''
def __init__(self, sender):
self.sender = sender
class CommandMessage(Message):
'''For commands, adds ``command`` field.'''
def __init__(self, sender, command):
Message.__init__(self, sender)
self.command = command
'''For commands, adds ``command`` field.'''
def __init__(self, sender, command):
Message.__init__(self, sender)
self.command = command
class ModifyMessage(Message):
'''
For when resources are modified, adds ``subject`` and ``details``
fields.
'''
def __init__(self, sender, subject, **details):
Message.__init__(self, sender)
self.subject = subject
self.details = details
'''
For when resources are modified, adds ``subject`` and ``details``
fields.
'''
def __init__(self, sender, subject, **details):
Message.__init__(self, sender)
self.subject = subject
self.details = details
class OpenMessage(Message):
'''
For when resources are modified, adds ``name``, content_id``,
``status``, and ``traceback`` fields.
'''
def __init__(self, sender, name):
Message.__init__(self, sender)
self.name = name
self.content_id = self.thing = None
self.status = PENDING
self.traceback = None
'''
For when resources are modified, adds ``name``, content_id``,
``status``, and ``traceback`` fields.
'''
def __init__(self, sender, name):
Message.__init__(self, sender)
self.name = name
self.content_id = self.thing = None
self.status = PENDING
self.traceback = None
class PersistMessage(Message):
'''
For when resources are modified, adds ``content_id`` and ``details``
fields.
'''
def __init__(self, sender, content_id, **details):
Message.__init__(self, sender)
self.content_id = content_id
self.details = details
'''
For when resources are modified, adds ``content_id`` and ``details``
fields.
'''
def __init__(self, sender, content_id, **details):
Message.__init__(self, sender)
self.content_id = content_id
self.details = details
class ShutdownMessage(Message):
'''Signals that the system is shutting down.'''
'''Signals that the system is shutting down.'''
# Joy Interpreter & Context
class World(object):
'''
This object contains the system context, the stack, dictionary, a
reference to the display broadcast method, and the log.
'''
'''
This object contains the system context, the stack, dictionary, a
reference to the display broadcast method, and the log.
'''
def __init__(self, stack_id, stack_holder, dictionary, notify, log):
self.stack_holder = stack_holder
self.dictionary = dictionary
self.notify = notify
self.stack_id = stack_id
self.log = log.lines
self.log_id = log.content_id
def __init__(self, stack_id, stack_holder, dictionary, notify, log):
self.stack_holder = stack_holder
self.dictionary = dictionary
self.notify = notify
self.stack_id = stack_id
self.log = log.lines
self.log_id = log.content_id
def handle(self, message):
'''
Deal with updates to the stack and commands.
'''
if (isinstance(message, ModifyMessage)
and message.subject is self.stack_holder
):
self._log_lines('', '%s <-' % self.format_stack())
if not isinstance(message, CommandMessage):
return
c, s, d = message.command, self.stack_holder[0], self.dictionary
self._log_lines('', '-> %s' % (c,))
self.stack_holder[0], _, self.dictionary = run(c, s, d)
mm = ModifyMessage(self, self.stack_holder, content_id=self.stack_id)
self.notify(mm)
def handle(self, message):
'''
Deal with updates to the stack and commands.
'''
if (isinstance(message, ModifyMessage)
and message.subject is self.stack_holder
):
self._log_lines('', '%s <-' % self.format_stack())
if not isinstance(message, CommandMessage):
return
c, s, d = message.command, self.stack_holder[0], self.dictionary
self._log_lines('', '-> %s' % (c,))
self.stack_holder[0], _, self.dictionary = run(c, s, d)
mm = ModifyMessage(self, self.stack_holder, content_id=self.stack_id)
self.notify(mm)
def _log_lines(self, *lines):
self.log.extend(lines)
self.notify(ModifyMessage(self, self.log, content_id=self.log_id))
def _log_lines(self, *lines):
self.log.extend(lines)
self.notify(ModifyMessage(self, self.log, content_id=self.log_id))
def format_stack(self):
try:
return stack_to_string(self.stack_holder[0])
except:
print(format_exc(), file=stderr)
return str(self.stack_holder[0])
def format_stack(self):
try:
return stack_to_string(self.stack_holder[0])
except:
print(format_exc(), file=stderr)
return str(self.stack_holder[0])
def push(sender, item, notify, stack_name='stack.pickle'):
'''
Helper function to push an item onto the system stack with message.
'''
om = OpenMessage(sender, stack_name)
notify(om)
if om.status == SUCCESS:
om.thing[0] = item, om.thing[0]
notify(ModifyMessage(sender, om.thing, content_id=om.content_id))
return om.status
'''
Helper function to push an item onto the system stack with message.
'''
om = OpenMessage(sender, stack_name)
notify(om)
if om.status == SUCCESS:
om.thing[0] = item, om.thing[0]
notify(ModifyMessage(sender, om.thing, content_id=om.content_id))
return om.status
def open_viewer_on_string(sender, content, notify):
'''
Helper function to open a text viewer on a string.
Typically used to show tracebacks.
'''
push(sender, content, notify)
notify(CommandMessage(sender, 'good_viewer_location open_viewer'))
'''
Helper function to open a text viewer on a string.
Typically used to show tracebacks.
'''
push(sender, content, notify)
notify(CommandMessage(sender, 'good_viewer_location open_viewer'))
# main loop
class TheLoop(object):
'''
The main loop manages tasks and the PyGame event queue
and framerate clock.
'''
'''
The main loop manages tasks and the PyGame event queue
and framerate clock.
'''
FRAME_RATE = 24
FRAME_RATE = 24
def __init__(self, display, clock):
self.display = display
self.clock = clock
self.tasks = {}
self.running = False
def __init__(self, display, clock):
self.display = display
self.clock = clock
self.tasks = {}
self.running = False
def install_task(self, F, milliseconds):
'''
Install a task to run every so many milliseconds.
'''
try:
task_event_id = AVAILABLE_TASK_EVENTS.pop()
except KeyError:
raise RuntimeError('out of task ids')
self.tasks[task_event_id] = F
pygame.time.set_timer(task_event_id, milliseconds)
return task_event_id
def install_task(self, F, milliseconds):
'''
Install a task to run every so many milliseconds.
'''
try:
task_event_id = AVAILABLE_TASK_EVENTS.pop()
except KeyError:
raise RuntimeError('out of task ids')
self.tasks[task_event_id] = F
pygame.time.set_timer(task_event_id, milliseconds)
return task_event_id
def remove_task(self, task_event_id):
'''
Remove an installed task.
'''
assert task_event_id in self.tasks, repr(task_event_id)
pygame.time.set_timer(task_event_id, 0)
del self.tasks[task_event_id]
AVAILABLE_TASK_EVENTS.add(task_event_id)
def remove_task(self, task_event_id):
'''
Remove an installed task.
'''
assert task_event_id in self.tasks, repr(task_event_id)
pygame.time.set_timer(task_event_id, 0)
del self.tasks[task_event_id]
AVAILABLE_TASK_EVENTS.add(task_event_id)
def __del__(self):
# Best effort to cancel all running tasks.
for task_event_id in self.tasks:
pygame.time.set_timer(task_event_id, 0)
def __del__(self):
# Best effort to cancel all running tasks.
for task_event_id in self.tasks:
pygame.time.set_timer(task_event_id, 0)
def run_task(self, task_event_id):
'''
Give a task its time to shine.
'''
task = self.tasks[task_event_id]
try:
task()
except:
traceback = format_exc()
self.remove_task(task_event_id)
print(traceback, file=stderr)
print('TASK removed due to ERROR', task, file=stderr)
open_viewer_on_string(self, traceback, self.display.broadcast)
def run_task(self, task_event_id):
'''
Give a task its time to shine.
'''
task = self.tasks[task_event_id]
try:
task()
except:
traceback = format_exc()
self.remove_task(task_event_id)
print(traceback, file=stderr)
print('TASK removed due to ERROR', task, file=stderr)
open_viewer_on_string(self, traceback, self.display.broadcast)
def loop(self):
'''
The actual main loop machinery.
def loop(self):
'''
The actual main loop machinery.
Maintain a ``running`` flag, pump the PyGame event queue and
handle the events (dispatching to the display), tick the clock.
Maintain a ``running`` flag, pump the PyGame event queue and
handle the events (dispatching to the display), tick the clock.
When the loop is exited (by clicking the window close button or
pressing the ``escape`` key) it broadcasts a ``ShutdownMessage``.
'''
self.running = True
while self.running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
self.running = False
elif event.type in self.tasks:
self.run_task(event.type)
else:
self.display.dispatch_event(event)
pygame.display.update()
self.clock.tick(self.FRAME_RATE)
self.display.broadcast(ShutdownMessage(self))
When the loop is exited (by clicking the window close button or
pressing the ``escape`` key) it broadcasts a ``ShutdownMessage``.
'''
self.running = True
while self.running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
self.running = False
elif event.type in self.tasks:
self.run_task(event.type)
else:
self.display.dispatch_event(event)
pygame.display.update()
self.clock.tick(self.FRAME_RATE)
self.display.broadcast(ShutdownMessage(self))

View File

@ -3,17 +3,17 @@ import sys, traceback
# To enable "hot" reloading in the IDLE shell.
for name in 'core main display viewer text_viewer stack_viewer persist_task'.split():
try:
del sys.modules[name]
except KeyError:
pass
try:
del sys.modules[name]
except KeyError:
pass
from . import main
try:
A = A # (screen, clock, pt), three things that we DON'T want to recreate
# each time we restart main().
A = A # (screen, clock, pt), three things that we DON'T want to recreate
# each time we restart main().
except NameError:
A = main.init()
A = main.init()
d = main.main(*A)

View File

@ -38,473 +38,473 @@ from sys import stderr
from traceback import format_exc
import pygame
from .core import (
open_viewer_on_string,
GREY,
MOUSE_EVENTS,
)
open_viewer_on_string,
GREY,
MOUSE_EVENTS,
)
from .viewer import Viewer
from joy.vui import text_viewer
class Display(object):
'''
Manage tracks and viewers on a screen (Pygame surface.)
'''
Manage tracks and viewers on a screen (Pygame surface.)
The size and number of tracks are defined by passing in at least two
ratios, e.g. Display(screen, 1, 4, 4) would create three tracks, one
small one on the left and two larger ones of the same size, each four
times wider than the left one.
The size and number of tracks are defined by passing in at least two
ratios, e.g. Display(screen, 1, 4, 4) would create three tracks, one
small one on the left and two larger ones of the same size, each four
times wider than the left one.
All tracks take up the whole height of the display screen. Tracks
manage zero or more Viewers. When you "grow" a viewer a new track is
created that overlays or hides one or two existing tracks, and when
the last viewer in an overlay track is closed the track closes too
and reveals the hidden tracks (and their viewers, if any.)
All tracks take up the whole height of the display screen. Tracks
manage zero or more Viewers. When you "grow" a viewer a new track is
created that overlays or hides one or two existing tracks, and when
the last viewer in an overlay track is closed the track closes too
and reveals the hidden tracks (and their viewers, if any.)
In order to facilitate command underlining while mouse dragging the
lookup parameter must be a function that accepts a string and returns
a Boolean indicating whether that string is a valid Joy function name.
Typically you pass in the __contains__ method of the Joy dict. This
is a case of breaking "loose coupling" to gain efficiency, as otherwise
we would have to e.g. send some sort of lookup message to the
World context object, going through the whole Display.broadcast()
machinery, etc. Not something you want to do on each MOUSEMOTION
event.
'''
In order to facilitate command underlining while mouse dragging the
lookup parameter must be a function that accepts a string and returns
a Boolean indicating whether that string is a valid Joy function name.
Typically you pass in the __contains__ method of the Joy dict. This
is a case of breaking "loose coupling" to gain efficiency, as otherwise
we would have to e.g. send some sort of lookup message to the
World context object, going through the whole Display.broadcast()
machinery, etc. Not something you want to do on each MOUSEMOTION
event.
'''
def __init__(self, screen, lookup, *track_ratios):
self.screen = screen
self.w, self.h = screen.get_width(), screen.get_height()
self.lookup = lookup
self.focused_viewer = None
self.tracks = [] # (x, track)
self.handlers = [] # Non-viewers that should receive messages.
# Create the tracks.
if not track_ratios: track_ratios = 1, 4
x, total = 0, sum(track_ratios)
for ratio in track_ratios[:-1]:
track_width = old_div(self.w * ratio, total)
assert track_width >= 10 # minimum width 10 pixels
self._open_track(x, track_width)
x += track_width
self._open_track(x, self.w - x)
def __init__(self, screen, lookup, *track_ratios):
self.screen = screen
self.w, self.h = screen.get_width(), screen.get_height()
self.lookup = lookup
self.focused_viewer = None
self.tracks = [] # (x, track)
self.handlers = [] # Non-viewers that should receive messages.
# Create the tracks.
if not track_ratios: track_ratios = 1, 4
x, total = 0, sum(track_ratios)
for ratio in track_ratios[:-1]:
track_width = old_div(self.w * ratio, total)
assert track_width >= 10 # minimum width 10 pixels
self._open_track(x, track_width)
x += track_width
self._open_track(x, self.w - x)
def _open_track(self, x, w):
'''Helper function to create the pygame surface and Track.'''
track_surface = self.screen.subsurface((x, 0, w, self.h))
self.tracks.append((x, Track(track_surface)))
def _open_track(self, x, w):
'''Helper function to create the pygame surface and Track.'''
track_surface = self.screen.subsurface((x, 0, w, self.h))
self.tracks.append((x, Track(track_surface)))
def open_viewer(self, x, y, class_):
'''
Open a viewer of class_ at the x, y location on the display,
return the viewer.
'''
track = self._track_at(x)[0]
V = track.open_viewer(y, class_)
V.focus(self)
return V
def open_viewer(self, x, y, class_):
'''
Open a viewer of class_ at the x, y location on the display,
return the viewer.
'''
track = self._track_at(x)[0]
V = track.open_viewer(y, class_)
V.focus(self)
return V
def close_viewer(self, viewer):
'''Close the viewer.'''
for x, track in self.tracks:
if track.close_viewer(viewer):
if not track.viewers and track.hiding:
i = self.tracks.index((x, track))
self.tracks[i:i + 1] = track.hiding
assert sorted(self.tracks) == self.tracks
for _, exposed_track in track.hiding:
exposed_track.redraw()
if viewer is self.focused_viewer:
self.focused_viewer = None
break
def close_viewer(self, viewer):
'''Close the viewer.'''
for x, track in self.tracks:
if track.close_viewer(viewer):
if not track.viewers and track.hiding:
i = self.tracks.index((x, track))
self.tracks[i:i + 1] = track.hiding
assert sorted(self.tracks) == self.tracks
for _, exposed_track in track.hiding:
exposed_track.redraw()
if viewer is self.focused_viewer:
self.focused_viewer = None
break
def change_viewer(self, viewer, y, relative=False):
'''
Adjust the top of the viewer to a new y within the boundaries of
its neighbors.
def change_viewer(self, viewer, y, relative=False):
'''
Adjust the top of the viewer to a new y within the boundaries of
its neighbors.
If relative is False new_y should be in screen coords, else new_y
should be relative to the top of the viewer.
'''
for _, track in self.tracks:
if track.change_viewer(viewer, y, relative):
break
If relative is False new_y should be in screen coords, else new_y
should be relative to the top of the viewer.
'''
for _, track in self.tracks:
if track.change_viewer(viewer, y, relative):
break
def grow_viewer(self, viewer):
'''
Cause the viewer to take up its whole track or, if it does
already, take up another track, up to the whole screen.
def grow_viewer(self, viewer):
'''
Cause the viewer to take up its whole track or, if it does
already, take up another track, up to the whole screen.
This is the inverse of closing a viewer. "Growing" a viewer
actually creates a new copy and a new track to hold it. The old
tracks and viewers are retained, and they get restored when the
covering track closes, which happens automatically when the last
viewer in the covering track is closed.
'''
for x, track in self.tracks:
for _, V in track.viewers:
if V is viewer:
return self._grow_viewer(x, track, viewer)
This is the inverse of closing a viewer. "Growing" a viewer
actually creates a new copy and a new track to hold it. The old
tracks and viewers are retained, and they get restored when the
covering track closes, which happens automatically when the last
viewer in the covering track is closed.
'''
for x, track in self.tracks:
for _, V in track.viewers:
if V is viewer:
return self._grow_viewer(x, track, viewer)
def _grow_viewer(self, x, track, viewer):
'''Helper function to "grow" a viewer.'''
new_viewer = None
def _grow_viewer(self, x, track, viewer):
'''Helper function to "grow" a viewer.'''
new_viewer = None
if viewer.h < self.h:
# replace the track with a new track that contains
# a copy of the viewer at full height.
new_track = Track(track.surface) # Reuse it, why not?
new_viewer = copy(viewer)
new_track._grow_by(new_viewer, 0, self.h - viewer.h)
new_track.viewers.append((0, new_viewer))
new_track.hiding = [(x, track)]
self.tracks[self.tracks.index((x, track))] = x, new_track
if viewer.h < self.h:
# replace the track with a new track that contains
# a copy of the viewer at full height.
new_track = Track(track.surface) # Reuse it, why not?
new_viewer = copy(viewer)
new_track._grow_by(new_viewer, 0, self.h - viewer.h)
new_track.viewers.append((0, new_viewer))
new_track.hiding = [(x, track)]
self.tracks[self.tracks.index((x, track))] = x, new_track
elif viewer.w < self.w:
# replace two tracks
i = self.tracks.index((x, track))
try: # prefer the one on the right
xx, xtrack = self.tracks[i + 1]
except IndexError:
i -= 1 # okay, the one on the left
xx, xtrack = self.tracks[i]
hiding = [(xx, xtrack), (x, track)]
else:
hiding = [(x, track), (xx, xtrack)]
# We know there has to be at least one other track because it
# there weren't then that implies that the one track takes up
# the whole display screen (the only way you can get just one
# track is by growing a viewer to cover the whole screen.)
# Ergo, viewer.w == self.w, so this branch doesn't run.
new_x = min(x, xx)
new_w = track.w + xtrack.w
r = new_x, 0, new_w, self.h
new_track = Track(self.screen.subsurface(r))
new_viewer = copy(viewer)
r = 0, 0, new_w, self.h
new_viewer.resurface(new_track.surface.subsurface(r))
new_track.viewers.append((0, new_viewer))
new_track.hiding = hiding
self.tracks[i:i + 2] = [(new_x, new_track)]
new_viewer.draw()
elif viewer.w < self.w:
# replace two tracks
i = self.tracks.index((x, track))
try: # prefer the one on the right
xx, xtrack = self.tracks[i + 1]
except IndexError:
i -= 1 # okay, the one on the left
xx, xtrack = self.tracks[i]
hiding = [(xx, xtrack), (x, track)]
else:
hiding = [(x, track), (xx, xtrack)]
# We know there has to be at least one other track because it
# there weren't then that implies that the one track takes up
# the whole display screen (the only way you can get just one
# track is by growing a viewer to cover the whole screen.)
# Ergo, viewer.w == self.w, so this branch doesn't run.
new_x = min(x, xx)
new_w = track.w + xtrack.w
r = new_x, 0, new_w, self.h
new_track = Track(self.screen.subsurface(r))
new_viewer = copy(viewer)
r = 0, 0, new_w, self.h
new_viewer.resurface(new_track.surface.subsurface(r))
new_track.viewers.append((0, new_viewer))
new_track.hiding = hiding
self.tracks[i:i + 2] = [(new_x, new_track)]
new_viewer.draw()
return new_viewer
return new_viewer
def _move_viewer(self, to, rel_y, viewer, _x, y):
'''
Helper function to move (really copy) a viewer to a new location.
'''
h = to.split(rel_y)
new_viewer = copy(viewer)
if not isinstance(to, Track):
to = next(T for _, T in self.tracks
for _, V in T.viewers
if V is to)
new_viewer.resurface(to.surface.subsurface((0, y, to.w, h)))
to.viewers.append((y, new_viewer))
to.viewers.sort() # bisect.insort() would be overkill here.
new_viewer.draw()
self.close_viewer(viewer)
def _move_viewer(self, to, rel_y, viewer, _x, y):
'''
Helper function to move (really copy) a viewer to a new location.
'''
h = to.split(rel_y)
new_viewer = copy(viewer)
if not isinstance(to, Track):
to = next(T for _, T in self.tracks
for _, V in T.viewers
if V is to)
new_viewer.resurface(to.surface.subsurface((0, y, to.w, h)))
to.viewers.append((y, new_viewer))
to.viewers.sort() # bisect.insort() would be overkill here.
new_viewer.draw()
self.close_viewer(viewer)
def _track_at(self, x):
'''
Return the track at x along with the track-relative x coordinate,
raise ValueError if x is off-screen.
'''
for track_x, track in self.tracks:
if x < track_x + track.w:
return track, x - track_x
raise ValueError('x outside display: %r' % (x,))
def _track_at(self, x):
'''
Return the track at x along with the track-relative x coordinate,
raise ValueError if x is off-screen.
'''
for track_x, track in self.tracks:
if x < track_x + track.w:
return track, x - track_x
raise ValueError('x outside display: %r' % (x,))
def at(self, x, y):
'''
Return the viewer (which can be a Track) at the x, y location,
along with the relative-to-viewer-surface x and y coordinates.
If there is no viewer at the location the Track will be returned
instead.
'''
track, x = self._track_at(x)
viewer, y = track.viewer_at(y)
return viewer, x, y
def at(self, x, y):
'''
Return the viewer (which can be a Track) at the x, y location,
along with the relative-to-viewer-surface x and y coordinates.
If there is no viewer at the location the Track will be returned
instead.
'''
track, x = self._track_at(x)
viewer, y = track.viewer_at(y)
return viewer, x, y
def iter_viewers(self):
'''
Iterate through all viewers yielding (viewer, x, y) three-tuples.
The x and y coordinates are screen pixels of the top-left corner
of the viewer.
'''
for x, T in self.tracks:
for y, V in T.viewers:
yield V, x, y
def iter_viewers(self):
'''
Iterate through all viewers yielding (viewer, x, y) three-tuples.
The x and y coordinates are screen pixels of the top-left corner
of the viewer.
'''
for x, T in self.tracks:
for y, V in T.viewers:
yield V, x, y
def done_resizing(self):
'''
Helper method called directly by ``MenuViewer.mouse_up()`` to (hackily)
update the display when done resizing a viewer.
'''
for _, track in self.tracks: # This should be done by a Message?
if track.resizing_viewer:
track.resizing_viewer.draw()
track.resizing_viewer = None
break
def done_resizing(self):
'''
Helper method called directly by ``MenuViewer.mouse_up()`` to (hackily)
update the display when done resizing a viewer.
'''
for _, track in self.tracks: # This should be done by a Message?
if track.resizing_viewer:
track.resizing_viewer.draw()
track.resizing_viewer = None
break
def broadcast(self, message):
'''
Broadcast a message to all viewers (except the sender) and all
registered handlers.
'''
for _, track in self.tracks:
track.broadcast(message)
for handler in self.handlers:
handler(message)
def broadcast(self, message):
'''
Broadcast a message to all viewers (except the sender) and all
registered handlers.
'''
for _, track in self.tracks:
track.broadcast(message)
for handler in self.handlers:
handler(message)
def redraw(self):
'''
Redraw all tracks (which will redraw all viewers.)
'''
for _, track in self.tracks:
track.redraw()
def redraw(self):
'''
Redraw all tracks (which will redraw all viewers.)
'''
for _, track in self.tracks:
track.redraw()
def focus(self, viewer):
'''
Set system focus to a given viewer (or no viewer if a track.)
'''
if isinstance(viewer, Track):
if self.focused_viewer: self.focused_viewer.unfocus()
self.focused_viewer = None
elif viewer is not self.focused_viewer:
if self.focused_viewer: self.focused_viewer.unfocus()
self.focused_viewer = viewer
viewer.focus(self)
def focus(self, viewer):
'''
Set system focus to a given viewer (or no viewer if a track.)
'''
if isinstance(viewer, Track):
if self.focused_viewer: self.focused_viewer.unfocus()
self.focused_viewer = None
elif viewer is not self.focused_viewer:
if self.focused_viewer: self.focused_viewer.unfocus()
self.focused_viewer = viewer
viewer.focus(self)
def dispatch_event(self, event):
'''
Display event handling.
'''
try:
if event.type in {pygame.KEYUP, pygame.KEYDOWN}:
self._keyboard_event(event)
elif event.type in MOUSE_EVENTS:
self._mouse_event(event)
else:
print((
'received event %s Use pygame.event.set_allowed().'
% pygame.event.event_name(event.type)
), file=stderr)
# Catch all exceptions and open a viewer.
except:
err = format_exc()
print(err, file=stderr) # To be safe just print it right away.
open_viewer_on_string(self, err, self.broadcast)
def dispatch_event(self, event):
'''
Display event handling.
'''
try:
if event.type in {pygame.KEYUP, pygame.KEYDOWN}:
self._keyboard_event(event)
elif event.type in MOUSE_EVENTS:
self._mouse_event(event)
else:
print((
'received event %s Use pygame.event.set_allowed().'
% pygame.event.event_name(event.type)
), file=stderr)
# Catch all exceptions and open a viewer.
except:
err = format_exc()
print(err, file=stderr) # To be safe just print it right away.
open_viewer_on_string(self, err, self.broadcast)
def _keyboard_event(self, event):
if event.key == pygame.K_PAUSE and event.type == pygame.KEYUP:
# At least on my keyboard the break/pause key sends K_PAUSE.
# The main use of this is to open a TextViewer if you
# accidentally close all the viewers, so you can recover.
raise KeyboardInterrupt('break')
if not self.focused_viewer:
return
if event.type == pygame.KEYUP:
self.focused_viewer.key_up(self, event.key, event.mod)
elif event.type == pygame.KEYDOWN:
self.focused_viewer.key_down(
self, event.unicode, event.key, event.mod)
# This is not UnicodeType. TODO does this need to be fixed?
# self, event.str, event.key, event.mod)
def _keyboard_event(self, event):
if event.key == pygame.K_PAUSE and event.type == pygame.KEYUP:
# At least on my keyboard the break/pause key sends K_PAUSE.
# The main use of this is to open a TextViewer if you
# accidentally close all the viewers, so you can recover.
raise KeyboardInterrupt('break')
if not self.focused_viewer:
return
if event.type == pygame.KEYUP:
self.focused_viewer.key_up(self, event.key, event.mod)
elif event.type == pygame.KEYDOWN:
self.focused_viewer.key_down(
self, event.unicode, event.key, event.mod)
# This is not UnicodeType. TODO does this need to be fixed?
# self, event.str, event.key, event.mod)
def _mouse_event(self, event):
V, x, y = self.at(*event.pos)
def _mouse_event(self, event):
V, x, y = self.at(*event.pos)
if event.type == pygame.MOUSEMOTION:
if not isinstance(V, Track):
V.mouse_motion(self, x, y, *(event.rel + event.buttons))
if event.type == pygame.MOUSEMOTION:
if not isinstance(V, Track):
V.mouse_motion(self, x, y, *(event.rel + event.buttons))
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
self.focus(V)
V.mouse_down(self, x, y, event.button)
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
self.focus(V)
V.mouse_down(self, x, y, event.button)
else:
assert event.type == pygame.MOUSEBUTTONUP
else:
assert event.type == pygame.MOUSEBUTTONUP
# Check for moving viewer.
if (event.button == 2
and self.focused_viewer
and V is not self.focused_viewer
and V.MINIMUM_HEIGHT < y < V.h - self.focused_viewer.MINIMUM_HEIGHT
):
self._move_viewer(V, y, self.focused_viewer, *event.pos)
# Check for moving viewer.
if (event.button == 2
and self.focused_viewer
and V is not self.focused_viewer
and V.MINIMUM_HEIGHT < y < V.h - self.focused_viewer.MINIMUM_HEIGHT
):
self._move_viewer(V, y, self.focused_viewer, *event.pos)
else:
V.mouse_up(self, x, y, event.button)
else:
V.mouse_up(self, x, y, event.button)
def init_text(self, pt, x, y, filename):
'''
Open and return a ``TextViewer`` on a given file (which must be present
in the ``JOYHOME`` directory.)
'''
viewer = self.open_viewer(x, y, text_viewer.TextViewer)
viewer.content_id, viewer.lines = pt.open(filename)
viewer.draw()
return viewer
def init_text(self, pt, x, y, filename):
'''
Open and return a ``TextViewer`` on a given file (which must be present
in the ``JOYHOME`` directory.)
'''
viewer = self.open_viewer(x, y, text_viewer.TextViewer)
viewer.content_id, viewer.lines = pt.open(filename)
viewer.draw()
return viewer
class Track(Viewer):
'''
Manage a vertical strip of the display, and the viewers on it.
'''
'''
Manage a vertical strip of the display, and the viewers on it.
'''
def __init__(self, surface):
Viewer.__init__(self, surface)
self.viewers = [] # (y, viewer)
self.hiding = None
self.resizing_viewer = None
self.draw()
def __init__(self, surface):
Viewer.__init__(self, surface)
self.viewers = [] # (y, viewer)
self.hiding = None
self.resizing_viewer = None
self.draw()
def split(self, y):
'''
Split the Track at the y coordinate and return the height
available for a new viewer. Tracks manage a vertical strip of
the display screen so they don't resize their surface when split.
'''
h = self.viewers[0][0] if self.viewers else self.h
assert h > y
return h - y
def split(self, y):
'''
Split the Track at the y coordinate and return the height
available for a new viewer. Tracks manage a vertical strip of
the display screen so they don't resize their surface when split.
'''
h = self.viewers[0][0] if self.viewers else self.h
assert h > y
return h - y
def draw(self, rect=None):
'''Draw the track onto its surface, clearing all content.
def draw(self, rect=None):
'''Draw the track onto its surface, clearing all content.
If rect is passed only draw to that area. This supports e.g.
closing a viewer that then exposes part of the track.
'''
self.surface.fill(GREY, rect=rect)
If rect is passed only draw to that area. This supports e.g.
closing a viewer that then exposes part of the track.
'''
self.surface.fill(GREY, rect=rect)
def viewer_at(self, y):
'''
Return the viewer at y along with the viewer-relative y coordinate,
if there's no viewer at y return this track and y.
'''
for viewer_y, viewer in self.viewers:
if viewer_y < y <= viewer_y + viewer.h:
return viewer, y - viewer_y
return self, y
def viewer_at(self, y):
'''
Return the viewer at y along with the viewer-relative y coordinate,
if there's no viewer at y return this track and y.
'''
for viewer_y, viewer in self.viewers:
if viewer_y < y <= viewer_y + viewer.h:
return viewer, y - viewer_y
return self, y
def open_viewer(self, y, class_):
'''Open and return a viewer of class at y.'''
# Todo: if y coincides with some other viewer's y replace it.
viewer, viewer_y = self.viewer_at(y)
h = viewer.split(viewer_y)
new_viewer = class_(self.surface.subsurface((0, y, self.w, h)))
new_viewer.draw()
self.viewers.append((y, new_viewer))
self.viewers.sort() # Could use bisect module but how many
# viewers will you ever have?
return new_viewer
def open_viewer(self, y, class_):
'''Open and return a viewer of class at y.'''
# Todo: if y coincides with some other viewer's y replace it.
viewer, viewer_y = self.viewer_at(y)
h = viewer.split(viewer_y)
new_viewer = class_(self.surface.subsurface((0, y, self.w, h)))
new_viewer.draw()
self.viewers.append((y, new_viewer))
self.viewers.sort() # Could use bisect module but how many
# viewers will you ever have?
return new_viewer
def close_viewer(self, viewer):
'''Close the viewer, reuse the freed space.'''
for y, V in self.viewers:
if V is viewer:
self._close_viewer(y, V)
return True
return False
def close_viewer(self, viewer):
'''Close the viewer, reuse the freed space.'''
for y, V in self.viewers:
if V is viewer:
self._close_viewer(y, V)
return True
return False
def _close_viewer(self, y, viewer):
'''Helper function to do the actual closing.'''
i = self.viewers.index((y, viewer))
del self.viewers[i]
if i: # The previous viewer gets the space.
previous_y, previous_viewer = self.viewers[i - 1]
self._grow_by(previous_viewer, previous_y, viewer.h)
else: # This track gets the space.
self.draw((0, y, self.w, viewer.surface.get_height()))
viewer.close()
def _close_viewer(self, y, viewer):
'''Helper function to do the actual closing.'''
i = self.viewers.index((y, viewer))
del self.viewers[i]
if i: # The previous viewer gets the space.
previous_y, previous_viewer = self.viewers[i - 1]
self._grow_by(previous_viewer, previous_y, viewer.h)
else: # This track gets the space.
self.draw((0, y, self.w, viewer.surface.get_height()))
viewer.close()
def _grow_by(self, viewer, y, h):
'''Grow a viewer (located at y) by height h.
def _grow_by(self, viewer, y, h):
'''Grow a viewer (located at y) by height h.
This might seem like it should be a method of the viewer, but
the viewer knows nothing of its own y location on the screen nor
the parent track's surface (to make a new subsurface) so it has
to be a method of the track, which has both.
'''
h = viewer.surface.get_height() + h
try:
surface = self.surface.subsurface((0, y, self.w, h))
except ValueError: # subsurface rectangle outside surface area
pass
else:
viewer.resurface(surface)
if h <= viewer.last_touch[1]: viewer.last_touch = 0, 0
viewer.draw()
This might seem like it should be a method of the viewer, but
the viewer knows nothing of its own y location on the screen nor
the parent track's surface (to make a new subsurface) so it has
to be a method of the track, which has both.
'''
h = viewer.surface.get_height() + h
try:
surface = self.surface.subsurface((0, y, self.w, h))
except ValueError: # subsurface rectangle outside surface area
pass
else:
viewer.resurface(surface)
if h <= viewer.last_touch[1]: viewer.last_touch = 0, 0
viewer.draw()
def change_viewer(self, viewer, new_y, relative=False):
'''
Adjust the top of the viewer to a new y within the boundaries of
its neighbors.
def change_viewer(self, viewer, new_y, relative=False):
'''
Adjust the top of the viewer to a new y within the boundaries of
its neighbors.
If relative is False new_y should be in screen coords, else new_y
should be relative to the top of the viewer.
'''
for old_y, V in self.viewers:
if V is viewer:
if relative: new_y += old_y
if new_y != old_y: self._change_viewer(new_y, old_y, V)
return True
return False
If relative is False new_y should be in screen coords, else new_y
should be relative to the top of the viewer.
'''
for old_y, V in self.viewers:
if V is viewer:
if relative: new_y += old_y
if new_y != old_y: self._change_viewer(new_y, old_y, V)
return True
return False
def _change_viewer(self, new_y, old_y, viewer):
new_y = max(0, min(self.h, new_y))
i = self.viewers.index((old_y, viewer))
if new_y < old_y: # Enlarge self, shrink upper neighbor.
if i:
previous_y, previous_viewer = self.viewers[i - 1]
if new_y - previous_y < self.MINIMUM_HEIGHT:
return
previous_viewer.resizing = 1
h = previous_viewer.split(new_y - previous_y)
previous_viewer.resizing = 0
self.resizing_viewer = previous_viewer
else:
h = old_y - new_y
self._grow_by(viewer, new_y, h)
def _change_viewer(self, new_y, old_y, viewer):
new_y = max(0, min(self.h, new_y))
i = self.viewers.index((old_y, viewer))
if new_y < old_y: # Enlarge self, shrink upper neighbor.
if i:
previous_y, previous_viewer = self.viewers[i - 1]
if new_y - previous_y < self.MINIMUM_HEIGHT:
return
previous_viewer.resizing = 1
h = previous_viewer.split(new_y - previous_y)
previous_viewer.resizing = 0
self.resizing_viewer = previous_viewer
else:
h = old_y - new_y
self._grow_by(viewer, new_y, h)
else: # Shink self, enlarge upper neighbor.
# Enforce invariant.
try:
h, _ = self.viewers[i + 1]
except IndexError: # No next viewer.
h = self.h
if h - new_y < self.MINIMUM_HEIGHT:
return
else: # Shink self, enlarge upper neighbor.
# Enforce invariant.
try:
h, _ = self.viewers[i + 1]
except IndexError: # No next viewer.
h = self.h
if h - new_y < self.MINIMUM_HEIGHT:
return
# Change the viewer and adjust the upper viewer or track.
h = new_y - old_y
self._grow_by(viewer, new_y, -h) # grow by negative height!
if i:
previous_y, previous_viewer = self.viewers[i - 1]
previous_viewer.resizing = 1
self._grow_by(previous_viewer, previous_y, h)
previous_viewer.resizing = 0
self.resizing_viewer = previous_viewer
else:
self.draw((0, old_y, self.w, h))
# Change the viewer and adjust the upper viewer or track.
h = new_y - old_y
self._grow_by(viewer, new_y, -h) # grow by negative height!
if i:
previous_y, previous_viewer = self.viewers[i - 1]
previous_viewer.resizing = 1
self._grow_by(previous_viewer, previous_y, h)
previous_viewer.resizing = 0
self.resizing_viewer = previous_viewer
else:
self.draw((0, old_y, self.w, h))
self.viewers[i] = new_y, viewer
# self.viewers.sort() # Not necessary, invariant holds.
assert sorted(self.viewers) == self.viewers
self.viewers[i] = new_y, viewer
# self.viewers.sort() # Not necessary, invariant holds.
assert sorted(self.viewers) == self.viewers
def broadcast(self, message):
'''
Broadcast a message to all viewers on this track (except the sender.)
'''
for _, viewer in self.viewers:
if viewer is not message.sender:
viewer.handle(message)
def broadcast(self, message):
'''
Broadcast a message to all viewers on this track (except the sender.)
'''
for _, viewer in self.viewers:
if viewer is not message.sender:
viewer.handle(message)
def redraw(self):
'''Redraw the track and all of its viewers.'''
self.draw()
for _, viewer in self.viewers:
viewer.draw()
def redraw(self):
'''Redraw the track and all of its viewers.'''
self.draw()
for _, viewer in self.viewers:
viewer.draw()

View File

@ -25,9 +25,9 @@ import base64, zlib
def create(fn='Iosevka12.BMP'):
with open(fn, 'rb') as f:
data = f.read()
return base64.encodestring(zlib.compress(data))
with open(fn, 'rb') as f:
data = f.read()
return base64.encodestring(zlib.compress(data))
data = StringIO(zlib.decompress(base64.decodestring('''\
@ -186,4 +186,4 @@ lnalXc/9SsNb2vUirzS8pV0v8gJv/w/2vRht''')))
if __name__ == '__main__':
print(create())
print(create())

View File

@ -24,8 +24,8 @@ JOY_HOME directory.
These contents are kept in this Python module as a base64-encoded zip
file, so you can just do, e.g.:
import init_joy_home
init_joy_home.initialize(JOY_HOME)
import init_joy_home
init_joy_home.initialize(JOY_HOME)
'''
from __future__ import print_function
@ -35,17 +35,17 @@ import base64, os, io, zipfile
def initialize(joy_home):
Z.extractall(joy_home)
Z.extractall(joy_home)
def create_data(from_dir='./default_joy_home'):
f = io.StringIO()
z = zipfile.ZipFile(f, mode='w')
for fn in os.listdir(from_dir):
from_fn = os.path.join(from_dir, fn)
z.write(from_fn, fn)
z.close()
return base64.encodestring(f.getvalue())
f = io.StringIO()
z = zipfile.ZipFile(f, mode='w')
for fn in os.listdir(from_dir):
from_fn = os.path.join(from_dir, fn)
z.write(from_fn, fn)
z.close()
return base64.encodestring(f.getvalue())
Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\
@ -275,4 +275,4 @@ c3RhY2sucGlja2xlUEsFBgAAAAAGAAYAUwEAACcwAAAAAA==''')))
if __name__ == '__main__':
print(create_data())
print(create_data())

View File

@ -41,139 +41,139 @@ FULLSCREEN = '-f' in sys.argv
JOY_HOME = os.environ.get('JOY_HOME')
if JOY_HOME is None:
JOY_HOME = os.path.expanduser('~/.thun')
if not os.path.isabs(JOY_HOME):
raise ValueError('what directory?')
JOY_HOME = os.path.expanduser('~/.thun')
if not os.path.isabs(JOY_HOME):
raise ValueError('what directory?')
def load_definitions(pt, dictionary):
'''Load definitions from ``definitions.txt``.'''
lines = pt.open('definitions.txt')[1]
for line in lines:
if '==' in line:
DefinitionWrapper.add_def(line, dictionary)
'''Load definitions from ``definitions.txt``.'''
lines = pt.open('definitions.txt')[1]
for line in lines:
if '==' in line:
DefinitionWrapper.add_def(line, dictionary)
def load_primitives(home, name_space):
'''Load primitives from ``library.py``.'''
fn = os.path.join(home, 'library.py')
if os.path.exists(fn):
execfile(fn, name_space)
'''Load primitives from ``library.py``.'''
fn = os.path.join(home, 'library.py')
if os.path.exists(fn):
execfile(fn, name_space)
def init():
'''
Initialize the system.
* Init PyGame
* Create main window
* Start the PyGame clock
* Set the event mask
* Create the PersistTask
'''
Initialize the system.
* Init PyGame
* Create main window
* Start the PyGame clock
* Set the event mask
* Create the PersistTask
'''
print('Initializing Pygame...')
pygame.init()
print('Creating window...')
if FULLSCREEN:
screen = pygame.display.set_mode()
else:
screen = pygame.display.set_mode((1024, 768))
clock = pygame.time.Clock()
pygame.event.set_allowed(None)
pygame.event.set_allowed(core.ALLOWED_EVENTS)
pt = persist_task.PersistTask(JOY_HOME)
return screen, clock, pt
'''
print('Initializing Pygame...')
pygame.init()
print('Creating window...')
if FULLSCREEN:
screen = pygame.display.set_mode()
else:
screen = pygame.display.set_mode((1024, 768))
clock = pygame.time.Clock()
pygame.event.set_allowed(None)
pygame.event.set_allowed(core.ALLOWED_EVENTS)
pt = persist_task.PersistTask(JOY_HOME)
return screen, clock, pt
def init_context(screen, clock, pt):
'''
More initialization
'''
More initialization
* Create the Joy dictionary
* Create the Display
* Open the log, menu, and scratch text viewers, and the stack pickle
* Start the main loop
* Create the World object
* Register PersistTask and World message handlers with the Display
* Load user function definitions.
* Create the Joy dictionary
* Create the Display
* Open the log, menu, and scratch text viewers, and the stack pickle
* Start the main loop
* Create the World object
* Register PersistTask and World message handlers with the Display
* Load user function definitions.
'''
D = initialize()
d = display.Display(
screen,
D.__contains__,
*((144 - 89, 144, 89) if FULLSCREEN else (89, 144))
)
log = d.init_text(pt, 0, 0, 'log.txt')
tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.txt')
t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt')
loop = core.TheLoop(d, clock)
stack_id, stack_holder = pt.open('stack.pickle')
world = core.World(stack_id, stack_holder, D, d.broadcast, log)
loop.install_task(pt.task_run, 10000) # save files every ten seconds
d.handlers.append(pt.handle)
d.handlers.append(world.handle)
load_definitions(pt, D)
return locals()
'''
D = initialize()
d = display.Display(
screen,
D.__contains__,
*((144 - 89, 144, 89) if FULLSCREEN else (89, 144))
)
log = d.init_text(pt, 0, 0, 'log.txt')
tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.txt')
t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt')
loop = core.TheLoop(d, clock)
stack_id, stack_holder = pt.open('stack.pickle')
world = core.World(stack_id, stack_holder, D, d.broadcast, log)
loop.install_task(pt.task_run, 10000) # save files every ten seconds
d.handlers.append(pt.handle)
d.handlers.append(world.handle)
load_definitions(pt, D)
return locals()
def error_guard(loop, n=10):
'''
Run a loop function, retry for ``n`` exceptions.
Prints tracebacks on ``sys.stderr``.
'''
error_count = 0
while error_count < n:
try:
loop()
break
except:
traceback.print_exc(file=sys.stderr)
error_count += 1
'''
Run a loop function, retry for ``n`` exceptions.
Prints tracebacks on ``sys.stderr``.
'''
error_count = 0
while error_count < n:
try:
loop()
break
except:
traceback.print_exc(file=sys.stderr)
error_count += 1
class FileFaker(object):
'''Pretends to be a file object but writes to log instead.'''
'''Pretends to be a file object but writes to log instead.'''
def __init__(self, log):
self.log = log
def __init__(self, log):
self.log = log
def write(self, text):
'''Write text to log.'''
self.log.append(text)
def write(self, text):
'''Write text to log.'''
self.log.append(text)
def flush(self):
pass
def flush(self):
pass
def main(screen, clock, pt):
'''
Main function.
'''
Main function.
* Call ``init_context()``
* Load primitives
* Create an ``evaluate`` function that lets you just eval some Python code
* Redirect ``stdout`` to the log using a ``FileFaker`` object, and...
* Start the main loop.
'''
name_space = init_context(screen, clock, pt)
load_primitives(pt.home, name_space.copy())
* Call ``init_context()``
* Load primitives
* Create an ``evaluate`` function that lets you just eval some Python code
* Redirect ``stdout`` to the log using a ``FileFaker`` object, and...
* Start the main loop.
'''
name_space = init_context(screen, clock, pt)
load_primitives(pt.home, name_space.copy())
@SimpleFunctionWrapper
def evaluate(stack):
'''Evaluate the Python code text on the top of the stack.'''
code, stack = stack
exec(code, name_space.copy())
return stack
@SimpleFunctionWrapper
def evaluate(stack):
'''Evaluate the Python code text on the top of the stack.'''
code, stack = stack
exec(code, name_space.copy())
return stack
name_space['D']['evaluate'] = evaluate
name_space['D']['evaluate'] = evaluate
sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout
try:
error_guard(name_space['loop'].loop)
finally:
sys.stdout = old_stdout
sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout
try:
error_guard(name_space['loop'].loop)
finally:
sys.stdout = old_stdout
return name_space['d']
return name_space['d']

View File

@ -36,239 +36,239 @@ from joy.vui import core, init_joy_home
def open_repo(repo_dir=None, initialize=False):
'''
Open, or create, and return a Dulwich git repo object for the given
directory. If the dir path doesn't exist it will be created. If it
does exist but isn't a repo the result depends on the ``initialize``
argument. If it is ``False`` (the default) a ``NotGitRepository``
exception is raised, otherwise ``git init`` is effected in the dir.
'''
if not os.path.exists(repo_dir):
os.makedirs(repo_dir, 0o700)
return init_repo(repo_dir)
try:
return Repo(repo_dir)
except NotGitRepository:
if initialize:
return init_repo(repo_dir)
raise
'''
Open, or create, and return a Dulwich git repo object for the given
directory. If the dir path doesn't exist it will be created. If it
does exist but isn't a repo the result depends on the ``initialize``
argument. If it is ``False`` (the default) a ``NotGitRepository``
exception is raised, otherwise ``git init`` is effected in the dir.
'''
if not os.path.exists(repo_dir):
os.makedirs(repo_dir, 0o700)
return init_repo(repo_dir)
try:
return Repo(repo_dir)
except NotGitRepository:
if initialize:
return init_repo(repo_dir)
raise
def init_repo(repo_dir):
'''
Initialize a git repository in the directory. Stage and commit all
files (toplevel, not those in subdirectories if any) in the dir.
'''
repo = Repo.init(repo_dir)
init_joy_home.initialize(repo_dir)
repo.stage([
fn
for fn in os.listdir(repo_dir)
if os.path.isfile(os.path.join(repo_dir, fn))
])
repo.do_commit('Initial commit.', committer=core.COMMITTER)
return repo
'''
Initialize a git repository in the directory. Stage and commit all
files (toplevel, not those in subdirectories if any) in the dir.
'''
repo = Repo.init(repo_dir)
init_joy_home.initialize(repo_dir)
repo.stage([
fn
for fn in os.listdir(repo_dir)
if os.path.isfile(os.path.join(repo_dir, fn))
])
repo.do_commit('Initial commit.', committer=core.COMMITTER)
return repo
def make_repo_relative_path_maker(repo):
'''
Helper function to return a function that returns a path given a path,
that's relative to the repository.
'''
c = repo.controldir()
def repo_relative_path(path):
return os.path.relpath(path, os.path.commonprefix((c, path)))
return repo_relative_path
'''
Helper function to return a function that returns a path given a path,
that's relative to the repository.
'''
c = repo.controldir()
def repo_relative_path(path):
return os.path.relpath(path, os.path.commonprefix((c, path)))
return repo_relative_path
class Resource(object):
'''
Handle the content of a text files as a list of lines, deal with
saving it and staging the changes to a repo.
'''
'''
Handle the content of a text files as a list of lines, deal with
saving it and staging the changes to a repo.
'''
def __init__(self, filename, repo_relative_filename, thing=None):
self.filename = filename
self.repo_relative_filename = repo_relative_filename
self.thing = thing or self._from_file(open(filename))
def __init__(self, filename, repo_relative_filename, thing=None):
self.filename = filename
self.repo_relative_filename = repo_relative_filename
self.thing = thing or self._from_file(open(filename))
def _from_file(self, f):
return f.read().splitlines()
def _from_file(self, f):
return f.read().splitlines()
def _to_file(self, f):
for line in self.thing:
print(line, file=f)
def _to_file(self, f):
for line in self.thing:
print(line, file=f)
def persist(self, repo):
'''
Save the lines to the file and stage the file in the repo.
'''
with open(self.filename, 'w') as f:
os.chmod(self.filename, 0o600)
self._to_file(f)
f.flush()
os.fsync(f.fileno())
# For goodness's sake, write it to the disk already!
repo.stage([self.repo_relative_filename])
def persist(self, repo):
'''
Save the lines to the file and stage the file in the repo.
'''
with open(self.filename, 'w') as f:
os.chmod(self.filename, 0o600)
self._to_file(f)
f.flush()
os.fsync(f.fileno())
# For goodness's sake, write it to the disk already!
repo.stage([self.repo_relative_filename])
class PickledResource(Resource):
'''
A ``Resource`` subclass that uses ``pickle`` on its file/thing.
'''
'''
A ``Resource`` subclass that uses ``pickle`` on its file/thing.
'''
def _from_file(self, f):
return [pickle.load(f)]
def _from_file(self, f):
return [pickle.load(f)]
def _to_file(self, f):
pickle.dump(self.thing[0], f, protocol=2)
def _to_file(self, f):
pickle.dump(self.thing[0], f, protocol=2)
class PersistTask(object):
'''
This class deals with saving changes to the git repo.
'''
'''
This class deals with saving changes to the git repo.
'''
LIMIT = 10
MAX_SAVE = 10
LIMIT = 10
MAX_SAVE = 10
def __init__(self, home):
self.home = home
self.repo = open_repo(home)
self._r = make_repo_relative_path_maker(self.repo)
self.counter = Counter()
self.store = {}
def __init__(self, home):
self.home = home
self.repo = open_repo(home)
self._r = make_repo_relative_path_maker(self.repo)
self.counter = Counter()
self.store = {}
def open(self, name):
'''
Look up the named file in home and return its content_id and data.
'''
fn = os.path.join(self.home, name)
content_id = name # hash(fn)
try:
resource = self.store[content_id]
except KeyError:
R = PickledResource if name.endswith('.pickle') else Resource
resource = self.store[content_id] = R(fn, self._r(fn))
return content_id, resource.thing
def open(self, name):
'''
Look up the named file in home and return its content_id and data.
'''
fn = os.path.join(self.home, name)
content_id = name # hash(fn)
try:
resource = self.store[content_id]
except KeyError:
R = PickledResource if name.endswith('.pickle') else Resource
resource = self.store[content_id] = R(fn, self._r(fn))
return content_id, resource.thing
def handle(self, message):
'''
Handle messages, dispatch to ``handle_FOO()`` methods.
'''
if isinstance(message, core.OpenMessage):
self.handle_open(message)
elif isinstance(message, core.ModifyMessage):
self.handle_modify(message)
elif isinstance(message, core.PersistMessage):
self.handle_persist(message)
elif isinstance(message, core.ShutdownMessage):
for content_id in self.counter:
self.store[content_id].persist(self.repo)
self.commit('shutdown')
def handle(self, message):
'''
Handle messages, dispatch to ``handle_FOO()`` methods.
'''
if isinstance(message, core.OpenMessage):
self.handle_open(message)
elif isinstance(message, core.ModifyMessage):
self.handle_modify(message)
elif isinstance(message, core.PersistMessage):
self.handle_persist(message)
elif isinstance(message, core.ShutdownMessage):
for content_id in self.counter:
self.store[content_id].persist(self.repo)
self.commit('shutdown')
def handle_open(self, message):
'''
Foo.
'''
try:
message.content_id, message.thing = self.open(message.name)
except:
message.traceback = traceback.format_exc()
message.status = core.ERROR
else:
message.status = core.SUCCESS
def handle_open(self, message):
'''
Foo.
'''
try:
message.content_id, message.thing = self.open(message.name)
except:
message.traceback = traceback.format_exc()
message.status = core.ERROR
else:
message.status = core.SUCCESS
def handle_modify(self, message):
'''
Foo.
'''
try:
content_id = message.details['content_id']
except KeyError:
return
if not content_id:
return
self.counter[content_id] += 1
if self.counter[content_id] > self.LIMIT:
self.persist(content_id)
self.commit('due to activity')
def handle_modify(self, message):
'''
Foo.
'''
try:
content_id = message.details['content_id']
except KeyError:
return
if not content_id:
return
self.counter[content_id] += 1
if self.counter[content_id] > self.LIMIT:
self.persist(content_id)
self.commit('due to activity')
def handle_persist(self, message):
'''
Foo.
'''
try:
resource = self.store[message.content_id]
except KeyError:
resource = self.handle_persist_new(message)
resource.persist(self.repo)
self.commit('by request from %r' % (message.sender,))
def handle_persist(self, message):
'''
Foo.
'''
try:
resource = self.store[message.content_id]
except KeyError:
resource = self.handle_persist_new(message)
resource.persist(self.repo)
self.commit('by request from %r' % (message.sender,))
def handle_persist_new(self, message):
'''
Foo.
'''
name = message.content_id
check_filename(name)
fn = os.path.join(self.home, name)
thing = message.details['thing']
R = PickledResource if name.endswith('.pickle') else Resource # !!! refactor!
resource = self.store[name] = R(fn, self._r(fn), thing)
return resource
def handle_persist_new(self, message):
'''
Foo.
'''
name = message.content_id
check_filename(name)
fn = os.path.join(self.home, name)
thing = message.details['thing']
R = PickledResource if name.endswith('.pickle') else Resource # !!! refactor!
resource = self.store[name] = R(fn, self._r(fn), thing)
return resource
def persist(self, content_id):
'''
Persist a resource.
'''
del self.counter[content_id]
self.store[content_id].persist(self.repo)
def persist(self, content_id):
'''
Persist a resource.
'''
del self.counter[content_id]
self.store[content_id].persist(self.repo)
def task_run(self):
'''
Stage any outstanding changes.
'''
if not self.counter:
return
for content_id, _ in self.counter.most_common(self.MAX_SAVE):
self.persist(content_id)
self.commit()
def task_run(self):
'''
Stage any outstanding changes.
'''
if not self.counter:
return
for content_id, _ in self.counter.most_common(self.MAX_SAVE):
self.persist(content_id)
self.commit()
def commit(self, message='auto-commit'):
'''
Commit.
'''
return self.repo.do_commit(message, committer=core.COMMITTER)
def commit(self, message='auto-commit'):
'''
Commit.
'''
return self.repo.do_commit(message, committer=core.COMMITTER)
def scan(self):
'''
Return a sorted list of all the files in the home dir.
'''
return sorted([
fn
for fn in os.listdir(self.home)
if os.path.isfile(os.path.join(self.home, fn))
])
def scan(self):
'''
Return a sorted list of all the files in the home dir.
'''
return sorted([
fn
for fn in os.listdir(self.home)
if os.path.isfile(os.path.join(self.home, fn))
])
def check_filename(name):
'''
Sanity checks for filename.
'''
# TODO: improve this...
if len(name) > 64:
raise ValueError('bad name %r' % (name,))
left, dot, right = name.partition('.')
if not left.isalnum() or dot and not right.isalnum():
raise ValueError('bad name %r' % (name,))
'''
Sanity checks for filename.
'''
# TODO: improve this...
if len(name) > 64:
raise ValueError('bad name %r' % (name,))
left, dot, right = name.partition('.')
if not left.isalnum() or dot and not right.isalnum():
raise ValueError('bad name %r' % (name,))
if __name__ == '__main__':
JOY_HOME = os.path.expanduser('~/.thun')
pt = PersistTask(JOY_HOME)
content_id, thing = pt.open('stack.pickle')
pt.persist(content_id)
print(pt.counter)
mm = core.ModifyMessage(None, None, content_id=content_id)
pt.handle(mm)
print(pt.counter)
JOY_HOME = os.path.expanduser('~/.thun')
pt = PersistTask(JOY_HOME)
content_id, thing = pt.open('stack.pickle')
pt.persist(content_id)
print(pt.counter)
mm = core.ModifyMessage(None, None, content_id=content_id)
pt.handle(mm)
print(pt.counter)

View File

@ -32,44 +32,44 @@ MAX_WIDTH = 64
def fsi(item):
'''Format Stack Item'''
if isinstance(item, tuple):
res = '[%s]' % expression_to_string(item)
elif isinstance(item, str):
res = '"%s"' % item
else:
res = str(item)
if len(res) > MAX_WIDTH:
return res[:MAX_WIDTH - 3] + '...'
return res
'''Format Stack Item'''
if isinstance(item, tuple):
res = '[%s]' % expression_to_string(item)
elif isinstance(item, str):
res = '"%s"' % item
else:
res = str(item)
if len(res) > MAX_WIDTH:
return res[:MAX_WIDTH - 3] + '...'
return res
class StackViewer(text_viewer.TextViewer):
def __init__(self, surface):
super(StackViewer, self).__init__(surface)
self.stack_holder = None
self.content_id = 'stack viewer'
def __init__(self, surface):
super(StackViewer, self).__init__(surface)
self.stack_holder = None
self.content_id = 'stack viewer'
def _attach(self, display):
if self.stack_holder:
return
om = core.OpenMessage(self, 'stack.pickle')
display.broadcast(om)
if om.status != core.SUCCESS:
raise RuntimeError('stack unavailable')
self.stack_holder = om.thing
def _attach(self, display):
if self.stack_holder:
return
om = core.OpenMessage(self, 'stack.pickle')
display.broadcast(om)
if om.status != core.SUCCESS:
raise RuntimeError('stack unavailable')
self.stack_holder = om.thing
def _update(self):
self.lines[:] = list(map(fsi, iter_stack(self.stack_holder[0]))) or ['']
def _update(self):
self.lines[:] = list(map(fsi, iter_stack(self.stack_holder[0]))) or ['']
def focus(self, display):
self._attach(display)
super(StackViewer, self).focus(display)
def focus(self, display):
self._attach(display)
super(StackViewer, self).focus(display)
def handle(self, message):
if (isinstance(message, core.ModifyMessage)
and message.subject is self.stack_holder
):
self._update()
self.draw_body()
def handle(self, message):
if (isinstance(message, core.ModifyMessage)
and message.subject is self.stack_holder
):
self._update()
self.draw_body()

File diff suppressed because it is too large Load Diff

View File

@ -32,208 +32,208 @@ from joy.vui.core import BACKGROUND, FOREGROUND
class Viewer(object):
'''
Base Viewer class
'''
'''
Base Viewer class
'''
MINIMUM_HEIGHT = 11
MINIMUM_HEIGHT = 11
def __init__(self, surface):
self.resurface(surface)
self.last_touch = 0, 0
def __init__(self, surface):
self.resurface(surface)
self.last_touch = 0, 0
def resurface(self, surface):
self.w, self.h = surface.get_width(), surface.get_height()
self.surface = surface
def resurface(self, surface):
self.w, self.h = surface.get_width(), surface.get_height()
self.surface = surface
def split(self, y):
'''
Split the viewer at the y coordinate (which is relative to the
viewer's surface and must be inside it somewhere) and return the
remaining height. The upper part of the viewer remains (and gets
redrawn on a new surface) and the lower space is now available
for e.g. a new viewer.
'''
assert y >= self.MINIMUM_HEIGHT
new_viewer_h = self.h - y
self.resurface(self.surface.subsurface((0, 0, self.w, y)))
if y <= self.last_touch[1]: self.last_touch = 0, 0
self.draw()
return new_viewer_h
def split(self, y):
'''
Split the viewer at the y coordinate (which is relative to the
viewer's surface and must be inside it somewhere) and return the
remaining height. The upper part of the viewer remains (and gets
redrawn on a new surface) and the lower space is now available
for e.g. a new viewer.
'''
assert y >= self.MINIMUM_HEIGHT
new_viewer_h = self.h - y
self.resurface(self.surface.subsurface((0, 0, self.w, y)))
if y <= self.last_touch[1]: self.last_touch = 0, 0
self.draw()
return new_viewer_h
def handle(self, message):
assert self is not message.sender
pass
def handle(self, message):
assert self is not message.sender
pass
def draw(self):
'''Draw the viewer onto its surface.'''
self.surface.fill(BACKGROUND)
x, y, h = self.w - 1, self.MINIMUM_HEIGHT, self.h - 1
# Right-hand side.
pygame.draw.line(self.surface, FOREGROUND, (x, 0), (x, h))
# Between header and body.
pygame.draw.line(self.surface, FOREGROUND, (0, y), (x, y))
# Bottom.
pygame.draw.line(self.surface, FOREGROUND, (0, h), (x, h))
def draw(self):
'''Draw the viewer onto its surface.'''
self.surface.fill(BACKGROUND)
x, y, h = self.w - 1, self.MINIMUM_HEIGHT, self.h - 1
# Right-hand side.
pygame.draw.line(self.surface, FOREGROUND, (x, 0), (x, h))
# Between header and body.
pygame.draw.line(self.surface, FOREGROUND, (0, y), (x, y))
# Bottom.
pygame.draw.line(self.surface, FOREGROUND, (0, h), (x, h))
def close(self):
'''Close the viewer and release any resources, etc...'''
def close(self):
'''Close the viewer and release any resources, etc...'''
def focus(self, display):
pass
def focus(self, display):
pass
def unfocus(self):
pass
def unfocus(self):
pass
# Event handling.
# Event handling.
def mouse_down(self, display, x, y, button):
self.last_touch = x, y
def mouse_down(self, display, x, y, button):
self.last_touch = x, y
def mouse_up(self, display, x, y, button):
pass
def mouse_up(self, display, x, y, button):
pass
def mouse_motion(self, display, x, y, dx, dy, button0, button1, button2):
pass
def mouse_motion(self, display, x, y, dx, dy, button0, button1, button2):
pass
def key_up(self, display, key, mod):
if key == pygame.K_q and mod & pygame.KMOD_CTRL: # Ctrl-q
display.close_viewer(self)
return True
if key == pygame.K_g and mod & pygame.KMOD_CTRL: # Ctrl-g
display.grow_viewer(self)
return True
def key_up(self, display, key, mod):
if key == pygame.K_q and mod & pygame.KMOD_CTRL: # Ctrl-q
display.close_viewer(self)
return True
if key == pygame.K_g and mod & pygame.KMOD_CTRL: # Ctrl-g
display.grow_viewer(self)
return True
def key_down(self, display, uch, key, mod):
pass
def key_down(self, display, uch, key, mod):
pass
class MenuViewer(Viewer):
'''
MenuViewer class
'''
'''
MenuViewer class
'''
MINIMUM_HEIGHT = 26
MINIMUM_HEIGHT = 26
def __init__(self, surface):
Viewer.__init__(self, surface)
self.resizing = 0
self.bg = 100, 150, 100
def __init__(self, surface):
Viewer.__init__(self, surface)
self.resizing = 0
self.bg = 100, 150, 100
def resurface(self, surface):
Viewer.resurface(self, surface)
n = self.MINIMUM_HEIGHT - 2
self.close_rect = pygame.rect.Rect(self.w - 2 - n, 1, n, n)
self.grow_rect = pygame.rect.Rect(1, 1, n, n)
self.body_rect = pygame.rect.Rect(
0, self.MINIMUM_HEIGHT + 1,
self.w - 1, self.h - self.MINIMUM_HEIGHT - 2)
def resurface(self, surface):
Viewer.resurface(self, surface)
n = self.MINIMUM_HEIGHT - 2
self.close_rect = pygame.rect.Rect(self.w - 2 - n, 1, n, n)
self.grow_rect = pygame.rect.Rect(1, 1, n, n)
self.body_rect = pygame.rect.Rect(
0, self.MINIMUM_HEIGHT + 1,
self.w - 1, self.h - self.MINIMUM_HEIGHT - 2)
def draw(self):
'''Draw the viewer onto its surface.'''
Viewer.draw(self)
if not self.resizing:
self.draw_menu()
self.draw_body()
def draw(self):
'''Draw the viewer onto its surface.'''
Viewer.draw(self)
if not self.resizing:
self.draw_menu()
self.draw_body()
def draw_menu(self):
# menu buttons
pygame.draw.rect(self.surface, FOREGROUND, self.close_rect, 1)
pygame.draw.rect(self.surface, FOREGROUND, self.grow_rect, 1)
def draw_menu(self):
# menu buttons
pygame.draw.rect(self.surface, FOREGROUND, self.close_rect, 1)
pygame.draw.rect(self.surface, FOREGROUND, self.grow_rect, 1)
def draw_body(self):
self.surface.fill(self.bg, self.body_rect)
def draw_body(self):
self.surface.fill(self.bg, self.body_rect)
def mouse_down(self, display, x, y, button):
Viewer.mouse_down(self, display, x, y, button)
if y <= self.MINIMUM_HEIGHT:
self.menu_click(display, x, y, button)
else:
bx, by = self.body_rect.topleft
self.body_click(display, x - bx, y - by, button)
def mouse_down(self, display, x, y, button):
Viewer.mouse_down(self, display, x, y, button)
if y <= self.MINIMUM_HEIGHT:
self.menu_click(display, x, y, button)
else:
bx, by = self.body_rect.topleft
self.body_click(display, x - bx, y - by, button)
def body_click(self, display, x, y, button):
if button == 1:
self.draw_an_a(x, y)
def body_click(self, display, x, y, button):
if button == 1:
self.draw_an_a(x, y)
def menu_click(self, display, x, y, button):
if button == 1:
self.resizing = 1
elif button == 3:
if self.close_rect.collidepoint(x, y):
display.close_viewer(self)
return True
elif self.grow_rect.collidepoint(x, y):
display.grow_viewer(self)
return True
def menu_click(self, display, x, y, button):
if button == 1:
self.resizing = 1
elif button == 3:
if self.close_rect.collidepoint(x, y):
display.close_viewer(self)
return True
elif self.grow_rect.collidepoint(x, y):
display.grow_viewer(self)
return True
def mouse_up(self, display, x, y, button):
def mouse_up(self, display, x, y, button):
if button == 1 and self.resizing:
if self.resizing == 2:
self.resizing = 0
self.draw()
display.done_resizing()
self.resizing = 0
return True
if button == 1 and self.resizing:
if self.resizing == 2:
self.resizing = 0
self.draw()
display.done_resizing()
self.resizing = 0
return True
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
if self.resizing and button0:
self.resizing = 2
display.change_viewer(self, rel_y, relative=True)
return True
else:
self.resizing = 0
#self.draw_an_a(x, y)
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
if self.resizing and button0:
self.resizing = 2
display.change_viewer(self, rel_y, relative=True)
return True
else:
self.resizing = 0
#self.draw_an_a(x, y)
def key_up(self, display, key, mod):
if Viewer.key_up(self, display, key, mod):
return True
def key_up(self, display, key, mod):
if Viewer.key_up(self, display, key, mod):
return True
def draw_an_a(self, x, y):
# Draw a crude letter A.
lw, lh = 10, 14
try: surface = self.surface.subsurface((x - lw, y - lh, lw, lh))
except ValueError: return
draw_a(surface, blend=1)
def draw_an_a(self, x, y):
# Draw a crude letter A.
lw, lh = 10, 14
try: surface = self.surface.subsurface((x - lw, y - lh, lw, lh))
except ValueError: return
draw_a(surface, blend=1)
class SomeViewer(MenuViewer):
def __init__(self, surface):
MenuViewer.__init__(self, surface)
def __init__(self, surface):
MenuViewer.__init__(self, surface)
def resurface(self, surface):
MenuViewer.resurface(self, surface)
def resurface(self, surface):
MenuViewer.resurface(self, surface)
def draw_menu(self):
MenuViewer.draw_menu(self)
def draw_menu(self):
MenuViewer.draw_menu(self)
def draw_body(self):
pass
def draw_body(self):
pass
def body_click(self, display, x, y, button):
pass
def body_click(self, display, x, y, button):
pass
def menu_click(self, display, x, y, button):
if MenuViewer.menu_click(self, display, x, y, button):
return True
def menu_click(self, display, x, y, button):
if MenuViewer.menu_click(self, display, x, y, button):
return True
def mouse_up(self, display, x, y, button):
if MenuViewer.mouse_up(self, display, x, y, button):
return True
def mouse_up(self, display, x, y, button):
if MenuViewer.mouse_up(self, display, x, y, button):
return True
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y,
button0, button1, button2):
return True
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y,
button0, button1, button2):
return True
def key_down(self, display, uch, key, mod):
try:
print(chr(key), end=' ')
except ValueError:
pass
def key_down(self, display, uch, key, mod):
try:
print(chr(key), end=' ')
except ValueError:
pass
# Note that Oberon book says that if you split at the exact top of a viewer
@ -243,7 +243,7 @@ class SomeViewer(MenuViewer):
def draw_a(surface, color=FOREGROUND, blend=False):
w, h = surface.get_width() - 2, surface.get_height() - 2
pygame.draw.aalines(surface, color, False, (
(1, h), (old_div(w, 2), 1), (w, h), (1, old_div(h, 2))
), blend)
w, h = surface.get_width() - 2, surface.get_height() - 2
pygame.draw.aalines(surface, color, False, (
(1, h), (old_div(w, 2), 1), (w, h), (1, old_div(h, 2))
), blend)

View File

@ -23,25 +23,25 @@ from textwrap import dedent
setup(
name='Thun',
version='0.2.0',
description='Python Implementation of Joy',
long_description=dedent('''\
Joy is a programming language created by Manfred von Thun that is easy to
use and understand and has many other nice properties. This Python
package implements an interpreter for a dialect of Joy that attempts to
stay very close to the spirit of Joy but does not precisely match the
behaviour of the original version written in C.'''),
author='Simon Forman',
author_email='forman.simon@gmail.com',
url='https://joypy.osdn.io',
license='GPLv3+',
packages=['joy', 'joy.utils', 'joy.gui', 'joy.vui'],
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
'Programming Language :: Python :: 2.7',
'Programming Language :: Other',
'Topic :: Software Development :: Interpreters',
],
)
name='Thun',
version='0.2.0',
description='Python Implementation of Joy',
long_description=dedent('''\
Joy is a programming language created by Manfred von Thun that is easy to
use and understand and has many other nice properties. This Python
package implements an interpreter for a dialect of Joy that attempts to
stay very close to the spirit of Joy but does not precisely match the
behaviour of the original version written in C.'''),
author='Simon Forman',
author_email='forman.simon@gmail.com',
url='https://joypy.osdn.io',
license='GPLv3+',
packages=['joy', 'joy.utils', 'joy.gui', 'joy.vui'],
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
'Programming Language :: Python :: 2.7',
'Programming Language :: Other',
'Topic :: Software Development :: Interpreters',
],
)