From: Ed Page Date: Thu, 22 Apr 2010 22:42:17 +0000 (-0500) Subject: Initial import of all of the helpful utils X-Git-Url: http://git.maemo.org/git/?p=gc-dialer;a=commitdiff_plain;h=16b36198292fdb9618d2b568e5cce2e5b3c55198 Initial import of all of the helpful utils --- 16b36198292fdb9618d2b568e5cce2e5b3c55198 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5ab7695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library 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.1 of the License, or (at your option) any later version. + + This library 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 library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..af801b1 --- /dev/null +++ b/Makefile @@ -0,0 +1,106 @@ +PROJECT_NAME= +SOURCE_PATH=src +SOURCE=$(shell find $(SOURCE_PATH) -iname "*.py") +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 +TAG_FILE=~/.ctags/$(PROJECT_NAME).tags +TODO_FILE=./TODO + +DEBUGGER=winpdb +UNIT_TEST=nosetests --with-doctest -w . +SYNTAX_TEST=support/test_syntax.py +STYLE_TEST=../../Python/tools/pep8.py --ignore=W191,E501 +LINT_RC=./support/pylint.rc +LINT=pylint --rcfile=$(LINT_RC) +PROFILE_GEN=python -m cProfile -o .profile +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).py + +profile: $(OBJ) + $(PROFILE_GEN) $(PROGRAM) + $(PROFILE_VIEW) + +debug: $(OBJ) + $(DEBUGGER) $(PROGRAM) + +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)) ; ) + #$(foreach file, $(OBJ), 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 + +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 + +lint: $(OBJ) + $(foreach file, $(SOURCE), $(LINT) $(file) ; ) + +tags: $(TAG_FILE) + +todo: $(TODO_FILE) + +clean: + rm -Rf $(OBJ) + rm -Rf $(BUILD_PATH) + rm -Rf $(TODO_FILE) + +distclean: + rm -Rf $(OBJ) + rm -Rf $(BUILD_PATH) + rm -Rf $(TAG_FILE) + 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 + +$(TAG_FILE): $(OBJ) + mkdir -p $(dir $(TAG_FILE)) + $(CTAGS) -o $(TAG_FILE) $(SOURCE) + +$(TODO_FILE): $(SOURCE) + @- $(TODO_FINDER) $(SOURCE) > $(TODO_FILE) + +%.pyc: %.py + $(SYNTAX_TEST) $< + +#Makefile Debugging +#Target to print any variable, can be added to the dependencies of any other target +#Userfule flags for make, -d, -p, -n +print-%: ; @$(error $* is $($*) ($(value $*)) (from $(origin $*))) diff --git a/README b/README new file mode 100644 index 0000000..ac2fe23 --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +Building a package +=================== +Run + make package diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..4265cc3 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 0000000..bcaa5a9 --- /dev/null +++ b/src/constants.py @@ -0,0 +1,10 @@ +import os + +__pretty_app_name__ = "" +__app_name__ = "" +__version__ = "0.1.0" +__build__ = 0 +__app_magic__ = 0xdeadbeef +_data_path_ = os.path.join(os.path.expanduser("~"), ".%s" % __app_name__) +_user_settings_ = "%s/settings.ini" % _data_path_ +_user_logpath_ = "%s/%s.log" % (_data_path_, __app_name__) diff --git a/src/hildonize.py b/src/hildonize.py new file mode 100644 index 0000000..77d585a --- /dev/null +++ b/src/hildonize.py @@ -0,0 +1,741 @@ +#!/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): + appMenu = hildon.AppMenu() + window.set_app_menu(appMenu) + gtkMenu.get_parent().remove(gtkMenu) + return appMenu + + +def _hildon_hildonize_menu(window, gtkMenu): + 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): + 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 _hildon_set_pix_cell_thumb_selectable(renderer): + renderer.set_property("stock-size", 48) + + +def _null_set_pix_cell_thumb_selectable(renderer): + pass + + +if IS_HILDON_SUPPORTED: + set_pix_cell_thumb_selectable = _hildon_set_pix_cell_thumb_selectable +else: + set_pix_cell_thumb_selectable = _null_set_pix_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_window_to_portrait(window): + # gtk documentation is unclear whether this does a "=" or a "|=" + flags = hildon.PORTRAIT_MODE_SUPPORT | hildon.PORTRAIT_MODE_REQUEST + hildon.hildon_gtk_window_set_portrait_flags(window, flags) + + +def _hildon_window_to_landscape(window): + # gtk documentation is unclear whether this does a "=" or a "&= ~" + flags = hildon.PORTRAIT_MODE_SUPPORT + hildon.hildon_gtk_window_set_portrait_flags(window, flags) + + +def _null_window_to_portrait(window): + pass + + +def _null_window_to_landscape(window): + pass + + +try: + hildon.PORTRAIT_MODE_SUPPORT + hildon.PORTRAIT_MODE_REQUEST + hildon.hildon_gtk_window_set_portrait_flags + + 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() + if parent is not None: + 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 and IS_FREMANTLE_SUPPORTED: + appMenu = hildon.AppMenu() + for i in xrange(5): + b = gtk.Button(str(i)) + appMenu.append(b) + win.set_app_menu(appMenu) + win.show_all() + appMenu.show_all() + gtk.main() + elif False: + print touch_selector(win, "Test", ["A", "B", "C", "D"], 2) + elif False: + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C") + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah") + elif False: + import pprint + name, value = "", "" + goodLocals = [ + (name, value) for (name, value) in locals().iteritems() + if not name.startswith("_") + ] + pprint.pprint(goodLocals) + elif False: + import time + show_information_banner(win, "Hello World") + time.sleep(5) + elif 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 new file mode 100755 index 0000000..203beaf --- /dev/null +++ b/support/builddeb.py @@ -0,0 +1,149 @@ +#!/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__ = """ +. +Homepage: +""" +__author__ = "Ed Page" +__email__ = "eopage@byu.net" +__version__ = constants.__version__ +__build__ = constants.__build__ +__changelog__ = """ +""" + + +__postinstall__ = """#!/bin/sh -e + +gtk-update-icon-cache -f /usr/share/icons/hicolor +""" + + +def find_files(path): + for root, dirs, files in os.walk(path): + for file in files: + if file.startswith("src-"): + fileParts = file.split("-") + unused, relPathParts, newName = fileParts[0], fileParts[1:-1], fileParts[-1] + assert unused == "src" + 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 = "" + p.upgradeDescription = __changelog__.split("\n\n", 1)[0] + p.author = __author__ + p.mail = __email__ + p.license = "lgpl" + p.depends = ", ".join([ + "python2.6 | python2.5", + "python-gtk2 | python2.5-gtk2", + "python-xml | python2.5-xml", + "python-dbus | python2.5-dbus", + ]) + maemoSpecificDepends = ", python-osso | python2.5-osso, python-hildon | python2.5-hildon" + p.depends += { + "debian": ", python-glade2", + "diablo": maemoSpecificDepends + ", python2.5-conic", + "fremantle": maemoSpecificDepends + ", python-glade2, python-alarm", + }[distribution] + p.recommends = ", ".join([ + ]) + p.section = { + "debian": "", + "diablo": "", + "fremantle": "", + }[distribution] + p.arch = "all" + p.urgency = "low" + p.distribution = "diablo fremantle debian" + p.repository = "extras" + p.changelog = __changelog__ + p.postinstall = __postinstall__ + p.icon = { + "debian": "", + "diablo": "", + "fremantle": "", # Fremantle natively uses 48x48 + }[distribution] + p["/usr/bin"] = [ "" ] + for relPath, files in unflatten_files(find_files(".")).iteritems(): + fullPath = "" + if relPath: + fullPath += os.sep+relPath + p[fullPath] = list( + "|".join((oldName, newName)) + for (oldName, newName) in files + ) + p["/usr/share/applications/hildon"] = [""] + p["/usr/share/icons/hicolor/26x26/hildon"] = [""] + p["/usr/share/icons/hicolor/64x64/hildon"] = [""] + p["/usr/share/icons/hicolor/scalable/hildon"] = [""] + + if distribution == "debian": + print p + print p.generate( + version="%s-%s" % (__version__, __build__), + changelog=__changelog__, + build=True, + tar=False, + changes=False, + dsc=False, + ) + print "Building for %s finished" % distribution + else: + print p + 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: + try: + import optparse + except ImportError: + optparse = None + + if optparse is not None: + parser = optparse.OptionParser() + (commandOptions, commandArgs) = parser.parse_args() + else: + commandArgs = None + commandArgs = ["diablo"] + build_package(commandArgs[0]) diff --git a/support/fake_py2deb.py b/support/fake_py2deb.py new file mode 100644 index 0000000..5d6149d --- /dev/null +++ b/support/fake_py2deb.py @@ -0,0 +1,56 @@ +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 new file mode 100644 index 0000000..4a47513 --- /dev/null +++ b/support/py2deb.py @@ -0,0 +1,991 @@ +#!/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 __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/pylint.rc b/support/pylint.rc new file mode 100644 index 0000000..2a371a1 --- /dev/null +++ b/support/pylint.rc @@ -0,0 +1,305 @@ +# lint Python modules using external checkers. +# +# This is the main checker controling the other ones and the reports +# generation. It is itself both a raw checker and an astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# Set the cache size for astng objects. +cache-size=500 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable only checker(s) with the given id(s). This option conflicts with the +# disable-checker option +#enable-checker= + +# Enable all checker(s) except those with the given id(s). This option +# conflicts with the enable-checker option +#disable-checker= + +# Enable all messages in the listed categories. +#enable-msg-cat= + +# Disable all messages in the listed categories. +#disable-msg-cat= + +# Enable the message(s) with the given id(s). +#enable-msg= + +# Disable the message(s) with the given id(s). +disable-msg=W0403,W0612,W0613,C0103,C0111,C0301,R0903,W0142,W0603,R0904,R0921,R0201 + +[REPORTS] + +# set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=colorized + +# Include message's id in output +include-ids=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells wether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note).You have access to the variables errors warning, statement which +# respectivly contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (R0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (R0004). +comment=no + +# Enable the report(s) with the given id(s). +#enable-report= + +# Disable the report(s) with the given id(s). +#disable-report= + + +# checks for +# * unused variables / imports +# * undefined variables +# * redefinition of variable from builtins or from an outer scope +# * use of variable before assigment +# +[VARIABLES] + +# Tells wether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching names used for dummy variables (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +# checks for : +# * doc strings +# * modules / classes / functions / methods / arguments / variables name +# * number of arguments, local variables, branchs, returns and statements in +# functions, methods +# * required module attributes +# * dangerous default values as arguments +# * redefinition of function / method / class +# * uses of the global statement +# +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-zA-Z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-zA-Z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-zA-Z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + + +# try to find bugs in the code using type inference +# +[TYPECHECK] + +# Tells wether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# When zope mode is activated, consider the acquired-members option to ignore +# access to some undefined attributes. +zope=no + +# List of members which are usually get through zope's acquisition mecanism and +# so shouldn't trigger E0201 when accessed (need zope=yes to be considered). +acquired-members=REQUEST,acl_users,aq_parent + + +# checks for sign of poor/misdesign: +# * number of methods, attributes, local variables... +# * size, complexity of functions, methods +# +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=15 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=1 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +# checks for : +# * methods without self as first argument +# * overridden methods signature +# * access only to existant members via self +# * attributes not defined in the __init__ method +# * supported interfaces implementation +# * unreachable code +# +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + + +# checks for +# * external modules dependencies +# * relative / wildcard imports +# * cyclic imports +# * uses of deprecated modules +# +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report R0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report R0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report R0402 must +# not be disabled) +int-import-graph= + + +# checks for similarities and duplicated code. This computation may be +# memory / CPU intensive, so you should disable it if you experiments some +# problems. +# +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +# checks for: +# * warning notes in the code like FIXME, XXX +# * PEP 263: source code with non ascii character but no encoding declaration +# +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +# checks for : +# * unauthorized constructions +# * strict indentation +# * line length +# * use of <> instead of != +# +[FORMAT] + +# Maximum number of characters on a single line. +# @note Limiting this to the most extreme cases +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string='\t' diff --git a/support/test_syntax.py b/support/test_syntax.py new file mode 100755 index 0000000..65a373c --- /dev/null +++ b/support/test_syntax.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +import commands + + +verbose = False + + +def syntax_test(file): + commandTemplate = """ + python -t -t -W all -c "import py_compile; py_compile.compile ('%(filename)s', doraise=False)" """ + compileCommand = commandTemplate % {"filename": file} + (status, text) = commands.getstatusoutput (compileCommand) + text = text.rstrip() + passed = len(text) == 0 + + if passed: + output = ("Syntax is correct for "+file) if verbose else "" + else: + output = ("Syntax is invalid for %s\n" % file) if verbose else "" + output += text + return (passed, output) + + +if __name__ == "__main__": + import sys + import os + import optparse + + opar = optparse.OptionParser() + opar.add_option("-v", "--verbose", dest="verbose", help="Toggle verbosity", action="store_true", default=False) + options, args = opar.parse_args(sys.argv[1:]) + verbose = options.verbose + + completeOutput = [] + allPassed = True + for filename in args: + passed, output = syntax_test(filename) + if not passed: + allPassed = False + if output.strip(): + completeOutput.append(output) + print "\n".join(completeOutput) + + sys.exit(0 if allPassed else 1); diff --git a/support/todo.py b/support/todo.py new file mode 100755 index 0000000..90cbd04 --- /dev/null +++ b/support/todo.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +from __future__ import with_statement +import itertools + + +verbose = False + + +def tag_parser(file, tag): + """ + >>> nothing = [] + >>> for todo in tag_parser(nothing, "@todo"): + ... print todo + ... + >>> one = ["@todo Help!"] + >>> for todo in tag_parser(one, "@todo"): + ... print todo + ... + 1: @todo Help! + >>> mixed = ["one", "@todo two", "three"] + >>> for todo in tag_parser(mixed, "@todo"): + ... print todo + ... + 2: @todo two + >>> embedded = ["one @todo two", "three"] + >>> for todo in tag_parser(embedded, "@todo"): + ... print todo + ... + 1: @todo two + >>> continuation = ["one", "@todo two", " three"] + >>> for todo in tag_parser(continuation, "@todo"): + ... print todo + ... + 2: @todo two three + >>> series = ["one", "@todo two", "@todo three"] + >>> for todo in tag_parser(series, "@todo"): + ... print todo + ... + 2: @todo two + 3: @todo three + """ + currentTodo = [] + prefix = None + for lineNumber, line in enumerate(file): + column = line.find(tag) + if column != -1: + if currentTodo: + yield "\n".join (currentTodo) + prefix = line[0:column] + currentTodo = ["%d: %s" % (lineNumber+1, line[column:].strip())] + elif prefix is not None and len(prefix)+1 < len(line) and line.startswith(prefix) and line[len(prefix)].isspace(): + currentTodo.append (line[len(prefix):].rstrip()) + elif currentTodo: + yield "\n".join (currentTodo) + currentTodo = [] + prefix = None + if currentTodo: + yield "\n".join (currentTodo) + + +def tag_finder(filename, tag): + todoList = [] + + with open(filename) as file: + body = "\n".join (tag_parser(file, tag)) + passed = not body + if passed: + output = "No %s's for %s" % (tag, filename) if verbose else "" + else: + header = "%s's for %s:\n" % (tag, filename) if verbose else "" + output = header + body + output += "\n" if verbose else "" + + return (passed, output) + + +if __name__ == "__main__": + import sys + import os + import optparse + + opar = optparse.OptionParser() + opar.add_option("-v", "--verbose", dest="verbose", help="Toggle verbosity", action="store_true", default=False) + options, args = opar.parse_args(sys.argv[1:]) + verbose = options.verbose + + bugsAsError = True + todosAsError = False + + completeOutput = [] + allPassed = True + for filename in args: + bugPassed, bugOutput = tag_finder(filename, "@bug") + todoPassed, todoOutput = tag_finder(filename, "@todo") + output = "\n".join ([bugOutput, todoOutput]) + if (not bugPassed and bugsAsError) or (not todoPassed and todosAsError): + allPassed = False + output = output.strip() + if output: + completeOutput.append(filename+":\n"+output+"\n\n") + print "\n".join(completeOutput) + + sys.exit(0 if allPassed else 1); diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..a2da797 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python + + +from __future__ import with_statement + +import inspect +import contextlib +import functools + + +def TODO(func): + """ + unittest test method decorator that ignores + exceptions raised by test + + Used to annotate test methods for code that may + not be written yet. Ignores failures in the + annotated test method; fails if the text + unexpectedly succeeds. + !author http://kbyanc.blogspot.com/2007/06/pythons-unittest-module-aint-that-bad.html + + Example: + >>> import unittest + >>> class ExampleTestCase(unittest.TestCase): + ... @TODO + ... def testToDo(self): + ... MyModule.DoesNotExistYet('boo') + ... + """ + + @functools.wraps(func) + def wrapper(*args, **kw): + try: + func(*args, **kw) + succeeded = True + except: + succeeded = False + assert succeeded is False, \ + "%s marked TODO but passed" % func.__name__ + return wrapper + + +def PlatformSpecific(platformList): + """ + unittest test method decorator that only + runs test method if os.name is in the + given list of platforms + !author http://kbyanc.blogspot.com/2007/06/pythons-unittest-module-aint-that-bad.html + Example: + >>> import unittest + >>> class ExampleTestCase(unittest.TestCase): + ... @PlatformSpecific(('mac', )) + ... def testMacOnly(self): + ... MyModule.SomeMacSpecificFunction() + ... + """ + + def decorator(func): + import os + + @functools.wraps(func) + def wrapper(*args, **kw): + if os.name in platformList: + return func(*args, **kw) + return wrapper + return decorator + + +def CheckReferences(func): + """ + !author http://kbyanc.blogspot.com/2007/06/pythons-unittest-module-aint-that-bad.html + """ + + @functools.wraps(func) + def wrapper(*args, **kw): + refCounts = [] + for i in range(5): + func(*args, **kw) + refCounts.append(XXXGetRefCount()) + assert min(refCounts) != max(refCounts), "Reference counts changed - %r" % refCounts + + return wrapper + + +@contextlib.contextmanager +def expected(exception): + """ + >>> with expected2(ZeroDivisionError): + ... 1 / 0 + >>> with expected2(AssertionError("expected ZeroDivisionError to have been thrown")): + ... with expected(ZeroDivisionError): + ... 1 / 2 + Traceback (most recent call last): + File "/usr/lib/python2.5/doctest.py", line 1228, in __run + compileflags, 1) in test.globs + File "", line 3, in + 1 / 2 + File "/media/data/Personal/Development/bzr/Recollection-trunk/src/libraries/recipes/context.py", line 139, in __exit__ + assert t is not None, ("expected {0:%s} to have been thrown" % (self._t.__name__)) + AssertionError: expected {0:ZeroDivisionError} to have been thrown + >>> with expected2(Exception("foo")): + ... raise Exception("foo") + >>> with expected2(Exception("bar")): + ... with expected(Exception("foo")): # this won't catch it + ... raise Exception("bar") + ... assert False, "should not see me" + >>> with expected2(Exception("can specify")): + ... raise Exception("can specify prefixes") + >>> with expected2(Exception("Base class fun")): + True + >>> True + False + """ + if isinstance(exception, Exception): + excType, excValue = type(exception), str(exception) + elif isinstance(exception, type): + excType, excValue = exception, "" + + try: + yield + except Exception, e: + if not (excType in inspect.getmro(type(e)) and str(e).startswith(excValue)): + raise + else: + raise AssertionError("expected {0:%s} to have been thrown" % excType.__name__) + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/www/download.html b/www/download.html new file mode 100644 index 0000000..a2b137b --- /dev/null +++ b/www/download.html @@ -0,0 +1,36 @@ + + + + + +

