# Copyright 2007 Chris Ball, based on Collabora's "hellomesh" demo.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Pippy Activity: A simple Python programming activity ."""
from __future__ import with_statement
import gtk
import logging
import telepathy
import telepathy.client
import pango
import vte
import re, os, os.path
import gobject
import time
from signal import SIGTERM
from gettext import gettext as _
from dbus.service import method, signal
from dbus.gobject_service import ExportedGObject
from activity import ViewSourceActivity
from sugar.activity.activity import ActivityToolbox, \
get_bundle_path, get_bundle_name
from sugar.presence import presenceservice
from sugar.presence.tubeconn import TubeConnection
SERVICE = "org.laptop.Pippy"
IFACE = SERVICE
PATH = "/org/laptop/Pippy"
text_buffer = None
class PippyActivity(ViewSourceActivity):
"""Pippy Activity as specified in activity.info"""
def __init__(self, handle):
"""Set up the Pippy activity."""
super(PippyActivity, self).__init__(handle)
self._logger = logging.getLogger('pippy-activity')
# Top toolbar with share and close buttons:
toolbox = ActivityToolbox(self)
# add 'make bundle' entry to 'keep' palette.
palette = toolbox.get_activity_toolbar().keep.get_palette()
# XXX: should clear out old palette entries?
from sugar.graphics.menuitem import MenuItem
from sugar.graphics.icon import Icon
menu_item = MenuItem(_('As Pippy Document'))
menu_item.set_image(Icon(file=('%s/activity/activity-icon.svg' % get_bundle_path()), icon_size=gtk.ICON_SIZE_MENU))
menu_item.connect('activate', self.keepbutton_cb)
palette.menu.append(menu_item)
menu_item.show()
menu_item = MenuItem(_('As Activity Bundle'))
menu_item.set_image(Icon(file=('%s/activity/activity-default.svg' % get_bundle_path()), icon_size=gtk.ICON_SIZE_MENU))
menu_item.connect('activate', self.makebutton_cb)
palette.menu.append(menu_item)
menu_item.show()
self.set_toolbox(toolbox)
toolbox.show()
# Main layout.
hbox = gtk.HBox()
vbox = gtk.VBox()
# The sidebar.
sidebar = gtk.VBox()
self.model = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_STRING)
treeview = gtk.TreeView(self.model)
cellrenderer = gtk.CellRendererText()
treecolumn = gtk.TreeViewColumn(_("Examples"), cellrenderer, text=1)
treeview.get_selection().connect("changed", self.selection_cb)
treeview.append_column(treecolumn)
treeview.set_size_request(220, 900)
# Create scrollbars around the view.
scrolled = gtk.ScrolledWindow()
scrolled.add(treeview)
sidebar.pack_start(scrolled)
hbox.pack_start(sidebar)
root = os.path.join(get_bundle_path(), 'data')
for d in sorted(os.listdir(root)):
if not os.path.isdir(os.path.join(root,d)): continue #skip non-dirs
direntry = { "name": _(d.capitalize()),
"path": os.path.join(root,d) + "/" }
olditer = self.model.insert_before(None, None)
self.model.set_value(olditer, 0, direntry)
self.model.set_value(olditer, 1, direntry["name"])
for _file in sorted(os.listdir(os.path.join(root, d))):
if _file.endswith('~'): continue # skip emacs backups
entry = { "name": _(_file.capitalize()),
"path": os.path.join(root, d, _file) }
_iter = self.model.insert_before(olditer, None)
self.model.set_value(_iter, 0, entry)
self.model.set_value(_iter, 1, entry["name"])
treeview.expand_all()
# Source buffer
import gtksourceview2
global text_buffer
text_buffer = gtksourceview2.Buffer()
lang_manager = gtksourceview2.language_manager_get_default()
langs = lang_manager.list_languages()
for lang in langs:
for m in lang.get_mime_types():
if m == "text/x-python":
text_buffer.set_language(lang)
text_buffer.set_highlight(True)
# The GTK source view window
self.text_view = gtksourceview2.View(text_buffer)
self.text_view.set_size_request(900, 350)
self.text_view.set_editable(True)
self.text_view.set_cursor_visible(True)
self.text_view.set_show_line_numbers(True)
self.text_view.set_wrap_mode(gtk.WRAP_CHAR)
self.text_view.modify_font(pango.FontDescription("Monospace 10"))
# We could change the color theme here, if we want to.
#mgr = gtksourceview2.style_manager_get_default()
#style_scheme = mgr.get_scheme('kate')
#self.text_buffer.set_style_scheme(style_scheme)
codesw = gtk.ScrolledWindow()
codesw.set_policy(gtk.POLICY_AUTOMATIC,
gtk.POLICY_AUTOMATIC)
codesw.add(self.text_view)
vbox.pack_start(codesw)
# An hbox for the buttons
buttonhbox = gtk.HBox()
# The "go" button
goicon = gtk.Image()
goicon.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_BUTTON)
gobutton = gtk.Button(label=_("_Run!"))
gobutton.set_image(goicon)
gobutton.connect('clicked', self.gobutton_cb)
gobutton.set_size_request(650, 2)
buttonhbox.pack_start(gobutton)
# The "stop" button
stopbutton = gtk.Button(stock=gtk.STOCK_STOP)
stopbutton.connect('clicked', self.stopbutton_cb)
stopbutton.set_size_request(200, 2)
buttonhbox.pack_start(stopbutton)
# The "clear" button
clearbutton = gtk.Button(stock=gtk.STOCK_CLEAR)
clearbutton.connect('clicked', self.clearbutton_cb)
clearbutton.set_size_request(150, 2)
buttonhbox.pack_end(clearbutton)
vbox.pack_start(buttonhbox)
# An hbox to hold the vte window and its scrollbar.
outbox = gtk.HBox()
# The vte python window
self._vte = vte.Terminal()
self._vte.set_size(30, 5)
self._vte.set_size_request(200, 300)
font = 'Monospace 10'
self._vte.set_font(pango.FontDescription(font))
self._vte.set_colors(gtk.gdk.color_parse ('#000000'),
gtk.gdk.color_parse ('#E7E7E7'),
[])
self._vte.connect('child_exited', self.child_exited_cb)
self._child_exited_handler = None
outbox.pack_start(self._vte)
outsb = gtk.VScrollbar(self._vte.get_adjustment())
outsb.show()
outbox.pack_start(outsb, False, False, 0)
vbox.pack_end(outbox)
hbox.pack_end(vbox)
self.set_canvas(hbox)
self.show_all()
self.hellotube = None
# get the Presence Service
self.pservice = presenceservice.get_instance()
try:
name, path = self.pservice.get_preferred_connection()
self.tp_conn_name = name
self.tp_conn_path = path
self.conn = telepathy.client.Connection(name, path)
except TypeError:
self._logger.debug('No Telepathy CM, offline')
self.initiating = None
self.connect('shared', self._shared_cb)
# Buddy object for you
owner = self.pservice.get_owner()
self.owner = owner
if self._shared_activity:
# we are joining the activity
self.connect('joined', self._joined_cb)
self._shared_activity.connect('buddy-joined',
self._buddy_joined_cb)
self._shared_activity.connect('buddy-left',
self._buddy_left_cb)
if self.get_shared():
# we've already joined
self._joined_cb()
def selection_cb(self, column):
self.save()
model, _iter = column.get_selected()
value = model.get_value(_iter,0)
self._logger.debug("clicked! %s" % value['path'])
_file = open(value['path'], 'r')
lines = _file.readlines()
global text_buffer
text_buffer.set_text("".join(lines))
self.metadata['title'] = value['name']
self.stopbutton_cb(None)
self._reset_vte()
self.text_view.grab_focus()
def clearbutton_cb(self, button):
self.save()
global text_buffer
text_buffer.set_text("")
self.metadata['title'] = _('%s Activity') % get_bundle_name()
self.stopbutton_cb(None)
self._reset_vte()
self.text_view.grab_focus()
def _write_text_buffer(self, filename):
global text_buffer
start, end = text_buffer.get_bounds()
text = text_buffer.get_text(start, end)
with open(filename, 'w') as f:
for line in text:
f.write(line)
def _reset_vte(self):
self._vte.grab_focus()
self._vte.feed("\x1B[H\x1B[J\x1B[0;39m")
def gobutton_cb(self, button):
from shutil import copy2
self.stopbutton_cb(button) # try stopping old code first.
self._reset_vte()
# FIXME: We're losing an odd race here
# gtk.main_iteration(block=False)
pippy_app_name = '%s/tmp/pippy_app.py' % self.get_activity_root()
self._write_text_buffer(pippy_app_name)
# write activity.py here too, to support pippy-based activities.
copy2('%s/activity.py' % get_bundle_path(),
'%s/tmp/activity.py' % self.get_activity_root())
self._pid = self._vte.fork_command \
(command="/bin/sh",
argv=["/bin/sh", "-c",
"python %s; sleep 1" % pippy_app_name],
envv=["PYTHONPATH=%s/library" % get_bundle_path()],
directory=get_bundle_path())
def stopbutton_cb(self, button):
try:
os.kill(self._pid, SIGTERM)
except:
pass # process must already be dead.
def keepbutton_cb(self, __):
self.copy()
def makebutton_cb(self, __):
from shutil import copytree, copy2, rmtree
from tempfile import mkdtemp
# get the name of this pippy program.
title = self.metadata['title']
if title == 'Pippy Activity':
from sugar.graphics.alert import Alert
from sugar.graphics.icon import Icon
alert = Alert()
alert.props.title =_ ('Save as Activity Error')
alert.props.msg = _('Please give your activity a meaningful name before attempting to save it as an activity.')
ok_icon = Icon(icon_name='dialog-ok')
alert.add_button(gtk.RESPONSE_OK, _('Ok'), ok_icon)
alert.connect('response', self.dismiss_alert_cb)
self.add_alert(alert)
return
self.stopbutton_cb(None) # try stopping old code first.
self._reset_vte()
self._vte.feed(_("Creating activity bundle..."))
self._vte.feed("\r\n")
TMPDIR='instance' # XXX: should be 'tmp', once trac #1731 is fixed.
app_temp = mkdtemp('.activity', 'Pippy',
os.path.join(self.get_activity_root(), TMPDIR))
sourcefile = os.path.join(app_temp, 'xyzzy.py')
# invoke ourself to build the activity bundle.
try:
# write out application code
self._write_text_buffer(sourcefile)
# hook up a callback for when the bundle builder is done.
# we can't use gobject.child_watch_add because vte will reap our
# children before we can.
self._child_exited_handler = lambda: self.bundle_cb(title, app_temp)
# invoke bundle builder
self._pid = self._vte.fork_command \
(command="/usr/bin/python",
argv=["/usr/bin/python",
"%s/pippy_app.py" % get_bundle_path(),
'-p', '%s/library' % get_bundle_path(),
'-d', app_temp,
title, sourcefile],
directory=app_temp)
except:
rmtree(app_temp, ignore_errors=True) # clean up!
raise
def child_exited_cb(self, *args):
"""Called whenever a child exits. If there's a handler, run it."""
h, self._child_exited_handler = self._child_exited_handler, None
if h is not None: h()
def bundle_cb(self, title, app_temp):
"""Called when we're done building a bundle for a source file."""
from sugar import profile
from shutil import rmtree
from sugar.datastore import datastore
try:
# find the .xo file: were we successful?
bundle_file=[ f for f in os.listdir(app_temp) if f.endswith('.xo') ]
if len(bundle_file) != 1:
self._logger.debug("Couldn't find bundle: %s"%str(bundle_file))
return # something went wrong.
# hand off to journal
os.chmod(app_temp, 755)
jobject = datastore.create()
metadata = {
'title': '%s Bundle' % title,
'title_set_by_user': '1',
'buddies': '',
'preview': '',
'icon-color': profile.get_color().to_string(),
'mime_type': 'application/vnd.olpc-sugar',
}
for k, v in metadata.items():
jobject.metadata[k] = v # the dict.update method is missing =(
jobject.file_path = os.path.join(app_temp, bundle_file[0])
datastore.write(jobject)
self._vte.feed("\r\n")
self._vte.feed(_("Activity saved to journal."))
self._vte.feed("\r\n")
self.journal_show_object(jobject.object_id)
jobject.destroy()
finally:
rmtree(app_temp, ignore_errors=True) # clean up!
def dismiss_alert_cb(self, alert, response_id):
self.remove_alert(alert)
def write_file(self, file_path):
self.metadata['mime_type'] = 'text/x-python'
global text_buffer
start, end = text_buffer.get_bounds()
text = text_buffer.get_text(start, end)
_file = open(file_path, 'w')
_file.write(text)
def read_file(self, file_path):
text = open(file_path).read()
global text_buffer
text_buffer.set_text(text)
def _shared_cb(self, activity):
self._logger.debug('My activity was shared')
self.initiating = True
self._sharing_setup()
self._logger.debug('This is my activity: making a tube...')
_id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
SERVICE, {})
# presence service should be tubes-aware and give us more help
# with this
def _sharing_setup(self):
if self._shared_activity is None:
self._logger.error('Failed to share or join activity')
return
self.conn = self._shared_activity.telepathy_conn
self.tubes_chan = self._shared_activity.telepathy_tubes_chan
self.text_chan = self._shared_activity.telepathy_text_chan
self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
'NewTube', self._new_tube_cb)
self._shared_activity.connect('buddy-joined', self._buddy_joined_cb)
self._shared_activity.connect('buddy-left', self._buddy_left_cb)
# Optional - included for example:
# Find out who's already in the shared activity:
for buddy in self._shared_activity.get_joined_buddies():
self._logger.debug('Buddy %s is already in the activity',
buddy.props.nick)
def _list_tubes_reply_cb(self, tubes):
for tube_info in tubes:
self._new_tube_cb(*tube_info)
def _list_tubes_error_cb(self, e):
self._logger.error('ListTubes() failed: %s', e)
def _joined_cb(self, activity):
if not self._shared_activity:
return
self._logger.debug('Joined an existing shared activity')
self.initiating = False
self._sharing_setup()
self._logger.debug('This is not my activity: waiting for a tube...')
self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
reply_handler=self._list_tubes_reply_cb,
error_handler=self._list_tubes_error_cb)
def _new_tube_cb(self, id, initiator, type, service, params, state):
self._logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
'params=%r state=%d', id, initiator, type, service,
params, state)
if (type == telepathy.TUBE_TYPE_DBUS and
service == SERVICE):
if state == telepathy.TUBE_STATE_LOCAL_PENDING:
self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
tube_conn = TubeConnection(self.conn,
self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
self.hellotube = HelloTube(tube_conn, self.initiating, self._get_buddy)
def _buddy_joined_cb (self, activity, buddy):
self._logger.debug('Buddy %s joined' % buddy.props.nick)
def _buddy_left_cb (self, activity, buddy):
self._logger.debug('Buddy %s left' % buddy.props.nick)
def _get_buddy(self, cs_handle):
"""Get a Buddy from a channel specific handle."""
self._logger.debug('Trying to find owner of handle %u...', cs_handle)
group = self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]
my_csh = group.GetSelfHandle()
self._logger.debug('My handle in that group is %u', my_csh)
if my_csh == cs_handle:
handle = self.conn.GetSelfHandle()
self._logger.debug('CS handle %u belongs to me, %u', cs_handle, handle)
elif group.GetGroupFlags() & telepathy.CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
handle = group.GetHandleOwners([cs_handle])[0]
self._logger.debug('CS handle %u belongs to %u', cs_handle, handle)
else:
handle = cs_handle
self._logger.debug('non-CS handle %u belongs to itself', handle)
# XXX: deal with failure to get the handle owner
assert handle != 0
# XXX: we're assuming that we have Buddy objects for all contacts -
# this might break when the server becomes scalable.
return self.pservice.get_buddy_by_telepathy_handle(self.conn.service_name, self.conn.object_path, handle)
class HelloTube(ExportedGObject):
"""The bit that talks over the TUBES!!!"""
def __init__(self, tube, is_initiator, get_buddy):
super(HelloTube, self).__init__(tube, PATH)
self._logger = logging.getLogger('pippy-activity.HelloTube')
self.tube = tube
self.is_initiator = is_initiator
self.entered = False # Have we set up the tube?
self.helloworld = False # Have we said Hello and received World?
self._get_buddy = get_buddy # Converts handle to Buddy object
self.tube.watch_participants(self.participant_change_cb)
def participant_change_cb(self, added, removed):
self._logger.debug('Adding participants: %r' % added)
for handle, bus_name in added:
buddy = self._get_buddy(handle)
if buddy is not None:
self._logger.debug('Buddy %s was added' % buddy.props.nick)
for handle in removed:
buddy = self._get_buddy(handle)
if buddy is not None:
self._logger.debug('Buddy %s was removed' % buddy.props.nick)
if not self.entered:
#self.tube.add_signal_receiver(self.insert_cb, 'Insert', IFACE,
# path=PATH, sender_keyword='sender')
if self.is_initiator:
self._logger.debug("I'm initiating the tube, will "
"watch for hellos.")
self.add_hello_handler()
else:
self._logger.debug('Hello, everyone! What did I miss?')
self.Hello()
self.entered = True
@signal(dbus_interface=IFACE, signature='')
def Hello(self):
"""Say Hello to whoever else is in the tube."""
self._logger.debug('I said Hello.')
@method(dbus_interface=IFACE, in_signature='s', out_signature='')
def World(self, game_state):
"""To be called on the incoming XO after they Hello."""
if not self.helloworld:
self._logger.debug('Somebody said World.')
# We have the host's text buffer now; set ours to its contents.
self.helloworld = game_state
global text_buffer
text_buffer.set_text(game_state)
# now I can World others
self.add_hello_handler()
else:
self._logger.debug("I've already been welcomed, doing nothing")
def add_hello_handler(self):
self._logger.debug('Adding hello handler.')
self.tube.add_signal_receiver(self.hello_cb, 'Hello', IFACE,
path=PATH, sender_keyword='sender')
def hello_cb(self, sender=None):
"""Somebody Helloed me. World them."""
if sender == self.tube.get_unique_name():
# sender is my bus name, so ignore my own signal
return
self._logger.debug('Newcomer %s has joined', sender)
self._logger.debug('Welcoming newcomer and sending them the game state')
# Throw our text buffer down the tube, to be caught in World().
global text_buffer
start, end = text_buffer.get_bounds()
game_state = text_buffer.get_text(start, end)
self.tube.get_object(sender, PATH).World(game_state,
dbus_interface=IFACE)
############# TEMPLATES AND INLINE FILES ##############
ACTIVITY_INFO_TEMPLATE = """
[Activity]
name = %(title)s
bundle_id = %(bundle_id)s
service_name = %(bundle_id)s
class = %(class)s
icon = activity-icon
activity_version = %(version)d
mime_types = %(mime_types)s
show_launcher = yes
%(extra_info)s
"""
PIPPY_ICON = \
"""
]>
"""
PIPPY_DEFAULT_ICON = \
"""
]>
"""
############# ACTIVITY META-INFORMATION ###############
# this is used by Pippy to generate a bundle for itself.
def pippy_activity_version():
"""Returns the version number of the generated activity bundle."""
return 14
def pippy_activity_extra_files():
"""Returns a map of 'extra' files which should be included in the
generated activity bundle."""
# Cheat here and generate the map from the fs contents.
extra = {}
bp = get_bundle_path()
for d in [ 'po', 'data' ]: # everybody gets library already
for root, dirs, files in os.walk(os.path.join(bp, d)):
for name in files:
fn = os.path.join(root, name).replace(bp+'/', '')
extra[fn] = open(os.path.join(root, name), 'r').read()
extra['activity/activity-default.svg'] = PIPPY_DEFAULT_ICON
return extra
def pippy_activity_news():
"""Return the NEWS file for this activity."""
# Cheat again.
return open(os.path.join(get_bundle_path(), 'NEWS')).read()
def pippy_activity_icon():
"""Return an SVG document specifying the icon for this activity."""
return PIPPY_ICON
def pippy_activity_class():
"""Return the class which should be started to run this activity."""
return 'pippy_app.PippyActivity'
def pippy_activity_bundle_id():
"""Return the bundle_id for the generated activity."""
return 'org.laptop.Pippy'
def pippy_activity_mime_types():
"""Return the mime types handled by the generated activity, as a list."""
return 'text/x-python'
################# ACTIVITY BUNDLER ################
def main():
"""Create a bundle from a pippy-style source file"""
from optparse import OptionParser
from pyclbr import readmodule_ex
from tempfile import mkdtemp
from shutil import copytree, copy2, rmtree
from sugar import profile
from sugar.activity import bundlebuilder
import sys
parser = OptionParser(usage='%prog [options] [title] [sourcefile]')
parser.add_option('-d', '--dir', dest='dir',default='.',metavar='DIR',
help='Put generated bundle in the specified directory.')
parser.add_option('-p', '--pythonpath', dest='path', action='append',
default=[], metavar='DIR',
help='Append directory to python search path.')
(options, args) = parser.parse_args()
if len(args) != 2:
parser.error('The title and sourcefile arguments are required.')
title = args[0]
sourcefile = args[1]
pytitle = re.sub(r'[^A-Za-z0-9_]', '', title)
if re.match(r'[0-9]', pytitle) is not None:
pytitle = '_' + pytitle # first character cannot be numeric
# first take a gander at the source file and see if it's got extra info
# for us.
sourcedir, basename = os.path.split(sourcefile)
if not sourcedir: sourcedir = '.'
module, ext = os.path.splitext(basename)
# things we look for:
bundle_info = {
'version': 1,
'extra_files': {},
'news': 'No news.',
'icon': PIPPY_DEFAULT_ICON,
'class': 'activity.VteActivity',
'bundle_id': ('org.laptop.pippy.%s' % pytitle),
'mime_types': '',
'extra_info': '',
}
# are any of these things in the module?
try_import = False
info = readmodule_ex(module, [ sourcedir ] + options.path)
for func in bundle_info.keys():
p_a_func = 'pippy_activity_%s' % func
if p_a_func in info: try_import = True
if try_import:
# yes, let's try to execute them to get better info about our bundle
oldpath = list(sys.path)
sys.path[0:0] = [ sourcedir ] + options.path
modobj = __import__(module)
for func in bundle_info.keys():
p_a_func = 'pippy_activity_%s' % func
if p_a_func in modobj.__dict__:
bundle_info[func] = modobj.__dict__[p_a_func]()
sys.path = oldpath
# okay! We've done the hard part. Now let's build a bundle.
# create a new temp dir in which to create the bundle.
app_temp = mkdtemp('.activity', 'Pippy') # hope TMPDIR is set correctly!
bundle = get_bundle_path()
try:
copytree('%s/library' % bundle, '%s/library' % app_temp)
copy2('%s/activity.py' % bundle, '%s/activity.py' % app_temp)
# create activity.info file.
bundle_info['title'] = title
bundle_info['pytitle'] = pytitle
# put 'extra' files in place.
extra_files = {
'activity/activity.info': ACTIVITY_INFO_TEMPLATE % bundle_info,
'activity/activity-icon.svg': bundle_info['icon'],
'NEWS': bundle_info['news'],
}
extra_files.update(bundle_info['extra_files'])
for path, contents in extra_files.items():
# safety first!
assert '..' not in path
dirname, filename = os.path.split(path)
dirname = os.path.join(app_temp, dirname)
if not os.path.exists(dirname): os.makedirs(dirname)
with open(os.path.join(dirname, filename), 'w') as f:
f.write(contents)
# put script into $app_temp/pippy_app.py
copy2(sourcefile, '%s/pippy_app.py' % app_temp)
# write MANIFEST file.
with open('%s/MANIFEST' % app_temp, 'w') as f:
for dirpath, dirnames, filenames in os.walk(app_temp):
for name in filenames:
fn = os.path.join(dirpath, name).replace(app_temp+'/', '')
if fn=='MANIFEST': continue
f.write('%s\n' % fn)
# invoke bundle builder
olddir = os.getcwd()
oldargv = sys.argv
os.chdir(app_temp)
sys.argv = [ 'setup.py', 'dist' ]
bundlebuilder.start(pytitle)
sys.argv = oldargv
os.chdir(olddir)
# move to destination directory.
copy2('%s/%s-%d.xo' % (app_temp, pytitle, bundle_info['version']),
'%s/%s-%d.xo' % (options.dir, pytitle, bundle_info['version']))
finally:
rmtree(app_temp, ignore_errors=True)
if __name__ == '__main__':
from gettext import gettext as _
import sys
if False: # change this to True to test within Pippy
sys.argv = sys.argv + [ '-d','/tmp','Pippy', '/home/olpc/pippy_app.py' ]
#print _("Working..."),
#sys.stdout.flush()
main()
#print _("done!")
sys.exit(0)