#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2007,2008,2009 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 pango import vte import re import os import gobject import time from port.style import font_zoom from signal import SIGTERM from gettext import gettext as _ from sugar.activity import activity from activity import ViewSourceActivity from activity import TARGET_TYPE_TEXT from sugar.activity.activity import ActivityToolbox from sugar.activity.activity import EditToolbar from sugar.activity.activity import get_bundle_path from sugar.activity.activity import get_bundle_name from sugar.graphics import style from sugar.graphics.toolbutton import ToolButton import groupthink.sugar_tools import groupthink.gtk_tools text_buffer = None # magic prefix to use utf-8 source encoding PYTHON_PREFIX = """#!/usr/bin/python # -*- coding: utf-8 -*- """ OLD_TOOLBAR = False try: from sugar.graphics.toolbarbox import ToolbarBox from sugar.graphics.toolbarbox import ToolbarButton from sugar.activity.widgets import StopButton except ImportError: OLD_TOOLBAR = True # get screen sizes SIZE_X = gtk.gdk.screen_width() SIZE_Y = gtk.gdk.screen_height() groupthink_mimetype = 'pickle/groupthink-pippy' class PippyActivity(ViewSourceActivity, groupthink.sugar_tools.GroupActivity): """Pippy Activity as specified in activity.info""" def early_setup(self): global text_buffer import gtksourceview2 text_buffer = gtksourceview2.Buffer() def initialize_display(self): self._logger = logging.getLogger('pippy-activity') # Activity toolbar with title input, share button and export buttons: if OLD_TOOLBAR: activity_toolbar = self.toolbox.get_activity_toolbar() else: activity_toolbar = self.activity_button.page # Hide keep button for Sugar versions prior to 0.94: activity_toolbar.keep.hide() separator = gtk.SeparatorToolItem() separator.show() activity_toolbar.insert(separator, -1) export_doc_button = ToolButton('pippy-export_doc') export_doc_button.set_tooltip(_("Export as Pippy Document")) export_doc_button.connect('clicked', self._export_document_cb) export_doc_button.show() activity_toolbar.insert(export_doc_button, -1) export_example_button = ToolButton('pippy-export_example') export_example_button.set_tooltip(_("Export as Pippy Example")) export_example_button.connect('clicked', self._export_example_cb) export_example_button.show() activity_toolbar.insert(export_example_button, -1) create_bundle_button = ToolButton('pippy-create_bundle') create_bundle_button.set_tooltip(_("Create Activity Bundle")) create_bundle_button.connect('clicked', self._create_bundle_cb) create_bundle_button.show() activity_toolbar.insert(create_bundle_button, -1) self._edit_toolbar = activity.EditToolbar() if OLD_TOOLBAR: activity_toolbar = gtk.Toolbar() self.toolbox.add_toolbar(_('Actions'), activity_toolbar) self.toolbox.set_current_toolbar(1) self.toolbox.add_toolbar(_('Edit'), self._edit_toolbar) else: edit_toolbar_button = ToolbarButton() edit_toolbar_button.set_page(self._edit_toolbar) edit_toolbar_button.props.icon_name = 'toolbar-edit' edit_toolbar_button.props.label = _('Edit') self.get_toolbar_box().toolbar.insert(edit_toolbar_button, -1) self._edit_toolbar.show() self._edit_toolbar.undo.connect('clicked', self.__undobutton_cb) self._edit_toolbar.redo.connect('clicked', self.__redobutton_cb) self._edit_toolbar.copy.connect('clicked', self.__copybutton_cb) self._edit_toolbar.paste.connect('clicked', self.__pastebutton_cb) if OLD_TOOLBAR: actions_toolbar = activity_toolbar else: actions_toolbar = self.get_toolbar_box().toolbar # The "go" button goicon_bw = gtk.Image() goicon_bw.set_from_file("%s/icons/run_bw.svg" % os.getcwd()) goicon_color = gtk.Image() goicon_color.set_from_file("%s/icons/run_color.svg" % os.getcwd()) gobutton = ToolButton(label=_("Run!")) gobutton.props.accelerator = _('r') gobutton.set_icon_widget(goicon_bw) gobutton.set_tooltip(_("Run!")) gobutton.connect('clicked', self.flash_cb, dict({'bw': goicon_bw, 'color': goicon_color})) gobutton.connect('clicked', self.gobutton_cb) actions_toolbar.insert(gobutton, -1) # The "stop" button stopicon_bw = gtk.Image() stopicon_bw.set_from_file("%s/icons/stopit_bw.svg" % os.getcwd()) stopicon_color = gtk.Image() stopicon_color.set_from_file("%s/icons/stopit_color.svg" % os.getcwd()) stopbutton = ToolButton(label=_("Stop")) stopbutton.props.accelerator = _('s') stopbutton.set_icon_widget(stopicon_bw) stopbutton.connect('clicked', self.flash_cb, dict({'bw': stopicon_bw, 'color': stopicon_color})) stopbutton.connect('clicked', self.stopbutton_cb) stopbutton.set_tooltip(_("Stop")) actions_toolbar.insert(stopbutton, -1) # The "clear" button clearicon_bw = gtk.Image() clearicon_bw.set_from_file("%s/icons/eraser_bw.svg" % os.getcwd()) clearicon_color = gtk.Image() clearicon_color.set_from_file("%s/icons/eraser_color.svg" % os.getcwd()) clearbutton = ToolButton(label=_("Clear")) clearbutton.props.accelerator = _('c') clearbutton.set_icon_widget(clearicon_bw) clearbutton.connect('clicked', self.clearbutton_cb) clearbutton.connect('clicked', self.flash_cb, dict({'bw': clearicon_bw, 'color': clearicon_color})) clearbutton.set_tooltip(_("Clear")) actions_toolbar.insert(clearbutton, -1) activity_toolbar.show() if not OLD_TOOLBAR: separator = gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) self.get_toolbar_box().toolbar.insert(separator, -1) separator.show() stop = StopButton(self) self.get_toolbar_box().toolbar.insert(stop, -1) # Main layout. self.hpane = gtk.HPaned() self.vpane = gtk.VPaned() # The sidebar. self.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(int(SIZE_X * 0.3), SIZE_Y) # Create scrollbars around the view. scrolled = gtk.ScrolledWindow() scrolled.add(treeview) self.sidebar.pack_start(scrolled) self.hpane.add1(self.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"]) # Adding local examples root = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],'data') direntry_examples = { "name": _("My examples"), "path": root + "/" } self.example_iter = self.model.insert_before(None, None) self.model.set_value(self.example_iter, 0, direntry_examples) self.model.set_value(self.example_iter, 1, direntry_examples["name"]) for _file in sorted(os.listdir(root)): file_name = os.path.join(root, _file) if os.path.isfile(file_name): entry = {"name": _file, "path": file_name} _iter = self.model.insert_before(self.example_iter, 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 lang_manager = gtksourceview2.language_manager_get_default() if hasattr(lang_manager, 'list_languages'): langs = lang_manager.list_languages() else: lang_ids = lang_manager.get_language_ids() langs = [lang_manager.get_language(lang_id) for lang_id in lang_ids] for lang in langs: for m in lang.get_mime_types(): if m == "text/x-python": text_buffer.set_language(lang) if hasattr(text_buffer, 'set_highlight'): text_buffer.set_highlight(True) else: text_buffer.set_highlight_syntax(True) # The GTK source view window self.text_view = gtksourceview2.View(text_buffer) self.text_view.set_size_request(0, int(SIZE_Y * 0.5)) 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.set_insert_spaces_instead_of_tabs(True) self.text_view.set_tab_width(2) self.text_view.set_auto_indent(True) self.text_view.modify_font(pango.FontDescription("Monospace " + str(font_zoom(style.FONT_SIZE)))) # 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) self.vpane.add1(codesw) # An hbox to hold the vte window and its scrollbar. outbox = gtk.HBox() # The vte python window self._vte = vte.Terminal() self._vte.set_encoding('utf-8') self._vte.set_size(30, 5) font = 'Monospace ' + str(font_zoom(style.FONT_SIZE)) 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 self._vte.drag_dest_set(gtk.DEST_DEFAULT_ALL, [("text/plain", 0, TARGET_TYPE_TEXT)], gtk.gdk.ACTION_COPY) self._vte.connect('drag_data_received', self.vte_drop_cb) outbox.pack_start(self._vte) outsb = gtk.VScrollbar(self._vte.get_adjustment()) outsb.show() outbox.pack_start(outsb, False, False, 0) self.vpane.add2(outbox) self.hpane.add2(self.vpane) return self.hpane def when_shared(self): self.hpane.remove(self.hpane.get_child1()) global text_buffer self.cloud.sharefield = \ groupthink.gtk_tools.TextBufferSharePoint(text_buffer) # HACK : There are issues with undo/redoing while in shared # mode. So disable the 'undo' and 'redo' buttons when the activity # is shared. self._edit_toolbar.undo.set_sensitive(False) self._edit_toolbar.redo.set_sensitive(False) def vte_drop_cb(self, widget, context, x, y, selection, targetType, time): if targetType == TARGET_TYPE_TEXT: self._vte.feed_child(selection.data) 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 timer_cb(self, button, icons): button.set_icon_widget(icons['bw']) button.show_all() return False def flash_cb(self, button, icons): button.set_icon_widget(icons['color']) button.show_all() gobject.timeout_add(400, self.timer_cb, button, icons) 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: # write utf-8 coding prefix if there's not already one if re.match(r'coding[:=]\s*([-\w.]+)', '\n'.join(text.splitlines()[:2])) is None: f.write(PYTHON_PREFIX) 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 __undobutton_cb(self, button): global text_buffer if text_buffer.can_undo(): text_buffer.undo() def __redobutton_cb(self, button): global text_buffer if text_buffer.can_redo(): text_buffer.redo() def __copybutton_cb(self, button): global text_buffer text_buffer.copy_clipboard(gtk.Clipboard()) def __pastebutton_cb(self, button): global text_buffer text_buffer.paste_clipboard(gtk.Clipboard(), None, True) 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:%s" % (get_bundle_path(), os.getenv("PYTHONPATH", ""))], directory=get_bundle_path()) def stopbutton_cb(self, button): try: os.kill(self._pid, SIGTERM) except: pass # process must already be dead. def _export_document_cb(self, __): self.copy() def _create_bundle_cb(self, __): from shutil import copytree from shutil import copy2 from shutil import 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' 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 _export_example_cb(self, __): # 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 Example Error') alert.props.msg = _('Please give your activity a meaningful name before attempting to save it as an example.') 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 example...")) self._vte.feed("\r\n") local_data = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],'data') local_file = os.path.join(local_data,title) if os.path.exists(local_file): from sugar.graphics.alert import ConfirmationAlert alert = ConfirmationAlert() alert.props.title =_ ('Save as Example Warning') alert.props.msg = _('This example already exists. Do you want to overwrite it?') alert.connect('response', self.confirmation_alert_cb, local_file) self.add_alert(alert) else: self.write_file(local_file) self._reset_vte() self._vte.feed(_("Saved as example.")) self._vte.feed("\r\n") self.add_to_example_list(local_file) 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, 0755) 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 confirmation_alert_cb(self, alert, response_id, local_file): #callback for conf alert self.remove_alert(alert) if response_id is gtk.RESPONSE_OK: self.write_file(local_file) self._reset_vte() self._vte.feed(_("Saved as example.")) self._vte.feed("\r\n") else: self._reset_vte() def add_to_example_list(self,local_file): # def for add example entry = { "name": _(os.path.basename(local_file)), "path": local_file } _iter = self.model.insert_before(self.example_iter, None) self.model.set_value(_iter, 0, entry) self.model.set_value(_iter, 1, entry["name"]) def save_to_journal(self, file_path, cloudstring): _file = open(file_path, 'w') if not self._shared_activity: self.metadata['mime_type'] = 'text/x-python' global text_buffer start, end = text_buffer.get_bounds() text = text_buffer.get_text(start, end) _file.write(text) else: self.metadata['mime_type'] = groupthink_mimetype _file.write(cloudstring) def load_from_journal(self, file_path): if self.metadata['mime_type'] == 'text/x-python': text = open(file_path).read() # discard the '#!/usr/bin/python' and 'coding: utf-8' lines, # if present text = re.sub(r'^' + re.escape(PYTHON_PREFIX), '', text) global text_buffer text_buffer.set_text(text) elif self.metadata['mime_type'] == groupthink_mimetype: return open(file_path).read() ############# 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 39 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', 'groupthink', 'post']: # everybody gets library 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', groupthink_mimetype] def pippy_activity_extra_info(): return """ license = GPLv2+ update_url = http://activities.sugarlabs.org """ ################# 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 sorted(os.walk(app_temp)): for name in sorted(filenames): fn = os.path.join(dirpath, name) fn = fn.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_xo'] bundlebuilder.start() sys.argv = oldargv os.chdir(olddir) # move to destination directory. copy2('%s/dist/%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)