+

+

+

[About] [Screenshots] [Download] +

Download

+ +

Packages

+

Maemo 5 and Later: Go to the application installer, enable Extras, and download

+ +

Maemo 4.1 and Earlier: Add the Extras Repository and check the App Manager

+ +

Linux: .deb files

+ +

Development

+

Source

+

For the most up to date version check out git. +

+

Requires

+
    +
  • PyGTK / Glade
  • +
  • Python bindings for hildon, osso, dbus, and conic
  • +
+ +

Bugs

+ +

Discuss your issue on Maemo.Org Talk

+ +

File a bug report

+

View existing bug reports

+ + diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..b794594 --- /dev/null +++ b/www/index.html @@ -0,0 +1,30 @@ + + + + + +

+

+

+ +

[About] [Screenshots] [Download] + +

About

+

+

+ + +

This has been tested on Maemo 5, Maemo 4.1, and Ubuntu 9.04. Go check out some reviews or join the conversation

+ +

This is Free Software and available under the LGPLv2.1. + +

Features

+ +

(See Maemo.Org Talk)

+ +
    +
  • +
+ + + diff --git a/www/screenshots.html b/www/screenshots.html new file mode 100644 index 0000000..a7bdf98 --- /dev/null +++ b/www/screenshots.html @@ -0,0 +1,14 @@ + + + + + +

+

+

+

[About] [Screenshots] [Download] + +

Screen shots

+ + +