From: Ed Page Date: Wed, 10 Aug 2011 02:26:48 +0000 (-0500) Subject: First attempt at distutils support now that the locations have been updated X-Git-Url: http://git.maemo.org/git/?p=gc-dialer;a=commitdiff_plain;h=17279d9fa822ed43c03d41ee9333fea8f7557418 First attempt at distutils support now that the locations have been updated --- diff --git a/DialCentral b/DialCentral index a20d4fe..f483f56 100755 --- a/DialCentral +++ b/DialCentral @@ -2,13 +2,7 @@ # -*- coding: utf-8 -*- -import sys - - -sys.path.append("/opt/dialcentral/lib") - - -import dialcentral_qt +from dialcentral import dialcentral_qt if __name__ == "__main__": diff --git a/Makefile b/Makefile index 9f128fa..24cb868 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,19 @@ PROJECT_NAME=dialcentral -SOURCE_PATH=src +PACKAGE_NAME=$(PROJECT_NAME) + +SOURCE_PATH=$(PACKAGE_NAME) SOURCE=$(shell find $(SOURCE_PATH) -iname "*.py") -PROGRAM=$(SOURCE_PATH)/$(PROJECT_NAME).py -DATA_PATH=data -DATA_TYPES=*.ini *.map *.glade *.png -DATA=$(foreach type, $(DATA_TYPES), $(shell find $(DATA_PATH) -iname "$(type)")) + +PROGRAM=DialCentral +ICON_SIZES=26 32 48 80 +ICONS=$(foreach size, $(ICON_SIZES), data/icons/$(size)/$(PROJECT_NAME).png) +PACKAGE_VARIANTS=fremantle harmattan ubuntu +DESKTOP_FILES=$(foreach variant, $(PACKAGE_VARIANTS), data/$(variant)/$(PROJECT_NAME).desktop) +SETUP_FILES=$(foreach variant, $(PACKAGE_VARIANTS), ./setup.$(variant).py) +DIST_BASE_PATH=./dist +DIST_PATHS=$(foreach variant, $(PACKAGE_VARIANTS), $(DIST_BASE_PATH)_$(variant)) $(DIST_BASE_PATH)_diablo + OBJ=$(SOURCE:.py=.pyc) -BUILD_PATH=./build TAG_FILE=~/.ctags/$(PROJECT_NAME).tags TODO_FILE=./TODO @@ -21,12 +28,13 @@ PROFILE_VIEW=python -m pstats .profile TODO_FINDER=support/todo.py CTAGS=ctags-exuberant + .PHONY: all run profile debug test build lint tags todo clean distclean all: test run: $(OBJ) - $(SOURCE_PATH)/$(PROJECT_NAME)_qt.py + $(PROGRAM) profile: $(OBJ) $(PROFILE_GEN) $(PROGRAM) @@ -38,36 +46,27 @@ debug: $(OBJ) test: $(OBJ) $(UNIT_TEST) -package: $(OBJ) - rm -Rf $(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)) ; ) - cp support/$(PROJECT_NAME).desktop $(BUILD_PATH)/generic - cp support/icons/hicolor/26x26/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/generic/26x26-$(PROJECT_NAME).png - cp support/icons/hicolor/64x64/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/generic/64x64-$(PROJECT_NAME).png - cp support/icons/hicolor/scalable/hildon/$(PROJECT_NAME).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)/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)/debian - cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/debian - cd $(BUILD_PATH)/debian ; python builddeb.py debian +package: $(OBJ) $(ICONS) $(SETUP_FILES) $(DESKTOP_FILES) + rm -Rf $(DIST_BASE_PATH)_*/* + ./setup.fremantle.py sdist_diablo \ + -d $(DIST_BASE_PATH)_diablo \ + --install-purelib=/usr/lib/python2.5/site-packages + ./setup.fremantle.py sdist_fremantle \ + -d $(DIST_BASE_PATH)_fremantle \ + --install-purelib=/usr/lib/python2.5/site-packages + ./setup.harmattan.py sdist_harmattan \ + -d $(DIST_BASE_PATH)_harmattan + --install-purelib=/usr/lib/python2.6/dist-packages + ./setup.ubuntu.py sdist_ubuntu \ + -d $(DIST_BASE_PATH)_ubuntu + mkdir $(DIST_BASE_PATH)_ubuntu/build + cd $(DIST_BASE_PATH)_ubuntu/build ; tar -zxvf ../*.tar.gz + cd $(DIST_BASE_PATH)_ubuntu/build ; dpkg-buildpackage -tc -rfakeroot -us -uc upload: - dput fremantle-extras-builder $(BUILD_PATH)/fremantle/$(PROJECT_NAME)*.changes - dput diablo-extras-builder $(BUILD_PATH)/diablo/$(PROJECT_NAME)*.changes - cp $(BUILD_PATH)/debian/*.deb ./www/$(PROJECT_NAME).deb + dput diablo-extras-builder $(DIST_BASE_PATH)_diablo/$(PROJECT_NAME)*.changes + dput fremantle-extras-builder $(DIST_BASE_PATH)_fremantle/$(PROJECT_NAME)*.changes + cp $(DIST_BASE_PATH)_ubuntu/*.deb www/$(PROJECT_NAME).deb lint: $(OBJ) $(foreach file, $(SOURCE), $(LINT) $(file) ; ) @@ -78,18 +77,60 @@ todo: $(TODO_FILE) clean: rm -Rf $(OBJ) - rm -Rf $(BUILD_PATH) rm -Rf $(TODO_FILE) + rm -f $(ICONS) $(SETUP_FILES) $(DESKTOP_FILES) + rm -Rf $(DIST_PATHS) -distclean: - rm -Rf $(OBJ) - rm -Rf $(BUILD_PATH) - rm -Rf $(TAG_FILE) +distclean: clean find $(SOURCE_PATH) -name "*.*~" | xargs rm -f find $(SOURCE_PATH) -name "*.swp" | xargs rm -f find $(SOURCE_PATH) -name "*.bak" | xargs rm -f find $(SOURCE_PATH) -name ".*.swp" | xargs rm -f + +$(SETUP_FILES): VARIANT=$(word 2, $(subst ., ,$@)) + +setup.fremantle.py: setup.py src/constants.py + cog.py -c \ + -D DESKTOP_FILE_PATH=/usr/share/applications/hildon \ + -D INPUT_DESKTOP_FILE=data/$(VARIANT)/$(PROJECT_NAME).desktop \ + -D ICON_CATEGORY=hildon \ + -D ICON_SIZES=26,32,48 \ + -o $@ $< + chmod +x $@ + +setup.harmattan.py: setup.py src/constants.py + cog.py -c \ + -D DESKTOP_FILE_PATH=/usr/share/applications \ + -D INPUT_DESKTOP_FILE=data/$(VARIANT)/$(PROJECT_NAME).desktop \ + -D ICON_CATEGORY=hildon \ + -D ICON_SIZES=32,80 \ + -o $@ $< + chmod +x $@ + +setup.ubuntu.py: setup.py src/constants.py + cog.py -c \ + -D DESKTOP_FILE_PATH=/usr/share/applications \ + -D INPUT_DESKTOP_FILE=data/$(VARIANT)/$(PROJECT_NAME).desktop \ + -D ICON_CATEGORY=apps \ + -D ICON_SIZES=32,48 \ + -o $@ $< + chmod +x $@ + +$(ICONS): SIZE=$(word 3, $(subst /, ,$@)) +$(ICONS): data/$(PROJECT_NAME).png support/scale.py + mkdir -p $(dir $@) + support/scale.py --input $< --output $@ --size $(SIZE) + +$(DESKTOP_FILES): VARIANT=$(word 2, $(subst /, ,$@)) +$(DESKTOP_FILES): data/template.desktop + mkdir -p $(dir $@) + cog.py -d \ + -D VARIANT=$(VARIANT) \ + -D PROGRAM=$(PROGRAM) \ + -o $@ $< + + $(TAG_FILE): $(OBJ) mkdir -p $(dir $(TAG_FILE)) $(CTAGS) -o $(TAG_FILE) $(SOURCE) diff --git a/data/app/LICENSE b/data/app/LICENSE deleted file mode 100644 index fb44a62..0000000 --- a/data/app/LICENSE +++ /dev/null @@ -1,11 +0,0 @@ -http://www.gentleface.com/free_icon_set.html -The Creative Commons Attribution-NonCommercial -- FREE -http://creativecommons.org/licenses/by-nc-nd/3.0/ - -Sound: -http://www.freesound.org/samplesViewSingle.php?id=2166 -http://creativecommons.org/licenses/sampling+/1.0/ - -placed.png, received.png, placed.png -Free for commercial use -http://www.iconeden.com/icon/free/get/bright-free-stock-iconset diff --git a/data/app/bell.flac b/data/app/bell.flac deleted file mode 100644 index 419420e..0000000 Binary files a/data/app/bell.flac and /dev/null differ diff --git a/data/app/bell.wav b/data/app/bell.wav deleted file mode 100644 index 6b7fc1b..0000000 Binary files a/data/app/bell.wav and /dev/null differ diff --git a/data/app/contacts.png b/data/app/contacts.png deleted file mode 100644 index aa1a7ce..0000000 Binary files a/data/app/contacts.png and /dev/null differ diff --git a/data/app/dialpad.png b/data/app/dialpad.png deleted file mode 100644 index b54013b..0000000 Binary files a/data/app/dialpad.png and /dev/null differ diff --git a/data/app/history.png b/data/app/history.png deleted file mode 100644 index 887989a..0000000 Binary files a/data/app/history.png and /dev/null differ diff --git a/data/app/messages.png b/data/app/messages.png deleted file mode 100644 index e117918..0000000 Binary files a/data/app/messages.png and /dev/null differ diff --git a/data/app/missed.png b/data/app/missed.png deleted file mode 100644 index 34f71c4..0000000 Binary files a/data/app/missed.png and /dev/null differ diff --git a/data/app/placed.png b/data/app/placed.png deleted file mode 100644 index 329771d..0000000 Binary files a/data/app/placed.png and /dev/null differ diff --git a/data/app/received.png b/data/app/received.png deleted file mode 100644 index 2b45263..0000000 Binary files a/data/app/received.png and /dev/null differ diff --git a/data/template.desktop b/data/template.desktop index 3b446d7..8a72f1e 100644 --- a/data/template.desktop +++ b/data/template.desktop @@ -1,8 +1,22 @@ [Desktop Entry] -Encoding=UTF-8 -Version=1.0 -Type=Application Name=DialCentral -Exec=/usr/bin/run-standalone.sh /opt/dialcentral/bin/dialcentral.py +GenericName=Google Voice Client +Comment=Google Voice Client +#[[[cog +# if VARIANT == "fremantle": +# cog.outl("Exec=/usr/bin/run-standalone.sh /usr/local/bin/%s" % PROGRAM) +# elif VARIANT == "harmattan": +# cog.outl("Exec=/usr/bin/invoker --single-instance --type=e /usr/local/bin/%s" % PROGRAM) +# elif VARIANT == "ubuntu": +# cog.outl("Exec=/usr/local/bin/%s" % PROGRAM) +# else: +# raise RuntimeError("Unsupported desktop file flavor %r" % PROGRAM) +#]]] +Exec=/usr/local/bin/DialCentral +#[[[end]]] Icon=dialcentral Categories=Network;InstantMessaging;Qt; +Type=Application +Encoding=UTF-8 +Version=1.0 +X-Osso-Type=application/x-executable diff --git a/dialcentral/backends/gv_backend.py b/dialcentral/backends/gv_backend.py index 17bbc90..3281591 100644 --- a/dialcentral/backends/gv_backend.py +++ b/dialcentral/backends/gv_backend.py @@ -32,7 +32,7 @@ import logging from gvoice import gvoice -from util import io as io_utils +from dialcentral.util import io as io_utils _moduleLogger = logging.getLogger(__name__) diff --git a/dialcentral/backends/qt_backend.py b/dialcentral/backends/qt_backend.py index 88e52fa..da088e5 100644 --- a/dialcentral/backends/qt_backend.py +++ b/dialcentral/backends/qt_backend.py @@ -5,7 +5,7 @@ from __future__ import division import logging -import util.qt_compat as qt_compat +from dialcentral.util import qt_compat if qt_compat.USES_PYSIDE: try: import QtMobility.Contacts as _QtContacts diff --git a/dialcentral/constants.py b/dialcentral/constants.py index b9d3c79..56ebddc 100644 --- a/dialcentral/constants.py +++ b/dialcentral/constants.py @@ -10,4 +10,5 @@ _user_settings_ = "%s/settings.ini" % _data_path_ _custom_notifier_settings_ = "%s/notifier.ini" % _data_path_ _user_logpath_ = "%s/%s.log" % (_data_path_, __app_name__) _notifier_logpath_ = "%s/notifier.log" % _data_path_ + IS_MAEMO = True diff --git a/dialcentral/data/LICENSE b/dialcentral/data/LICENSE new file mode 100644 index 0000000..fb44a62 --- /dev/null +++ b/dialcentral/data/LICENSE @@ -0,0 +1,11 @@ +http://www.gentleface.com/free_icon_set.html +The Creative Commons Attribution-NonCommercial -- FREE +http://creativecommons.org/licenses/by-nc-nd/3.0/ + +Sound: +http://www.freesound.org/samplesViewSingle.php?id=2166 +http://creativecommons.org/licenses/sampling+/1.0/ + +placed.png, received.png, placed.png +Free for commercial use +http://www.iconeden.com/icon/free/get/bright-free-stock-iconset diff --git a/dialcentral/data/bell.flac b/dialcentral/data/bell.flac new file mode 100644 index 0000000..419420e Binary files /dev/null and b/dialcentral/data/bell.flac differ diff --git a/dialcentral/data/bell.wav b/dialcentral/data/bell.wav new file mode 100644 index 0000000..6b7fc1b Binary files /dev/null and b/dialcentral/data/bell.wav differ diff --git a/dialcentral/data/contacts.png b/dialcentral/data/contacts.png new file mode 100644 index 0000000..aa1a7ce Binary files /dev/null and b/dialcentral/data/contacts.png differ diff --git a/dialcentral/data/dialpad.png b/dialcentral/data/dialpad.png new file mode 100644 index 0000000..b54013b Binary files /dev/null and b/dialcentral/data/dialpad.png differ diff --git a/dialcentral/data/history.png b/dialcentral/data/history.png new file mode 100644 index 0000000..887989a Binary files /dev/null and b/dialcentral/data/history.png differ diff --git a/dialcentral/data/messages.png b/dialcentral/data/messages.png new file mode 100644 index 0000000..e117918 Binary files /dev/null and b/dialcentral/data/messages.png differ diff --git a/dialcentral/data/missed.png b/dialcentral/data/missed.png new file mode 100644 index 0000000..34f71c4 Binary files /dev/null and b/dialcentral/data/missed.png differ diff --git a/dialcentral/data/placed.png b/dialcentral/data/placed.png new file mode 100644 index 0000000..329771d Binary files /dev/null and b/dialcentral/data/placed.png differ diff --git a/dialcentral/data/received.png b/dialcentral/data/received.png new file mode 100644 index 0000000..2b45263 Binary files /dev/null and b/dialcentral/data/received.png differ diff --git a/dialcentral/dialcentral_qt.py b/dialcentral/dialcentral_qt.py index a464ad6..3dcf46f 100755 --- a/dialcentral/dialcentral_qt.py +++ b/dialcentral/dialcentral_qt.py @@ -30,8 +30,7 @@ _moduleLogger = logging.getLogger(__name__) class Dialcentral(qwrappers.ApplicationWrapper): _DATA_PATHS = [ - os.path.join(os.path.dirname(__file__), "../share"), - os.path.join(os.path.dirname(__file__), "../data"), + os.path.join(os.path.dirname(__file__), "data"), ] def __init__(self, app): diff --git a/dialcentral/util/qml_utils.py b/dialcentral/util/qml_utils.py new file mode 100644 index 0000000..fbbca93 --- /dev/null +++ b/dialcentral/util/qml_utils.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +""" +QML Tips: + Large images: + QML asynchronous = true; cache = false; [1] + Insert properties at top of element declarations [1] + Non-visible items: set opacity to 0 [2] + Use Loader [1] + Keep QML files small [1] + +[1] http://sf2011.meego.com/program/sessions/performance-tips-and-tricks-qtqml-applications-0 +[2] http://doc.qt.nokia.com/4.7/qdeclarativeperformance.html +""" + +from __future__ import with_statement +from __future__ import division + +import logging + +import util.qt_compat as qt_compat +QtCore = qt_compat.QtCore +QtGui = qt_compat.import_module("QtGui") +QtDeclarative = qt_compat.import_module("QtDeclarative") + + +_moduleLogger = logging.getLogger(__name__) + + +class DeclarativeView(QtDeclarative.QDeclarativeView): + + def __init__(self): + QtDeclarative.QDeclarativeView.__init__(self) + + closing = qt_compat.Signal() + + def closeEvent(self, event): + self.closing.emit() + event.ignore() + + +def disable_default_window_painting(view): + """ + See http://doc.qt.nokia.com/4.7-snapshot/qdeclarativeperformance.html + """ + view.setAttribute(QtCore.Qt.WA_OpaquePaintEvent) + view.setAttribute(QtCore.Qt.WA_NoSystemBackground) + view.viewport().setAttribute(QtCore.Qt.WA_OpaquePaintEvent) + view.viewport().setAttribute(QtCore.Qt.WA_NoSystemBackground) + + +if __name__ == "__main__": + pass + diff --git a/dialcentral/util/qore_utils.py b/dialcentral/util/qore_utils.py index 153558d..9e85c58 100644 --- a/dialcentral/util/qore_utils.py +++ b/dialcentral/util/qore_utils.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python + +import contextlib import logging import qt_compat @@ -97,3 +100,323 @@ class FutureThread(QtCore.QObject): callback(result) except Exception: _moduleLogger.exception("Callback errored") + + +def create_single_column_list_model(columnName, **kwargs): + """ + >>> class Single(object): pass + >>> SingleListModel = create_single_column_list_model("s") + >>> slm = SingleListModel([Single(), Single(), Single()]) + """ + + class SingleColumnListModel(QtCore.QAbstractListModel): + + def __init__(self, l = None): + QtCore.QAbstractListModel.__init__(self) + self._list = l if l is not None else [] + self.setRoleNames({0: columnName}) + + def __len__(self): + return len(self._list) + + def __getitem__(self, key): + return self._list[key] + + def __setitem__(self, key, value): + with scoped_model_reset(self): + self._list[key] = value + + def __delitem__(self, key): + with scoped_model_reset(self): + del self._list[key] + + def __iter__(self): + return iter(self._list) + + def __repr__(self): + return '<%s (%s)>' % ( + self.__class__.__name__, + columnName, + ) + + def rowCount(self, parent=QtCore.QModelIndex()): + return len(self._list) + + def data(self, index, role): + if index.isValid() and role == 0: + return self._list[index.row()] + return None + + if "name" in kwargs: + SingleColumnListModel.__name__ = kwargs["name"] + + return SingleColumnListModel + + +def create_tupled_list_model(*columnNames, **kwargs): + """ + >>> class Column0(object): pass + >>> class Column1(object): pass + >>> class Column2(object): pass + >>> MultiColumnedListModel = create_tupled_list_model("c0", "c1", "c2") + >>> mclm = MultiColumnedListModel([(Column0(), Column1(), Column2())]) + """ + + class TupledListModel(QtCore.QAbstractListModel): + + def __init__(self, l = None): + QtCore.QAbstractListModel.__init__(self) + self._list = l if l is not None else [] + self.setRoleNames(dict(enumerate(columnNames))) + + def __len__(self): + return len(self._list) + + def __getitem__(self, key): + return self._list[key] + + def __setitem__(self, key, value): + with scoped_model_reset(self): + self._list[key] = value + + def __delitem__(self, key): + with scoped_model_reset(self): + del self._list[key] + + def __iter__(self): + return iter(self._list) + + def __repr__(self): + return '<%s (%s)>' % ( + self.__class__.__name__, + ', '.join(columnNames), + ) + + def rowCount(self, parent=QtCore.QModelIndex()): + return len(self._list) + + def data(self, index, role): + if index.isValid() and 0 <= role and role < len(columnNames): + return self._list[index.row()][role] + return None + + if "name" in kwargs: + TupledListModel.__name__ = kwargs["name"] + + return TupledListModel + + +class FileSystemModel(QtCore.QAbstractListModel): + """ + Wrapper around QtGui.QFileSystemModel + """ + + FILEINFOS = [ + "fileName", + "isDir", + "filePath", + "completeSuffix", + "baseName", + ] + + EXTINFOS = [ + "type", + ] + + ALLINFOS = FILEINFOS + EXTINFOS + + def __init__(self, model, path): + QtCore.QAbstractListModel.__init__(self) + self._path = path + + self._model = model + self._rootIndex = self._model.index(self._path) + + self._child = None + self.setRoleNames(dict(enumerate(self.ALLINFOS))) + self._model.directoryLoaded.connect(self._on_directory_loaded) + + childChanged = qt_compat.Signal(QtCore.QObject) + + def _child(self): + assert self._child is not None + return self._child + + child = qt_compat.Property(QtCore.QObject, _child, notify=childChanged) + + backendChanged = qt_compat.Signal() + + def _parent(self): + finfo = self._model.fileInfo(self._rootIndex) + return finfo.fileName() + + parent = qt_compat.Property(str, _parent, notify=backendChanged) + + @qt_compat.Slot(str) + def browse_to(self, path): + if self._child is None: + self._child = FileSystemModel(self._model, path) + else: + self._child.switch_to(path) + self.childChanged.emit() + return self._child + + @qt_compat.Slot(str) + def switch_to(self, path): + with scoped_model_reset(self): + self._path = path + self._rootIndex = self._model.index(self._path) + self.backendChanged.emit() + + def __len__(self): + return self._model.rowCount(self._rootIndex) + + def __getitem__(self, key): + return self._model.index(key, 0, self._rootIndex) + + def __iter__(self): + return (self[i] for i in xrange(len(self))) + + def rowCount(self, parent=QtCore.QModelIndex()): + return len(self) + + def data(self, index, role): + if index.isValid() and 0 <= role and role < len(self.ALLINFOS): + internalIndex = self._translate_index(index) + info = self._model.fileInfo(internalIndex) + if role < len(self.FILEINFOS): + field = self.FILEINFOS[role] + value = getattr(info, field)() + else: + role -= len(self.FILEINFOS) + field = self.EXTINFOS[role] + if field == "type": + return self._model.type(internalIndex) + else: + raise NotImplementedError("Out of range that was already checked") + return value + return None + + def _on_directory_loaded(self, path): + if self._path == path: + self.backendChanged.emit() + self.reset() + + def _translate_index(self, externalIndex): + internalIndex = self._model.index(externalIndex.row(), 0, self._rootIndex) + return internalIndex + + +@contextlib.contextmanager +def scoped_model_reset(model): + model.beginResetModel() + try: + yield + finally: + model.endResetModel() + + +def create_qobject(*classDef, **kwargs): + """ + >>> Car = create_qobject( + ... ('model', str), + ... ('brand', str), + ... ('year', int), + ... ('inStock', bool), + ... name='Car' + ... ) + >>> print Car + + >>> + >>> c = Car(model='Fiesta', brand='Ford', year=1337) + >>> print c.model, c.brand, c.year, c.inStock + Fiesta Ford 1337 False + >>> print c + + >>> + >>> c.inStock = True + >>> + >>> print c.model, c.brand, c.year, c.inStock + Fiesta Ford 1337 True + >>> print c + + """ + + class AutoQObject(QtCore.QObject): + + def __init__(self, **initKwargs): + QtCore.QObject.__init__(self) + for key, val in classDef: + setattr(self, '_'+key, initKwargs.get(key, val())) + + def __repr__(self): + values = ( + '%s=%r' % (key, getattr(self, '_'+key)) + for key, value in classDef + ) + return '<%s (%s)>' % ( + kwargs.get('name', self.__class__.__name__), + ', '.join(values), + ) + + for key, value in classDef: + nfy = locals()['_nfy_'+key] = qt_compat.Signal() + + def _get(key): + def f(self): + return self.__dict__['_'+key] + return f + + def _set(key): + def f(self, value): + setattr(self, '_'+key, value) + getattr(self, '_nfy_'+key).emit() + return f + + setter = locals()['_set_'+key] = _set(key) + getter = locals()['_get_'+key] = _get(key) + + locals()[key] = qt_compat.Property(value, getter, setter, notify=nfy) + del nfy, _get, _set, getter, setter + + return AutoQObject + + +class QObjectProxy(object): + """ + Proxy for accessing properties and slots as attributes + + This class acts as a proxy for the object for which it is + created, and makes property access more Pythonic while + still allowing access to slots (as member functions). + + Attribute names starting with '_' are not proxied. + """ + + def __init__(self, rootObject): + self._rootObject = rootObject + m = self._rootObject.metaObject() + self._properties = [ + m.property(i).name() + for i in xrange(m.propertyCount()) + ] + + def __getattr__(self, key): + value = self._rootObject.property(key) + + # No such property, so assume we call a slot + if value is None and key not in self._properties: + return getattr(self._rootObject, key) + + return value + + def __setattr__(self, key, value): + if key.startswith('_'): + object.__setattr__(self, key, value) + else: + self._rootObject.setProperty(key, value) + + +if __name__ == "__main__": + import doctest + print doctest.testmod() diff --git a/dialcentral/util/qt_compat.py b/dialcentral/util/qt_compat.py index 2ab7fa4..409c00d 100644 --- a/dialcentral/util/qt_compat.py +++ b/dialcentral/util/qt_compat.py @@ -3,12 +3,15 @@ from __future__ import with_statement from __future__ import division -#try: -# import PySide.QtCore as _QtCore -# QtCore = _QtCore -# USES_PYSIDE = True -#except ImportError: -if True: +_TRY_PYSIDE = False + +try: + if not _TRY_PYSIDE: + raise ImportError() + import PySide.QtCore as _QtCore + QtCore = _QtCore + USES_PYSIDE = True +except ImportError: import sip sip.setapi('QString', 2) sip.setapi('QVariant', 2) diff --git a/dialcentral/util/qwrappers.py b/dialcentral/util/qwrappers.py index 2c50c8a..09270cd 100644 --- a/dialcentral/util/qwrappers.py +++ b/dialcentral/util/qwrappers.py @@ -9,8 +9,8 @@ import qt_compat QtCore = qt_compat.QtCore QtGui = qt_compat.import_module("QtGui") -from util import qui_utils -from util import misc as misc_utils +import qui_utils +import misc as misc_utils _moduleLogger = logging.getLogger(__name__) diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..8eb7735 --- /dev/null +++ b/setup.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +reload(sys).setdefaultencoding("UTF-8") +import os + +try: + from sdist_maemo import sdist_maemo as _sdist_maemo + sdist_maemo = _sdist_maemo +except ImportError: + sdist_maemo = None + print 'sdist_maemo command not available' + +from distutils.core import setup + + +#[[[cog +# import cog +# from dialcentral import constants +# cog.outl('APP_NAME="%s"' % constants.__app_name__) +# cog.outl('PRETTY_APP_NAME="%s"' % constants.__pretty_app_name__) +# cog.outl('VERSION="%s"' % constants.__version__) +# cog.outl('BUILD="%s"' % constants.__build__) +# cog.outl('DESKTOP_FILE_PATH="%s"' % DESKTOP_FILE_PATH) +# cog.outl('INPUT_DESKTOP_FILE="%s"' % INPUT_DESKTOP_FILE) +# cog.outl('ICON_CATEGORY="%s"' % ICON_CATEGORY) +# cog.outl('ICON_SIZES=[%s]' % ICON_SIZES) +#]]] +APP_NAME="dialcentral" +PRETTY_APP_NAME="DialCentral" +VERSION="1.3.6" +BUILD="0" +DESKTOP_FILE_PATH="/usr/share/applications" +INPUT_DESKTOP_FILE="data/ubuntu/dialcentral.desktop" +ICON_CATEGORY="apps" +ICON_SIZES=[32,48] +#[[[end]]] (checksum: 6b362845c8509854ec12f330afd9c7b7) + +CHANGES = """Switching from py2deb.py to sdist_maemo +""".strip() +BUGTRACKER_URL = "https://bugs.maemo.org/enter_bug.cgi?product=Dialcentral" + + +def is_package(path): + return ( + os.path.isdir(path) and + os.path.isfile(os.path.join(path, '__init__.py')) + ) + + +def find_packages(path, base="", includeRoot=False): + """ Find all packages in path """ + if includeRoot: + assert not base, "Base not supported with includeRoot: %r" % base + rootPath, module_name = os.path.split(path) + yield module_name + base = module_name + for item in os.listdir(path): + dir = os.path.join(path, item) + if is_package( dir ): + if base: + module_name = "%(base)s.%(item)s" % vars() + else: + module_name = item + yield module_name + for mname in find_packages(dir, module_name): + yield mname + + +setup( + name=APP_NAME, + version=VERSION, + description="Touch screen enhanced interface to the GoogleVoice phone service", + long_description="Touch screen enhanced interface to the GoogleVoice phone service", + author="Ed Page", + author_email="eopage@byu.net", + maintainer="Ed Page", + maintainer_email="eopage@byu.net", + url="http://wiki.maemo.org/DialCentral", + license="GNU LGPLv2.1", + scripts=[ + "DialCentral", + ], + packages=list(find_packages(APP_NAME, includeRoot=True)), + package_data={ + "dialcentral": ["data/*.wav", "data/*.png"], + }, + data_files=[ + (DESKTOP_FILE_PATH, [INPUT_DESKTOP_FILE]), + ] + + [ + ( + "/usr/share/icons/hicolor/%sx%s/%s" % (size, size, ICON_CATEGORY), + ["data/icons/%s/%s.png" % (size, APP_NAME)] + ) + for size in ICON_SIZES + ], + requires=[ + "PySide", + "simplejson", + "xml", + ], + cmdclass={ + 'sdist_ubuntu': sdist_maemo, + 'sdist_diablo': sdist_maemo, + 'sdist_fremantle': sdist_maemo, + 'sdist_harmattan': sdist_maemo, + }, + options={ + "sdist_ubuntu": { + "debian_package": APP_NAME, + "section": "comm", + "copyright": "lgpl", + "changelog": CHANGES, + "buildversion": str(BUILD), + "depends": "python, python-pyside.qtcore, python-pyside.qtgui, python-simplejson, python-xml, python-dbus, python-gst0.10", + "architecture": "any", + }, + "sdist_diablo": { + "debian_package": APP_NAME, + "Maemo_Display_Name": PRETTY_APP_NAME, + #"Maemo_Upgrade_Description": CHANGES, + "Maemo_Bugtracker": BUGTRACKER_URL, + "Maemo_Icon_26": "data/icons/26/%s.png" % APP_NAME, + "section": "user/network", + "copyright": "lgpl", + "changelog": CHANGES, + "buildversion": str(BUILD), + "depends": "python2.5, python2.5-qt4-core, python2.5-qt4-gui, python-simplejson, python-xml | python2.5-xml, python-dbus | python2.5-dbus", + "architecture": "any", + }, + "sdist_fremantle": { + "debian_package": APP_NAME, + "Maemo_Display_Name": PRETTY_APP_NAME, + #"Maemo_Upgrade_Description": CHANGES, + "Maemo_Bugtracker": BUGTRACKER_URL, + "Maemo_Icon_26": "data/icons/48/%s.png" % APP_NAME, + "section": "user/network", + "copyright": "lgpl", + "changelog": CHANGES, + "buildversion": str(BUILD), + "depends": "python, python-pyside.qtcore, python-pyside.qtgui, python-pyside.qtmaemo5, python-simplejson, python-gst0.10, python-xml | python2.5-xml, python-dbus | python2.5-dbus", + "architecture": "any", + }, + "sdist_harmattan": { + "debian_package": APP_NAME, + "Maemo_Display_Name": PRETTY_APP_NAME, + #"Maemo_Upgrade_Description": CHANGES, + "Maemo_Bugtracker": BUGTRACKER_URL, + "Maemo_Icon_26": "data/icons/48/%s.png" % APP_NAME, + "MeeGo_Desktop_Entry_Filename": APP_NAME, + #"MeeGo_Desktop_Entry": "", + "section": "user/science", + "copyright": "lgpl", + "changelog": CHANGES, + "buildversion": str(BUILD), + "depends": "python, python-pyside.qtcore, python-pyside.qtgui, python-simplejson, python-xml", + "architecture": "any", + }, + "bdist_rpm": { + "requires": "REPLACEME", + "icon": "data/icons/48/%s.png" % APP_NAME, + "group": "REPLACEME", + }, + }, +) diff --git a/support/builddeb.py b/support/builddeb.py deleted file mode 100755 index 7b904e4..0000000 --- a/support/builddeb.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/python2.5 - -import os -import sys - -try: - import py2deb -except ImportError: - import fake_py2deb as py2deb - -import constants - - -__appname__ = constants.__app_name__ -__description__ = """Touch screen enhanced interface to the GoogleVoice phone service -Features: -. -* Dialpad for quick call -. -* Checking voicemails, texts, call history -. -* Sending texts -. -* Notification support for texts, voicemail, and/or missed calls -. -Homepage: http://gc-dialer.garage.maemo.org/ -""" -__author__ = "Ed Page" -__email__ = "eopage@byu.net" -__version__ = constants.__version__ -__build__ = constants.__build__ -__changelog__ = """ -* Adding back in GTalk callback for those who can use it -* Polishing button sizing and some wording -* Fixed a bug with alert LED on Maemo 4.1 -""".strip() - - -__postinstall__ = """#!/bin/sh -e - -gtk-update-icon-cache -f /usr/share/icons/hicolor -""" % {"name": constants.__app_name__} - -__preremove__ = """#!/bin/sh -e - -python /opt/dialcentral/lib/alarm_handler.py -d || true -""" - - -def find_files(prefix, path): - for root, dirs, files in os.walk(path): - for file in files: - if file.startswith(prefix+"-"): - fileParts = file.split("-") - unused, relPathParts, newName = fileParts[0], fileParts[1:-1], fileParts[-1] - assert unused == prefix - relPath = os.sep.join(relPathParts) - yield relPath, file, newName - - -def unflatten_files(files): - d = {} - for relPath, oldName, newName in files: - if relPath not in d: - d[relPath] = [] - d[relPath].append((oldName, newName)) - return d - - -def build_package(distribution): - try: - os.chdir(os.path.dirname(sys.argv[0])) - except: - pass - - py2deb.Py2deb.SECTIONS = py2deb.SECTIONS_BY_POLICY[distribution] - p = py2deb.Py2deb(__appname__) - p.prettyName = constants.__pretty_app_name__ - p.description = __description__ - p.bugTracker = "https://bugs.maemo.org/enter_bug.cgi?product=Dialcentral" - p.author = __author__ - p.mail = __email__ - p.license = "lgpl" - p.depends = ", ".join([ - "python2.6 | python2.5", - "python-xml | python2.5-xml", - "python-dbus | python2.5-dbus", - "python-simplejson", - ]) - p.depends += { - "debian": ", python-qt4, python-gst0.10", - "diablo": ", python2.5-qt4-core, python2.5-qt4-gui", - "fremantle": ", python2.5-qt4-core, python2.5-qt4-gui, python2.5-qt4-maemo5, python-gst0.10", - #"fremantle": ", python-pyside.qtgui, python-pyside.qtcore, python-pyside.qtmaemo5, python-qtmobility.contacts", - }[distribution] - p.recommends = ", ".join([ - ]) - p.section = { - "debian": "comm", - "diablo": "user/network", - "fremantle": "user/network", - }[distribution] - p.arch = "all" - p.urgency = "low" - p.distribution = "diablo fremantle debian" - p.repository = "extras" - p.changelog = __changelog__ - p.postinstall = __postinstall__ - p.preremove = __preremove__ - p.icon = { - "debian": "26x26-dialcentral.png", - "diablo": "26x26-dialcentral.png", - "fremantle": "64x64-dialcentral.png", # Fremantle natively uses 48x48 - }[distribution] - p["/opt/%s/bin" % __appname__] = [ "%s.py" % __appname__ ] - for relPath, files in unflatten_files(find_files("src", ".")).iteritems(): - fullPath = "/opt/%s/lib" % __appname__ - if relPath: - fullPath += os.sep+relPath - p[fullPath] = list( - "|".join((oldName, newName)) - for (oldName, newName) in files - ) - for relPath, files in unflatten_files(find_files("data", ".")).iteritems(): - fullPath = "/opt/%s/share" % __appname__ - if relPath: - fullPath += os.sep+relPath - p[fullPath] = list( - "|".join((oldName, newName)) - for (oldName, newName) in files - ) - p["/usr/share/applications/hildon"] = ["dialcentral.desktop"] - p["/usr/share/icons/hicolor/26x26/hildon"] = ["26x26-dialcentral.png|dialcentral.png"] - p["/usr/share/icons/hicolor/64x64/hildon"] = ["64x64-dialcentral.png|dialcentral.png"] - p["/usr/share/icons/hicolor/scalable/hildon"] = ["scale-dialcentral.png|dialcentral.png"] - - print p - if distribution == "debian": - print p.generate( - version="%s-%s" % (__version__, __build__), - changelog=__changelog__, - build=True, - tar=False, - changes=False, - dsc=False, - ) - else: - print p.generate( - version="%s-%s" % (__version__, __build__), - changelog=__changelog__, - build=False, - tar=True, - changes=True, - dsc=True, - ) - print "Building for %s finished" % distribution - - -if __name__ == "__main__": - if len(sys.argv) == 1: - distribution = "fremantle" - else: - distribution = sys.argv[1] - build_package(distribution) diff --git a/support/fake_py2deb.py b/support/fake_py2deb.py deleted file mode 100644 index 5d6149d..0000000 --- a/support/fake_py2deb.py +++ /dev/null @@ -1,56 +0,0 @@ -import pprint - - -class Py2deb(object): - - def __init__(self, appName): - self._appName = appName - self.description = "" - self.author = "" - self.mail = "" - self.license = "" - self.depends = "" - self.section = "" - self.arch = "" - self.ugency = "" - self.distribution = "" - self.repository = "" - self.changelog = "" - self.postinstall = "" - self.icon = "" - self._install = {} - - def generate(self, appVersion, appBuild, changelog, tar, dsc, changes, build, src): - return """ -Package: %s -version: %s-%s -Changes: -%s - -Build Options: - Tar: %s - Dsc: %s - Changes: %s - Build: %s - Src: %s - """ % ( - self._appName, appVersion, appBuild, changelog, tar, dsc, changes, build, src - ) - - def __str__(self): - parts = [] - parts.append("%s Package Settings:" % (self._appName, )) - for settingName in dir(self): - if settingName.startswith("_"): - continue - parts.append("\t%s: %s" % (settingName, getattr(self, settingName))) - - parts.append(pprint.pformat(self._install)) - - return "\n".join(parts) - - def __getitem__(self, key): - return self._install[key] - - def __setitem__(self, key, item): - self._install[key] = item diff --git a/support/py2deb.py b/support/py2deb.py deleted file mode 100644 index 0518480..0000000 --- a/support/py2deb.py +++ /dev/null @@ -1,994 +0,0 @@ -#!/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") - # TODO Renable signing - # 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") - # TODO Renable signing - # 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 __getitem__(self, k): - return self.__files[k] - - 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) diff --git a/support/scale.py b/support/scale.py new file mode 100755 index 0000000..f9eb784 --- /dev/null +++ b/support/scale.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +from __future__ import with_statement +from __future__ import division + +import os +import logging + +from PIL import Image + + +_moduleLogger = logging.getLogger(__name__) + + +def main(args): + import optparse + parser = optparse.OptionParser() + parser.add_option( + "--input", dest="input", + help="Input image to scale", metavar="INPUT" + ) + parser.add_option( + "--output", dest="output", + help="Scaled image", metavar="OUTPUT" + ) + parser.add_option( + "--size", dest="size", + help="Icon size", metavar="SIZE" + ) + options, positional = parser.parse_args(args) + if positional: + parser.error("No positional arguments supported") + if None in [options.input, options.output, options.size]: + parser.error("Missing argument") + if options.size == "guess": + parts = reversed(os.path.split(options.output)) + for part in parts: + try: + options.size = int(part) + _moduleLogger.info("Assuming image size of %r" % options.size) + break + except ValueError: + pass + + icon = Image.open(options.input) + icon.thumbnail((options.size, options.size), Image.ANTIALIAS) + icon.save(options.output) + + +if __name__ == "__main__": + import sys + retcode = main(sys.argv[1:]) + sys.exit(retcode)