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
+161 -161
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))
+7 -7
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)
+412 -412
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()
+4 -4
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())
+11 -11
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())
+103 -103
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']
+197 -197
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)
+33 -33
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()
+565 -565
View File
File diff suppressed because it is too large Load Diff
+161 -161
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)