From 5af6fcdce8b0904557e2dedbe1c2dd035223bf1a Mon Sep 17 00:00:00 2001 From: epage Date: Sat, 24 Oct 2009 19:56:27 +0000 Subject: [PATCH] Updating from Dialcentral git-svn-id: file:///svnroot/ejpi/trunk@50 df6cc7de-23d0-4ae0-bb86-c17aa67b2a9d --- Makefile | 43 ++- src/constants.py | 3 +- src/ejpi_glade.py | 4 + src/gtk_toolbox.py | 466 ++++++++++++++++++++++-- src/hildonize.py | 732 +++++++++++++++++++++++++++++++++++++ support/builddeb.py | 51 ++- support/ejpi.desktop | 2 +- support/py2deb.py | 989 ++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 2228 insertions(+), 62 deletions(-) create mode 100755 src/hildonize.py create mode 100644 support/py2deb.py diff --git a/Makefile b/Makefile index e5ed1a0..3d6c4e9 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PROGRAM=$(SOURCE_PATH)/$(PROJECT_NAME).py DATA_TYPES=*.ini *.map *.glade *.png DATA=$(foreach type, $(DATA_TYPES), $(shell find $(SOURCE_PATH) -iname "$(type)")) OBJ=$(SOURCE:.py=.pyc) -BUILD_PATH=./build/ +BUILD_PATH=./build TAG_FILE=~/.ctags/$(PROJECT_NAME).tags TODO_FILE=./TODO @@ -37,20 +37,35 @@ debug: $(OBJ) test: $(OBJ) $(UNIT_TEST) -build: $(OBJ) +package: $(OBJ) rm -Rf $(BUILD_PATH) - mkdir $(BUILD_PATH) - cp $(SOURCE_PATH)/constants.py $(BUILD_PATH) - cp $(SOURCE_PATH)/$(PROJECT_NAME).py $(BUILD_PATH) - $(foreach file, $(DATA), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) - $(foreach file, $(SOURCE), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) - $(foreach file, $(OBJ), cp $(file) $(BUILD_PATH)/$(subst /,-,$(file)) ; ) - cp support/$(PROJECT_NAME).desktop $(BUILD_PATH) - cp support/icons/26.png $(BUILD_PATH)/26x26-$(PROJECT_NAME).png - cp support/icons/64.png $(BUILD_PATH)/64x64-$(PROJECT_NAME).png - cp support/icons/scalable.png $(BUILD_PATH)/scale-$(PROJECT_NAME).png - cp support/builddeb.py $(BUILD_PATH) - cp support/fake_py2deb.py $(BUILD_PATH) + + mkdir -p $(BUILD_PATH)/generic + cp $(SOURCE_PATH)/constants.py $(BUILD_PATH)/generic + cp $(SOURCE_PATH)/$(PROJECT_NAME).py $(BUILD_PATH)/generic + $(foreach file, $(DATA), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; ) + $(foreach file, $(SOURCE), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; ) + #$(foreach file, $(OBJ), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; ) + cp support/$(PROJECT_NAME).desktop $(BUILD_PATH)/generic + cp support/icons/26.png $(BUILD_PATH)/generic/26x26-$(PROJECT_NAME).png + cp support/icons/64.png $(BUILD_PATH)/generic/64x64-$(PROJECT_NAME).png + cp support/icons/scalable.png $(BUILD_PATH)/generic/scale-$(PROJECT_NAME).png + cp support/builddeb.py $(BUILD_PATH)/generic + cp support/py2deb.py $(BUILD_PATH)/generic + cp support/fake_py2deb.py $(BUILD_PATH)/generic + + mkdir -p $(BUILD_PATH)/chinook + cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/chinook + cd $(BUILD_PATH)/chinook ; python builddeb.py chinook + mkdir -p $(BUILD_PATH)/diablo + cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/diablo + cd $(BUILD_PATH)/diablo ; python builddeb.py diablo + mkdir -p $(BUILD_PATH)/fremantle + cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/fremantle + cd $(BUILD_PATH)/fremantle ; python builddeb.py fremantle + mkdir -p $(BUILD_PATH)/mer + cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/mer + cd $(BUILD_PATH)/mer ; python builddeb.py mer lint: $(OBJ) $(foreach file, $(SOURCE), $(LINT) $(file) ; ) diff --git a/src/constants.py b/src/constants.py index cbe768e..e0153d3 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,4 +1,5 @@ __pretty_app_name__ = "e**(j pi) + 1 = 0" __app_name__ = "ejpi" -__version__ = "0.9.5" +__version__ = "0.9.6" +__build__ = 1 __app_magic__ = 0xdeadbeef diff --git a/src/ejpi_glade.py b/src/ejpi_glade.py index 3350af1..bef1527 100755 --- a/src/ejpi_glade.py +++ b/src/ejpi_glade.py @@ -27,6 +27,7 @@ import sys import gc import os import string +import logging import warnings import gtk @@ -46,6 +47,8 @@ import gtk_toolbox import constants +_moduleLogger = logging.getLogger("ejpi_glade") + PLUGIN_SEARCH_PATHS = [ os.path.join(os.path.dirname(__file__), "plugins/"), ] @@ -402,6 +405,7 @@ class DummyOptions(object): if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) if len(sys.argv) > 1: try: import optparse diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py index 1f14788..dc1b403 100644 --- a/src/gtk_toolbox.py +++ b/src/gtk_toolbox.py @@ -2,16 +2,76 @@ from __future__ import with_statement +import os +import errno import sys -import traceback +import time +import itertools import functools import contextlib -import warnings +import logging +import threading +import Queue import gobject import gtk +_moduleLogger = logging.getLogger("gtk_toolbox") + + +def get_screen_orientation(): + width, height = gtk.gdk.get_default_root_window().get_size() + if width < height: + return gtk.ORIENTATION_VERTICAL + else: + return gtk.ORIENTATION_HORIZONTAL + + +def orientation_change_connect(handler, *args): + """ + @param handler(orientation, *args) -> None(?) + """ + initialScreenOrientation = get_screen_orientation() + orientationAndArgs = list(itertools.chain((initialScreenOrientation, ), args)) + + def _on_screen_size_changed(screen): + newScreenOrientation = get_screen_orientation() + if newScreenOrientation != orientationAndArgs[0]: + orientationAndArgs[0] = newScreenOrientation + handler(*orientationAndArgs) + + rootScreen = gtk.gdk.get_default_root_window() + return gtk.connect(rootScreen, "size-changed", _on_screen_size_changed) + + +@contextlib.contextmanager +def flock(path, timeout=-1): + WAIT_FOREVER = -1 + DELAY = 0.1 + timeSpent = 0 + + acquired = False + + while timeSpent <= timeout or timeout == WAIT_FOREVER: + try: + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR) + acquired = True + break + except OSError, e: + if e.errno != errno.EEXIST: + raise + time.sleep(DELAY) + timeSpent += DELAY + + assert acquired, "Failed to grab file-lock %s within timeout %d" % (path, timeout) + + try: + yield fd + finally: + os.unlink(path) + + @contextlib.contextmanager def gtk_lock(): gtk.gdk.threads_enter() @@ -80,6 +140,262 @@ def synchronous_gtk_message(original_func): return immediate_func +def autostart(func): + """ + >>> @autostart + ... def grep_sink(pattern): + ... print "Looking for %s" % pattern + ... while True: + ... line = yield + ... if pattern in line: + ... print line, + >>> g = grep_sink("python") + Looking for python + >>> g.send("Yeah but no but yeah but no") + >>> g.send("A series of tubes") + >>> g.send("python generators rock!") + python generators rock! + >>> g.close() + """ + + @functools.wraps(func) + def start(*args, **kwargs): + cr = func(*args, **kwargs) + cr.next() + return cr + + return start + + +@autostart +def printer_sink(format = "%s"): + """ + >>> pr = printer_sink("%r") + >>> pr.send("Hello") + 'Hello' + >>> pr.send("5") + '5' + >>> pr.send(5) + 5 + >>> p = printer_sink() + >>> p.send("Hello") + Hello + >>> p.send("World") + World + >>> # p.throw(RuntimeError, "Goodbye") + >>> # p.send("Meh") + >>> # p.close() + """ + while True: + item = yield + print format % (item, ) + + +@autostart +def null_sink(): + """ + Good for uses like with cochain to pick up any slack + """ + while True: + item = yield + + +@autostart +def comap(function, target): + """ + >>> p = printer_sink() + >>> cm = comap(lambda x: x+1, p) + >>> cm.send((0, )) + 1 + >>> cm.send((1.0, )) + 2.0 + >>> cm.send((-2, )) + -1 + """ + while True: + try: + item = yield + mappedItem = function(*item) + target.send(mappedItem) + except Exception, e: + _moduleLogger.exception("Forwarding exception!") + target.throw(e.__class__, str(e)) + + +def _flush_queue(queue): + while not queue.empty(): + yield queue.get() + + +@autostart +def queue_sink(queue): + """ + >>> q = Queue.Queue() + >>> qs = queue_sink(q) + >>> qs.send("Hello") + >>> qs.send("World") + >>> qs.throw(RuntimeError, "Goodbye") + >>> qs.send("Meh") + >>> qs.close() + >>> print [i for i in _flush_queue(q)] + [(None, 'Hello'), (None, 'World'), (, 'Goodbye'), (None, 'Meh'), (, None)] + """ + while True: + try: + item = yield + queue.put((None, item)) + except Exception, e: + queue.put((e.__class__, str(e))) + except GeneratorExit: + queue.put((GeneratorExit, None)) + raise + + +def decode_item(item, target): + if item[0] is None: + target.send(item[1]) + return False + elif item[0] is GeneratorExit: + target.close() + return True + else: + target.throw(item[0], item[1]) + return False + + +def nonqueue_source(queue, target): + isDone = False + while not isDone: + item = queue.get() + isDone = decode_item(item, target) + while not queue.empty(): + queue.get_nowait() + + +def threaded_stage(target, thread_factory = threading.Thread): + messages = Queue.Queue() + + run_source = functools.partial(nonqueue_source, messages, target) + thread = thread_factory(target=run_source) + thread.setDaemon(True) + thread.start() + + # Sink running in current thread + return queue_sink(messages) + + +class LoginWindow(object): + + def __init__(self, widgetTree): + """ + @note Thread agnostic + """ + self._dialog = widgetTree.get_widget("loginDialog") + self._parentWindow = widgetTree.get_widget("mainWindow") + self._serviceCombo = widgetTree.get_widget("serviceCombo") + self._usernameEntry = widgetTree.get_widget("usernameentry") + self._passwordEntry = widgetTree.get_widget("passwordentry") + + self._serviceList = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING) + self._serviceCombo.set_model(self._serviceList) + cell = gtk.CellRendererText() + self._serviceCombo.pack_start(cell, True) + self._serviceCombo.add_attribute(cell, 'text', 1) + self._serviceCombo.set_active(0) + + widgetTree.get_widget("loginbutton").connect("clicked", self._on_loginbutton_clicked) + widgetTree.get_widget("logins_close_button").connect("clicked", self._on_loginclose_clicked) + + def request_credentials(self, + parentWindow = None, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + self._serviceCombo.hide() + self._serviceList.clear() + + self._usernameEntry.set_text(defaultCredentials[0]) + self._passwordEntry.set_text(defaultCredentials[1]) + + try: + self._dialog.set_transient_for(parentWindow) + self._dialog.set_default_response(gtk.RESPONSE_OK) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + self._passwordEntry.set_text("") + finally: + self._dialog.hide() + + return username, password + + def request_credentials_from(self, + services, + parentWindow = None, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + self._serviceList.clear() + for serviceIdserviceName in services: + self._serviceList.append(serviceIdserviceName) + self._serviceCombo.set_active(0) + self._serviceCombo.show() + + self._usernameEntry.set_text(defaultCredentials[0]) + self._passwordEntry.set_text(defaultCredentials[1]) + + try: + self._dialog.set_transient_for(parentWindow) + self._dialog.set_default_response(gtk.RESPONSE_OK) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + finally: + self._dialog.hide() + + itr = self._serviceCombo.get_active_iter() + serviceId = int(self._serviceList.get_value(itr, 0)) + self._serviceList.clear() + return serviceId, username, password + + def _on_loginbutton_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + + def _on_loginclose_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_CANCEL) + + +def safecall(f, errorDisplay=None, default=None, exception=Exception): + ''' + Returns modified f. When the modified f is called and throws an + exception, the default value is returned + ''' + def _safecall(*args, **argv): + try: + return f(*args,**argv) + except exception, e: + if errorDisplay is not None: + errorDisplay.push_exception(e) + return default + return _safecall + + class ErrorDisplay(object): def __init__(self, widgetTree): @@ -99,31 +415,25 @@ class ErrorDisplay(object): self.push_message(message) def push_message(self, message): - if 0 < len(self.__messages): - self.__messages.append(message) - else: + self.__messages.append(message) + if 1 == len(self.__messages): self.__show_message(message) - def push_exception_with_lock(self, exception = None): + def push_exception_with_lock(self): with gtk_lock(): - self.push_exception(exception) + self.push_exception() - def push_exception(self, exception = None): - if exception is None: - userMessage = str(sys.exc_value) - warningMessage = str(traceback.format_exc()) - else: - userMessage = str(exception) - warningMessage = str(exception) + def push_exception(self): + userMessage = str(sys.exc_info()[1]) self.push_message(userMessage) - warnings.warn(warningMessage, stacklevel=3) + _moduleLogger.exception(userMessage) def pop_message(self): - if 0 < len(self.__messages): - self.__show_message(self.__messages[0]) - del self.__messages[0] - else: + del self.__messages[0] + if 0 == len(self.__messages): self.__hide_message() + else: + self.__errorDescription.set_text(self.__messages[0]) def _on_close(self, *args): self.pop_message() @@ -155,11 +465,8 @@ class DummyErrorDisplay(object): self.__show_message(message) def push_exception(self, exception = None): - if exception is None: - warningMessage = traceback.format_exc() - else: - warningMessage = exception - warnings.warn(warningMessage, stacklevel=3) + userMessage = str(sys.exc_value) + _moduleLogger.exception(userMessage) def pop_message(self): if 0 < len(self.__messages): @@ -167,7 +474,7 @@ class DummyErrorDisplay(object): del self.__messages[0] def __show_message(self, message): - warnings.warn(message, stacklevel=2) + _moduleLogger.debug(message) class MessageBox(gtk.MessageDialog): @@ -240,8 +547,8 @@ class PopupCalendar(object): try: self._calendar.select_month(self._displayDate.month, self._displayDate.year) self._calendar.select_day(self._displayDate.day) - except StandardError, e: - warnings.warn(e.message) + except Exception, e: + _moduleLogger.exception(e) class QuickAddView(object): @@ -291,7 +598,7 @@ class QuickAddView(object): self._taskNameEntry.set_text("") self._signalSink.stage.send(("add", name)) - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_add_edit(self, *args): @@ -300,13 +607,13 @@ class QuickAddView(object): self._taskNameEntry.set_text("") self._signalSink.stage.send(("add-edit", name)) - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_add_pressed(self, widget): try: self._addToEditTimerId = gobject.timeout_add(1000, self._on_add_edit) - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_add_released(self, widget): @@ -314,7 +621,7 @@ class QuickAddView(object): if self._addToEditTimerId is not None: gobject.source_remove(self._addToEditTimerId) self._addToEditTimerId = None - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_paste(self, *args): @@ -324,16 +631,109 @@ class QuickAddView(object): if addedText: entry += addedText self._taskNameEntry.set_text(entry) - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_clear(self, *args): try: self._taskNameEntry.set_text("") - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() +class TapOrHold(object): + + def __init__(self, widget): + self._widget = widget + self._isTap = True + self._isPointerInside = True + self._holdTimeoutId = None + self._tapTimeoutId = None + self._taps = 0 + + self._bpeId = None + self._breId = None + self._eneId = None + self._lneId = None + + def enable(self): + self._bpeId = self._widget.connect("button-press-event", self._on_button_press) + self._breId = self._widget.connect("button-release-event", self._on_button_release) + self._eneId = self._widget.connect("enter-notify-event", self._on_enter) + self._lneId = self._widget.connect("leave-notify-event", self._on_leave) + + def disable(self): + self._widget.disconnect(self._bpeId) + self._widget.disconnect(self._breId) + self._widget.disconnect(self._eneId) + self._widget.disconnect(self._lneId) + + def on_tap(self, taps): + print "TAP", taps + + def on_hold(self, taps): + print "HOLD", taps + + def on_holding(self): + print "HOLDING" + + def on_cancel(self): + print "CANCEL" + + def _on_button_press(self, *args): + # Hack to handle weird notebook behavior + self._isPointerInside = True + self._isTap = True + + if self._tapTimeoutId is not None: + gobject.source_remove(self._tapTimeoutId) + self._tapTimeoutId = None + + # Handle double taps + if self._holdTimeoutId is None: + self._tapTimeoutId = None + + self._taps = 1 + self._holdTimeoutId = gobject.timeout_add(1000, self._on_hold_timeout) + else: + self._taps = 2 + + def _on_button_release(self, *args): + assert self._tapTimeoutId is None + # Handle release after timeout if user hasn't double-clicked + self._tapTimeoutId = gobject.timeout_add(100, self._on_tap_timeout) + + def _on_actual_press(self, *args): + if self._holdTimeoutId is not None: + gobject.source_remove(self._holdTimeoutId) + self._holdTimeoutId = None + + if self._isPointerInside: + if self._isTap: + self.on_tap(self._taps) + else: + self.on_hold(self._taps) + else: + self.on_cancel() + + def _on_tap_timeout(self, *args): + self._tapTimeoutId = None + self._on_actual_press() + return False + + def _on_hold_timeout(self, *args): + self._holdTimeoutId = None + self._isTap = False + self.on_holding() + return False + + def _on_enter(self, *args): + self._isPointerInside = True + + def _on_leave(self, *args): + self._isPointerInside = False + + if __name__ == "__main__": if True: win = gtk.Window() diff --git a/src/hildonize.py b/src/hildonize.py new file mode 100755 index 0000000..a95a4ac --- /dev/null +++ b/src/hildonize.py @@ -0,0 +1,732 @@ +#!/usr/bin/env python + +""" +Open Issues + @bug not all of a message is shown + @bug Buttons are too small +""" + + +import gobject +import gtk +import dbus + + +class _NullHildonModule(object): + pass + + +try: + import hildon as _hildon + hildon = _hildon # Dumb but gets around pyflakiness +except (ImportError, OSError): + hildon = _NullHildonModule + + +IS_HILDON_SUPPORTED = hildon is not _NullHildonModule + + +class _NullHildonProgram(object): + + def add_window(self, window): + pass + + +def _hildon_get_app_class(): + return hildon.Program + + +def _null_get_app_class(): + return _NullHildonProgram + + +try: + hildon.Program + get_app_class = _hildon_get_app_class +except AttributeError: + get_app_class = _null_get_app_class + + +def _hildon_set_application_title(window, title): + pass + + +def _null_set_application_title(window, title): + window.set_title(title) + + +if IS_HILDON_SUPPORTED: + set_application_title = _hildon_set_application_title +else: + set_application_title = _null_set_application_title + + +def _fremantle_hildonize_window(app, window): + oldWindow = window + newWindow = hildon.StackableWindow() + oldWindow.get_child().reparent(newWindow) + app.add_window(newWindow) + return newWindow + + +def _hildon_hildonize_window(app, window): + oldWindow = window + newWindow = hildon.Window() + oldWindow.get_child().reparent(newWindow) + app.add_window(newWindow) + return newWindow + + +def _null_hildonize_window(app, window): + return window + + +try: + hildon.StackableWindow + hildonize_window = _fremantle_hildonize_window +except AttributeError: + try: + hildon.Window + hildonize_window = _hildon_hildonize_window + except AttributeError: + hildonize_window = _null_hildonize_window + + +def _fremantle_hildonize_menu(window, gtkMenu, buttons): + appMenu = hildon.AppMenu() + for button in buttons: + appMenu.append(button) + window.set_app_menu(appMenu) + gtkMenu.get_parent().remove(gtkMenu) + return appMenu + + +def _hildon_hildonize_menu(window, gtkMenu, ignoredButtons): + hildonMenu = gtk.Menu() + for child in gtkMenu.get_children(): + child.reparent(hildonMenu) + window.set_menu(hildonMenu) + gtkMenu.destroy() + return hildonMenu + + +def _null_hildonize_menu(window, gtkMenu, ignoredButtons): + return gtkMenu + + +try: + hildon.AppMenu + GTK_MENU_USED = False + IS_FREMANTLE_SUPPORTED = True + hildonize_menu = _fremantle_hildonize_menu +except AttributeError: + GTK_MENU_USED = True + IS_FREMANTLE_SUPPORTED = False + if IS_HILDON_SUPPORTED: + hildonize_menu = _hildon_hildonize_menu + else: + hildonize_menu = _null_hildonize_menu + + +def _hildon_set_button_auto_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT) + + +def _null_set_button_auto_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_AUTO_HEIGHT + gtk.Button.set_theme_size + set_button_auto_selectable = _hildon_set_button_auto_selectable +except AttributeError: + set_button_auto_selectable = _null_set_button_auto_selectable + + +def _hildon_set_button_finger_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT) + + +def _null_set_button_finger_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_FINGER_HEIGHT + gtk.Button.set_theme_size + set_button_finger_selectable = _hildon_set_button_finger_selectable +except AttributeError: + set_button_finger_selectable = _null_set_button_finger_selectable + + +def _hildon_set_button_thumb_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT) + + +def _null_set_button_thumb_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_THUMB_HEIGHT + gtk.Button.set_theme_size + set_button_thumb_selectable = _hildon_set_button_thumb_selectable +except AttributeError: + set_button_thumb_selectable = _null_set_button_thumb_selectable + + +def _hildon_set_cell_thumb_selectable(renderer): + renderer.set_property("scale", 1.5) + + +def _null_set_cell_thumb_selectable(renderer): + pass + + +if IS_HILDON_SUPPORTED: + set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable +else: + set_cell_thumb_selectable = _null_set_cell_thumb_selectable + + +def _fremantle_show_information_banner(parent, message): + hildon.hildon_banner_show_information(parent, "", message) + + +def _hildon_show_information_banner(parent, message): + hildon.hildon_banner_show_information(parent, None, message) + + +def _null_show_information_banner(parent, message): + pass + + +if IS_FREMANTLE_SUPPORTED: + show_information_banner = _fremantle_show_information_banner +else: + try: + hildon.hildon_banner_show_information + show_information_banner = _hildon_show_information_banner + except AttributeError: + show_information_banner = _null_show_information_banner + + +def _fremantle_show_busy_banner_start(parent, message): + hildon.hildon_gtk_window_set_progress_indicator(parent, True) + return parent + + +def _fremantle_show_busy_banner_end(banner): + hildon.hildon_gtk_window_set_progress_indicator(banner, False) + + +def _hildon_show_busy_banner_start(parent, message): + return hildon.hildon_banner_show_animation(parent, None, message) + + +def _hildon_show_busy_banner_end(banner): + banner.destroy() + + +def _null_show_busy_banner_start(parent, message): + return None + + +def _null_show_busy_banner_end(banner): + assert banner is None + + +try: + hildon.hildon_gtk_window_set_progress_indicator + show_busy_banner_start = _fremantle_show_busy_banner_start + show_busy_banner_end = _fremantle_show_busy_banner_end +except AttributeError: + try: + hildon.hildon_banner_show_animation + show_busy_banner_start = _hildon_show_busy_banner_start + show_busy_banner_end = _hildon_show_busy_banner_end + except AttributeError: + show_busy_banner_start = _null_show_busy_banner_start + show_busy_banner_end = _null_show_busy_banner_end + + +def _hildon_hildonize_text_entry(textEntry): + textEntry.set_property('hildon-input-mode', 7) + + +def _null_hildonize_text_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_text_entry = _hildon_hildonize_text_entry +else: + hildonize_text_entry = _null_hildonize_text_entry + + +def _hildon_mark_window_rotatable(window): + # gtk documentation is unclear whether this does a "=" or a "|=" + window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT) + + +def _null_mark_window_rotatable(window): + pass + + +try: + hildon.HILDON_PORTRAIT_MODE_SUPPORT + mark_window_rotatable = _hildon_mark_window_rotatable +except AttributeError: + mark_window_rotatable = _null_mark_window_rotatable + + +def _hildon_window_to_portrait(window): + # gtk documentation is unclear whether this does a "=" or a "|=" + window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT) + + +def _hildon_window_to_landscape(window): + # gtk documentation is unclear whether this does a "=" or a "&= ~" + window.unset_flags(hildon.HILDON_PORTRAIT_MODE_REQUEST) + + +def _null_window_to_portrait(window): + pass + + +def _null_window_to_landscape(window): + pass + + +try: + hildon.HILDON_PORTRAIT_MODE_SUPPORT + hildon.HILDON_PORTRAIT_MODE_REQUEST + + window_to_portrait = _hildon_window_to_portrait + window_to_landscape = _hildon_window_to_landscape +except AttributeError: + window_to_portrait = _null_window_to_portrait + window_to_landscape = _null_window_to_landscape + + +def get_device_orientation(): + bus = dbus.SystemBus() + try: + rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request") + mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request") + orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation() + except dbus.exception.DBusException: + # catching for documentation purposes that when a system doesn't + # support this, this is what to expect + raise + + if orientation == "": + return gtk.ORIENTATION_HORIZONTAL + elif orientation == "": + return gtk.ORIENTATION_VERTICAL + else: + raise RuntimeError("Unknown orientation: %s" % orientation) + + +def _hildon_hildonize_password_entry(textEntry): + textEntry.set_property('hildon-input-mode', 7 | (1 << 29)) + + +def _null_hildonize_password_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_password_entry = _hildon_hildonize_password_entry +else: + hildonize_password_entry = _null_hildonize_password_entry + + +def _hildon_hildonize_combo_entry(comboEntry): + comboEntry.set_property('hildon-input-mode', 1 << 4) + + +def _null_hildonize_combo_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_combo_entry = _hildon_hildonize_combo_entry +else: + hildonize_combo_entry = _null_hildonize_combo_entry + + +def _fremantle_hildonize_scrollwindow(scrolledWindow): + pannableWindow = hildon.PannableArea() + + child = scrolledWindow.get_child() + scrolledWindow.remove(child) + pannableWindow.add(child) + + parent = scrolledWindow.get_parent() + parent.remove(scrolledWindow) + parent.add(pannableWindow) + + return pannableWindow + + +def _hildon_hildonize_scrollwindow(scrolledWindow): + hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True) + return scrolledWindow + + +def _null_hildonize_scrollwindow(scrolledWindow): + return scrolledWindow + + +try: + hildon.PannableArea + hildonize_scrollwindow = _fremantle_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow +except AttributeError: + try: + hildon.hildon_helper_set_thumb_scrollbar + hildonize_scrollwindow = _hildon_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow + except AttributeError: + hildonize_scrollwindow = _null_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow + + +def _hildon_request_number(parent, title, range, default): + spinner = hildon.NumberEditor(*range) + spinner.set_value(default) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(spinner) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return spinner.get_value() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +def _null_request_number(parent, title, range, default): + adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0) + spinner = gtk.SpinButton(adjustment, 0, 0) + spinner.set_wrap(False) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(spinner) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return spinner.get_value_as_int() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.NumberEditor # TODO deprecated in fremantle + request_number = _hildon_request_number +except AttributeError: + request_number = _null_request_number + + +def _hildon_touch_selector(parent, title, items, defaultIndex): + model = gtk.ListStore(gobject.TYPE_STRING) + for item in items: + model.append((item, )) + + selector = hildon.TouchSelector() + selector.append_text_column(model, True) + selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE) + selector.set_active(0, defaultIndex) + + dialog = hildon.PickerDialog(parent) + dialog.set_selector(selector) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return selector.get_active(0) + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData): + dialog.response(gtk.RESPONSE_OK) + pathData[0] = path + + +def _null_touch_selector(parent, title, items, defaultIndex = -1): + parentSize = parent.get_size() + + model = gtk.ListStore(gobject.TYPE_STRING) + for item in items: + model.append((item, )) + + cell = gtk.CellRendererText() + set_cell_thumb_selectable(cell) + column = gtk.TreeViewColumn(title) + column.pack_start(cell, expand=True) + column.add_attribute(cell, "text", 0) + + treeView = gtk.TreeView() + treeView.set_model(model) + treeView.append_column(column) + selection = treeView.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + if 0 < defaultIndex: + selection.select_path((defaultIndex, )) + + scrolledWin = gtk.ScrolledWindow() + scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledWin.add(treeView) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(scrolledWin) + dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) + + scrolledWin = hildonize_scrollwindow(scrolledWin) + pathData = [None] + treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + if pathData[0] is None: + raise RuntimeError("No selection made") + return pathData[0][0] + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.PickerDialog + hildon.TouchSelector + touch_selector = _hildon_touch_selector +except AttributeError: + touch_selector = _null_touch_selector + + +def _hildon_touch_selector_entry(parent, title, items, defaultItem): + # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way + try: + selector = hildon.TouchSelectorEntry(text=True) + except TypeError: + selector = hildon.hildon_touch_selector_entry_new_text() + defaultIndex = -1 + for i, item in enumerate(items): + selector.append_text(item) + if item == defaultItem: + defaultIndex = i + + dialog = hildon.PickerDialog(parent) + dialog.set_selector(selector) + + if 0 < defaultIndex: + selector.set_active(0, defaultIndex) + else: + selector.get_entry().set_text(defaultItem) + + try: + dialog.show_all() + response = dialog.run() + finally: + dialog.hide() + + if response == gtk.RESPONSE_OK: + return selector.get_entry().get_text() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + + +def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex): + custom = entry.get_text().strip() + if custom: + result[0] = custom + selection.unselect_all() + else: + result[0] = None + selection.select_path((defaultIndex, )) + + +def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result): + dialog.response(gtk.RESPONSE_OK) + result[0] = customEntry.get_text() + + +def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result): + dialog.response(gtk.RESPONSE_OK) + model = treeView.get_model() + itr = model.get_iter(path) + if itr is not None: + result[0] = model.get_value(itr, 0) + + +def _null_touch_selector_entry(parent, title, items, defaultItem): + parentSize = parent.get_size() + + model = gtk.ListStore(gobject.TYPE_STRING) + defaultIndex = -1 + for i, item in enumerate(items): + model.append((item, )) + if item == defaultItem: + defaultIndex = i + + cell = gtk.CellRendererText() + set_cell_thumb_selectable(cell) + column = gtk.TreeViewColumn(title) + column.pack_start(cell, expand=True) + column.add_attribute(cell, "text", 0) + + treeView = gtk.TreeView() + treeView.set_model(model) + treeView.append_column(column) + selection = treeView.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + + scrolledWin = gtk.ScrolledWindow() + scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledWin.add(treeView) + + customEntry = gtk.Entry() + + layout = gtk.VBox() + layout.pack_start(customEntry, expand=False) + layout.pack_start(scrolledWin) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(layout) + dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) + + scrolledWin = hildonize_scrollwindow(scrolledWin) + + result = [None] + if 0 < defaultIndex: + selection.select_path((defaultIndex, )) + result[0] = defaultItem + else: + customEntry.set_text(defaultItem) + + customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result) + customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex) + treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + _, itr = selection.get_selected() + if itr is not None: + return model.get_value(itr, 0) + else: + enteredText = customEntry.get_text().strip() + if enteredText: + return enteredText + elif result[0] is not None: + return result[0] + else: + raise RuntimeError("No selection made") + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.PickerDialog + hildon.TouchSelectorEntry + touch_selector_entry = _hildon_touch_selector_entry +except AttributeError: + touch_selector_entry = _null_touch_selector_entry + + +if __name__ == "__main__": + app = get_app_class()() + + label = gtk.Label("Hello World from a Label!") + + win = gtk.Window() + win.add(label) + win = hildonize_window(app, win) + if False: + print touch_selector(win, "Test", ["A", "B", "C", "D"], 2) + if True: + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C") + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah") + if False: + import pprint + name, value = "", "" + goodLocals = [ + (name, value) for (name, value) in locals().iteritems() + if not name.startswith("_") + ] + pprint.pprint(goodLocals) + if False: + import time + show_information_banner(win, "Hello World") + time.sleep(5) + if False: + import time + banner = show_busy_banner_start(win, "Hello World") + time.sleep(5) + show_busy_banner_end(banner) diff --git a/support/builddeb.py b/support/builddeb.py index 41bce27..05836c5 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -12,12 +12,15 @@ import constants __appname__ = constants.__app_name__ -__description__ = "A Touch Screen Optimized RPN Calculator using Pie Menus" +__description__ = """A Touch Screen Optimized RPN Calculator using Pie Menus +. +Homepage: http://ejpi.garage.maemo.org/ +""" __author__ = "Ed Page" __email__ = "eopage@byu.net" __version__ = constants.__version__ -__build__ = 0 -__changelog__ = ''' +__build__ = constants.__build__ +__changelog__ = """ 0.9.5 0.9.4 @@ -46,13 +49,13 @@ __changelog__ = ''' * Modifiable history * Supports different number types and bases * Basic trig support -''' +""" -__postinstall__ = '''#!/bin/sh -e +__postinstall__ = """#!/bin/sh -e gtk-update-icon-cache -f /usr/share/icons/hicolor -''' +""" def find_files(path): @@ -81,23 +84,41 @@ def build_package(distribution): except: pass + py2deb.Py2deb.SECTIONS = py2deb.SECTIONS_BY_POLICY[distribution] p = py2deb.Py2deb(__appname__) + p.prettyName = constants.__pretty_app_name__ p.description = __description__ + p.upgradeDescription = __changelog__.split("\n\n", 1)[0] p.author = __author__ p.mail = __email__ p.license = "lgpl" - p.depends = { - "diablo": "python2.5, python2.5-gtk2", - "mer": "python2.6, python-gtk2, python-glade2", + p.depends = ", ".join([ + "python2.6 | python2.5", + "python-gtk2 | python2.5-gtk2", + "python-xml | python2.5-xml", + ]) + maemoSpecificDepends = ", python-osso | python2.5-osso, python-hildon | python2.5-hildon" + p.depends += { + "debian": ", python-glade2", + "chinook": maemoSpecificDepends, + "diablo": maemoSpecificDepends, + "fremantle": maemoSpecificDepends + ", python-glade2", + "mer": maemoSpecificDepends + ", python-glade2", }[distribution] p.section = "user/accessories" p.arch = "all" p.urgency = "low" - p.distribution = "chinook diablo fremantle mer" + p.distribution = "chinook diablo fremantle mer debian" p.repository = "extras" p.changelog = __changelog__ p.postinstall = __postinstall__ - p.icon = "26x26-ejpi.png" + p.icon = { + "debian": "26x26-ejpi.png", + "chinook": "26x26-ejpi.png", + "diablo": "26x26-ejpi.png", + "fremantle": "64x64-ejpi.png", # Fremantle natively uses 48x48 + "mer": "64x64-ejpi.png", + }[distribution] p["/usr/bin"] = [ "ejpi.py" ] for relPath, files in unflatten_files(find_files(".")).iteritems(): fullPath = "/usr/lib/ejpi" @@ -114,8 +135,12 @@ def build_package(distribution): print p print p.generate( - __version__, __build__, changelog=__changelog__, - tar=True, dsc=True, changes=True, build=False, src=True + version="%s-%s" % (__version__, __build__), + changelog=__changelog__, + build=False, + tar=True, + changes=True, + dsc=True, ) print "Building for %s finished" % distribution diff --git a/support/ejpi.desktop b/support/ejpi.desktop index e3605ba..6c3e232 100644 --- a/support/ejpi.desktop +++ b/support/ejpi.desktop @@ -3,5 +3,5 @@ Encoding=UTF-8 Version=1.0 Type=Application Name=ejpi -Exec=/usr/bin/ejpi.py +Exec=/usr/bin/run-standalone.sh /usr/bin/ejpi.py Icon=ejpi diff --git a/support/py2deb.py b/support/py2deb.py new file mode 100644 index 0000000..6018f91 --- /dev/null +++ b/support/py2deb.py @@ -0,0 +1,989 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +## +## Copyright (C) 2009 manatlan manatlan[at]gmail(dot)com +## +## 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; version 2 only. +## +## 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. +## +""" +Known limitations : +- don't sign package (-us -uc) +- no distinctions between author and maintainer(packager) + +depends on : +- dpkg-dev (dpkg-buildpackage) +- alien +- python +- fakeroot + +changelog + - ??? ?/??/20?? (By epage) + - PEP8 + - added recommends + - fixed bug where it couldn't handle the contents of the pre/post scripts being specified + - Added customization based on the targeted policy for sections (Maemo support) + - Added maemo specific tarball, dsc, changes file generation support (including icon support) + - Added armel architecture + - Reduced the size of params being passed around by reducing the calls to locals() + - Added respository, distribution, priority + - Made setting control file a bit more flexible + - 0.5 05/09/2009 + - pre/post install/remove scripts enabled + - deb package install py2deb in dist-packages for py2.6 + - 0.4 14/10/2008 + - use os.environ USERNAME or USER (debian way) + - install on py 2.(4,5,6) (*FIX* do better here) + +""" + +import os +import hashlib +import sys +import shutil +import time +import string +import StringIO +import stat +import commands +import base64 +import tarfile +from glob import glob +from datetime import datetime +import socket # gethostname() +from subprocess import Popen, PIPE + +#~ __version__ = "0.4" +__version__ = "0.5" +__author__ = "manatlan" +__mail__ = "manatlan@gmail.com" + + +PERMS_URW_GRW_OR = stat.S_IRUSR | stat.S_IWUSR | \ + stat.S_IRGRP | stat.S_IWGRP | \ + stat.S_IROTH + +UID_ROOT = 0 +GID_ROOT = 0 + + +def run(cmds): + p = Popen(cmds, shell=False, stdout=PIPE, stderr=PIPE) + time.sleep(0.01) # to avoid "IOError: [Errno 4] Interrupted system call" + out = string.join(p.stdout.readlines()).strip() + outerr = string.join(p.stderr.readlines()).strip() + return out + + +def deb2rpm(file): + txt=run(['alien', '-r', file]) + return txt.split(" generated")[0] + + +def py2src(TEMP, name): + l=glob("%(TEMP)s/%(name)s*.tar.gz" % locals()) + if len(l) != 1: + raise Py2debException("don't find source package tar.gz") + + tar = os.path.basename(l[0]) + shutil.move(l[0], tar) + + return tar + + +def md5sum(filename): + f = open(filename, "r") + try: + return hashlib.md5(f.read()).hexdigest() + finally: + f.close() + + +class Py2changes(object): + + def __init__(self, ChangedBy, description, changes, files, category, repository, **kwargs): + self.options = kwargs # TODO: Is order important? + self.description = description + self.changes=changes + self.files=files + self.category=category + self.repository=repository + self.ChangedBy=ChangedBy + + def getContent(self): + content = ["%s: %s" % (k, v) + for k,v in self.options.iteritems()] + + if self.description: + description=self.description.replace("\n","\n ") + content.append('Description: ') + content.append(' %s' % description) + if self.changes: + changes=self.changes.replace("\n","\n ") + content.append('Changes: ') + content.append(' %s' % changes) + if self.ChangedBy: + content.append("Changed-By: %s" % self.ChangedBy) + + content.append('Files:') + + for onefile in self.files: + md5 = md5sum(onefile) + size = os.stat(onefile).st_size.__str__() + content.append(' ' + md5 + ' ' + size + ' ' + self.category +' '+self.repository+' '+os.path.basename(onefile)) + + return "\n".join(content) + "\n\n" + + +def py2changes(params): + changescontent = Py2changes( + "%(author)s <%(mail)s>" % params, + "%(description)s" % params, + "%(changelog)s" % params, + ( + "%(TEMP)s/%(name)s_%(version)s.tar.gz" % params, + "%(TEMP)s/%(name)s_%(version)s.dsc" % params, + ), + "%(section)s" % params, + "%(repository)s" % params, + Format='1.7', + Date=time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()), + Source="%(name)s" % params, + Architecture="%(arch)s" % params, + Version="%(version)s" % params, + Distribution="%(distribution)s" % params, + Urgency="%(urgency)s" % params, + Maintainer="%(author)s <%(mail)s>" % params + ) + f = open("%(TEMP)s/%(name)s_%(version)s.changes" % params,"wb") + f.write(changescontent.getContent()) + f.close() + + fileHandle = open('/tmp/py2deb2.tmp', 'w') + fileHandle.write('#!/bin/sh\n') + fileHandle.write("cd " +os.getcwd()+ "\n") + fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.changes\n" % params) + fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.changes.asc %(TEMP)s/%(name)s_%(version)s.changes\n" % params) + fileHandle.write('\nexit') + fileHandle.close() + commands.getoutput("chmod 777 /tmp/py2deb2.tmp") + commands.getoutput("/tmp/py2deb2.tmp") + + ret = [] + + l=glob("%(TEMP)s/%(name)s*.tar.gz" % params) + if len(l)!=1: + raise Py2debException("don't find source package tar.gz") + tar = os.path.basename(l[0]) + shutil.move(l[0],tar) + ret.append(tar) + + l=glob("%(TEMP)s/%(name)s*.dsc" % params) + if len(l)!=1: + raise Py2debException("don't find source package dsc") + tar = os.path.basename(l[0]) + shutil.move(l[0],tar) + ret.append(tar) + + l=glob("%(TEMP)s/%(name)s*.changes" % params) + if len(l)!=1: + raise Py2debException("don't find source package changes") + tar = os.path.basename(l[0]) + shutil.move(l[0],tar) + ret.append(tar) + + return ret + + +class Py2dsc(object): + + def __init__(self, StandardsVersion, BuildDepends, files, **kwargs): + self.options = kwargs # TODO: Is order important? + self.StandardsVersion = StandardsVersion + self.BuildDepends=BuildDepends + self.files=files + + @property + def content(self): + content = ["%s: %s" % (k, v) + for k,v in self.options.iteritems()] + + if self.BuildDepends: + content.append("Build-Depends: %s" % self.BuildDepends) + if self.StandardsVersion: + content.append("Standards-Version: %s" % self.StandardsVersion) + + content.append('Files:') + + for onefile in self.files: + print onefile + md5 = md5sum(onefile) + size = os.stat(onefile).st_size.__str__() + content.append(' '+md5 + ' ' + size +' '+os.path.basename(onefile)) + + return "\n".join(content)+"\n\n" + + +def py2dsc(TEMP, name, version, depends, author, mail, arch): + dsccontent = Py2dsc( + "%(version)s" % locals(), + "%(depends)s" % locals(), + ("%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals(),), + Format='1.0', + Source="%(name)s" % locals(), + Version="%(version)s" % locals(), + Maintainer="%(author)s <%(mail)s>" % locals(), + Architecture="%(arch)s" % locals(), + ) + + filename = "%(TEMP)s/%(name)s_%(version)s.dsc" % locals() + + f = open(filename, "wb") + try: + f.write(dsccontent.content) + finally: + f.close() + + fileHandle = open('/tmp/py2deb.tmp', 'w') + try: + fileHandle.write('#!/bin/sh\n') + fileHandle.write("cd " + os.getcwd() + "\n") + fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.dsc\n" % locals()) + fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.dsc.asc %(filename)s\n" % locals()) + fileHandle.write('\nexit') + fileHandle.close() + finally: + f.close() + + commands.getoutput("chmod 777 /tmp/py2deb.tmp") + commands.getoutput("/tmp/py2deb.tmp") + + return filename + + +class Py2tar(object): + + def __init__(self, dataDirectoryPath): + self._dataDirectoryPath = dataDirectoryPath + + def packed(self): + return self._getSourcesFiles() + + def _getSourcesFiles(self): + directoryPath = self._dataDirectoryPath + + outputFileObj = StringIO.StringIO() # TODO: Do more transparently? + + tarOutput = tarfile.TarFile.open('sources', + mode = "w:gz", + fileobj = outputFileObj) + + # Note: We can't use this because we need to fiddle permissions: + # tarOutput.add(directoryPath, arcname = "") + + for root, dirs, files in os.walk(directoryPath): + archiveRoot = root[len(directoryPath):] + + tarinfo = tarOutput.gettarinfo(root, archiveRoot) + # TODO: Make configurable? + tarinfo.uid = UID_ROOT + tarinfo.gid = GID_ROOT + tarinfo.uname = "" + tarinfo.gname = "" + tarOutput.addfile(tarinfo) + + for f in files: + tarinfo = tarOutput.gettarinfo(os.path.join(root, f), + os.path.join(archiveRoot, f)) + tarinfo.uid = UID_ROOT + tarinfo.gid = GID_ROOT + tarinfo.uname = "" + tarinfo.gname = "" + tarOutput.addfile(tarinfo, file(os.path.join(root, f))) + + tarOutput.close() + + data_tar_gz = outputFileObj.getvalue() + + return data_tar_gz + + +def py2tar(DEST, TEMP, name, version): + tarcontent = Py2tar("%(DEST)s" % locals()) + filename = "%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals() + f = open(filename, "wb") + try: + f.write(tarcontent.packed()) + finally: + f.close() + return filename + + +class Py2debException(Exception): + pass + + +SECTIONS_BY_POLICY = { + # http://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections + "debian": "admin, base, comm, contrib, devel, doc, editors, electronics, embedded, games, gnome, graphics, hamradio, interpreters, kde, libs, libdevel, mail, math, misc, net, news, non-free, oldlibs, otherosfs, perl, python, science, shells, sound, tex, text, utils, web, x11", + # http://maemo.org/forrest-images/pdf/maemo-policy.pdf + "chinook": "accessories, communication, games, multimedia, office, other, programming, support, themes, tools", + # http://wiki.maemo.org/Task:Package_categories + "diablo": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities", + # http://wiki.maemo.org/Task:Fremantle_application_categories + "mer": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities", + # http://wiki.maemo.org/Task:Fremantle_application_categories + "fremantle": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities", +} + + +LICENSE_AGREEMENT = { + "gpl": """ + This package 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 package 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 package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. +""", + "lgpl":""" + This package is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This package 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU Lesser General +Public License can be found in `/usr/share/common-licenses/LGPL'. +""", + "bsd": """ + Redistribution and use in source and binary forms, with or without + modification, are permitted under the terms of the BSD License. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +On Debian systems, the complete text of the BSD License can be +found in `/usr/share/common-licenses/BSD'. +""", + "artistic": """ + This program is free software; you can redistribute it and/or modify it + under the terms of the "Artistic License" which comes with Debian. + + THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES + OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +On Debian systems, the complete text of the Artistic License +can be found in `/usr/share/common-licenses/Artistic'. +""" +} + + +class Py2deb(object): + """ + heavily based on technic described here : + http://wiki.showmedo.com/index.php?title=LinuxJensMakingDeb + """ + ## STATICS + clear = False # clear build folder after py2debianization + + SECTIONS = SECTIONS_BY_POLICY["debian"] + + #http://www.debian.org/doc/debian-policy/footnotes.html#f69 + ARCHS = "all i386 ia64 alpha amd64 armeb arm hppa m32r m68k mips mipsel powerpc ppc64 s390 s390x sh3 sh3eb sh4 sh4eb sparc darwin-i386 darwin-ia64 darwin-alpha darwin-amd64 darwin-armeb darwin-arm darwin-hppa darwin-m32r darwin-m68k darwin-mips darwin-mipsel darwin-powerpc darwin-ppc64 darwin-s390 darwin-s390x darwin-sh3 darwin-sh3eb darwin-sh4 darwin-sh4eb darwin-sparc freebsd-i386 freebsd-ia64 freebsd-alpha freebsd-amd64 freebsd-armeb freebsd-arm freebsd-hppa freebsd-m32r freebsd-m68k freebsd-mips freebsd-mipsel freebsd-powerpc freebsd-ppc64 freebsd-s390 freebsd-s390x freebsd-sh3 freebsd-sh3eb freebsd-sh4 freebsd-sh4eb freebsd-sparc kfreebsd-i386 kfreebsd-ia64 kfreebsd-alpha kfreebsd-amd64 kfreebsd-armeb kfreebsd-arm kfreebsd-hppa kfreebsd-m32r kfreebsd-m68k kfreebsd-mips kfreebsd-mipsel kfreebsd-powerpc kfreebsd-ppc64 kfreebsd-s390 kfreebsd-s390x kfreebsd-sh3 kfreebsd-sh3eb kfreebsd-sh4 kfreebsd-sh4eb kfreebsd-sparc knetbsd-i386 knetbsd-ia64 knetbsd-alpha knetbsd-amd64 knetbsd-armeb knetbsd-arm knetbsd-hppa knetbsd-m32r knetbsd-m68k knetbsd-mips knetbsd-mipsel knetbsd-powerpc knetbsd-ppc64 knetbsd-s390 knetbsd-s390x knetbsd-sh3 knetbsd-sh3eb knetbsd-sh4 knetbsd-sh4eb knetbsd-sparc netbsd-i386 netbsd-ia64 netbsd-alpha netbsd-amd64 netbsd-armeb netbsd-arm netbsd-hppa netbsd-m32r netbsd-m68k netbsd-mips netbsd-mipsel netbsd-powerpc netbsd-ppc64 netbsd-s390 netbsd-s390x netbsd-sh3 netbsd-sh3eb netbsd-sh4 netbsd-sh4eb netbsd-sparc openbsd-i386 openbsd-ia64 openbsd-alpha openbsd-amd64 openbsd-armeb openbsd-arm openbsd-hppa openbsd-m32r openbsd-m68k openbsd-mips openbsd-mipsel openbsd-powerpc openbsd-ppc64 openbsd-s390 openbsd-s390x openbsd-sh3 openbsd-sh3eb openbsd-sh4 openbsd-sh4eb openbsd-sparc hurd-i386 hurd-ia64 hurd-alpha hurd-amd64 hurd-armeb hurd-arm hurd-hppa hurd-m32r hurd-m68k hurd-mips hurd-mipsel hurd-powerpc hurd-ppc64 hurd-s390 hurd-s390x hurd-sh3 hurd-sh3eb hurd-sh4 hurd-sh4eb hurd-sparc armel".split(" ") + + # license terms taken from dh_make + LICENSES = list(LICENSE_AGREEMENT.iterkeys()) + + def __setitem__(self, path, files): + + if not type(files)==list: + raise Py2debException("value of key path '%s' is not a list"%path) + if not files: + raise Py2debException("value of key path '%s' should'nt be empty"%path) + if not path.startswith("/"): + raise Py2debException("key path '%s' malformed (don't start with '/')"%path) + if path.endswith("/"): + raise Py2debException("key path '%s' malformed (shouldn't ends with '/')"%path) + + nfiles=[] + for file in files: + + if ".." in file: + raise Py2debException("file '%s' contains '..', please avoid that!"%file) + + + if "|" in file: + if file.count("|")!=1: + raise Py2debException("file '%s' is incorrect (more than one pipe)"%file) + + file, nfile = file.split("|") + else: + nfile=file # same localisation + + if os.path.isdir(file): + raise Py2debException("file '%s' is a folder, and py2deb refuse folders !"%file) + + if not os.path.isfile(file): + raise Py2debException("file '%s' doesn't exist"%file) + + if file.startswith("/"): # if an absolute file is defined + if file==nfile: # and not renamed (pipe trick) + nfile=os.path.basename(file) # it's simply copied to 'path' + + nfiles.append((file, nfile)) + + nfiles.sort(lambda a, b: cmp(a[1], b[1])) #sort according new name (nfile) + + self.__files[path]=nfiles + + def __delitem__(self, k): + del self.__files[k] + + def __init__(self, + name, + description="no description", + license="gpl", + depends="", + section="utils", + arch="all", + + url="", + author = None, + mail = None, + + preinstall = None, + postinstall = None, + preremove = None, + postremove = None + ): + + if author is None: + author = ("USERNAME" in os.environ) and os.environ["USERNAME"] or None + if author is None: + author = ("USER" in os.environ) and os.environ["USER"] or "unknown" + + if mail is None: + mail = author+"@"+socket.gethostname() + + self.name = name + self.prettyName = "" + self.description = description + self.upgradeDescription = "" + self.bugTracker = "" + self.license = license + self.depends = depends + self.recommends = "" + self.section = section + self.arch = arch + self.url = url + self.author = author + self.mail = mail + self.icon = "" + self.distribution = "" + self.respository = "" + self.urgency = "low" + + self.preinstall = preinstall + self.postinstall = postinstall + self.preremove = preremove + self.postremove = postremove + + self.__files={} + + def __repr__(self): + name = self.name + license = self.license + description = self.description + depends = self.depends + recommends = self.recommends + section = self.section + arch = self.arch + url = self.url + author = self.author + mail = self.mail + + preinstall = self.preinstall + postinstall = self.postinstall + preremove = self.preremove + postremove = self.postremove + + paths=self.__files.keys() + paths.sort() + files=[] + for path in paths: + for file, nfile in self.__files[path]: + #~ rfile=os.path.normpath(os.path.join(path, nfile)) + rfile=os.path.join(path, nfile) + if nfile==file: + files.append(rfile) + else: + files.append(rfile + " (%s)"%file) + + files.sort() + files = "\n".join(files) + + + lscripts = [ preinstall and "preinst", + postinstall and "postinst", + preremove and "prerm", + postremove and "postrm", + ] + scripts = lscripts and ", ".join([i for i in lscripts if i]) or "None" + return """ +---------------------------------------------------------------------- +NAME : %(name)s +---------------------------------------------------------------------- +LICENSE : %(license)s +URL : %(url)s +AUTHOR : %(author)s +MAIL : %(mail)s +---------------------------------------------------------------------- +DEPENDS : %(depends)s +RECOMMENDS : %(recommends)s +ARCH : %(arch)s +SECTION : %(section)s +---------------------------------------------------------------------- +DESCRIPTION : +%(description)s +---------------------------------------------------------------------- +SCRIPTS : %(scripts)s +---------------------------------------------------------------------- +FILES : +%(files)s +""" % locals() + + def generate(self, version, changelog="", rpm=False, src=False, build=True, tar=False, changes=False, dsc=False): + """ generate a deb of version 'version', with or without 'changelog', with or without a rpm + (in the current folder) + return a list of generated files + """ + if not sum([len(i) for i in self.__files.values()])>0: + raise Py2debException("no files are defined") + + if not changelog: + changelog="* no changelog" + + name = self.name + description = self.description + license = self.license + depends = self.depends + recommends = self.recommends + section = self.section + arch = self.arch + url = self.url + distribution = self.distribution + repository = self.repository + urgency = self.urgency + author = self.author + mail = self.mail + files = self.__files + preinstall = self.preinstall + postinstall = self.postinstall + preremove = self.preremove + postremove = self.postremove + + if section not in Py2deb.SECTIONS: + raise Py2debException("section '%s' is unknown (%s)" % (section, str(Py2deb.SECTIONS))) + + if arch not in Py2deb.ARCHS: + raise Py2debException("arch '%s' is unknown (%s)"% (arch, str(Py2deb.ARCHS))) + + if license not in Py2deb.LICENSES: + raise Py2debException("License '%s' is unknown (%s)" % (license, str(Py2deb.LICENSES))) + + # create dates (buildDate, buildDateYear) + d=datetime.now() + buildDate=d.strftime("%a, %d %b %Y %H:%M:%S +0000") + buildDateYear=str(d.year) + + #clean description (add a space before each next lines) + description=description.replace("\r", "").strip() + description = "\n ".join(description.split("\n")) + + #clean changelog (add 2 spaces before each next lines) + changelog=changelog.replace("\r", "").strip() + changelog = "\n ".join(changelog.split("\n")) + + TEMP = ".py2deb_build_folder" + DEST = os.path.join(TEMP, name) + DEBIAN = os.path.join(DEST, "debian") + + packageContents = locals() + + # let's start the process + try: + shutil.rmtree(TEMP) + except: + pass + + os.makedirs(DEBIAN) + try: + rules=[] + dirs=[] + for path in files: + for ofile, nfile in files[path]: + if os.path.isfile(ofile): + # it's a file + + if ofile.startswith("/"): # if absolute path + # we need to change dest + dest=os.path.join(DEST, nfile) + else: + dest=os.path.join(DEST, ofile) + + # copy file to be packaged + destDir = os.path.dirname(dest) + if not os.path.isdir(destDir): + os.makedirs(destDir) + + shutil.copy2(ofile, dest) + + ndir = os.path.join(path, os.path.dirname(nfile)) + nname = os.path.basename(nfile) + + # make a line RULES to be sure the destination folder is created + # and one for copying the file + fpath = "/".join(["$(CURDIR)", "debian", name+ndir]) + rules.append('mkdir -p "%s"' % fpath) + rules.append('cp -a "%s" "%s"' % (ofile, os.path.join(fpath, nname))) + + # append a dir + dirs.append(ndir) + + else: + raise Py2debException("unknown file '' "%ofile) # shouldn't be raised (because controlled before) + + # make rules right + rules= "\n\t".join(rules) + "\n" + packageContents["rules"] = rules + + # make dirs right + dirs= [i[1:] for i in set(dirs)] + dirs.sort() + + #========================================================================== + # CREATE debian/dirs + #========================================================================== + open(os.path.join(DEBIAN, "dirs"), "w").write("\n".join(dirs)) + + #========================================================================== + # CREATE debian/changelog + #========================================================================== + clog="""%(name)s (%(version)s) stable; urgency=low + + %(changelog)s + + -- %(author)s <%(mail)s> %(buildDate)s +""" % packageContents + + open(os.path.join(DEBIAN, "changelog"), "w").write(clog) + + #========================================================================== + #Create pre/post install/remove + #========================================================================== + def mkscript(name, dest): + if name and name.strip()!="": + if os.path.isfile(name): # it's a file + content = file(name).read() + else: # it's a script + content = name + open(os.path.join(DEBIAN, dest), "w").write(content) + + mkscript(preinstall, "preinst") + mkscript(postinstall, "postinst") + mkscript(preremove, "prerm") + mkscript(postremove, "postrm") + + + #========================================================================== + # CREATE debian/compat + #========================================================================== + open(os.path.join(DEBIAN, "compat"), "w").write("5\n") + + #========================================================================== + # CREATE debian/control + #========================================================================== + generalParagraphFields = [ + "Source: %(name)s", + "Maintainer: %(author)s <%(mail)s>", + "Section: %(section)s", + "Priority: extra", + "Build-Depends: debhelper (>= 5)", + "Standards-Version: 3.7.2", + ] + + specificParagraphFields = [ + "Package: %(name)s", + "Architecture: %(arch)s", + "Depends: %(depends)s", + "Recommends: %(recommends)s", + "Description: %(description)s", + ] + + if self.prettyName: + prettyName = "XSBC-Maemo-Display-Name: %s" % self.prettyName.strip() + specificParagraphFields.append("\n ".join(prettyName.split("\n"))) + + if self.bugTracker: + bugTracker = "XSBC-Bugtracker: %s" % self.bugTracker.strip() + specificParagraphFields.append("\n ".join(bugTracker.split("\n"))) + + if self.upgradeDescription: + upgradeDescription = "XSBC-Maemo-Upgrade-Description: %s" % self.upgradeDescription.strip() + specificParagraphFields.append("\n ".join(upgradeDescription.split("\n"))) + + if self.icon: + f = open(self.icon, "rb") + try: + rawIcon = f.read() + finally: + f.close() + uueIcon = base64.b64encode(rawIcon) + uueIconLines = [] + for i, c in enumerate(uueIcon): + if i % 60 == 0: + uueIconLines.append("") + uueIconLines[-1] += c + uueIconLines[0:0] = ("XSBC-Maemo-Icon-26:", ) + specificParagraphFields.append("\n ".join(uueIconLines)) + + generalParagraph = "\n".join(generalParagraphFields) + specificParagraph = "\n".join(specificParagraphFields) + controlContent = "\n\n".join((generalParagraph, specificParagraph)) % packageContents + open(os.path.join(DEBIAN, "control"), "w").write(controlContent) + + #========================================================================== + # CREATE debian/copyright + #========================================================================== + packageContents["txtLicense"] = LICENSE_AGREEMENT[license] + packageContents["pv"] =__version__ + txt="""This package was py2debianized(%(pv)s) by %(author)s <%(mail)s> on +%(buildDate)s. + +It was downloaded from %(url)s + +Upstream Author: %(author)s <%(mail)s> + +Copyright: %(buildDateYear)s by %(author)s + +License: + +%(txtLicense)s + +The Debian packaging is (C) %(buildDateYear)s, %(author)s <%(mail)s> and +is licensed under the GPL, see above. + + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. +""" % packageContents + open(os.path.join(DEBIAN, "copyright"), "w").write(txt) + + #========================================================================== + # CREATE debian/rules + #========================================================================== + txt="""#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + + + +CFLAGS = -Wall -g + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # ====================================================== + #$(MAKE) DESTDIR="$(CURDIR)/debian/%(name)s" install + mkdir -p "$(CURDIR)/debian/%(name)s" + + %(rules)s + # ====================================================== + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs debian/changelog + dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure +""" % packageContents + open(os.path.join(DEBIAN, "rules"), "w").write(txt) + os.chmod(os.path.join(DEBIAN, "rules"), 0755) + + ########################################################################### + ########################################################################### + ########################################################################### + + generatedFiles = [] + + if build: + #http://www.debian.org/doc/manuals/maint-guide/ch-build.fr.html + ret = os.system('cd "%(DEST)s"; dpkg-buildpackage -tc -rfakeroot -us -uc' % packageContents) + if ret != 0: + raise Py2debException("buildpackage failed (see output)") + + l=glob("%(TEMP)s/%(name)s*.deb" % packageContents) + if len(l) != 1: + raise Py2debException("didn't find builded deb") + + tdeb = l[0] + deb = os.path.basename(tdeb) + shutil.move(tdeb, deb) + + generatedFiles = [deb, ] + + if rpm: + rpmFilename = deb2rpm(deb) + generatedFiles.append(rpmFilename) + + if src: + tarFilename = py2src(TEMP, name) + generatedFiles.append(tarFilename) + + if tar: + tarFilename = py2tar(DEST, TEMP, name, version) + generatedFiles.append(tarFilename) + + if dsc: + dscFilename = py2dsc(TEMP, name, version, depends, author, mail, arch) + generatedFiles.append(dscFilename) + + if changes: + changesFilenames = py2changes(packageContents) + generatedFiles.extend(changesFilenames) + + return generatedFiles + + #~ except Exception,m: + #~ raise Py2debException("build error :"+str(m)) + + finally: + if Py2deb.clear: + shutil.rmtree(TEMP) + + +if __name__ == "__main__": + try: + os.chdir(os.path.dirname(sys.argv[0])) + except: + pass + + p=Py2deb("python-py2deb") + p.description="Generate simple deb(/rpm/tgz) from python (2.4, 2.5 and 2.6)" + p.url = "http://www.manatlan.com/page/py2deb" + p.author=__author__ + p.mail=__mail__ + p.depends = "dpkg-dev, fakeroot, alien, python" + p.section="python" + p["/usr/lib/python2.6/dist-packages"] = ["py2deb.py", ] + p["/usr/lib/python2.5/site-packages"] = ["py2deb.py", ] + p["/usr/lib/python2.4/site-packages"] = ["py2deb.py", ] + #~ p.postinstall = "s.py" + #~ p.preinstall = "s.py" + #~ p.postremove = "s.py" + #~ p.preremove = "s.py" + print p + print p.generate(__version__, changelog = __doc__, src=True) -- 1.7.9.5