From 0fb0d38bebc59d0a236f719c1298c32f46cb511b Mon Sep 17 00:00:00 2001 From: epage Date: Fri, 30 Jan 2009 01:14:15 +0000 Subject: [PATCH] Initial checkin git-svn-id: file:///svnroot/ejpi/trunk@1 df6cc7de-23d0-4ae0-bb86-c17aa67b2a9d --- LICENSE | 504 +++++++++++++++++++ Makefile | 47 ++ src/__init__.py | 1 + src/calc.glade | 233 +++++++++ src/ejpi_cli.py | 82 ++++ src/ejpi_glade.py | 437 +++++++++++++++++ src/gtkhistory.py | 119 +++++ src/history.py | 240 +++++++++ src/libraries/__init__.py | 6 + src/libraries/gtkpie.py | 881 ++++++++++++++++++++++++++++++++++ src/libraries/gtkpieboard.py | 177 +++++++ src/libraries/images/alt.png | Bin 0 -> 520 bytes src/libraries/images/arrows.png | Bin 0 -> 450 bytes src/libraries/images/backspace.png | Bin 0 -> 251 bytes src/libraries/images/clear.png | Bin 0 -> 258 bytes src/libraries/images/control.png | Bin 0 -> 554 bytes src/libraries/images/newline.png | Bin 0 -> 280 bytes src/libraries/images/shift.png | Bin 0 -> 466 bytes src/libraries/images/space.png | Bin 0 -> 252 bytes src/libraries/images/super.png | Bin 0 -> 519 bytes src/libraries/images/symbols.dia | Bin 0 -> 1859 bytes src/libraries/images/tab.png | Bin 0 -> 254 bytes src/libraries/recipes/__init__.py | 1 + src/libraries/recipes/algorithms.py | 504 +++++++++++++++++++ src/libraries/recipes/concurrent.py | 278 +++++++++++ src/libraries/recipes/datatypes.py | 386 +++++++++++++++ src/libraries/recipes/gtk_utils.py | 49 ++ src/libraries/recipes/io.py | 128 +++++ src/libraries/recipes/misc.py | 593 +++++++++++++++++++++++ src/libraries/recipes/operators.py | 231 +++++++++ src/libraries/recipes/overloading.py | 256 ++++++++++ src/libraries/recipes/test_utils.py | 129 +++++ src/libraries/recipes/xml_utils.py | 81 ++++ src/operation.py | 406 ++++++++++++++++ src/plugin_utils.py | 316 ++++++++++++ src/plugins/__init__.py | 1 + src/plugins/alphabet.ini | 14 + src/plugins/alphabet.map | 59 +++ src/plugins/alphabet.py | 26 + src/plugins/builtins.ini | 14 + src/plugins/builtins.map | 64 +++ src/plugins/builtins.py | 32 ++ src/plugins/computer.ini | 14 + src/plugins/computer.map | 51 ++ src/plugins/computer.py | 43 ++ src/plugins/trig.ini | 14 + src/plugins/trig.map | 51 ++ src/plugins/trig.py | 80 +++ support/pylint.rc | 305 ++++++++++++ 49 files changed, 6853 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 src/__init__.py create mode 100644 src/calc.glade create mode 100755 src/ejpi_cli.py create mode 100755 src/ejpi_glade.py create mode 100755 src/gtkhistory.py create mode 100644 src/history.py create mode 100644 src/libraries/__init__.py create mode 100755 src/libraries/gtkpie.py create mode 100755 src/libraries/gtkpieboard.py create mode 100644 src/libraries/images/alt.png create mode 100644 src/libraries/images/arrows.png create mode 100644 src/libraries/images/backspace.png create mode 100644 src/libraries/images/clear.png create mode 100644 src/libraries/images/control.png create mode 100644 src/libraries/images/newline.png create mode 100644 src/libraries/images/shift.png create mode 100644 src/libraries/images/space.png create mode 100644 src/libraries/images/super.png create mode 100644 src/libraries/images/symbols.dia create mode 100644 src/libraries/images/tab.png create mode 100644 src/libraries/recipes/__init__.py create mode 100644 src/libraries/recipes/algorithms.py create mode 100644 src/libraries/recipes/concurrent.py create mode 100644 src/libraries/recipes/datatypes.py create mode 100644 src/libraries/recipes/gtk_utils.py create mode 100644 src/libraries/recipes/io.py create mode 100644 src/libraries/recipes/misc.py create mode 100644 src/libraries/recipes/operators.py create mode 100644 src/libraries/recipes/overloading.py create mode 100644 src/libraries/recipes/test_utils.py create mode 100755 src/libraries/recipes/xml_utils.py create mode 100644 src/operation.py create mode 100644 src/plugin_utils.py create mode 100644 src/plugins/__init__.py create mode 100644 src/plugins/alphabet.ini create mode 100644 src/plugins/alphabet.map create mode 100644 src/plugins/alphabet.py create mode 100644 src/plugins/builtins.ini create mode 100644 src/plugins/builtins.map create mode 100644 src/plugins/builtins.py create mode 100644 src/plugins/computer.ini create mode 100644 src/plugins/computer.map create mode 100644 src/plugins/computer.py create mode 100644 src/plugins/trig.ini create mode 100644 src/plugins/trig.map create mode 100644 src/plugins/trig.py create mode 100644 support/pylint.rc 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..209f39f --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +PROJECT_NAME=ejpi +SOURCE_PATH=src +SOURCE=$(shell find $(SOURCE_PATH) -iname *.py) +OBJ=$(SOURCE:.py=.pyc) +TAG_FILE=~/.ctags/$(PROJECT_NAME).tags + +DEBUGGER=winpdb +UNIT_TEST=nosetests -w $(TEST_PATH) +STYLE_TEST=../../Python/tools/pep8.py --ignore=W191 +LINT_RC=./support/pylint.rc +LINT=pylint --rcfile=$(LINT_RC) +COVERAGE_TEST=figleaf +PROFILER=pyprofiler +CTAGS=ctags-exuberant + +.PHONY: all run debug test lint tags package clean + +all: test package + +run: $(SOURCE) + $(SOURCE_PATH)/ejpi_glade.py + +debug: $(SOURCE) + $(DEBUGGER) $(SOURCE_PATH)/ejpi_glade.py + +test: $(SOURCE) + $(SOURCE_PATH)/ejpi_glade.py -t + +package: + ./builddeb.py + +lint: + $(foreach file, $(SOURCE), $(LINT) $(file) ; ) + +tags: $(TAG_FILE) + +clean: + rm -Rf $(OBJ) + +$(TAG_FILE): $(SOURCE) + mkdir -p $(dir $(TAG_FILE)) + $(CTAGS) -o $(TAG_FILE) $(SOURCE) + +#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/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/calc.glade b/src/calc.glade new file mode 100644 index 0000000..9e99d79 --- /dev/null +++ b/src/calc.glade @@ -0,0 +1,233 @@ + + + + + + Cluttered Calc + 800 + 480 + + + True + + + True + + + True + _File + True + + + True + + + True + gtk-quit + True + True + + + + + + + + + + True + _Edit + True + + + True + + + True + gtk-paste + True + True + + + + + + True + gtk-delete + True + True + + + + + + + + + + True + _Help + True + + + True + + + True + gtk-about + True + True + + + + + + + + + + False + + + + + True + + + True + + + True + True + False + + + False + + + + + True + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK + + + True + + + True + gtk-dialog-error + + + False + False + + + + + True + True + + + 1 + + + + + True + Close Error Message + gtk-close + + + False + 2 + + + + + + + False + False + 1 + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + False + False + True + True + False + True + True + True + + + + + 2 + + + + + + + True + + + + + + True + + + False + 1 + + + + + True + True + GTK_POS_BOTTOM + True + + + + + + True + None + + + tab + False + + + + + 2 + + + + + 1 + + + + + 1 + + + + + + diff --git a/src/ejpi_cli.py b/src/ejpi_cli.py new file mode 100755 index 0000000..8fc34c3 --- /dev/null +++ b/src/ejpi_cli.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +import os + +import plugin_utils +import history + + +PLUGIN_SEARCH_PATHS = [ + os.path.join(os.path.dirname(__file__), "plugins/"), +] + + +OPERATIONS = {} + +CONSTANTS = {} + + +class CliEntry(object): + + def __init__(self): + self.value = "" + + def set_value(self, value): + self.value = value + + def get_value(self): + return self.value + + def clear(self): + self.value = "" + + +def parse_command(userInput): + return OPERATIONS[userInput.strip()] + + +def ambiguous_parse(calc, userInput): + try: + Node = parse_command(userInput) + calc.apply_operation(Node) + return True + except KeyError: + return False + + +def repl(): + entry = CliEntry() + stack = history.CalcHistory() + rpnCalc = history.RpnCalcHistory( + stack, + entry, history.ErrorWarning(), + CONSTANTS, OPERATIONS + ) + while True: + userInput = raw_input(">") + isUsed = ambiguous_parse(rpnCalc, userInput) + if not isUsed: + entry.set_value(userInput) + rpnCalc.push_entry() + + if 0 < len(stack): + node = stack.peek() + print "\t= %s" % str(node) + print "\t~= %s" % str(node.simplify(**CONSTANTS)) + + +def main(): + constantPlugins = plugin_utils.ConstantPluginManager() + constantPlugins.add_path(*PLUGIN_SEARCH_PATHS) + constantPlugins.enable_plugin(constantPlugins.lookup_plugin("Builtin")) + CONSTANTS.update(constantPlugins.constants) + + operatorPlugins = plugin_utils.OperatorPluginManager() + operatorPlugins.add_path(*PLUGIN_SEARCH_PATHS) + operatorPlugins.enable_plugin(operatorPlugins.lookup_plugin("Builtin")) + OPERATIONS.update(operatorPlugins.operators) + + repl() + +if __name__ == "__main__": + main() diff --git a/src/ejpi_glade.py b/src/ejpi_glade.py new file mode 100755 index 0000000..d2cdbd8 --- /dev/null +++ b/src/ejpi_glade.py @@ -0,0 +1,437 @@ +#!/usr/bin/python + +""" +@todo Add preference file + @li enable/disable plugins + @li plugin search path + @li Number format + @li Current tab +@todo Expand operations to support + @li mathml then to cairo? + @li cairo directly? +@todo Expanded copy/paste (Unusure how far to go) + @li Copy formula, value, serialized, mathml, latex? + @li Paste serialized, value? + +Some useful things on Maemo +@li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Statesave.html +@li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Autosave.html +""" + + +from __future__ import with_statement + + +import sys +import gc +import os +import string +import warnings + +import gtk +import gtk.glade + +try: + import hildon +except ImportError: + hildon = None + +from libraries import gtkpie +from libraries import gtkpieboard +import plugin_utils +import history +import gtkhistory + + +PLUGIN_SEARCH_PATHS = [ + os.path.join(os.path.dirname(__file__), "plugins/"), +] + + +class ValueEntry(object): + + def __init__(self, widget): + self.__widget = widget + self.__actualEntryDisplay = "" + + def get_value(self): + value = self.__actualEntryDisplay.strip() + if any( + 0 < value.find(whitespace) + for whitespace in string.whitespace + ): + self.clear() + raise ValueError('Invalid input "%s"' % value) + return value + + def set_value(self, value): + value = value.strip() + if any( + 0 < value.find(whitespace) + for whitespace in string.whitespace + ): + raise ValueError('Invalid input "%s"' % value) + self.__actualEntryDisplay = value + self.__widget.set_text(value) + + def append(self, value): + value = value.strip() + if any( + 0 < value.find(whitespace) + for whitespace in string.whitespace + ): + raise ValueError('Invalid input "%s"' % value) + self.set_value(self.get_value() + value) + + def pop(self): + value = self.get_value()[0:-1] + self.set_value(value) + + def clear(self): + self.set_value("") + + value = property(get_value, set_value, clear) + + +class ErrorDisplay(history.ErrorReporting): + + def __init__(self, widgetTree): + super(ErrorDisplay, self).__init__() + self.__errorBox = widgetTree.get_widget("errorEventBox") + self.__errorDescription = widgetTree.get_widget("errorDescription") + self.__errorClose = widgetTree.get_widget("errorClose") + self.__parentBox = self.__errorBox.get_parent() + + self.__errorBox.connect("button_release_event", self._on_close) + + self.__messages = [] + self.__parentBox.remove(self.__errorBox) + + def push_message(self, message): + if 0 < len(self.__messages): + self.__messages.append(message) + else: + self.__show_message(message) + + def pop_message(self): + if 0 < len(self.__messages): + self.__show_message(self.__messages[0]) + del self.__messages[0] + else: + self.__hide_message() + + def _on_close(self, *args): + self.pop_message() + + def __show_message(self, message): + self.__errorDescription.set_text(message) + self.__parentBox.pack_start(self.__errorBox, False, False) + self.__parentBox.reorder_child(self.__errorBox, 1) + + def __hide_message(self): + self.__errorDescription.set_text("") + self.__parentBox.remove(self.__errorBox) + + +class Calculator(object): + + __pretty_app_name__ = "e^(j pi) + 1 = 0" + __app_name__ = "ejpi" + __version__ = "0.1.0" + __app_magic__ = 0xdeadbeef + + _glade_files = [ + '/usr/lib/ejpi/calc.glade', + os.path.join(os.path.dirname(__file__), "calc.glade"), + os.path.join(os.path.dirname(__file__), "../lib/calc.glade"), + ] + + _plugin_search_paths = [ + os.path.join(os.path.dirname(__file__), "plugins/") + ] + + _user_data = os.path.expanduser("~/.%s/" % __app_name__) + _user_settings = "%s/settings.ini" % _user_data + _user_history = "%s/history.stack" % _user_data + + def __init__(self): + self.__constantPlugins = plugin_utils.ConstantPluginManager() + self.__constantPlugins.add_path(*self._plugin_search_paths) + self.__constantPlugins.enable_plugin(self.__constantPlugins.lookup_plugin("Builtin")) + self.__constantPlugins.enable_plugin(self.__constantPlugins.lookup_plugin("Trigonometry")) + self.__constantPlugins.enable_plugin(self.__constantPlugins.lookup_plugin("Computer")) + self.__constantPlugins.enable_plugin(self.__constantPlugins.lookup_plugin("Alphabet")) + + self.__operatorPlugins = plugin_utils.OperatorPluginManager() + self.__operatorPlugins.add_path(*self._plugin_search_paths) + self.__operatorPlugins.enable_plugin(self.__operatorPlugins.lookup_plugin("Builtin")) + self.__operatorPlugins.enable_plugin(self.__operatorPlugins.lookup_plugin("Trigonometry")) + self.__operatorPlugins.enable_plugin(self.__operatorPlugins.lookup_plugin("Computer")) + self.__operatorPlugins.enable_plugin(self.__operatorPlugins.lookup_plugin("Alphabet")) + + self.__keyboardPlugins = plugin_utils.KeyboardPluginManager() + self.__keyboardPlugins.add_path(*self._plugin_search_paths) + self.__activeKeyboards = {} + + for path in self._glade_files: + if os.path.isfile(path): + self._widgetTree = gtk.glade.XML(path) + break + else: + self.display_error_message("Cannot find calc.glade") + gtk.main_quit() + try: + os.makedirs(self._user_data) + except OSError, e: + if e.errno != 17: + raise + + self._clipboard = gtk.clipboard_get() + self.__window = self._widgetTree.get_widget("mainWindow") + + global hildon + self._app = None + self._isFullScreen = False + if hildon is not None and self.__window is gtk.Window: + warnings.warn("Hildon installed but glade file not updated to work with hildon", UserWarning, 2) + hildon = None + elif hildon is not None: + self._app = hildon.Program() + self.__window = hildon.Window() + self._widgetTree.get_widget("mainLayout").reparent(self.__window) + self._app.add_window(self.__window) + hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('scrollingHistory'), True) + + gtkMenu = self._widgetTree.get_widget("mainMenubar") + menu = gtk.Menu() + for child in gtkMenu.get_children(): + child.reparent(menu) + self.__window.set_menu(menu) + gtkMenu.destroy() + + self.__window.connect("key-press-event", self._on_key_press) + self.__window.connect("window-state-event", self._on_window_state_change) + else: + warnings.warn("No Hildon", UserWarning, 2) + + self.__errorDisplay = ErrorDisplay(self._widgetTree) + self.__userEntry = ValueEntry(self._widgetTree.get_widget("entryView")) + self.__stackView = self._widgetTree.get_widget("historyView") + + self.__historyStore = gtkhistory.GtkCalcHistory(self.__stackView) + self.__history = history.RpnCalcHistory( + self.__historyStore, + self.__userEntry, self.__errorDisplay, + self.__constantPlugins.constants, self.__operatorPlugins.operators + ) + self.__load_history() + + self.__sliceStyle = gtkpie.generate_pie_style(self.__window) + self.__handler = gtkpieboard.KeyboardHandler(self._on_entry_direct) + self.__handler.register_command_handler("push", self._on_push) + self.__handler.register_command_handler("unpush", self._on_unpush) + self.__handler.register_command_handler("backspace", self._on_entry_backspace) + self.__handler.register_command_handler("clear", self._on_entry_clear) + + builtinKeyboardId = self.__keyboardPlugins.lookup_plugin("Builtin") + self.__keyboardPlugins.enable_plugin(builtinKeyboardId) + self.__builtinPlugin = self.__keyboardPlugins.keyboards["Builtin"].construct_keyboard() + self.__builtinKeyboard = self.__builtinPlugin.setup(self.__history, self.__sliceStyle, self.__handler) + self._widgetTree.get_widget("functionLayout").pack_start(self.__builtinKeyboard) + self._widgetTree.get_widget("functionLayout").reorder_child(self.__builtinKeyboard, 0) + self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Trigonometry")) + self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Computer")) + self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Alphabet")) + + callbackMapping = { + "on_calculator_quit": self._on_close, + "on_paste": self._on_paste, + "on_clear_entry": self._on_clear_all, + "on_about": self._on_about_activate, + } + self._widgetTree.signal_autoconnect(callbackMapping) + + if self.__window: + if hildon is None: + self.__window.set_title("%s" % self.__pretty_app_name__) + self.__window.connect("destroy", self._on_close) + self.__window.show_all() + + try: + import osso + except ImportError: + osso = None + + self._osso = None + if osso is not None: + self._osso = osso.Context(Calculator.__app_name__, Calculator.__version__, False) + device = osso.DeviceState(self._osso) + device.set_device_state_callback(self._on_device_state_change, 0) + else: + warnings.warn("No OSSO", UserWarning, 2) + + def display_error_message(self, msg): + error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg) + + def close(dialog, response, editor): + editor.about_dialog = None + dialog.destroy() + error_dialog.connect("response", close, self) + error_dialog.run() + + def enable_plugin(self, pluginId): + self.__keyboardPlugins.enable_plugin(pluginId) + pluginData = self.__keyboardPlugins.plugin_info(pluginId) + pluginName = pluginData[0] + plugin = self.__keyboardPlugins.keyboards[pluginName].construct_keyboard() + pluginKeyboard = plugin.setup(self.__history, self.__sliceStyle, self.__handler) + + keyboardTabs = self._widgetTree.get_widget("pluginKeyboards") + keyboardTabs.append_page(pluginKeyboard, gtk.Label(pluginName)) + keyboardPageNum = keyboardTabs.page_num(pluginKeyboard) + assert keyboardPageNum not in self.__activeKeyboards + self.__activeKeyboards[keyboardPageNum] = { + "pluginName": pluginName, + "plugin": plugin, + "pluginKeyboard": pluginKeyboard, + } + + def __load_history(self): + serialized = [] + try: + with open(self._user_history, "rU") as f: + serialized = ( + (part.strip() for part in line.split(" ")) + for line in f.readlines() + ) + except IOError, e: + if e.errno != 2: + raise + self.__history.deserialize_stack(serialized) + + def __save_history(self): + serialized = self.__history.serialize_stack() + with open(self._user_history, "w") as f: + for lineData in serialized: + line = " ".join(data for data in lineData) + f.write("%s\n" % line) + + def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData): + """ + For system_inactivity, we have no background tasks to pause + + @note Hildon specific + """ + if memory_low: + gc.collect() + + if save_unsaved_data or shutdown: + self.__save_history() + + def _on_window_state_change(self, widget, event, *args): + """ + @note Hildon specific + """ + if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: + self._isFullScreen = True + else: + self._isFullScreen = False + + def _on_close(self, *args, **kwds): + try: + self.__save_history() + finally: + gtk.main_quit() + + def _on_paste(self, *args): + contents = self._clipboard.wait_for_text() + self.__userEntry.append(contents) + + def _on_key_press(self, widget, event, *args): + """ + @note Hildon specific + """ + if event.keyval == gtk.keysyms.F6: + if self._isFullScreen: + self.__window.unfullscreen() + else: + self.__window.fullscreen() + + def _on_push(self, *args): + self.__history.push_entry() + + def _on_unpush(self, *args): + self.__historyStore.unpush() + + def _on_entry_direct(self, keys, modifiers): + if "shift" in modifiers: + keys = keys.upper() + self.__userEntry.append(keys) + + def _on_entry_backspace(self, *args): + self.__userEntry.pop() + + def _on_entry_clear(self, *args): + self.__userEntry.clear() + + def _on_clear_all(self, *args): + self.__history.clear() + + def _on_about_activate(self, *args): + dlg = gtk.AboutDialog() + dlg.set_name(self.__pretty_app_name__) + dlg.set_version(self.__version__) + dlg.set_copyright("Copyright 2008 - LGPL") + dlg.set_comments("") + dlg.set_website("") + dlg.set_authors([""]) + dlg.run() + dlg.destroy() + + +def run_doctest(): + import doctest + + failureCount, testCount = doctest.testmod() + if not failureCount: + print "Tests Successful" + sys.exit(0) + else: + sys.exit(1) + + +def run_calculator(): + gtk.gdk.threads_init() + + if hildon is not None: + gtk.set_application_name(Calculator.__pretty_app_name__) + handle = Calculator() + gtk.main() + + +class DummyOptions(object): + + def __init__(self): + self.test = False + + +if __name__ == "__main__": + if len(sys.argv) > 1: + try: + import optparse + except ImportError: + optparse = None + + if optparse is not None: + parser = optparse.OptionParser() + parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests") + (commandOptions, commandArgs) = parser.parse_args() + else: + commandOptions = DummyOptions() + commandArgs = [] + + if commandOptions.test: + run_doctest() + else: + gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), ) + run_calculator() diff --git a/src/gtkhistory.py b/src/gtkhistory.py new file mode 100755 index 0000000..d61739a --- /dev/null +++ b/src/gtkhistory.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +""" +http://www.grigoriev.ru/svgmath/ (MathML->SVG in Python) +http://helm.cs.unibo.it/mml-widget/ (MathML widget in C++) +""" + + +import gobject +import gtk + + +import history +import operation + + +class GtkCalcHistory(history.AbstractHistory): + + BUTTON_IDX = 0 + VIEW_DATA_IDX = 1 + VIEW_RESULT_IDX = 2 + DATA_IDX = 3 + RESULT_IDX = 4 + + def __init__(self, view): + super(GtkCalcHistory, self).__init__() + self.__prettyRenderer = operation.render_number() + self._historyView = view + + # stock-id, display, value + self.__historyStore = gtk.ListStore( + gobject.TYPE_STRING, # stock id for pixbuf + gobject.TYPE_STRING, # view of data + gobject.TYPE_STRING, # view of result + object, # data + object, # result + ) + self._historyView.set_model(self.__historyStore) + + # create the TreeViewColumns to display the data + self.__closeColumn = gtk.TreeViewColumn('') + self._historyView.append_column(self.__closeColumn) + + self.__historyColumn = gtk.TreeViewColumn('History') + self.__historyColumn.set_sort_column_id(0) + self.__historyColumn.set_expand(True) + self._historyView.append_column(self.__historyColumn) + + self.__resultColumn = gtk.TreeViewColumn('') + self.__resultColumn.set_sort_column_id(0) + self._historyView.append_column(self.__resultColumn) + + # create a CellRenderers to render the data + self.__closeCell = gtk.CellRendererPixbuf() + self.__closeColumn.pack_start(self.__closeCell, False) + self.__closeColumn.set_attributes(self.__closeCell, stock_id=0) + + self.__expressionCell = gtk.CellRendererText() + self.__historyColumn.pack_start(self.__expressionCell, True) + self.__historyColumn.set_attributes(self.__expressionCell, text=1) + + self.__valueCell = gtk.CellRendererText() + self.__resultColumn.pack_end(self.__valueCell, False) + self.__resultColumn.set_attributes(self.__valueCell, text=2) + + self._historyView.set_reorderable(True) + self._historyView.connect("row-activated", self._on_close_activated) + + def push(self, node): + simpleNode = node.simplify() + self.__historyStore.prepend([ + gtk.STOCK_CLOSE, + operation.render_operation(self.__prettyRenderer, node), + operation.render_operation(self.__prettyRenderer, simpleNode), + node, + simpleNode + ]) + + def pop(self): + if len(self.__historyStore) == 0: + raise IndexError("Not enough items in the history for the operation") + + row = self.__historyStore[0] + data = row[self.DATA_IDX] + del self.__historyStore[0] + + return data + + def peek(self): + if len(self.__historyStore) == 0: + raise IndexError("Not enough items in the history for the operation") + row = self.__historyStore[0] + data = row[self.DATA_IDX] + return data + + def clear(self): + self.__historyStore.clear() + + def __len__(self): + return len(self.__historyStore) + + def __iter__(self): + for row in iter(self.__historyStore): + data = row[self.DATA_IDX] + yield data + + def _on_close_activated(self, treeView, path, viewColumn): + if viewColumn is self.__closeColumn: + del self.__historyStore[path[0]] + elif viewColumn is self.__resultColumn: + row = self.__historyStore[path[0]] + data = row[self.RESULT_IDX] + self.push(data) + elif viewColumn is self.__historyColumn: + row = self.__historyStore[path[0]] + data = row[self.DATA_IDX] + self.push(data) + else: + assert False diff --git a/src/history.py b/src/history.py new file mode 100644 index 0000000..bf0014d --- /dev/null +++ b/src/history.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python + + +import weakref +import warnings + +from libraries.recipes import algorithms +import operation + + +__BASE_MAPPINGS = { + "0x": 16, + "0o": 8, + "0b": 2, +} + + +def parse_number(userInput): + try: + base = __BASE_MAPPINGS.get(userInput[0:2], 10) + if base != 10: + userInput = userInput[2:] # Remove prefix + value = int(userInput, base) + return value, base + except ValueError: + pass + + try: + value = float(userInput) + return value, 10 + except ValueError: + pass + + try: + value = complex(userInput) + return value, 10 + except ValueError: + pass + + raise ValueError('Cannot parse "%s" as a number' % userInput) + + +class ErrorReporting(object): + + def push_message(self, message): + raise NotImplementedError + + def push_exception(self, exception): + self.push_message(exception.message) + warnings.warn(exception, stacklevel=3) + + def pop_message(self): + raise NotImplementedError + + +class ErrorIgnore(ErrorReporting): + + def push_message(self, message): + pass + + def pop_message(self): + pass + + +class ErrorWarning(ErrorReporting): + + def push_message(self, message): + warnings.warn(message, stacklevel=2) + + def pop_message(self): + pass + + +class AbstractHistory(object): + """ + Is it just me or is this class name begging for some jokes? + """ + + def push(self, node): + raise NotImplementedError + + def pop(self): + raise NotImplementedError + + def unpush(self): + node = self.pop() + for child in node.get_children(): + self.push(child) + + def peek(self): + raise NotImplementedError + + def clear(self): + raise NotImplementedError + + def __len__(self): + raise NotImplementedError + + def __iter__(self): + raise NotImplementedError + + +class CalcHistory(AbstractHistory): + + def __init__(self): + super(CalcHistory, self).__init__() + self.__nodeStack = [] + + def push(self, node): + assert node is not None + self.__nodeStack.append(node) + return node + + def pop(self): + popped = self.__nodeStack[-1] + del self.__nodeStack[-1] + return popped + + def peek(self): + return self.__nodeStack[-1] + + def clear(self): + self.__nodeStack = [] + + def __len__(self): + return len(self.__nodeStack) + + def __iter__(self): + return self.__nodeStack[::-1] + + +class RpnCalcHistory(object): + + def __init__(self, history, entry, errorReporting, constants, operations): + self.history = history + self.__entry = weakref.ref(entry) + + self.__errorReporter = errorReporting + self.__constants = constants + self.__operations = operations + + self.__serialRenderer = operation.render_number() + + @property + def errorReporter(self): + return self.__errorReporter + + @property + def OPERATIONS(self): + return self.__operations + + @property + def CONSTANTS(self): + return self.__constants + + def clear(self): + self.history.clear() + self.__entry().clear() + + def push_entry(self): + """ + @todo Add operation duplication. If value is empty, peek at the top + item. If it has children, grab the last one, push it and reapply the + operation. If there are no children then just duplicate the item + """ + + value = self.__entry().get_value() + + valueNode = None + if 0 < len(value): + valueNode = self._parse_value(value) + self.history.push(valueNode) + + self.__entry().clear() + return valueNode + + def apply_operation(self, Node): + try: + self.push_entry() + + node = self._apply_operation(Node) + return node + except StandardError, e: + self.errorReporter.push_exception(e) + return None + + def serialize_stack(self): + serialized = ( + stackNode.serialize(self.__serialRenderer) + for stackNode in self.history + ) + serialized = list(serialized) + serialized.reverse() + return serialized + + def deserialize_stack(self, data): + for possibleNode in data: + for nodeValue in possibleNode: + if nodeValue in self.OPERATIONS: + Node = self.OPERATIONS[nodeValue] + self._apply_operation(Node) + else: + node = self._parse_value(nodeValue) + self.history.push(node) + + def _parse_value(self, userInput): + try: + value, base = parse_number(userInput) + return operation.Value(value, base) + except ValueError: + pass + + try: + return self.CONSTANTS[userInput] + except KeyError: + pass + + return operation.Variable(userInput) + + def _apply_operation(self, Node): + numArgs = Node.argumentCount + + if len(self.history) < numArgs: + raise ValueError( + "Not enough arguments. The stack has %d but %s needs %d" % ( + len(self.history), Node.symbol, numArgs + ) + ) + + args = [arg for arg in algorithms.func_repeat(numArgs, self.history.pop)] + args.reverse() + + try: + node = Node(*args) + except StandardError: + for arg in args: + self.history.push(arg) + raise + self.history.push(node) + return node diff --git a/src/libraries/__init__.py b/src/libraries/__init__.py new file mode 100644 index 0000000..3e621cd --- /dev/null +++ b/src/libraries/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from pkgutil import extend_path + + +#__path__ = extend_path(__path__, __name__) diff --git a/src/libraries/gtkpie.py b/src/libraries/gtkpie.py new file mode 100755 index 0000000..57dbdd6 --- /dev/null +++ b/src/libraries/gtkpie.py @@ -0,0 +1,881 @@ +#!/usr/bin/env python + +""" +@todo Handle sizing in a better manner http://www.gtkmm.org/docs/gtkmm-2.4/docs/tutorial/html/sec-custom-widgets.html +""" + + +from __future__ import division + +import os +import weakref +import math +import copy +import warnings + +import gobject +import gtk +import cairo +import pango + +try: + import rsvg +except ImportError: + rsvg = None + + +def deg_to_rad(deg): + return (2 * math.pi * deg) / 360.0 + + +def rad_to_deg(rad): + return (360.0 * rad) / (2 * math.pi) + + +def normalize_radian_angle(radAng): + """ + Restricts @param radAng to the range [0..2pi) + """ + twoPi = 2 * math.pi + + while radAng < 0: + radAng += twoPi + while twoPi <= radAng: + radAng -= twoPi + + return radAng + + +def delta_to_rtheta(dx, dy): + distance = math.sqrt(dx**2 + dy**2) + + angleInRads = math.atan2(-dy, dx) + if angleInRads < 0: + angleInRads = 2*math.pi + angleInRads + return distance, angleInRads + + +class FontCache(object): + + def __init__(self): + self.__fontCache = {} + + def get_font(self, s): + if s in self.__fontCache: + return self.__fontCache[s] + + descr = pango.FontDescription(s) + self.__fontCache[s] = descr + + return descr + + +FONTS = FontCache() + + +class ImageCache(object): + + def __init__(self): + self.__imageCache = {} + self.__imagePaths = [ + os.path.join(os.path.dirname(__file__), "images"), + ] + + def add_path(self, path): + self.__imagePaths.append(path) + + def get_image(self, s): + if s in self.__imageCache: + return self.__imageCache[s] + + image = None + + if s.lower().endswith(".png"): + for path in self.__imagePaths: + imagePath = os.path.join(path, s) + try: + image = cairo.ImageSurface.create_from_png(imagePath) + break + except: + warnings.warn("Unable to load image %s" % imagePath) + elif s.lower().endswith(".svg") and rsvg is not None: + for path in self.__imagePaths: + imagePath = os.path.join(path, s) + try: + image = rsvg.Handle(file=imagePath) + except: + warnings.warn("Unable to load image %s" % imagePath) + else: + print "Don't know how to load image file type:", s + + if image is not None: + self.__imageCache[s] = image + + return image + + +IMAGES = ImageCache() + + +def convert_color(gtkColor): + r = gtkColor.red / 65535 + g = gtkColor.green / 65535 + b = gtkColor.blue / 65535 + return r, g, b + + +def generate_pie_style(widget): + """ + @bug This seems to always pick the same colors irregardless of the theme + """ + # GTK states: + # * gtk.STATE_NORMAL - The state of a sensitive widget that is not active and does not have the focus + # * gtk.STATE_ACTIVE - The state of a sensitive widget when it is active e.g. a button that is pressed but not yet released + # * gtk.STATE_PRELIGHT - The state of a sensitive widget that has the focus e.g. a button that has the mouse pointer over it. + # * gtk.STATE_SELECTED - The state of a widget that is selected e.g. selected text in a gtk.Entry widget + # * gtk.STATE_INSENSITIVE - The state of a widget that is insensitive and will not respond to any events e.g. cannot be activated, selected or prelit. + + gtkStyle = widget.get_style() + sliceStyle = dict( + (gtkStyleState, { + "text": convert_color(gtkStyle.text[gtkStyleState]), + "fill": convert_color(gtkStyle.bg[gtkStyleState]), + "stroke": convert_color(gtkStyle.bg[gtkStyleState]), + }) + for gtkStyleState in ( + gtk.STATE_NORMAL, gtk.STATE_ACTIVE, gtk.STATE_PRELIGHT, gtk.STATE_SELECTED, gtk.STATE_INSENSITIVE + ) + ) + + return sliceStyle + + +class PieSlice(object): + + SLICE_CENTER = 0 + SLICE_EAST = 1 + SLICE_SOUTH_EAST = 2 + SLICE_SOUTH = 3 + SLICE_SOUTH_WEST = 4 + SLICE_WEST = 5 + SLICE_NORTH_WEST = 6 + SLICE_NORTH = 7 + SLICE_NORTH_EAST = 8 + + MAX_ANGULAR_SLICES = 8 + + SLICE_DIRECTIONS = [ + SLICE_CENTER, + SLICE_EAST, + SLICE_SOUTH_EAST, + SLICE_SOUTH, + SLICE_SOUTH_WEST, + SLICE_WEST, + SLICE_NORTH_WEST, + SLICE_NORTH, + SLICE_NORTH_EAST, + ] + + SLICE_DIRECTION_NAMES = [ + "CENTER", + "EAST", + "SOUTH_EAST", + "SOUTH", + "SOUTH_WEST", + "WEST", + "NORTH_WEST", + "NORTH", + "NORTH_EAST", + ] + + def __init__(self, handler = (lambda p, s, d: None)): + self._direction = self.SLICE_CENTER + self._pie = None + self._style = None + self._handler = handler + + def menu_init(self, pie, direction): + self._direction = direction + self._pie = weakref.ref(pie) + self._style = pie.sliceStyle + + def calculate_minimum_radius(self, context, textLayout): + return 0 + + def draw_fg(self, styleState, isSelected, context, textLayout): + if isSelected: + styleState = gtk.STATE_ACTIVE + self._draw_fg(styleState, context, textLayout) + + def draw_bg(self, styleState, isSelected, context, textLayout): + if isSelected: + styleState = gtk.STATE_ACTIVE + self._draw_bg(styleState, context, textLayout) + + def _draw_fg(self, styleState, context, textLayout): + pass + + def _draw_bg(self, styleState, context, textLayout): + centerPosition = self._pie().centerPosition + radius = max(self._pie().radius, self.calculate_minimum_radius(context, textLayout)) + outerRadius = self._pie().outerRadius + + fillColor = self._style[styleState]["fill"] + strokeColor = self._style[styleState]["stroke"] + if self._direction == self.SLICE_CENTER: + if fillColor: + context.arc( + centerPosition[0], + centerPosition[1], + radius, + 0, + 2 * math.pi + ) + + context.set_source_rgb(*fillColor) + if strokeColor: + context.fill_preserve() + else: + context.fill() + + if strokeColor: + context.arc( + centerPosition[0], + centerPosition[1], + radius, + 0, + 2 * math.pi + ) + context.set_source_rgb(*strokeColor) + context.stroke() + else: + sliceCenterAngle = self.quadrant_to_theta(self._direction) + sliceArcWidth = 2*math.pi / self.MAX_ANGULAR_SLICES + sliceStartAngle = sliceCenterAngle - sliceArcWidth/2 + sliceEndAngle = sliceCenterAngle + sliceArcWidth/2 + + context.arc( + centerPosition[0], + centerPosition[1], + radius, + sliceStartAngle, + sliceEndAngle, + ) + context.arc_negative( + centerPosition[0], + centerPosition[1], + outerRadius, + sliceEndAngle, + sliceStartAngle, + ) + context.close_path() + + if fillColor: + context.set_source_rgb(*fillColor) + if strokeColor: + context.fill_preserve() + else: + context.fill() + + if strokeColor: + context.set_source_rgb(*strokeColor) + context.stroke() + + def activate(self): + self._handler(self._pie(), self, self._direction) + + @classmethod + def rtheta_to_quadrant(cls, distance, angleInRads, innerRadius): + if distance < innerRadius: + quadrant = 0 + else: + gradians = angleInRads / (2*math.pi) + preciseQuadrant = gradians * cls.MAX_ANGULAR_SLICES + cls.MAX_ANGULAR_SLICES / (2 * 2*math.pi) + quadrantWithWrap = int(preciseQuadrant) + quadrant = quadrantWithWrap % cls.MAX_ANGULAR_SLICES + quadrant += 1 + + return quadrant + + @classmethod + def quadrant_to_theta(cls, quadrant): + assert quadrant != 0 + quadrant -= 1 + + gradians = quadrant / cls.MAX_ANGULAR_SLICES + radians = gradians * 2*math.pi + + return radians + + +class NullPieSlice(PieSlice): + + def draw_bg(self, styleState, isSelected, context, textLayout): + super(NullPieSlice, self).draw_bg(styleState, False, context, textLayout) + + +class LabelPieSlice(PieSlice): + + def _align_label(self, labelWidth, labelHeight): + centerPosition = self._pie().centerPosition + if self._direction == PieSlice.SLICE_CENTER: + labelX = centerPosition[0] - labelWidth/2 + labelY = centerPosition[1] - labelHeight/2 + else: + if self._direction in (PieSlice.SLICE_NORTH_WEST, PieSlice.SLICE_WEST, PieSlice.SLICE_SOUTH_WEST): + outerX = 0 + labelX = outerX + elif self._direction in (PieSlice.SLICE_SOUTH, PieSlice.SLICE_NORTH): + outerX = centerPosition[0] + labelX = outerX - labelWidth/2 + elif self._direction in (PieSlice.SLICE_NORTH_EAST, PieSlice.SLICE_EAST, PieSlice.SLICE_SOUTH_EAST): + outerX = centerPosition[0] * 2 + labelX = outerX - labelWidth + else: + assert False, "Direction %d is incorrect" % self._direction + + if self._direction in (PieSlice.SLICE_NORTH_EAST, PieSlice.SLICE_NORTH, PieSlice.SLICE_NORTH_WEST): + outerY = 0 + labelY = outerY + elif self._direction in (PieSlice.SLICE_EAST, PieSlice.SLICE_WEST): + outerY = centerPosition[1] + labelY = outerY - labelHeight/2 + elif self._direction in (PieSlice.SLICE_SOUTH_EAST, PieSlice.SLICE_SOUTH, PieSlice.SLICE_SOUTH_WEST): + outerY = centerPosition[1] * 2 + labelY = outerY - labelHeight + else: + assert False, "Direction %d is incorrect" % self._direction + + return int(labelX), int(labelY) + + +class TextLabelPieSlice(LabelPieSlice): + + def __init__(self, text, fontName = 'Helvetica 12', handler = (lambda p, s, d: None)): + super(TextLabelPieSlice, self).__init__(handler = handler) + self.__text = text + self.__fontName = fontName + + def calculate_minimum_radius(self, context, textLayout): + font = FONTS.get_font(self.__fontName) + textLayout.set_font_description(font) + textLayout.set_markup(self.__text) + + labelWidth, labelHeight = textLayout.get_pixel_size() + return min(labelWidth, labelHeight) / 2 + + def _draw_fg(self, styleState, context, textLayout): + super(TextLabelPieSlice, self)._draw_fg(styleState, context, textLayout) + + textColor = self._style[styleState]["text"] + font = FONTS.get_font(self.__fontName) + + context.set_source_rgb(*textColor) + textLayout.set_font_description(font) + textLayout.set_markup(self.__text) + labelWidth, labelHeight = textLayout.get_pixel_size() + labelX, labelY = self._align_label(labelWidth, labelHeight) + + context.move_to( + labelX, + labelY, + ) + + context.show_layout(textLayout) + + +class ImageLabelPieSlice(LabelPieSlice): + + def __init__(self, imagePath, handler = (lambda p, s, d: None)): + super(ImageLabelPieSlice, self).__init__(handler = handler) + self.__imagePath = imagePath + + def calculate_minimum_radius(self, context, textLayout): + image = IMAGES.get_image(self.__imagePath) + if image is None: + return + labelWidth, labelHeight = image.get_width(), image.get_height() + return min(labelWidth, labelHeight) / 2 + + def _draw_fg(self, styleState, context, textLayout): + super(ImageLabelPieSlice, self)._draw_fg(styleState, context, textLayout) + + image = IMAGES.get_image(self.__imagePath) + if image is None: + return + + labelWidth, labelHeight = image.get_width(), image.get_height() + labelX, labelY = self._align_label(labelWidth, labelHeight) + + context.set_source_surface( + image, + labelX, + labelY, + ) + + context.paint() + + +class PieMenu(gtk.DrawingArea): + + def __init__(self, style = None, **kwds): + super(PieMenu, self).__init__() + + self.sliceStyle = style + self.centerPosition = 0, 0 + self.radius = 20 + self.outerRadius = self.radius * 2 + + self.connect("expose_event", self._on_expose) + self.connect("motion_notify_event", self._on_motion_notify) + self.connect("leave_notify_event", self._on_leave_notify) + self.connect("proximity_in_event", self._on_motion_notify) + self.connect("proximity_out_event", self._on_leave_notify) + self.connect("button_press_event", self._on_button_press) + self.connect("button_release_event", self._on_button_release) + + self.set_events( + gtk.gdk.EXPOSURE_MASK | + gtk.gdk.POINTER_MOTION_MASK | + gtk.gdk.POINTER_MOTION_HINT_MASK | + gtk.gdk.BUTTON_MOTION_MASK | + gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.BUTTON_RELEASE_MASK | + gtk.gdk.PROXIMITY_IN_MASK | + gtk.gdk.PROXIMITY_OUT_MASK | + gtk.gdk.LEAVE_NOTIFY_MASK + ) + + self.__activeSlice = None + self.__slices = {} + for direction in PieSlice.SLICE_DIRECTIONS: + self.add_slice(NullPieSlice(), direction) + + self.__clickPosition = 0, 0 + self.__styleState = gtk.STATE_NORMAL + + def add_slice(self, slice, direction): + assert direction in PieSlice.SLICE_DIRECTIONS + + slice.menu_init(self, direction) + self.__slices[direction] = slice + + if direction == PieSlice.SLICE_CENTER: + self.__activeSlice = self.__slices[PieSlice.SLICE_CENTER] + + def __update_state(self, mousePosition): + rect = self.get_allocation() + newStyleState = self.__styleState + + if ( + 0 <= mousePosition[0] and mousePosition[1] < rect.width and + 0 <= mousePosition[1] and mousePosition[1] < rect.height + ): + if self.__clickPosition == (0, 0): + newStyleState = gtk.STATE_PRELIGHT + else: + if self.__clickPosition != (0, 0): + newStyleState = gtk.STATE_PRELIGHT + + if newStyleState != self.__styleState: + self.__generate_draw_event() + self.__styleState = newStyleState + + def __process_mouse_position(self, mousePosition): + self.__update_state(mousePosition) + if self.__clickPosition == (0, 0): + return + + delta = ( + mousePosition[0] - self.centerPosition[0], + - (mousePosition[1] - self.centerPosition[1]) + ) + distance, angleInRads = delta_to_rtheta(delta[0], delta[1]) + quadrant = PieSlice.rtheta_to_quadrant(distance, angleInRads, self.radius) + self.__select_slice(self.__slices[quadrant]) + + def __select_slice(self, newSlice): + if newSlice is self.__activeSlice: + return + + oldSlice = self.__activeSlice + self.__activeSlice = newSlice + self.__generate_draw_event() + + def __generate_draw_event(self): + if self.window is None: + return + rect = self.get_allocation() + self.window.invalidate_rect(rect, True) + + def _on_expose(self, widget, event): + # @bug Not getting all of the invalidation events needed on Hildon + # @bug Not highlighting proper slice besides center on Hildon + cairoContext = self.window.cairo_create() + pangoContext = self.create_pango_context() + textLayout = pango.Layout(pangoContext) + + rect = self.get_allocation() + + self.centerPosition = event.area.x + event.area.width / 2, event.area.y + event.area.height / 2 + self.radius = max(rect.width, rect.width) / 3 / 2 + self.outerRadius = max(rect.width, rect.height) + + for slice in self.__slices.itervalues(): + isSelected = (slice is self.__activeSlice) + if not isSelected: + cairoContext.rectangle( + event.area.x, + event.area.y, + event.area.width, + event.area.height, + ) + cairoContext.clip() + slice.draw_bg(self.__styleState, isSelected, cairoContext, textLayout) + + cairoContext.rectangle( + event.area.x, + event.area.y, + event.area.width, + event.area.height, + ) + cairoContext.clip() + isSelected = self.__clickPosition != (0, 0) + self.__activeSlice.draw_bg(self.__styleState, isSelected, cairoContext, textLayout) + + for slice in self.__slices.itervalues(): + isSelected = (slice is self.__activeSlice) + if not isSelected: + cairoContext.rectangle( + event.area.x, + event.area.y, + event.area.width, + event.area.height, + ) + cairoContext.clip() + slice.draw_fg(self.__styleState, isSelected, cairoContext, textLayout) + + cairoContext.rectangle( + event.area.x, + event.area.y, + event.area.width, + event.area.height, + ) + cairoContext.clip() + isSelected = self.__clickPosition != (0, 0) + self.__activeSlice.draw_fg(self.__styleState, isSelected, cairoContext, textLayout) + + def _on_leave_notify(self, widget, event): + newStyleState = gtk.STATE_NORMAL + if newStyleState != self.__styleState: + self.__generate_draw_event() + self.__styleState = newStyleState + + mousePosition = event.get_coords() + self.__process_mouse_position(mousePosition) + + def _on_motion_notify(self, widget, event): + mousePosition = event.get_coords() + self.__process_mouse_position(mousePosition) + + def _on_button_press(self, widget, event): + self.__clickPosition = event.get_coords() + + self._on_motion_notify(widget, event) + self.__generate_draw_event() + + def _on_button_release(self, widget, event): + self._on_motion_notify(widget, event) + + self.__activeSlice.activate() + self.__activeSlice = self.__slices[PieSlice.SLICE_CENTER] + self.__clickPosition = 0, 0 + + self.__generate_draw_event() + + +gobject.type_register(PieMenu) + + +class FakeEvent(object): + + def __init__(self, x, y, isHint): + self.x = x + self.y = y + self.is_hint = isHint + + def get_coords(self): + return self.x, self.y + + +class PiePopup(gtk.DrawingArea): + + def __init__(self, style = None, **kwds): + super(PiePopup, self).__init__() + + self.showAllSlices = True + self.sliceStyle = style + self.centerPosition = 0, 0 + self.radius = 20 + self.outerRadius = self.radius * 2 + + self.connect("expose_event", self._on_expose) + self.connect("motion_notify_event", self._on_motion_notify) + self.connect("proximity_in_event", self._on_motion_notify) + self.connect("proximity_out_event", self._on_leave_notify) + self.connect("leave_notify_event", self._on_leave_notify) + self.connect("button_press_event", self._on_button_press) + self.connect("button_release_event", self._on_button_release) + + self.set_events( + gtk.gdk.EXPOSURE_MASK | + gtk.gdk.POINTER_MOTION_MASK | + gtk.gdk.POINTER_MOTION_HINT_MASK | + gtk.gdk.BUTTON_MOTION_MASK | + gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.BUTTON_RELEASE_MASK | + gtk.gdk.PROXIMITY_IN_MASK | + gtk.gdk.PROXIMITY_OUT_MASK | + gtk.gdk.LEAVE_NOTIFY_MASK + ) + + self.__activeSlice = None + self.__slices = {} + self.__localSlices = {} + for direction in PieSlice.SLICE_DIRECTIONS: + self.add_slice(NullPieSlice(), direction) + #self.__activeSlice = NullPieSlice() + #self.__activeSlice.menu_init(self, PieSlice.SLICE_CENTER) + + self.__clickPosition = 0, 0 + self.__popupWindow = None + self.__pie = None + self.__popupTimeDelay = None + self.__styleState = gtk.STATE_NORMAL + + def add_slice(self, slice, direction): + assert direction in PieSlice.SLICE_DIRECTIONS + + self.__slices[direction] = slice + if self.showAllSlices or direction == PieSlice.SLICE_CENTER: + self.__localSlices[direction] = copy.copy(slice) + self.__localSlices[direction].menu_init(self, direction) + + if direction == PieSlice.SLICE_CENTER: + self.__activeSlice = self.__localSlices[PieSlice.SLICE_CENTER] + + def __update_state(self, mousePosition): + rect = self.get_allocation() + newStyleState = self.__styleState + + if ( + 0 <= mousePosition[0] and mousePosition[0] < rect.width and + 0 <= mousePosition[1] and mousePosition[1] < rect.height + ): + if self.__clickPosition == (0, 0): + newStyleState = gtk.STATE_PRELIGHT + else: + if self.__clickPosition != (0, 0): + newStyleState = gtk.STATE_PRELIGHT + + if newStyleState != self.__styleState: + self.__styleState = newStyleState + self.__generate_draw_event() + + def __generate_draw_event(self): + rect = self.get_allocation() + rect.x = 0 + rect.y = 0 + self.window.invalidate_rect(rect, True) + + def _on_expose(self, widget, event): + cairoContext = self.window.cairo_create() + pangoContext = self.create_pango_context() + textLayout = pango.Layout(pangoContext) + + rect = self.get_allocation() + + self.centerPosition = event.area.x + event.area.width / 2, event.area.y + event.area.height / 2 + self.radius = max(rect.width, rect.width) / 3 / 2 + self.outerRadius = max(rect.width, rect.height) + + for slice in self.__localSlices.itervalues(): + isSelected = (slice is self.__activeSlice) + if not isSelected: + cairoContext.rectangle( + event.area.x, + event.area.y, + event.area.width, + event.area.height, + ) + cairoContext.clip() + slice.draw_bg(self.__styleState, isSelected, cairoContext, textLayout) + + cairoContext.rectangle( + event.area.x, + event.area.y, + event.area.width, + event.area.height, + ) + cairoContext.clip() + isSelected = self.__clickPosition != (0, 0) + self.__activeSlice.draw_bg(self.__styleState, isSelected, cairoContext, textLayout) + + for slice in self.__localSlices.itervalues(): + isSelected = (slice is self.__activeSlice) + if not isSelected: + cairoContext.rectangle( + event.area.x, + event.area.y, + event.area.width, + event.area.height, + ) + cairoContext.clip() + slice.draw_fg(self.__styleState, isSelected, cairoContext, textLayout) + + cairoContext.rectangle( + event.area.x, + event.area.y, + event.area.width, + event.area.height, + ) + cairoContext.clip() + isSelected = self.__clickPosition != (0, 0) + self.__activeSlice.draw_fg(self.__styleState, isSelected, cairoContext, textLayout) + + def _on_leave_notify(self, widget, event): + newStyleState = gtk.STATE_NORMAL + if newStyleState != self.__styleState: + self.__styleState = newStyleState + self.__generate_draw_event() + + self._on_motion_notify(widget, event) + + def _on_motion_notify(self, widget, event): + self.__update_state(event.get_coords()) + if self.__popupWindow is None or self.__pie is None: + return + + mousePosition = event.get_root_coords() + piePosition = self.__popupWindow.get_position() + event.x = mousePosition[0] - piePosition[0] + event.y = mousePosition[1] - piePosition[1] + self.__pie._on_motion_notify(self.__pie, event) + + def _on_button_press(self, widget, event): + if len(self.__slices) == 0: + return + + self.__clickPosition = event.get_root_coords() + self.__generate_draw_event() + self.__popupTimeDelay = gobject.timeout_add(100, self._on_delayed_popup) + + def _on_delayed_popup(self): + self.__popup(self.__clickPosition) + gobject.source_remove(self.__popupTimeDelay) + self.__popupTimeDelay = None + return False + + def _on_button_release(self, widget, event): + if len(self.__slices) == 0: + return + + if self.__popupTimeDelay is None: + mousePosition = event.get_root_coords() + piePosition = self.__popupWindow.get_position() + eventX = mousePosition[0] - piePosition[0] + eventY = mousePosition[1] - piePosition[1] + pieRelease = FakeEvent(eventX, eventY, False) + self.__pie._on_button_release(self.__pie, pieRelease) + + self.__unpop() + else: + gobject.source_remove(self.__popupTimeDelay) + self.__popupTimeDelay = None + self.__activeSlice.activate() + + self.__clickPosition = 0, 0 + self.__generate_draw_event() + + def __popup(self, position): + # @bug Figure out what to do with this assert + assert self.__popupWindow is None and self.__pie is None + self.__popupWindow = gtk.Window(type = gtk.WINDOW_POPUP) + self.__popupWindow.set_title("") + + width, height = 256, 256 + popupX, popupY = position[0] - width/2, position[1] - height/2 + self.__popupWindow.move(int(popupX), int(popupY)) + self.__popupWindow.resize(width, height) + + self.__pie = PieMenu(self.sliceStyle) + self.__popupWindow.add(self.__pie) + for direction, slice in self.__slices.iteritems(): + self.__pie.add_slice(copy.copy(slice), direction) + pieClick = FakeEvent(width/2, height/2, False) + self.__pie._on_button_press(self.__pie, pieClick) + self.__pie.connect("button_release_event", self._on_button_release) + + #self.__pie.grab_add() + self.__pie.grab_focus() + + #gtk.gdk.pointer_grab( + # self.__pie.window, + # True, + # gtk.gdk.BUTTON_PRESS_MASK | + # gtk.gdk.BUTTON_RELEASE_MASK | + # gtk.gdk.ENTER_NOTIFY_MASK | + # gtk.gdk.LEAVE_NOTIFY_MASK | + # gtk.gdk.POINTER_MOTION_MASK + #) + + #gtk.gdk.keyboard_grab( + # self.__pie.window, + # owner_events=True + #) + + self.__popupWindow.show_all() + + def __unpop(self): + piePosition = self.__popupWindow.get_position() + + #self.__pie.grab_remove() + self.grab_focus() + #gtk.gdk.pointer_ungrab() + + self.__popupWindow.destroy() + self.__popupWindow = None + self.__pie = None + + +gobject.type_register(PiePopup) + + +def pie_main(isPop): + win = gtk.Window() + win.set_title("Pie Menu Test") + + sliceStyle = generate_pie_style(win) + if isPop: + target = PiePopup(sliceStyle) + else: + target = PieMenu(sliceStyle) + + def handler(pie, slice, direction): + print pie, slice, direction + target.add_slice(TextLabelPieSlice("C", handler=handler), PieSlice.SLICE_CENTER) + target.add_slice(TextLabelPieSlice("N", handler=handler), PieSlice.SLICE_NORTH) + target.add_slice(TextLabelPieSlice("S", handler=handler), PieSlice.SLICE_SOUTH) + target.add_slice(TextLabelPieSlice("E", handler=handler), PieSlice.SLICE_EAST) + target.add_slice(TextLabelPieSlice("W", handler=handler), PieSlice.SLICE_WEST) + + win.add(target) + win.resize(300, 300) + win.connect("destroy", lambda w: gtk.main_quit()) + win.show_all() + + +if __name__ == "__main__": + pie_main(False) + pie_main(True) + gtk.main() diff --git a/src/libraries/gtkpieboard.py b/src/libraries/gtkpieboard.py new file mode 100755 index 0000000..c2c7175 --- /dev/null +++ b/src/libraries/gtkpieboard.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python + + +from __future__ import division + +import copy +import warnings + +import gobject +import gtk + +import gtkpie + + +class PieKeyboard(gtk.Table): + + def __init__(self, style, rows, columns, alternateStyles=True): + super(PieKeyboard, self).__init__(rows, columns, homogeneous=True) + + self.__cells = {} + for row in xrange(rows): + for column in xrange(columns): + popup = gtkpie.PiePopup( + self._alternate_style(row, column, style) if alternateStyles else style + ) + self.attach(popup, column, column+1, row, row+1) + self.__cells[(row, column)] = popup + + def add_slice(self, row, column, slice, direction): + pie = self.__cells[(row, column)] + pie.add_slice(slice, direction) + + def add_slices(self, row, column, slices): + pie = self.__cells[(row, column)] + for direction, slice in slices.iteritems(): + pie.add_slice(slice, direction) + + def get_pie(self, row, column): + return self.__cells[(row, column)] + + @classmethod + def _alternate_style(cls, row, column, style): + i = row + column + isEven = (i % 2) == 0 + + if not isEven: + return style + + altStyle = copy.copy(style) + selected = altStyle[True] + notSelected = altStyle[False] + altStyle[False] = selected + altStyle[True] = notSelected + return altStyle + + +class KeyboardModifier(object): + + def __init__(self, name): + self.name = name + self.lock = False + self.once = False + + @property + def isActive(self): + return self.lock or self.once + + def on_toggle_lock(self, *args, **kwds): + self.lock = not self.lock + + def on_toggle_once(self, *args, **kwds): + self.once = not self.once + + def reset_once(self): + self.once = False + + +gobject.type_register(PieKeyboard) + + +def parse_keyboard_data(text): + return eval(text) + + +def load_keyboard(keyboardName, dataTree, keyboard, keyboardHandler): + for (row, column), pieData in dataTree.iteritems(): + showAllSlices = pieData["showAllSlices"] + keyboard.get_pie(row, column).showAllSlices = showAllSlices + for direction, directionName in enumerate(gtkpie.PieSlice.SLICE_DIRECTION_NAMES): + if directionName not in pieData: + continue + sliceName = "%s-(%d, %d)-%s" % (keyboardName, row, column, directionName) + + sliceData = pieData[directionName] + sliceAction = sliceData["action"] + sliceType = sliceData["type"] + if sliceType == "text": + text = sliceData["text"] + # font = sliceData["font"] # @TODO + slice = gtkpie.TextLabelPieSlice(text, handler=keyboardHandler) + elif sliceType == "image": + path = sliceData["path"] + slice = gtkpie.ImageLabelPieSlice(path, handler=keyboardHandler) + + slice.name = sliceName + keyboard.add_slice(row, column, slice, direction) + keyboardHandler.map_slice_action(slice, sliceAction) + + +class KeyboardHandler(object): + + def __init__(self, keyhandler): + self.__keyhandler = keyhandler + self.__commandHandlers = {} + self.__modifiers = {} + self.__sliceActions = {} + + self.register_modifier("Shift") + self.register_modifier("Super") + self.register_modifier("Control") + self.register_modifier("Alt") + + def register_command_handler(self, command, handler): + #@todo Make this handle multiple handlers or switch to gobject events + self.__commandHandlers["[%s]" % command] = handler + + def unregister_command_handler(self, command): + #@todo Make this handle multiple handlers or switch to gobject events + del self.__commandHandlers["[%s]" % command] + + def register_modifier(self, modifierName): + mod = KeyboardModifier(modifierName) + self.register_command_handler(modifierName, mod.on_toggle_lock) + self.__modifiers["<%s>" % modifierName] = mod + + def unregister_modifier(self, modifierName): + self.unregister_command_handler(modifierName) + del self.__modifiers["<%s>" % modifierName] + + def map_slice_action(self, slice, action): + self.__sliceActions[slice.name] = action + + def __call__(self, pie, slice, direction): + try: + action = self.__sliceActions[slice.name] + except KeyError: + return + + activeModifiers = [ + mod.name + for mod in self.__modifiers.itervalues() + if mod.isActive + ] + + needResetOnce = False + if action.startswith("[") and action.endswith("]"): + commandName = action[1:-1] + if action in self.__commandHandlers: + self.__commandHandlers[action](commandName, activeModifiers) + needResetOnce = True + else: + warnings.warn("Unknown command: [%s]" % commandName) + elif action.startswith("<") and action.endswith(">"): + modName = action[1:-1] + for mod in self.__modifiers.itervalues(): + if mod.name == modName: + mod.on_toggle_once() + break + else: + warnings.warn("Unknown modifier: <%s>" % modName) + else: + self.__keyhandler(action, activeModifiers) + needResetOnce = True + + if needResetOnce: + for mod in self.__modifiers.itervalues(): + mod.reset_once() diff --git a/src/libraries/images/alt.png b/src/libraries/images/alt.png new file mode 100644 index 0000000000000000000000000000000000000000..8ccd1104ee711bf86692865e19e2005032d151be GIT binary patch literal 520 zcmV+j0{8uiP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXA& z12zi}48KwU00D?eL_t(o!|j)`O2beThQC$|QV>)ei(N~h*u_QcTnG*ooTRHLh`xaj z&^ORG5IQ&rf~BK~gNwzX?IO6Ti>}rhmy>c0O`61<)WILZC7hH0|L?h%dv4&5q5;?n zPs1&g@-Scnd%$^&liC7KeKbcXih)YZRjmkbaZ=(P&{ta8zVLg(5|=F{11;gVyb{(V zK@+yunIf@HDC_-6wb4t2GS9Y?*fN1n%Drgqf{X5HznX0xNvb?hvke? zff}#`oB#{*Zf<0>4b*{6U|+_%5qk)`1P%@oID$=F0nfkLgt5Uf@CbYWx6?9k3A_RC z(=uRo!z3_PX8^ncd1dju=nUKy$VflPDI3U%PG(%Nnb3`>=@qO>LPtrwBm7mrq#K~8 zWS}SfzUWF@wPF^xgtt4X8y2KYW#_nz31zu+6oUSmX(7cw415EDV_$dOF}HdE0000< KMNUMnLSTZ@0nzmU literal 0 HcmV?d00001 diff --git a/src/libraries/images/arrows.png b/src/libraries/images/arrows.png new file mode 100644 index 0000000000000000000000000000000000000000..6ab51c07fb9430f7f865189a3cae254685256209 GIT binary patch literal 450 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}Y)RhkE)4%c zaKYZ?lYt_f1s;*b3=G`DAk4@xYmNj^kiEpy*OmPyBR8{xnfD*JDL_-4JY5_^GVZ;d zb&&6f0*A}%CIA1QewV2)9qX7jLG5c?B{SEG+`ZplpJcv%xIM-pd#!!g0r5CZrpS5W zoefg^zTcaoG=XuwNF&dAZC3?mki-WqLEWfMcL8HT;|EhSB3U9%D;S-ZHT?71QrqLm zxi9jWL@L>oEtnI-E5B=M$C9vTe+4$#ES=R~W&FG`!L%=- z>fEk1Gh+2`JdE#5*#0J9&BK`-M=P0E_5OI@yXMQ?7alF2mRt|f|2nU{IY@BE!Btv2 ozEstI@$&5d@_X}fZ3QzsM+@T^JEhNFzyN0OboFyt=akR{0QMBL>Hq)$ literal 0 HcmV?d00001 diff --git a/src/libraries/images/backspace.png b/src/libraries/images/backspace.png new file mode 100644 index 0000000000000000000000000000000000000000..c8d5506e0757c7e4862efa3ff60702cdfbb62ac2 GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i1|)m0dQL70(Y)*K0-AbW|YuPggaMs8*!?(BrBFrZMir;B5V$MLsU z9mN_HI9LMOf8U>Z?WYcFTEv&6_4_X#F#NY`db(Jo_>_Ztie#>_x*DX%m^29p8Td`E z{BZcv$yF2Pt1VWmI;?VmYqF!UT5B55n|-%r7mIB1wGKGwGv8BQL70(Y)*K0-AbW|YuPggaMnNV`vn3GG(#bx%*m~t-Nn={pH%?e80(k!TzG0zscF*>s36;D%SYuiRgB`nQ`@s1n;5{ x)zyMoCwIwCf1CL9OCxv8t2;%H;$$x}?^&m$@O#BB9iVF%JYD@<);T3K0RXLCSnvP< literal 0 HcmV?d00001 diff --git a/src/libraries/images/control.png b/src/libraries/images/control.png new file mode 100644 index 0000000000000000000000000000000000000000..f24980e57bf58028c3f7679b6a76a1ea16d8fef6 GIT binary patch literal 554 zcmV+_0@eMAP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXA& z12r~$NuYoL00F8=L_t(o!_C&eYZO5g2k=kM3#1Wj)JD|8GFB#bDb!Z5vbK=mA7HOm zHcB9*7TXk73MyEnu@FTNe}$+R(nW0)qXsUxY2LxGT=w?xX4ipXnK$2^_kC~PkD=u; zv4{LBw#TgNJyz-fAEU@LkC&Bo1`s%ajquJmC-jC{KO0PsWw`DeVOXUHrsKPjfxsG$ zVt*B=!1YW4e;Av1n+m%FfcG;I_=2xhaqtHZ8c6*Db7kR`*jQ&97!-2@dmHzx}!6K>!$H6S@-Z1_YzAVMah+ez&o5y)|2>K!h9b!Ai5)9?_d#k z@e8YXf}266_pj-3u#GH8ui#vpPqW)8t2R8a-j?N^hV@O9Go?>pC%(D6VS!Io?}2q3 z%1B^-ulGR8@MJ~;AMmmY@H}HST#B==Re{cCByhgY*|QM~-!cW@=V86z1ztzzdx(8w sQr>S`b9I{04{Sxn8t2%GruBb~U8Le}!3`s*L;wH)07*qoM6N<$g3G1vCjbBd literal 0 HcmV?d00001 diff --git a/src/libraries/images/newline.png b/src/libraries/images/newline.png new file mode 100644 index 0000000000000000000000000000000000000000..8b242f2466fab4cfa3976d2bd82f34fcfe34a049 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEjKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-AbW|YuPggaMs8+f_Ug0jHb9{no-U3d5r^MS zkLGJo;9<#p{CEAHf5M_t%(A}U8B%WSi`Jg%b$^;}U3~t`Iqx1Yc3oL|yMn!*SHoz* z3BE3wq>04=Pn0emdnufErHUa~En!x+af7nO=`)Tk#pMkeJLCk|uQNxLouAEV$ajfd zR7**L>yrBQFD55mtWA(^I2<0kYVyRobP0l+XkKcFkYg literal 0 HcmV?d00001 diff --git a/src/libraries/images/shift.png b/src/libraries/images/shift.png new file mode 100644 index 0000000000000000000000000000000000000000..789d7e3a0bc9f685d8d752b8e6dce24690decb57 GIT binary patch literal 466 zcmV;@0WJQCP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXA& z12!G|*a`yx00B}-L_t(o!|j)`P69y?hQB?fA)zp_B%viFBs4Z8)Fjv0`T};o2an)0 zP}o@zYGYwV1vJ)bqpgT*Ge^z~yBv2r8w>yBa>Y#M|7Lcxw+DYz6Tm7wlcJGv09fD} zxC_|SC2;HU+^Hu3qrg)gN!{L|#3x{_w6trf?^G(3GbMnT)K6Ur7m}bUD?ZL(Um2jE zZ!*aRZsgrm251)ic6tTok==@-u)d;2dbxG(UO+X8>ow2`~)M zxvc^aV(g|0L=xEiXoy6)bE^ShL*}1W05ByAn^>E$wlaWc;1hU^4UjFtJ@5{EMA#GF zC9shi-|Mph-hhs>c}ILU#R)Xzd(c(}Xp2u{UvL`fMl^K=$C9v6GG9pju`lTXSSbOl zq`p^6;LCS&t*BJ$E)N~WT^Z8=cnJtFkUK}W?5~-Y4FAgM2d;@QL70(Y)*K0-AbW|YuPggaMs8*!J%yBYf%1k$3z~wi!c-1Mt(DR!UTpoJNlhJ=|5Sg|8 zv;J|77p7`5yXS^CpZ&nMf%6{gzt-}me(_$e4DlN#L7^vA=O%~eqzQY?x@w)YL}{{f qb?F|z7(8A5T-G@yGywop-&Xwq literal 0 HcmV?d00001 diff --git a/src/libraries/images/super.png b/src/libraries/images/super.png new file mode 100644 index 0000000000000000000000000000000000000000..64617704eb6d4c900b81f21a7a9c05be986c56e7 GIT binary patch literal 519 zcmV+i0{H!jP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXA& z12raL{;=c#00DB{!z<8}@F)kM*O&dTP*l=l@ff#&Sz><%)3M|O}B&h|wgg_6aTpt5FE;P?4 zXB7b~#(BFa zvGA-EfDfH|!4+^LP39cvawPmm)>52i)C5{mv0RRp)iwXm_yx=yX7-AGO}zjB002ov JPDHLkV1m1S$u|H1 literal 0 HcmV?d00001 diff --git a/src/libraries/images/symbols.dia b/src/libraries/images/symbols.dia new file mode 100644 index 0000000000000000000000000000000000000000..a04fe5a90dbb8a110bd305fbadafa39b103bb81a GIT binary patch literal 1859 zcmV-J2fX+niwFP!000021MQt#bDKC6$KUfQ82XxGB<^jKPP;q1`;hIr~9tDU5uMBp-hIOB~?c`^U8>J>W2M1OG+_iYiOkp9OQ* zU*5=Hzx?*vmfzoAy_>t}4gI$a(ORP4@S}L|MqUxJc{7>pc00w}M~DQW;<;NT!ju1y z=b;HLG?8zwBB%Z?&KLU+0)Sn|;t-^f!m`?d^&tv{F3QdzlK;02-dfV>;| z)gsyCNoHnpWaotK4O-%9h|#xt(<#&5whQuiqzPNKM%@er6O9$ zU*5jD!-U}QRa&j=U~aFfvbB!L8ih;OFZ-hX=fgsnF7%`lY+O>A3E^ zaovUEx^tuZO%Re0xuooJ8U!9je(IHkTioAIG(#Q@u~W?g{jD!tLV||+FOV0tX+V1T z`Hbs&_AWzre){A+Dp6x)=g!IM{*hC6rPlYHtUtJsJN0l??OmS?W_~}6`QopW5Ip}n zV>G1JYKMrqjSXycA0n1pcaEcmFgq?&V`-HYn>6)$v0U4ZNe>kU?8JqK_BhPUf6Rw! zDf@vl;vDosF#Q+LNY=d11o?9m&ZXDV7yL-%1L+}4?)*mnOFizNVvDFXioQ|@ZGt?C z)N7y@TU_Qvo$=|kXgj3UNB+{oxqVI1bya^0prQiZr2Z6L17<&`B<_cX@Gg|8qjjPO zj>;9}!-p^oc2SLdIC0L^0X*`=m@{`*SPwZ#Qt`j^2|0A>9MYIMWOC+^p*S{l zau}I(-!d)-MqkiWpolw)Oo5_HN0H8qB8M}IatcA9m=`69KH;5o=lVhw@kfy(P;@aU z`WQSOkVwr)WDK!bZEz%!qd18Ia*Vf>NGkomO+WiN#2#MX&w|h|U&J1!MTvvtDp$Ex zPv@z|V7kPCVQfdMV0T}Uwa|PymS13+L#!I`3`_tIBGVivGBpvI=Z{Qlh&3 zna((oX^O}^e`MN2Oe?rXrkzJ7gyTe}BO>$sk?F8TCWL$=lg@JU$TUWYOok?j$QCx%KJT|-d9bLbK^GSb*YjGqIKEfrtdiqYUnQeWJwVh_u5)G9d#C>M+|B(e z2;J{=>_gtk#uaWu=km-1BR#gY;U!KfPAO<=NxX9w5Ee z=AK<^9VFKR{>il>F3%p9&H%}^Huv0G>maoj@K3E3amhO_f4!q2Nu!hCilBt-&l=Md z4+dj5Tal-QSb{1Js}GQmj#xP1wt0| zkp=9T@(zDl;5^F$2*!~Gj*taH76@4|-V^*iB2>ZP_x`Fv6$n-EWG6<{=BOH7bb{9O z?v}DrsJTJcJ`VhD@7pQqh5DP2pB55eg<|OHwpumtXkevwI5ooea!>)N;XG98h;B-v zm&?*n-wIP*-|bbUDO#wC&aWt|RTjg)4y{sOG(L7=uvc_dYludr(M4+%)JCn;I5}pu zmmAE2qfb35MMtyj=;Nv^Jt|NLQt`p=txA8iR3Lra)v1ss9dcUlRBMsW_^H(+EH4@C zs7l!1XLzVhn(i(*ac>sX2N5MefiMnL`pI9I06OgH8ubPY9XfC6zO5S;-M6eU^vNJ? xZKqLGW*OA1QkZ-Qog#F~PY&Po$n(rF-A!JFd3$yAuFTu3{{fFWz*19t005eIn??Wt literal 0 HcmV?d00001 diff --git a/src/libraries/images/tab.png b/src/libraries/images/tab.png new file mode 100644 index 0000000000000000000000000000000000000000..1040cac0eeec9fe43670c13efb8f72bf025ce58e GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3jKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-AbW|YuPggaMs8*!#SOPNHvom|JY5_^JdVGe z<|xLX$l>(+-v4uFS<@D}T~zwk(s5qqv10#DC%)+wJLa^ne*5rLjG~XbjAPuNf|XX! zrW6ZRZk%8m`ta=|jaBap0=KR^>> [v for v in ordered_itr({"a": 1, "b": 2})] + [('a', 1), ('b', 2)] + >>> [v for v in ordered_itr([3, 1, 10, -20])] + [-20, 1, 3, 10] + """ + if isinstance(collection, types.DictType): + keys = list(collection.iterkeys()) + keys.sort() + for key in keys: + yield key, collection[key] + else: + values = list(collection) + values.sort() + for value in values: + yield value + + +def itercat(*iterators): + """ + Concatenate several iterators into one. + + >>> [v for v in itercat([1, 2, 3], [4, 1, 3])] + [1, 2, 3, 4, 1, 3] + """ + for i in iterators: + for x in i: + yield x + + +def iterwhile(func, iterator): + """ + Iterate for as long as func(value) returns true. + >>> through = lambda b: b + >>> [v for v in iterwhile(through, [True, True, False])] + [True, True] + """ + iterator = iter(iterator) + while 1: + next = iterator.next() + if not func(next): + raise StopIteration + yield next + + +def iterfirst(iterator, count=1): + """ + Iterate through 'count' first values. + + >>> [v for v in iterfirst([1, 2, 3, 4, 5], 3)] + [1, 2, 3] + """ + iterator = iter(iterator) + for i in xrange(count): + yield iterator.next() + + +def iterstep(iterator, n): + """ + Iterate every nth value. + + >>> [v for v in iterstep([1, 2, 3, 4, 5], 1)] + [1, 2, 3, 4, 5] + >>> [v for v in iterstep([1, 2, 3, 4, 5], 2)] + [1, 3, 5] + >>> [v for v in iterstep([1, 2, 3, 4, 5], 3)] + [1, 4] + """ + iterator = iter(iterator) + while True: + yield iterator.next() + # skip n-1 values + for dummy in xrange(n-1): + iterator.next() + + +def itergroup(iterator, count, to_container=tuple): + """ + Iterate in groups of 'count' values. If there + aren't enough values, the last result is padded with + None. + + >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3): + ... print val + (1, 2, 3) + (4, 5, 6) + >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3, list): + ... print val + [1, 2, 3] + [4, 5, 6] + >>> for val in itergroup([1, 2, 3, 4, 5, 6, 7], 3): + ... print val + (1, 2, 3) + (4, 5, 6) + (7, None, None) + >>> for val in itergroup("123456", 3): + ... print val + ('1', '2', '3') + ('4', '5', '6') + >>> for val in itergroup("123456", 3, lambda i: "".join(s for s in i if s is not None)): + ... print repr(val) + '123' + '456' + """ + + iterator = iter(iterator) + values_left = [True] + + def values(): + values_left[0] = False + for x in range(count): + try: + yield iterator.next() + values_left[0] = True + except StopIteration: + yield None + while True: + value = to_container(values()) + if not values_left[0]: + raise StopIteration + yield value + + +def xzip(*iterators): + """Iterative version of builtin 'zip'.""" + iterators = itertools.imap(iter, iterators) + while 1: + yield tuple([x.next() for x in iterators]) + + +def xmap(func, *iterators): + """Iterative version of builtin 'map'.""" + iterators = itertools.imap(iter, iterators) + values_left = [1] + + def values(): + # Emulate map behaviour, i.e. shorter + # sequences are padded with None when + # they run out of values. + values_left[0] = 0 + for i in range(len(iterators)): + iterator = iterators[i] + if iterator is None: + yield None + else: + try: + yield iterator.next() + values_left[0] = 1 + except StopIteration: + iterators[i] = None + yield None + while 1: + args = tuple(values()) + if not values_left[0]: + raise StopIteration + yield func(*args) + + +def xfilter(func, iterator): + """Iterative version of builtin 'filter'.""" + iterator = iter(iterator) + while 1: + next = iterator.next() + if func(next): + yield next + + +def xreduce(func, iterator, default=None): + """Iterative version of builtin 'reduce'.""" + iterator = iter(iterator) + try: + prev = iterator.next() + except StopIteration: + return default + single = 1 + for next in iterator: + single = 0 + prev = func(prev, next) + if single: + return func(prev, default) + return prev + + +def daterange(begin, end, delta = datetime.timedelta(1)): + """ + Form a range of dates and iterate over them. + + Arguments: + begin -- a date (or datetime) object; the beginning of the range. + end -- a date (or datetime) object; the end of the range. + delta -- (optional) a datetime.timedelta object; how much to step each iteration. + Default step is 1 day. + + Usage: + """ + if not isinstance(delta, datetime.timedelta): + delta = datetime.timedelta(delta) + + ZERO = datetime.timedelta(0) + + if begin < end: + if delta <= ZERO: + raise StopIteration + test = end.__gt__ + else: + if delta >= ZERO: + raise StopIteration + test = end.__lt__ + + while test(begin): + yield begin + begin += delta + + +class LazyList(object): + """ + A Sequence whose values are computed lazily by an iterator. + + Module for the creation and use of iterator-based lazy lists. + this module defines a class LazyList which can be used to represent sequences + of values generated lazily. One can also create recursively defined lazy lists + that generate their values based on ones previously generated. + + Backport to python 2.5 by Michael Pust + """ + + __author__ = 'Dan Spitz' + + def __init__(self, iterable): + self._exhausted = False + self._iterator = iter(iterable) + self._data = [] + + def __len__(self): + """Get the length of a LazyList's computed data.""" + return len(self._data) + + def __getitem__(self, i): + """Get an item from a LazyList. + i should be a positive integer or a slice object.""" + if isinstance(i, int): + #index has not yet been yielded by iterator (or iterator exhausted + #before reaching that index) + if i >= len(self): + self.exhaust(i) + elif i < 0: + raise ValueError('cannot index LazyList with negative number') + return self._data[i] + + #LazyList slices are iterators over a portion of the list. + elif isinstance(i, slice): + start, stop, step = i.start, i.stop, i.step + if any(x is not None and x < 0 for x in (start, stop, step)): + raise ValueError('cannot index or step through a LazyList with' + 'a negative number') + #set start and step to their integer defaults if they are None. + if start is None: + start = 0 + if step is None: + step = 1 + + def LazyListIterator(): + count = start + predicate = ( + (lambda: True) + if stop is None + else (lambda: count < stop) + ) + while predicate(): + try: + yield self[count] + #slices can go out of actual index range without raising an + #error + except IndexError: + break + count += step + return LazyListIterator() + + raise TypeError('i must be an integer or slice') + + def __iter__(self): + """return an iterator over each value in the sequence, + whether it has been computed yet or not.""" + return self[:] + + def computed(self): + """Return an iterator over the values in a LazyList that have + already been computed.""" + return self[:len(self)] + + def exhaust(self, index = None): + """Exhaust the iterator generating this LazyList's values. + if index is None, this will exhaust the iterator completely. + Otherwise, it will iterate over the iterator until either the list + has a value for index or the iterator is exhausted. + """ + if self._exhausted: + return + if index is None: + ind_range = itertools.count(len(self)) + else: + ind_range = range(len(self), index + 1) + + for ind in ind_range: + try: + self._data.append(self._iterator.next()) + except StopIteration: #iterator is fully exhausted + self._exhausted = True + break + + +class RecursiveLazyList(LazyList): + + def __init__(self, prod, *args, **kwds): + super(RecursiveLazyList, self).__init__(prod(self, *args, **kwds)) + + +class RecursiveLazyListFactory: + + def __init__(self, producer): + self._gen = producer + + def __call__(self, *a, **kw): + return RecursiveLazyList(self._gen, *a, **kw) + + +def lazylist(gen): + """ + Decorator for creating a RecursiveLazyList subclass. + This should decorate a generator function taking the LazyList object as its + first argument which yields the contents of the list in order. + + >>> #fibonnacci sequence in a lazy list. + >>> @lazylist + ... def fibgen(lst): + ... yield 0 + ... yield 1 + ... for a, b in itertools.izip(lst, lst[1:]): + ... yield a + b + ... + >>> #now fibs can be indexed or iterated over as if it were an infinitely long list containing the fibonnaci sequence + >>> fibs = fibgen() + >>> + >>> #prime numbers in a lazy list. + >>> @lazylist + ... def primegen(lst): + ... yield 2 + ... for candidate in itertools.count(3): #start at next number after 2 + ... #if candidate is not divisible by any smaller prime numbers, + ... #it is a prime. + ... if all(candidate % p for p in lst.computed()): + ... yield candidate + ... + >>> #same for primes- treat it like an infinitely long list containing all prime numbers. + >>> primes = primegen() + >>> print fibs[0], fibs[1], fibs[2], primes[0], primes[1], primes[2] + 0 1 1 2 3 5 + >>> print list(fibs[:10]), list(primes[:10]) + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] + """ + return RecursiveLazyListFactory(gen) + + +def map_func(f): + """ + >>> import misc + >>> misc.validate_decorator(map_func) + """ + + @functools.wraps(f) + def wrapper(*args): + result = itertools.imap(f, args) + return result + return wrapper + + +def reduce_func(function): + """ + >>> import misc + >>> misc.validate_decorator(reduce_func(lambda x: x)) + """ + + def decorator(f): + + @functools.wraps(f) + def wrapper(*args): + result = reduce(function, f(args)) + return result + return wrapper + return decorator + + +def any_(iterable): + """ + @note Python Version <2.5 + + >>> any_([True, True]) + True + >>> any_([True, False]) + True + >>> any_([False, False]) + False + """ + + for element in iterable: + if element: + return True + return False + + +def all_(iterable): + """ + @note Python Version <2.5 + + >>> all_([True, True]) + True + >>> all_([True, False]) + False + >>> all_([False, False]) + False + """ + + for element in iterable: + if not element: + return False + return True + + +def for_every(pred, seq): + """ + for_every takes a one argument predicate function and a sequence. + @param pred The predicate function should return true or false. + @returns true if every element in seq returns true for predicate, else returns false. + + >>> for_every (lambda c: c > 5,(6,7,8,9)) + True + + @author Source:http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52907 + """ + + for i in seq: + if not pred(i): + return False + return True + + +def there_exists(pred, seq): + """ + there_exists takes a one argument predicate function and a sequence. + @param pred The predicate function should return true or false. + @returns true if any element in seq returns true for predicate, else returns false. + + >>> there_exists (lambda c: c > 5,(6,7,8,9)) + True + + @author Source:http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52907 + """ + + for i in seq: + if pred(i): + return True + return False + + +def func_repeat(quantity, func, *args, **kwd): + """ + Meant to be in connection with "reduce" + """ + for i in xrange(quantity): + yield func(*args, **kwd) + + +def function_map(preds, item): + """ + Meant to be in connection with "reduce" + """ + results = (pred(item) for pred in preds) + + return results + + +def functional_if(combiner, preds, item): + """ + Combines the result of a list of predicates applied to item according to combiner + + @see any, every for example combiners + """ + pass_bool = lambda b: b + + bool_results = function_map(preds, item) + return combiner(pass_bool, bool_results) diff --git a/src/libraries/recipes/concurrent.py b/src/libraries/recipes/concurrent.py new file mode 100644 index 0000000..3961723 --- /dev/null +++ b/src/libraries/recipes/concurrent.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python + +from __future__ import with_statement + +import os +import sys +import cPickle +import weakref +import threading +import functools +import contextlib + + +def synchronized(lock): + """ + Synchronization decorator. + + >>> import misc + >>> misc.validate_decorator(synchronized(object())) + """ + + def wrap(f): + + @functools.wraps(f) + def newFunction(*args, **kw): + lock.acquire() + try: + return f(*args, **kw) + finally: + lock.release() + return newFunction + return wrap + + +def threaded(f): + """ + This decorator calls the method in a new thread, so execution returns straight away + + >>> import misc + >>> misc.validate_decorator(threaded) + """ + + @functools.wraps(f) + def wrapper(*args, **kwargs): + t = threading.Thread(target=f, args=args, kwargs=kwargs) + t.setDaemon(True) + t.start() + return wrapper + + +def fork(f): + """ + Fork a function into a seperate process and block on it, for forcing reclaiming of resources for highly intensive functions + @return The original value through pickling. If it is unable to be pickled, then the pickling exception is passed through + @throws Through pickling, exceptions are passed back and re-raised + @note source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/511474 + + >>> import misc + >>> misc.validate_decorator(fork) + """ + + @functools.wraps(f) + def wrapper(*args, **kwds): + pread, pwrite = os.pipe() + pid = os.fork() + if pid > 0: + os.close(pwrite) + with os.fdopen(pread, 'rb') as f: + status, result = cPickle.load(f) + os.waitpid(pid, 0) + if status == 0: + return result + else: + raise result + else: + os.close(pread) + try: + result = f(*args, **kwds) + status = 0 + except Exception, exc: + result = exc + status = 1 + with os.fdopen(pwrite, 'wb') as f: + try: + cPickle.dump((status, result), f, cPickle.HIGHEST_PROTOCOL) + except cPickle.PicklingError, exc: + cPickle.dump((2, exc), f, cPickle.HIGHEST_PROTOCOL) + f.close() + sys.exit(0) + return wrapper + + +@contextlib.contextmanager +def qlock(queue, gblock = True, gtimeout = None, pblock = True, ptimeout = None): + """ + Locking with a queue, good for when you want to lock an item passed around + + >>> item = 5 + >>> lock = Queue.Queue() + >>> lock.put(item) + >>> with qlock(lock) as i: + ... print i + 5 + """ + item = queue.get(gblock, gtimeout) + yield item + queue.put(item, pblock, ptimeout) + + +class EventSource(object): + """ + Asynchronous implementation of the observer pattern + + >>> sourceRoot = EventSource() + >>> sourceChild1 = EventSource() + >>> sourceChild1.register_provided_events("1-event-0", "1-event-1") + >>> sourceChild2 = EventSource() + >>> sourceChild2.register_provided_events("1-event-0", "1-event-1") + >>> sourceRoot.add_children(sourceChild1, sourceChild2) + """ + + def __init__(self): + """ + @warning Not thread safe + """ + + self.__callbackQueues = {} + self.__children = [] + + def add_children(self, *childrenSources): + """ + @warning Not thread safe + """ + + self.__children.extend(childrenSources) + + def remove_children(self, *childrenSources): + """ + @warning Not thread safe + """ + + for child in childrenSources: + self.__children.remove(child) + + def register_provided_events(self, *events): + """ + @warning Not thread safe + """ + + self.__callbackQueues.update(dict((event, []) for event in events)) + + def notify_observers(self, event, message): + """ + @warning As threadsafe as the queue used. qlock is recommended for the message if it needs locking + """ + + for queue in self.__callbackQueues[event]: + queue.put(message) + + def _register_queue(self, event, queue): + """ + @warning Not thread safe + """ + + if event in self.__callbackQueues: + self.__callbackQueues[event].append(queue) + return self + else: + for child in self.__children: + source = child._register_queue(event, queue) + if source is not None: + return source + else: + return None + + def _unregister_queue(self, event, queue): + """ + @warning Not thread safe + """ + + if event in self.__callbackQueues: + self.__callbackQueues[event].remove(queue) + return self + else: + for child in self.__children: + source = child._unregister_queue(event, queue) + if source is not None: + return source + else: + return None + + +class StrongEventSourceProxy(object): + + def __init__(self, source): + """ + @warning Not thread safe + """ + + self.source = source + + def register(self, event, queue): + """ + @warning Not thread safe + """ + + actualSource = self.source._register_queue(event, queue) + ActualType = type(self) + return ActualType(actualSource) + + def unregister(self, event, queue): + """ + @warning Not thread safe + """ + + actualSource = self.source._unregister_queue(event, queue) + ActualType = type(self) + return ActualType(actualSource) + + +class WeakEventSourceProxy(object): + + def __init__(self, source): + """ + @warning Not thread safe + """ + + self.source = weakref.ref(source) + + def register(self, event, queue): + """ + @warning Not thread safe + """ + + actualSource = self.source()._register_queue(event, queue) + ActualType = type(self) + return ActualType(actualSource) + + def unregister(self, event, queue): + """ + @warning Not thread safe + """ + + actualSource = self.source()._unregister_queue(event, queue) + ActualType = type(self) + return ActualType(actualSource) + + +class EventObserver(object): + """ + + >>> import Queue + >>> class Observer(EventObserver): + ... def connect_to_source(self, eventSourceRoot): + ... self.queue = Queue.Queue() + ... self.source = eventSourceRoot.register("1-event-0", self.queue) + >>> + >>> sourceRoot = EventSource() + >>> sourceChild1 = EventSource() + >>> sourceChild1.register_provided_events("1-event-0", "1-event-1") + >>> sourceChild2 = EventSource() + >>> sourceChild2.register_provided_events("1-event-0", "1-event-1") + >>> sourceRoot.add_children(sourceChild1, sourceChild2) + >>> + >>> o1 = Observer() + >>> o1.connect_to_source(StrongEventSourceProxy(sourceRoot)) + >>> o2 = Observer() + >>> o2.connect_to_source(WeakEventSourceProxy(sourceRoot)) + >>> + >>> sourceChild1.notify_observers("1-event-0", "Hello World") + >>> o1.queue.get(False) + 'Hello World' + >>> o2.queue.get(False) + 'Hello World' + """ + + def connect_to_source(self, eventSourceRoot): + raise NotImplementedError diff --git a/src/libraries/recipes/datatypes.py b/src/libraries/recipes/datatypes.py new file mode 100644 index 0000000..2993cb3 --- /dev/null +++ b/src/libraries/recipes/datatypes.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python + + +""" +This module provides three types of queues, with these constructors: + Stack([items]) -- Create a Last In First Out queue, implemented as a list + Queue([items]) -- Create a First In First Out queue + PriorityQueue([items]) -- Create a queue where minimum item (by <) is first +Here [items] is an optional list of initial items; if omitted, queue is empty. +Each type supports the following methods and functions: + len(q) -- number of items in q (also q.__len__()) + q.append(item)-- add an item to the queue + q.extend(items) -- add each of the items to the queue + q.pop() -- remove and return the "first" item from the queue +""" + + +import types +import operator + + +def Stack(items=None): + "A stack, or last-in-first-out queue, is implemented as a list." + return items or [] + + +class Queue(object): + "A first-in-first-out queue." + + def __init__(self, initialItems=None): + self.start = 0 + self.items = initialItems or [] + + def __len__(self): + return len(self.items) - self.start + + def append(self, item): + self.items.append(item) + + def extend(self, items): + self.items.extend(items) + + def pop(self): + items = self.items + item = items[self.start] + self.start += 1 + if self.start > 100 and self.start > len(items)/2: + del items[:self.start] + self.start = 0 + return item + + +class PriorityQueue(object): + "A queue in which the minimum element (as determined by cmp) is first." + + def __init__(self, initialItems=None, comparator=operator.lt): + self.items = [] + self.cmp = comparator + if initialItems is not None: + self.extend(initialItems) + + def __len__(self): + return len(self.items) + + def append(self, item): + items, cmp_func = self.items, self.cmp + items.append(item) + i = len(items) - 1 + while i > 0 and cmp_func(item, items[i//2]): + items[i], i = items[i//2], i//2 + items[i] = item + + def extend(self, items): + for item in items: + self.append(item) + + def pop(self): + items = self.items + if len(items) == 1: + return items.pop() + e = items[0] + items[0] = items.pop() + self.heapify(0) + return e + + def heapify(self, i): + """ + itemsssumes items is an array whose left and right children are heaps, + move items[i] into the correct position.See CLR&S p. 130 + """ + items, cmp_func = self.items, self.cmp + left, right, N = 2*i + 1, 2*i + 2, len(items)-1 + if left <= N and cmp_func(items[left], items[i]): + smallest = left + else: + smallest = i + + if right <= N and cmp_func(items[right], items[smallest]): + smallest = right + if smallest != i: + items[i], items[smallest] = items[smallest], items[i] + self.heapify(smallest) + + +class AttrDict(object): + """ + Can act as a mixin to add dictionary access to members to ease dynamic attribute access + or as a wrapper around a class + + >>> class Mixin (AttrDict): + ... def __init__ (self): + ... AttrDict.__init__ (self) + ... self.x = 5 + ... + >>> mixinExample = Mixin () + >>> mixinExample.x + 5 + >>> mixinExample["x"] + 5 + >>> mixinExample["x"] = 10; mixinExample.x + 10 + >>> "x" in mixinExample + True + >>> class Wrapper (object): + ... def __init__ (self): + ... self.y = 10 + ... + >>> wrapper = Wrapper() + >>> wrapper.y + 10 + >>> wrapperExample = AttrDict (wrapper) + >>> wrapperExample["y"] + 10 + >>> wrapperExample["y"] = 20; wrapper.y + 20 + >>> "y" in wrapperExample + True + """ + + def __init__(self, obj = None): + self.__obj = obj if obj is not None else self + + def __getitem__(self, name): + return getattr(self.__obj, name) + + def __setitem__(self, name, value): + setattr(self.__obj, name, value) + + def __delitem__(self, name): + delattr(self.__obj, name) + + def __contains__(self, name): + return hasattr(self.__obj, name) + + +class Uncertain(object): + """ + Represents a numeric value with a known small uncertainty + (error, standard deviation...). + Numeric operators are overloaded to work with other Uncertain or + numeric objects. + The uncertainty (error) must be small. Otherwise the linearization + employed here becomes wrong. + + >>> pie = Uncertain(3.14, 0.01) + >>> ee = Uncertain(2.718, 0.001) + >>> pie, repr(pie) + (Uncertain(3.14, 0.01), 'Uncertain(3.14, 0.01)') + >>> ee, repr(ee) + (Uncertain(2.718, 0.001), 'Uncertain(2.718, 0.001)') + >>> pie + ee + Uncertain(5.858, 0.0100498756211) + >>> pie * ee + Uncertain(8.53452, 0.0273607748428) + """ + + def __init__(self, value=0., error=0., *a, **t): + self.value = value + self.error = abs(error) + super(Uncertain, self).__init__(*a, **t) + + # Conversions + + def __str__(self): + return "%g+-%g" % (self.value, self.error) + + def __repr__(self): + return "Uncertain(%s, %s)" % (self.value, self.error) + + def __complex__(self): + return complex(self.value) + + def __int__(self): + return int(self.value) + + def __long__(self): + return long(self.value) + + def __float__(self): + return self.value + + # Comparison + + def __eq__(self, other): + epsilon = max(self.error, other.error) + return abs(other.value - self.value) < epsilon + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return hash(self.value) ^ hash(self.error) + + def __le__(self, other): + return self.value < other.value or self == other + + def __lt__(self, other): + return self.value < other.value and self != other + + def __gt__(self, other): + return not (self <= other) + + def __ge__(self, other): + return not (self < other) + + def __nonzero__(self): + return self.error < abs(self.value) + + # Math + + def assign(self, other): + if isinstance(other, Uncertain): + self.value = other.value + self.error = other.error + else: + self.value = other + self.error = 0. + + def __add__(self, other): + if isinstance(other, Uncertain): + v = self.value + other.value + e = (self.error**2 + other.error**2) ** .5 + return Uncertain(v, e) + else: + return Uncertain(self.value+other, self.error) + + def __sub__(self, other): + return self + (-other) + + def __mul__(self, other): + if isinstance(other, Uncertain): + v = self.value * other.value + e = ((self.error * other.value)**2 + (other.error * self.value)**2) ** .5 + return Uncertain(v, e) + else: + return Uncertain(self.value*other, + self.error*other) + + def __div__(self, other): + return self*(1./other) + + def __truediv__(self, other): + return self*(1./other) + + def __radd__(self, other): + return self + other + + def __rsub__(self, other): + return -self + other + + def __rmul__(self, other): + return self * other + + def __rdiv__(self, other): + return (self/other)**-1. + + def __rtruediv__(self, other): + return (self/other)**-1. + + def __neg__(self): + return self*-1 + + def __pos__(self): + return self + + def __abs__(self): + return Uncertain(abs(self.value), self.error) + + +class Enumeration(object): + """ + C-Style enumeration mapping attributes to numbers + + >>> Color = Enumeration("Color", ["Red", "Green", "Blue"]) + >>> Color.Red, Color.Green, Color.Blue + (0, 1, 2) + >>> + >>> Color["Red"], Color.whatis(0) + (0, 'Red') + >>> Color.names(), Color.values() + (['Blue', 'Green', 'Red'], [2, 1, 0]) + >>> + >>> str(Color) + "Color: {'Blue': 2, 'Green': 1, 'Red': 0}" + >>> + >>> 0 in Color, 10 in Color + (True, False) + >>> "Red" in Color, "Black" in Color + (True, False) + """ + + def __init__(self, name, enumList): + self.__name__ = name + self.__doc__ = name + lookup = { } + reverseLookup = { } + + i = 0 + uniqueNames = [ ] + uniqueValues = [ ] + for x in enumList: + if type(x) == types.TupleType: + x, i = x + if type(x) != types.StringType: + raise TypeError("enum name is not a string: " + x) + if type(i) != types.IntType: + raise TypeError("enum value is not an integer: " + str(i)) + if x in uniqueNames: + raise ValueError("enum name is not unique: " + x) + if i in uniqueValues: + raise ValueError("enum value is not unique for " + x) + uniqueNames.append(x) + uniqueValues.append(i) + lookup[x] = i + reverseLookup[i] = x + i = i + 1 + + self.__lookup = lookup + self.__reverseLookup = reverseLookup + + def whatis(self, value): + return self.__reverseLookup[value] + + def names(self): + return self.__lookup.keys() + + def values(self): + return self.__lookup.values() + + def __getattr__(self, attr): + if attr not in self.__lookup: + raise (AttributeError) + return self.__lookup[attr] + + def __str__(self): + return str(self.__doc__)+": "+str(self.__lookup) + + def __len__(self): + return len(self.__lookup) + + def __contains__(self, x): + return (x in self.__lookup) or (x in self.__reverseLookup) + + def __getitem__(self, attr): + return self.__lookup[attr] + + def __iter__(self): + return self.__lookup.itervalues() + + def iterkeys(self): + return self.__lookup.iterkeys() + + def itervalues(self): + return self.__lookup.itervalues() + + def iteritems(self): + return self.__lookup.iteritems() + + +def make_enum(cls): + """ + @todo Make more object orientated (inheritance?) + """ + name = cls.__name__ + values = cls.__values__ + return Enumeration(name, values) diff --git a/src/libraries/recipes/gtk_utils.py b/src/libraries/recipes/gtk_utils.py new file mode 100644 index 0000000..7f83aec --- /dev/null +++ b/src/libraries/recipes/gtk_utils.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + + +from __future__ import with_statement + +import contextlib +import functools +import math + +import gobject +import gtk +import gtk.glade + + +def make_idler(func): + """ + Decorator that makes a generator-function into a function that will continue execution on next call + + >>> import misc + >>> misc.validate_decorator(make_idler) + + """ + a = [] + + @functools.wraps(func) + def decorated_func(*args, **kwds): + if not a: + a.append(func(*args, **kwds)) + try: + a[0].next() + return True + except StopIteration: + del a[:] + return False + + return decorated_func + + +@contextlib.contextmanager +def gtk_critical_section(): + #The API changed and I hope these are the right calls + gtk.gdk.threads_enter() + yield + gtk.gdk.threads_leave() + + +if __name__ == "__main__": + #gtk.gdk.threads_init() + pass diff --git a/src/libraries/recipes/io.py b/src/libraries/recipes/io.py new file mode 100644 index 0000000..6470cb6 --- /dev/null +++ b/src/libraries/recipes/io.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python + + +from __future__ import with_statement + +import os +import pickle +import contextlib +import itertools +import functools + + +@contextlib.contextmanager +def change_directory(directory): + previousDirectory = os.getcwd() + os.chdir(directory) + currentDirectory = os.getcwd() + + try: + yield previousDirectory, currentDirectory + finally: + os.chdir(previousDirectory) + + +@contextlib.contextmanager +def pickled(filename): + """ + Here is an example usage: + with pickled("foo.db") as p: + p("users", list).append(["srid", "passwd", 23]) + """ + + if os.path.isfile(filename): + data = pickle.load(open(filename)) + else: + data = {} + + def getter(item, factory): + if item in data: + return data[item] + else: + data[item] = factory() + return data[item] + + yield getter + + pickle.dump(data, open(filename, "w")) + + +@contextlib.contextmanager +def redirect(object_, attr, value): + """ + >>> import sys + ... with redirect(sys, 'stdout', open('stdout', 'w')): + ... print "hello" + ... + >>> print "we're back" + """ + orig = getattr(object_, attr) + setattr(object_, attr, value) + try: + yield + finally: + setattr(object_, attr, orig) + + +def pathsplit(path): + """ + >>> pathsplit("/a/b/c") + ['', 'a', 'b', 'c'] + >>> pathsplit("./plugins/builtins.ini") + ['.', 'plugins', 'builtins.ini'] + """ + pathParts = path.split(os.path.sep) + return pathParts + + +def commonpath(l1, l2, common=None): + """ + >>> commonpath(pathsplit('/a/b/c/d'), pathsplit('/a/b/c1/d1')) + (['', 'a', 'b'], ['c', 'd'], ['c1', 'd1']) + >>> commonpath(pathsplit("./plugins/"), pathsplit("./plugins/builtins.ini")) + (['.', 'plugins'], [''], ['builtins.ini']) + >>> commonpath(pathsplit("./plugins/builtins"), pathsplit("./plugins")) + (['.', 'plugins'], ['builtins'], []) + """ + if common is None: + common = [] + + if l1 == l2: + return l1, [], [] + + for i, (leftDir, rightDir) in enumerate(zip(l1, l2)): + if leftDir != rightDir: + return l1[0:i], l1[i:], l2[i:] + else: + if leftDir == rightDir: + i += 1 + return l1[0:i], l1[i:], l2[i:] + + +def relpath(p1, p2): + """ + >>> relpath('/', '/') + './' + >>> relpath('/a/b/c/d', '/') + '../../../../' + >>> relpath('/a/b/c/d', '/a/b/c1/d1') + '../../c1/d1' + >>> relpath('/a/b/c/d', '/a/b/c1/d1/') + '../../c1/d1' + >>> relpath("./plugins/builtins", "./plugins") + '../' + >>> relpath("./plugins/", "./plugins/builtins.ini") + 'builtins.ini' + """ + sourcePath = os.path.normpath(p1) + destPath = os.path.normpath(p2) + + (common, sourceOnly, destOnly) = commonpath(pathsplit(sourcePath), pathsplit(destPath)) + if len(sourceOnly) or len(destOnly): + relParts = itertools.chain( + (('..' + os.sep) * len(sourceOnly), ), + destOnly, + ) + return os.path.join(*relParts) + else: + return "."+os.sep diff --git a/src/libraries/recipes/misc.py b/src/libraries/recipes/misc.py new file mode 100644 index 0000000..2104c84 --- /dev/null +++ b/src/libraries/recipes/misc.py @@ -0,0 +1,593 @@ +#!/usr/bin/env python + +from __future__ import with_statement + +import sys +import cPickle + +import functools +import itertools +import contextlib +import inspect + +import optparse +import traceback +import warnings +import string + + +def printfmt(template): + """ + This hides having to create the Template object and call substitute/safe_substitute on it. For example: + + >>> num = 10 + >>> word = "spam" + >>> printfmt("I would like to order $num units of $word, please") #doctest: +SKIP + I would like to order 10 units of spam, please + """ + frame = inspect.stack()[-1][0] + try: + print string.Template(template).safe_substitute(frame.f_locals) + finally: + del frame + + +def is_special(name): + return name.startswith("__") and name.endswith("__") + + +def is_private(name): + return name.startswith("_") and not is_special(name) + + +def privatize(clsName, attributeName): + """ + At runtime, make an attributeName private + + Example: + >>> class Test(object): + ... pass + ... + >>> dir(Test) + ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__'] + >>> setattr(Test, privatize(Test.__name__, "me"), "Hello World") + >>> dir(Test) + ['_Test__me', '__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__'] + >>> print getattr(Test, obfuscate(Test.__name__, "__me")) + Hello World + >>> + >>> is_private(privatize(Test.__name__, "me")) + True + >>> is_special(privatize(Test.__name__, "me")) + False + """ + return "".join(["_", clsName, "__", attributeName]) + + +def obfuscate(clsName, attributeName): + """ + At runtime, turn a private name into the obfuscated form + + Example: + >>> class Test(object): + ... __me = "Hello World" + ... + >>> dir(Test) + ['_Test__me', '__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__'] + >>> print getattr(Test, obfuscate(Test.__name__, "__me")) + Hello World + >>> is_private(obfuscate(Test.__name__, "__me")) + True + >>> is_special(obfuscate(Test.__name__, "__me")) + False + """ + return "".join(["_", clsName, attributeName]) + + +class PAOptionParser(optparse.OptionParser, object): + """ + >>> if __name__ == '__main__': + ... #parser = PAOptionParser("My usage str") + ... parser = PAOptionParser() + ... parser.add_posarg("Foo", help="Foo usage") + ... parser.add_posarg("Bar", dest="bar_dest") + ... parser.add_posarg("Language", dest='tr_type', type="choice", choices=("Python", "Other")) + ... parser.add_option('--stocksym', dest='symbol') + ... values, args = parser.parse_args() + ... print values, args + ... + + python mycp.py -h + python mycp.py + python mycp.py foo + python mycp.py foo bar + + python mycp.py foo bar lava + Usage: pa.py [options] + + Positional Arguments: + Foo: Foo usage + Bar: + Language: + + pa.py: error: option --Language: invalid choice: 'lava' (choose from 'Python', 'Other' + """ + + def __init__(self, *args, **kw): + self.posargs = [] + super(PAOptionParser, self).__init__(*args, **kw) + + def add_posarg(self, *args, **kw): + pa_help = kw.get("help", "") + kw["help"] = optparse.SUPPRESS_HELP + o = self.add_option("--%s" % args[0], *args[1:], **kw) + self.posargs.append((args[0], pa_help)) + + def get_usage(self, *args, **kwargs): + params = (' '.join(["<%s>" % arg[0] for arg in self.posargs]), '\n '.join(["%s: %s" % (arg) for arg in self.posargs])) + self.usage = "%%prog %s [options]\n\nPositional Arguments:\n %s" % params + return super(PAOptionParser, self).get_usage(*args, **kwargs) + + def parse_args(self, *args, **kwargs): + args = sys.argv[1:] + args0 = [] + for p, v in zip(self.posargs, args): + args0.append("--%s" % p[0]) + args0.append(v) + args = args0 + args + options, args = super(PAOptionParser, self).parse_args(args, **kwargs) + if len(args) < len(self.posargs): + msg = 'Missing value(s) for "%s"\n' % ", ".join([arg[0] for arg in self.posargs][len(args):]) + self.error(msg) + return options, args + + +def explicitly(name, stackadd=0): + """ + This is an alias for adding to '__all__'. Less error-prone than using + __all__ itself, since setting __all__ directly is prone to stomping on + things implicitly exported via L{alias}. + + @note Taken from PyExport (which could turn out pretty cool): + @li @a http://codebrowse.launchpad.net/~glyph/ + @li @a http://glyf.livejournal.com/74356.html + """ + packageVars = sys._getframe(1+stackadd).f_locals + globalAll = packageVars.setdefault('__all__', []) + globalAll.append(name) + + +def public(thunk): + """ + This is a decorator, for convenience. Rather than typing the name of your + function twice, you can decorate a function with this. + + To be real, @public would need to work on methods as well, which gets into + supporting types... + + @note Taken from PyExport (which could turn out pretty cool): + @li @a http://codebrowse.launchpad.net/~glyph/ + @li @a http://glyf.livejournal.com/74356.html + """ + explicitly(thunk.__name__, 1) + return thunk + + +def _append_docstring(obj, message): + if obj.__doc__ is None: + obj.__doc__ = message + else: + obj.__doc__ += message + + +def validate_decorator(decorator): + + def simple(x): + return x + + f = simple + f.__name__ = "name" + f.__doc__ = "doc" + f.__dict__["member"] = True + + g = decorator(f) + + if f.__name__ != g.__name__: + print f.__name__, "!=", g.__name__ + + if g.__doc__ is None: + print decorator.__name__, "has no doc string" + elif not g.__doc__.startswith(f.__doc__): + print g.__doc__, "didn't start with", f.__doc__ + + if not ("member" in g.__dict__ and g.__dict__["member"]): + print "'member' not in ", g.__dict__ + + +def deprecated(func): + """ + This is a decorator which can be used to mark functions + as deprecated. It will result in a warning being emitted + when the function is used. + + >>> validate_decorator(deprecated) + """ + + @functools.wraps(func) + def newFunc(*args, **kwargs): + warnings.warn("Call to deprecated function %s." % func.__name__, category=DeprecationWarning) + return func(*args, **kwargs) + _append_docstring(newFunc, "\n@deprecated") + return newFunc + + +def enabled(func): + """ + This decorator doesn't add any behavior + + >>> validate_decorator(enabled) + """ + return func + + +def disabled(func): + """ + This decorator disables the provided function, and does nothing + + >>> validate_decorator(disabled) + """ + + @functools.wraps(func) + def emptyFunc(*args, **kargs): + pass + _append_docstring(emptyFunc, "\n@note Temporarily Disabled") + return emptyFunc + + +def metadata(document=True, **kwds): + """ + >>> validate_decorator(metadata(author="Ed")) + """ + + def decorate(func): + for k, v in kwds.iteritems(): + setattr(func, k, v) + if document: + _append_docstring(func, "\n@"+k+" "+v) + return func + return decorate + + +def prop(func): + """Function decorator for defining property attributes + + The decorated function is expected to return a dictionary + containing one or more of the following pairs: + fget - function for getting attribute value + fset - function for setting attribute value + fdel - function for deleting attribute + This can be conveniently constructed by the locals() builtin + function; see: + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183 + @author http://kbyanc.blogspot.com/2007/06/python-property-attribute-tricks.html + + Example: + >>> #Due to transformation from function to property, does not need to be validated + >>> #validate_decorator(prop) + >>> class MyExampleClass(object): + ... @prop + ... def foo(): + ... "The foo property attribute's doc-string" + ... def fget(self): + ... print "GET" + ... return self._foo + ... def fset(self, value): + ... print "SET" + ... self._foo = value + ... return locals() + ... + >>> me = MyExampleClass() + >>> me.foo = 10 + SET + >>> print me.foo + GET + 10 + """ + return property(doc=func.__doc__, **func()) + + +def print_handler(e): + """ + @see ExpHandler + """ + print "%s: %s" % (type(e).__name__, e) + + +def print_ignore(e): + """ + @see ExpHandler + """ + print 'Ignoring %s exception: %s' % (type(e).__name__, e) + + +def print_traceback(e): + """ + @see ExpHandler + """ + #print sys.exc_info() + traceback.print_exc(file=sys.stdout) + + +def ExpHandler(handler = print_handler, *exceptions): + """ + An exception handling idiom using decorators + Examples + Specify exceptions in order, first one is handled first + last one last. + + >>> validate_decorator(ExpHandler()) + >>> @ExpHandler(print_ignore, ZeroDivisionError) + ... @ExpHandler(None, AttributeError, ValueError) + ... def f1(): + ... 1/0 + >>> @ExpHandler(print_traceback, ZeroDivisionError) + ... def f2(): + ... 1/0 + >>> @ExpHandler() + ... def f3(*pargs): + ... l = pargs + ... return l[10] + >>> @ExpHandler(print_traceback, ZeroDivisionError) + ... def f4(): + ... return 1 + >>> + >>> + >>> f1() + Ignoring ZeroDivisionError exception: integer division or modulo by zero + >>> f2() # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + ZeroDivisionError: integer division or modulo by zero + >>> f3() + IndexError: tuple index out of range + >>> f4() + 1 + """ + + def wrapper(f): + localExceptions = exceptions + if not localExceptions: + localExceptions = [Exception] + t = [(ex, handler) for ex in localExceptions] + t.reverse() + + def newfunc(t, *args, **kwargs): + ex, handler = t[0] + try: + if len(t) == 1: + return f(*args, **kwargs) + else: + #Recurse for embedded try/excepts + dec_func = functools.partial(newfunc, t[1:]) + dec_func = functools.update_wrapper(dec_func, f) + return dec_func(*args, **kwargs) + except ex, e: + return handler(e) + + dec_func = functools.partial(newfunc, t) + dec_func = functools.update_wrapper(dec_func, f) + return dec_func + return wrapper + + +class bindclass(object): + """ + >>> validate_decorator(bindclass) + >>> class Foo(BoundObject): + ... @bindclass + ... def foo(this_class, self): + ... return this_class, self + ... + >>> class Bar(Foo): + ... @bindclass + ... def bar(this_class, self): + ... return this_class, self + ... + >>> f = Foo() + >>> b = Bar() + >>> + >>> f.foo() # doctest: +ELLIPSIS + (, <...Foo object at ...>) + >>> b.foo() # doctest: +ELLIPSIS + (, <...Bar object at ...>) + >>> b.bar() # doctest: +ELLIPSIS + (, <...Bar object at ...>) + """ + + def __init__(self, f): + self.f = f + self.__name__ = f.__name__ + self.__doc__ = f.__doc__ + self.__dict__.update(f.__dict__) + self.m = None + + def bind(self, cls, attr): + + def bound_m(*args, **kwargs): + return self.f(cls, *args, **kwargs) + bound_m.__name__ = attr + self.m = bound_m + + def __get__(self, obj, objtype=None): + return self.m.__get__(obj, objtype) + + +class ClassBindingSupport(type): + "@see bindclass" + + def __init__(mcs, name, bases, attrs): + type.__init__(mcs, name, bases, attrs) + for attr, val in attrs.iteritems(): + if isinstance(val, bindclass): + val.bind(mcs, attr) + + +class BoundObject(object): + "@see bindclass" + __metaclass__ = ClassBindingSupport + + +def bindfunction(f): + """ + >>> validate_decorator(bindfunction) + >>> @bindfunction + ... def factorial(thisfunction, n): + ... # Within this function the name 'thisfunction' refers to the factorial + ... # function(with only one argument), even after 'factorial' is bound + ... # to another object + ... if n > 0: + ... return n * thisfunction(n - 1) + ... else: + ... return 1 + ... + >>> factorial(3) + 6 + """ + + @functools.wraps(f) + def bound_f(*args, **kwargs): + return f(bound_f, *args, **kwargs) + return bound_f + + +class Memoize(object): + """ + Memoize(fn) - an instance which acts like fn but memoizes its arguments + Will only work on functions with non-mutable arguments + @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201 + + >>> validate_decorator(Memoize) + """ + + def __init__(self, fn): + self.fn = fn + self.__name__ = fn.__name__ + self.__doc__ = fn.__doc__ + self.__dict__.update(fn.__dict__) + self.memo = {} + + def __call__(self, *args): + if args not in self.memo: + self.memo[args] = self.fn(*args) + return self.memo[args] + + +class MemoizeMutable(object): + """Memoize(fn) - an instance which acts like fn but memoizes its arguments + Will work on functions with mutable arguments(slower than Memoize) + @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201 + + >>> validate_decorator(MemoizeMutable) + """ + + def __init__(self, fn): + self.fn = fn + self.__name__ = fn.__name__ + self.__doc__ = fn.__doc__ + self.__dict__.update(fn.__dict__) + self.memo = {} + + def __call__(self, *args, **kw): + text = cPickle.dumps((args, kw)) + if text not in self.memo: + self.memo[text] = self.fn(*args, **kw) + return self.memo[text] + + +callTraceIndentationLevel = 0 + + +def call_trace(f): + """ + Synchronization decorator. + + >>> validate_decorator(call_trace) + >>> @call_trace + ... def a(a, b, c): + ... pass + >>> a(1, 2, c=3) + Entering a((1, 2), {'c': 3}) + Exiting a((1, 2), {'c': 3}) + """ + + @functools.wraps(f) + def verboseTrace(*args, **kw): + global callTraceIndentationLevel + + print "%sEntering %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw) + callTraceIndentationLevel += 1 + try: + result = f(*args, **kw) + except: + callTraceIndentationLevel -= 1 + print "%sException %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw) + raise + callTraceIndentationLevel -= 1 + print "%sExiting %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw) + return result + + @functools.wraps(f) + def smallTrace(*args, **kw): + global callTraceIndentationLevel + + print "%sEntering %s" % ("\t"*callTraceIndentationLevel, f.__name__) + callTraceIndentationLevel += 1 + try: + result = f(*args, **kw) + except: + callTraceIndentationLevel -= 1 + print "%sException %s" % ("\t"*callTraceIndentationLevel, f.__name__) + raise + callTraceIndentationLevel -= 1 + print "%sExiting %s" % ("\t"*callTraceIndentationLevel, f.__name__) + return result + + #return smallTrace + return verboseTrace + + +@contextlib.contextmanager +def lexical_scope(*args): + """ + @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/520586 + Example: + >>> b = 0 + >>> with lexical_scope(1) as (a): + ... print a + ... + >>> with lexical_scope(1,2,3) as (a,b,c): + ... print a,b,c + ... + >>> with lexical_scope(): + ... d = 10 + ... def foo(): + ... pass + ... + >>> print dir() # check those temporary variables are deleted. + >>> print b + """ + + frame = inspect.currentframe().f_back.f_back + saved = frame.f_locals.keys() + try: + if not args: + yield + elif len(args) == 1: + yield args[0] + else: + yield args + finally: + f_locals = frame.f_locals + for key in (x for x in f_locals.keys() if x not in saved): + del f_locals[key] + del frame diff --git a/src/libraries/recipes/operators.py b/src/libraries/recipes/operators.py new file mode 100644 index 0000000..a6f7618 --- /dev/null +++ b/src/libraries/recipes/operators.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python + +""" +Example Operators for comparison +>>> class C(object): +... def __init__(self, x): +... self.x = x +... +>>> x, y, z = C(1), C(1), C(2) +>>> x == y, hash(x) == hash(y) +(False, False) +""" + + +import operator +import itertools + + +class KeyedEqualityOperators(object): + """ + Mixin for auto-implementing comparison operators + @note Requires inheriting class to implement a '__key__' function + Example: + >>> class C(KeyedEqualityOperators): + ... def __init__(self, x): + ... self.x = x + ... def __key__(self): + ... return self.x + ... + >>> x, y, z = C(1), C(1), C(2) + >>> x == y, hash(x) == hash(y) + (True, False) + """ + + def __init__(self): + self.__key__ = None + + def __eq__(self, other): + return self.__key__() == other.__key__() + + def __ne__(self, other): + return self.__key__() != other.__key__() + + +class KeyedComparisonOperators(KeyedEqualityOperators): + """ + Mixin for auto-implementing comparison operators + @note Requires inheriting class to implement a '__key__' function + Example: + >>> class C(KeyedComparisonOperators): + ... def __init__(self, x): + ... self.x = x + ... def __key__(self): + ... return self.x + ... + >>> x, y, z = C(1), C(1), C(2) + >>> x == y, y < z, hash(x) == hash(y) + (True, True, False) + """ + + def __init__(self): + self.__key__ = None + + def __cmp__(self, other): + return cmp(self.__key__(), other.__key__()) + + def __lt__(self, other): + return self.__key__() < other.__key__() + + def __le__(self, other): + return self.__key__() <= other.__key__() + + def __gt__(self, other): + return self.__key__() > other.__key__() + + def __ge__(self, other): + return self.__key__() >= other.__key__() + + +class KeyedHashing(object): + """ + Mixin for auto-implementing comparison operators + @note Requires inheriting class to implement a '__key__' function + Example: + >>> class C(KeyedHashing): + ... def __init__(self, x): + ... self.x = x + ... def __key__(self): + ... return self.x + ... + >>> x, y, z = C(1), C(1), C(2) + >>> x == y, hash(x) == hash(y) + (False, True) + """ + + def __init__(self): + self.__key__ = None + + def __hash__(self): + return hash(self.__key__()) + + +class NotEqualOperator(object): + """ + Mixin for auto-implementing comparison operators + @note Requires inheriting class to implement '__eq__' function + """ + + def __ne__(self, other): + return not (self == other) + + +class ComparisonOperators(NotEqualOperator): + """ + Mixin for auto-implementing comparison operators + @note Requires inheriting class to implement '__lt__' function + """ + + def __le__(self, other): + return(self < other) or(self == other) + + def __gt__(self, other): + return not(self <= other) + + def __ge__(self, other): + return not(self < other) + + +class infix(object): + """ + Recipe #384122 + http://code.activestate.com/recipes/384122/ + + >>> import operator + >>> x = infix(operator.mul) + >>> 1 |x| 2 |x| 10 + 20 + """ + + def __init__(self, func): + self.__name__ = func.__name__ + self.__doc__ = func.__doc__ + try: + self.__dict__.update(func.__dict__) + except AttributeError: + pass + self.function = func + + def __ror__(self, other): + return infix(lambda x: self.function(other, x)) + + def __or__(self, other): + return self.function(other) + + def __call__(self, lhs, rhs): + return self.function(lhs, rhs) + + +class Just(object): + """ + @see mreturn + """ + + def __init__(self, value): + self.value = value + + +@infix +def mbind(maybe, func): + """ + @see mreturn + """ + if maybe is None: + return None + else: + return func(maybe.value) + + +def mreturn(value): + """ + >>> class Sheep(object): + ... def __init__(self, name): + ... self.name = name + ... self.mother = None + ... self.father = None + ... + >>> def father(sheep): + ... if sheep.father is None: + ... return None + ... else: + ... return Just(sheep.father) + ... + >>> def mother(sheep): + ... if sheep.mother is None: + ... return None + ... else: + ... return Just(sheep.mother) + ... + >>> def mothersFather(sheep): + ... return mreturn(sheep) |mbind| mother |mbind| father + ... + >>> def mothersPaternalGrandfather(sheep): + ... return mreturn(sheep) |mbind| mother |mbind| father |mbind| father + ... + >>> shawn = Sheep("Shawn") + >>> gertrude = Sheep("Gertrude") + >>> ernie = Sheep("Ernie") + >>> frank = Sheep("Frank") + >>> + >>> shawn.mother = gertrude + >>> gertrude.father = ernie + >>> ernie.father = frank + >>> + >>> print mothersFather(shawn).value.name + Ernie + >>> print mothersPaternalGrandfather(shawn).value.name + Frank + >>> print mothersPaternalGrandfather(ernie) + None + """ + return Just(value) + + +def xor(*args): + truth = itertools.imap(operator.truth, args) + return reduce(operator.xor, truth) + + +def equiv(*args): + truth = itertools.imap(operator.truth, args) + return reduce(lambda a, b: not operator.xor(a, b), truth) diff --git a/src/libraries/recipes/overloading.py b/src/libraries/recipes/overloading.py new file mode 100644 index 0000000..89cb738 --- /dev/null +++ b/src/libraries/recipes/overloading.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python +import new + +# Make the environment more like Python 3.0 +__metaclass__ = type +from itertools import izip as zip +import textwrap +import inspect + + +__all__ = [ + "AnyType", + "overloaded" +] + + +AnyType = object + + +class overloaded: + """ + Dynamically overloaded functions. + + This is an implementation of (dynamically, or run-time) overloaded + functions; also known as generic functions or multi-methods. + + The dispatch algorithm uses the types of all argument for dispatch, + similar to (compile-time) overloaded functions or methods in C++ and + Java. + + Most of the complexity in the algorithm comes from the need to support + subclasses in call signatures. For example, if an function is + registered for a signature (T1, T2), then a call with a signature (S1, + S2) is acceptable, assuming that S1 is a subclass of T1, S2 a subclass + of T2, and there are no other more specific matches (see below). + + If there are multiple matches and one of those doesn't *dominate* all + others, the match is deemed ambiguous and an exception is raised. A + subtlety here: if, after removing the dominated matches, there are + still multiple matches left, but they all map to the same function, + then the match is not deemed ambiguous and that function is used. + Read the method find_func() below for details. + + @note Python 2.5 is required due to the use of predicates any() and all(). + @note only supports positional arguments + + @author http://www.artima.com/weblogs/viewpost.jsp?thread=155514 + + >>> import misc + >>> misc.validate_decorator (overloaded) + >>> + >>> + >>> + >>> + >>> ################# + >>> #Basics, with reusing names and without + >>> @overloaded + ... def foo(x): + ... "prints x" + ... print x + ... + >>> @foo.register(int) + ... def foo(x): + ... "prints the hex representation of x" + ... print hex(x) + ... + >>> from types import DictType + >>> @foo.register(DictType) + ... def foo_dict(x): + ... "prints the keys of x" + ... print [k for k in x.iterkeys()] + ... + >>> #combines all of the doc strings to help keep track of the specializations + >>> foo.__doc__ # doctest: +ELLIPSIS + "prints x\\n\\n...overloading.foo ():\\n\\tprints the hex representation of x\\n\\n...overloading.foo_dict ():\\n\\tprints the keys of x" + >>> foo ("text") + text + >>> foo (10) #calling the specialized foo + 0xa + >>> foo ({3:5, 6:7}) #calling the specialization foo_dict + [3, 6] + >>> foo_dict ({3:5, 6:7}) #with using a unique name, you still have the option of calling the function directly + [3, 6] + >>> + >>> + >>> + >>> + >>> ################# + >>> #Multiple arguments, accessing the default, and function finding + >>> @overloaded + ... def two_arg (x, y): + ... print x,y + ... + >>> @two_arg.register(int, int) + ... def two_arg_int_int (x, y): + ... print hex(x), hex(y) + ... + >>> @two_arg.register(float, int) + ... def two_arg_float_int (x, y): + ... print x, hex(y) + ... + >>> @two_arg.register(int, float) + ... def two_arg_int_float (x, y): + ... print hex(x), y + ... + >>> two_arg.__doc__ # doctest: +ELLIPSIS + "...overloading.two_arg_int_int (, ):\\n\\n...overloading.two_arg_float_int (, ):\\n\\n...overloading.two_arg_int_float (, ):" + >>> two_arg(9, 10) + 0x9 0xa + >>> two_arg(9.0, 10) + 9.0 0xa + >>> two_arg(15, 16.0) + 0xf 16.0 + >>> two_arg.default_func(9, 10) + 9 10 + >>> two_arg.find_func ((int, float)) == two_arg_int_float + True + >>> (int, float) in two_arg + True + >>> (str, int) in two_arg + False + >>> + >>> + >>> + >>> ################# + >>> #wildcard + >>> @two_arg.register(AnyType, str) + ... def two_arg_any_str (x, y): + ... print x, y.lower() + ... + >>> two_arg("Hello", "World") + Hello world + >>> two_arg(500, "World") + 500 world + """ + + def __init__(self, default_func): + # Decorator to declare new overloaded function. + self.registry = {} + self.cache = {} + self.default_func = default_func + self.__name__ = self.default_func.__name__ + self.__doc__ = self.default_func.__doc__ + self.__dict__.update (self.default_func.__dict__) + + def __get__(self, obj, type=None): + if obj is None: + return self + return new.instancemethod(self, obj) + + def register(self, *types): + """ + Decorator to register an implementation for a specific set of types. + + .register(t1, t2)(f) is equivalent to .register_func((t1, t2), f). + """ + + def helper(func): + self.register_func(types, func) + + originalDoc = self.__doc__ if self.__doc__ is not None else "" + typeNames = ", ".join ([str(type) for type in types]) + typeNames = "".join ([func.__module__+".", func.__name__, " (", typeNames, "):"]) + overloadedDoc = "" + if func.__doc__ is not None: + overloadedDoc = textwrap.fill (func.__doc__, width=60, initial_indent="\t", subsequent_indent="\t") + self.__doc__ = "\n".join ([originalDoc, "", typeNames, overloadedDoc]).strip() + + new_func = func + + #Masking the function, so we want to take on its traits + if func.__name__ == self.__name__: + self.__dict__.update (func.__dict__) + new_func = self + return new_func + + return helper + + def register_func(self, types, func): + """Helper to register an implementation.""" + self.registry[tuple(types)] = func + self.cache = {} # Clear the cache (later we can optimize this). + + def __call__(self, *args): + """Call the overloaded function.""" + types = tuple(map(type, args)) + func = self.cache.get(types) + if func is None: + self.cache[types] = func = self.find_func(types) + return func(*args) + + def __contains__ (self, types): + return self.find_func(types) is not self.default_func + + def find_func(self, types): + """Find the appropriate overloaded function; don't call it. + + @note This won't work for old-style classes or classes without __mro__ + """ + func = self.registry.get(types) + if func is not None: + # Easy case -- direct hit in registry. + return func + + # Phillip Eby suggests to use issubclass() instead of __mro__. + # There are advantages and disadvantages. + + # I can't help myself -- this is going to be intense functional code. + # Find all possible candidate signatures. + mros = tuple(inspect.getmro(t) for t in types) + n = len(mros) + candidates = [sig for sig in self.registry + if len(sig) == n and + all(t in mro for t, mro in zip(sig, mros))] + + if not candidates: + # No match at all -- use the default function. + return self.default_func + elif len(candidates) == 1: + # Unique match -- that's an easy case. + return self.registry[candidates[0]] + + # More than one match -- weed out the subordinate ones. + + def dominates(dom, sub, + orders=tuple(dict((t, i) for i, t in enumerate(mro)) + for mro in mros)): + # Predicate to decide whether dom strictly dominates sub. + # Strict domination is defined as domination without equality. + # The arguments dom and sub are type tuples of equal length. + # The orders argument is a precomputed auxiliary data structure + # giving dicts of ordering information corresponding to the + # positions in the type tuples. + # A type d dominates a type s iff order[d] <= order[s]. + # A type tuple (d1, d2, ...) dominates a type tuple of equal length + # (s1, s2, ...) iff d1 dominates s1, d2 dominates s2, etc. + if dom is sub: + return False + return all(order[d] <= order[s] for d, s, order in zip(dom, sub, orders)) + + # I suppose I could inline dominates() but it wouldn't get any clearer. + candidates = [cand + for cand in candidates + if not any(dominates(dom, cand) for dom in candidates)] + if len(candidates) == 1: + # There's exactly one candidate left. + return self.registry[candidates[0]] + + # Perhaps these multiple candidates all have the same implementation? + funcs = set(self.registry[cand] for cand in candidates) + if len(funcs) == 1: + return funcs.pop() + + # No, the situation is irreducibly ambiguous. + raise TypeError("ambigous call; types=%r; candidates=%r" % + (types, candidates)) diff --git a/src/libraries/recipes/test_utils.py b/src/libraries/recipes/test_utils.py new file mode 100644 index 0000000..349eded --- /dev/null +++ b/src/libraries/recipes/test_utils.py @@ -0,0 +1,129 @@ +#!/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 + + +class expected(object): + """ + >>> with expected(ZeroDivisionError): + ... 1 / 0 + >>> with expected(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 expected(Exception("foo")): + ... raise Exception("foo") + >>> with expected(Exception("bar")): + ... with expected(Exception("foo")): # this won't catch it + ... raise Exception("bar") + ... assert False, "should not see me" + >>> with expected(Exception("can specify")): + ... raise Exception("can specify prefixes") + >>> with expected(Exception("Base class fun")): + ... raise IndexError("Base class fun") + """ + + def __init__(self, e): + if isinstance(e, Exception): + self._t, self._v = type(e), str(e) + elif isinstance(e, type): + self._t, self._v = e, "" + else: + raise Exception("usage: with expected(Exception): ... or " + "with expected(Exception(\"text\")): ...") + + def __enter__(self): + try: + pass + except: + pass # this is a Python 3000 way of saying sys.exc_clear() + + def __exit__(self, t, v, tb): + assert t is not None, ("expected {0:%s} to have been thrown" % (self._t.__name__)) + return self._t in inspect.getmro(t) and str(v).startswith(self._v) diff --git a/src/libraries/recipes/xml_utils.py b/src/libraries/recipes/xml_utils.py new file mode 100755 index 0000000..55be820 --- /dev/null +++ b/src/libraries/recipes/xml_utils.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + + +from __future__ import with_statement + + +def flatten(elem, includeTail = False): + """ + Recursively extract text content. + + @note To get rid of all subelements to a given element, and keep just the text, you can do: + elem.text = flatten(elem); del elem[:] + """ + text = elem.text or "" + for e in elem: + text += flatten(e) + if includeTail and e.tail: + text += e.tail + return text + + +def append(elem, item): + """ + Universal append to an Element + @param elem ElementTree.Element + @param item Either None, Str/Unicode, or ElementTree.Element + """ + if item is None: + return + + if isinstance(item, basestring): + if len(elem): + elem[-1].tail = (elem[-1].tail or "") + item + else: + elem.text = (elem.text or "") + item + else: + elem.append(item) + + +def indent(elem, level=0, indentation=" "): + """ + Add indentation to the data of in an ElementTree + + >>> from xml.etree import ElementTree + >>> xmlRoot = ElementTree.fromstring("") + >>> indent(xmlRoot) + >>> ElementTree.dump(xmlRoot) + + + + + + """ + + i = "\n" + level*indentation + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + indentation + for e in elem: + indent(e, level+1, indentation) + if not e.tail or not e.tail.strip(): + e.tail = i + indentation + if not e.tail or not e.tail.strip(): + e.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +if __name__ == "__main__": + import sys + from xml.etree import ElementTree + if len(sys.argv) == 3: + xml = ElementTree.parse(sys.argv[1]) + indent(xml.getroot()) + with open(sys.argv[2], "w") as source: + xml.write(source) + elif len(sys.argv) == 1: + xml = ElementTree.parse(sys.stdin) + indent(xml.getroot()) + xml.write(sys.stdout) diff --git a/src/operation.py b/src/operation.py new file mode 100644 index 0000000..165cac3 --- /dev/null +++ b/src/operation.py @@ -0,0 +1,406 @@ +#!/usr/bin/env python + + +import itertools +import functools +import decimal + +from libraries.recipes import overloading +from libraries.recipes import algorithms + + +@overloading.overloaded +def serialize_value(value, base, renderer): + yield renderer(value, base) + + +@serialize_value.register(complex, overloading.AnyType, overloading.AnyType) +def serialize_complex(value, base, renderer): + if value.real == 0.0: + yield renderer(value.imag*1j, base) + elif value.imag == 0.0: + yield renderer(value.real, base) + else: + yield renderer(value.real, base) + yield renderer(value.imag*1j, base) + yield "+" + + +def render_float(value): + return str(value) + + +def render_float_dec(value): + floatText = str(value) + dec = decimal.Decimal(floatText) + return str(dec) + + +def render_float_eng(value): + floatText = str(value) + dec = decimal.Decimal(floatText) + return dec.to_eng_string() + + +def render_float_sci(value): + floatText = str(value) + dec = decimal.Decimal(floatText) + return dec.to_sci_string() + + +def render_complex(floatRender): + + def render_complex_real(value): + realRendered = floatRender(value.real) + imagRendered = floatRender(value.imag) + rendered = "%s+%sj" % (realRendered, imagRendered) + return rendered + + return render_complex_real + + +def _seperate_num(rendered, sep, count): + """ + >>> _seperate_num("123", ",", 3) + '123' + >>> _seperate_num("123456", ",", 3) + '123,456' + >>> _seperate_num("1234567", ",", 3) + '1,234,567' + """ + leadCount = len(rendered) % count + choppyRest = algorithms.itergroup(rendered[leadCount:], count) + rest = ( + "".join(group) + for group in choppyRest + ) + if 0 < leadCount: + lead = rendered[0:leadCount] + parts = itertools.chain((lead, ), rest) + else: + parts = rest + return sep.join(parts) + + +def render_integer_oct(value, sep=""): + rendered = oct(int(value)) + if 0 < len(sep): + assert rendered.startswith("0") + rendered = "0o%s" % _seperate_num(rendered[1:], sep, 3) + return rendered + + +def render_integer_dec(value, sep=""): + rendered = str(int(value)) + if 0 < len(sep): + rendered = "%s" % _seperate_num(rendered, sep, 3) + return rendered + + +def render_integer_hex(value, sep=""): + rendered = hex(int(value)) + if 0 < len(sep): + assert rendered.startswith("0x") + rendered = "0x%s" % _seperate_num(rendered[2:], sep, 3) + return rendered + + +def set_render_int_seperator(renderer, sep): + + @functools.wrap(renderer) + def render_with_sep(value): + return renderer(value, sep) + + return render_with_sep + + +class render_number(object): + + def __init__(self, + ints = None, + f = None, + c = None, + ): + if ints is not None: + self.render_int = ints + else: + self.render_int = { + 2: render_integer_hex, + 8: render_integer_oct, + 10: render_integer_dec, + 16: render_integer_hex, + } + self.render_float = f if c is not None else render_float + self.render_complex = c if c is not None else self + + def __call__(self, value, base): + return self.render(value, base) + + @overloading.overloaded + def render(self, value, base): + return str(value) + + @render.register(overloading.AnyType, int, overloading.AnyType) + def _render_int(self, value, base): + renderer = self.render_int.get(base, render_integer_dec) + return renderer(value) + + @render.register(overloading.AnyType, float, overloading.AnyType) + def _render_float(self, value, base): + return self.render_float(value) + + @render.register(overloading.AnyType, complex, overloading.AnyType) + def _render_complex(self, value, base): + return self.render_float(value) + + +class Operation(object): + + def __init__(self): + self.base = 10 + + def __str__(self): + raise NotImplementedError + + def get_children(self): + return [] + + def serialize(self, renderer): + for child in self.get_children(): + for childItem in child.serialize(renderer): + yield childItem + + def simplify(self): + """ + @returns an operation tree with all constant calculations performed and only variables left + """ + raise NotImplementedError + + def evaluate(self): + """ + @returns a value that the tree represents, if it can't be evaluated, + then an exception is throwd + """ + raise NotImplementedError + + def __call__(self): + return self.evaluate() + + +class Value(Operation): + + def __init__(self, value, base): + super(Value, self).__init__() + self.value = value + self.base = base + + def serialize(self, renderer): + for item in super(Value, self).serialize(renderer): + yield item + for component in serialize_value(self.value, self.base, renderer): + yield component + + def __str__(self): + return str(self.value) + + def simplify(self): + return self + + def evaluate(self): + return self.value + + +class Constant(Operation): + + def __init__(self, name, valueNode): + super(Constant, self).__init__() + self.name = name + self.__valueNode = valueNode + + def serialize(self, renderer): + for item in super(Constant, self).serialize(renderer): + yield item + yield self.name + + def __str__(self): + return self.name + + def simplify(self): + return self.__valueNode.simplify() + + def evaluate(self): + return self.__valueNode.evaluate() + + +class Variable(Operation): + + def __init__(self, name): + super(Variable, self).__init__() + self.name = name + + def serialize(self, renderer): + for item in super(Variable, self).serialize(renderer): + yield item + yield self.name + + def __str__(self): + return self.name + + def simplify(self): + return self + + def evaluate(self): + raise KeyError + + +class Function(Operation): + + REP_FUNCTION = 0 + REP_PREFIX = 1 + REP_INFIX = 2 + REP_POSTFIX = 3 + + _op = None + _rep = REP_FUNCTION + symbol = None + argumentCount = 0 + + def __init__(self, *args, **kwd): + super(Function, self).__init__() + self._args = args + self._kwd = kwd + self.__simple = self.__simplify() + self.__str = self.pretty_print(args, kwd) + + def serialize(self, renderer): + for item in super(Function, self).serialize(renderer): + yield item + yield self.symbol + + def get_children(self): + return ( + arg + for arg in self._args + ) + + def __str__(self): + return self.__str + + def simplify(self): + return self.__simple + + def evaluate(self): + selfArgs = [arg.evaluate() for arg in self._args] + return Value(self._op(*selfArgs)) + + def __simplify(self): + selfArgs = [arg.simplify() for arg in self._args] + selfKwd = dict( + (name, arg.simplify()) + for (name, arg) in self._kwd + ) + + try: + args = [arg.evaluate() for arg in selfArgs] + base = self.base + if base is None: + bases = [arg.base for arg in selfArgs] + base = bases[0] + result = self._op(*args) + + node = Value(result, base) + except KeyError: + node = type(self)(*selfArgs, **selfKwd) + + return node + + @classmethod + def pretty_print(cls, args = None, kwds = None): + if args is None: + args = [] + if kwds is None: + kwds = {} + + if cls._rep == cls.REP_FUNCTION: + positional = (str(arg) for arg in args) + named = ( + "%s=%s" % (str(key), str(value)) + for (key, value) in kwds.iteritems() + ) + return "%s(%s)" % ( + cls.symbol, + ", ".join(itertools.chain(named, positional)), + ) + elif cls._rep == cls.REP_PREFIX: + assert len(args) == 1 + return "%s %s" % (cls.symbol, args[0]) + elif cls._rep == cls.REP_POSTFIX: + assert len(args) == 1 + return "%s %s" % (args[0], cls.symbol) + elif cls._rep == cls.REP_INFIX: + assert len(args) == 2 + return "(%s %s %s)" % ( + str(args[0]), + str(cls.symbol), + str(args[1]), + ) + else: + raise AssertionError("Unsupported rep style") + + +def generate_function(op, rep, style, numArgs): + + class GenFunc(Function): + + def __init__(self, *args, **kwd): + super(GenFunc, self).__init__(*args, **kwd) + + _op = op + _rep = style + symbol = rep + argumentCount = numArgs + + GenFunc.__name__ = op.__name__ + return GenFunc + + +def change_base(base, rep): + + class GenFunc(Function): + + def __init__(self, *args, **kwd): + super(GenFunc, self).__init__(*args, **kwd) + self.base = base + + _op = lambda self, n: n + _rep = Function.REP_FUNCTION + symbol = rep + argumentCount = 1 + + GenFunc.__name__ = rep + return GenFunc + + +@overloading.overloaded +def render_operation(render_func, operation): + return str(operation) + + +@render_operation.register(overloading.AnyType, Value) +def render_value(render_func, operation): + return render_func(operation.value, operation.base) + + +@render_operation.register(overloading.AnyType, Variable) +@render_operation.register(overloading.AnyType, Constant) +def render_variable(render_func, operation): + return operation.name + + +@render_operation.register(overloading.AnyType, Function) +def render_function(render_func, operation): + args = [ + render_operation(render_func, arg) + for arg in operation.get_children() + ] + return operation.pretty_print(args) diff --git a/src/plugin_utils.py b/src/plugin_utils.py new file mode 100644 index 0000000..d01b4dc --- /dev/null +++ b/src/plugin_utils.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python + + +from __future__ import with_statement + + +import sys +import os +import inspect +import ConfigParser + +from libraries import gtkpie +from libraries import gtkpieboard +from libraries.recipes import io +import operation + + +class CommandStackHandler(object): + + def __init__(self, stack, command, operator): + self.command = command + + self.__stack = stack + self.__operator = operator + + def handler(self, commandName, activeModifiers): + self.__stack.apply_operation(self.__operator) + + +class PieKeyboardPlugin(object): + + def __init__(self, name, factory): + self.name = name + self.factory = factory + self.__handler = None + + def setup(self, calcStack, style, boardHandler): + self.__handler = boardHandler + + with open(self.factory.mapFile, "r") as mapfile: + boardTree = gtkpieboard.parse_keyboard_data("\n".join(mapfile.readlines())) + + rows, columns = boardTree["dimensions"] + keyboardName = boardTree["name"] + keyTree = boardTree["keys"] + + keyboard = gtkpieboard.PieKeyboard(style, rows, columns) + gtkpieboard.load_keyboard(keyboardName, keyTree, keyboard, self.__handler) + + for commandName, operator in self.factory.commands.iteritems(): + handler = CommandStackHandler(calcStack, commandName, operator) + self.__handler.register_command_handler(commandName, handler.handler) + + return keyboard + + def tear_down(self): + for commandName, operator in self.factory.commands.itervalues(): + self.__handler.unregister_command_handler(commandName) + + # Leave our self completely unusable + self.name = None + self.factory = None + self.__handler = None + + +class PieKeyboardPluginFactory(object): + + def __init__(self, pluginName, keyboardMapFile): + self.name = pluginName + self.mapFile = keyboardMapFile + self.commands = {} + + def register_operation(self, commandName, operator): + self.commands[commandName] = operator + + def construct_keyboard(self): + plugin = PieKeyboardPlugin(self.name, self) + return plugin + + +class PluginManager(object): + + def __init__(self, pluginType): + self._pluginType = pluginType + self._plugins = {} + self._enabled = set() + + self.__searchPaths = [] + + def add_path(self, *paths): + self.__searchPaths.append(paths) + self.__scan(paths) + + def rescan(self): + self._plugins = {} + self.__scan(self.__searchPaths) + + def plugin_info(self, pluginId): + pluginData = self._plugins[pluginId] + return pluginData["name"], pluginData["version"], pluginData["description"] + + def plugins(self): + for id, pluginData in self._plugins.iteritems(): + yield id, pluginData["name"], pluginData["version"], pluginData["description"] + + def enable_plugin(self, id): + assert id in self._plugins + self._load_module(id) + self._enabled.add(id) + + def disable_plugin(self, id): + self._enabled.remove(id) + + def lookup_plugin(self, name): + for id, data in self._plugins.iteritems(): + if data["name"] == name: + return id + + def _load_module(self, id): + pluginData = self._plugins[id] + + if "module" not in pluginData: + pluginPath = pluginData["pluginpath"] + dataPath = pluginData["datapath"] + assert dataPath.endswith(".ini") + + dataPath = io.relpath(pluginPath, dataPath) + pythonPath = dataPath[0:-len(".ini")] + modulePath = fspath_to_ipath(pythonPath, "") + + sys.path.append(pluginPath) + try: + module = __import__(modulePath) + finally: + sys.path.remove(pluginPath) + pluginData["module"] = module + else: + # @todo Decide if want to call reload + module = pluginData["module"] + + return module + + def __scan(self, paths): + pluginDataFiles = find_plugins(paths, ".ini") + + for pluginPath, pluginDataFile in pluginDataFiles: + config = ConfigParser.SafeConfigParser() + config.read(pluginDataFile) + + name = config.get(self._pluginType, "name") + version = config.get(self._pluginType, "version") + description = config.get(self._pluginType, "description") + + self._plugins[pluginDataFile] = { + "name": name, + "version": version, + "description": description, + "datapath": pluginDataFile, + "pluginpath": pluginPath, + } + + +class ConstantPluginManager(PluginManager): + + def __init__(self): + super(ConstantPluginManager, self).__init__("Constants") + self.__constants = {} + self.__constantsCache = {} + self.__isCacheDirty = False + + def enable_plugin(self, id): + super(ConstantPluginManager, self).enable_plugin(id) + self.__constants[id] = dict( + extract_instance_from_plugin(self._plugins[id]["module"], operation.Operation) + ) + self.__isCacheDirty = True + + def disable_plugin(self, id): + super(ConstantPluginManager, self).disable_plugin(id) + self.__isCacheDirty = True + + @property + def constants(self): + if self.__isCacheDirty: + self.__update_cache() + return self.__constantsCache + + def __update_cache(self): + self.__constantsCache.clear() + for pluginId in self._enabled: + self.__constantsCache.update(self.__constants[pluginId]) + self.__isCacheDirty = False + + +class OperatorPluginManager(PluginManager): + + def __init__(self): + super(OperatorPluginManager, self).__init__("Operator") + self.__operators = {} + self.__operatorsCache = {} + self.__isCacheDirty = False + + def enable_plugin(self, id): + super(OperatorPluginManager, self).enable_plugin(id) + operators = ( + extract_class_from_plugin(self._plugins[id]["module"], operation.Operation) + ) + self.__operators[id] = dict( + (op.symbol, op) + for op in operators + ) + self.__isCacheDirty = True + + def disable_plugin(self, id): + super(OperatorPluginManager, self).disable_plugin(id) + self.__isCacheDirty = True + + @property + def operators(self): + if self.__isCacheDirty: + self.__update_cache() + return self.__operatorsCache + + def __update_cache(self): + self.__operatorsCache.clear() + for pluginId in self._enabled: + self.__operatorsCache.update(self.__operators[pluginId]) + self.__isCacheDirty = False + + +class KeyboardPluginManager(PluginManager): + + def __init__(self): + super(KeyboardPluginManager, self).__init__("Keyboard") + self.__keyboards = {} + self.__keyboardsCache = {} + self.__isCacheDirty = False + + def enable_plugin(self, id): + super(KeyboardPluginManager, self).enable_plugin(id) + keyboards = ( + extract_instance_from_plugin(self._plugins[id]["module"], PieKeyboardPluginFactory) + ) + self.__keyboards[id] = dict( + (board.name, board) + for boardVariableName, board in keyboards + ) + self.__isCacheDirty = True + + def disable_plugin(self, id): + super(KeyboardPluginManager, self).disable_plugin(id) + self.__isCacheDirty = True + + @property + def keyboards(self): + if self.__isCacheDirty: + self.__update_cache() + return self.__keyboardsCache + + def __update_cache(self): + self.__keyboardsCache.clear() + for pluginId in self._enabled: + self.__keyboardsCache.update(self.__keyboards[pluginId]) + self.__isCacheDirty = False + + +def fspath_to_ipath(fsPath, extension = ".py"): + """ + >>> fspath_to_ipath("user/test/file.py") + 'user.test.file' + """ + assert fsPath.endswith(extension) + CURRENT_DIR = "."+os.sep + CURRENT_DIR_LEN = len(CURRENT_DIR) + if fsPath.startswith(CURRENT_DIR): + fsPath = fsPath[CURRENT_DIR_LEN:] + + if extension: + fsPath = fsPath[0:-len(extension)] + parts = fsPath.split(os.sep) + return ".".join(parts) + + +def find_plugins(searchPaths, fileType=".py"): + pythonFiles = ( + (path, os.path.join(root, file)) + for path in searchPaths + for root, dirs, files in os.walk(path) + for file in files + if file.endswith(fileType) + ) + return pythonFiles + + +def extract_class_from_plugin(pluginModule, cls): + try: + for item in pluginModule.__dict__.itervalues(): + try: + if cls in inspect.getmro(item): + yield item + except AttributeError: + pass + except AttributeError: + pass + + +def extract_instance_from_plugin(pluginModule, cls): + try: + for name, item in pluginModule.__dict__.iteritems(): + try: + if isinstance(item, cls): + yield name, item + except AttributeError: + pass + except AttributeError: + pass diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py new file mode 100644 index 0000000..4265cc3 --- /dev/null +++ b/src/plugins/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python diff --git a/src/plugins/alphabet.ini b/src/plugins/alphabet.ini new file mode 100644 index 0000000..cb845d1 --- /dev/null +++ b/src/plugins/alphabet.ini @@ -0,0 +1,14 @@ +[Operator] +name=Alphabet +version=0.1 +description= + +[Constants] +name=Alphabet +version=0.1 +description= + +[Keyboard] +name=Alphabet +version=0.1 +description= diff --git a/src/plugins/alphabet.map b/src/plugins/alphabet.map new file mode 100644 index 0000000..eae3549 --- /dev/null +++ b/src/plugins/alphabet.map @@ -0,0 +1,59 @@ +{ + "name": "Alphabet", + "dimensions": (3, 3), + "keys": { + (0, 0): { + "CENTER": {"action": "e", "type": "text", "text": "E", }, + "SOUTH": {"action": "q", "type": "text", "text": "Q", }, + "EAST": {"action": "W", "type": "text", "text": "W", }, + "showAllSlices": True, + }, + (0, 1): { + "CENTER": {"action": "t", "type": "text", "text": "T", }, + "WEST": {"action": "r", "type": "text", "text": "R", }, + "EAST": {"action": "y", "type": "text", "text": "Y", }, + "SOUTH": {"action": "u", "type": "text", "text": "U", }, + "showAllSlices": True, + }, + (0, 2): { + "CENTER": {"action": "i", "type": "text", "text": "I", }, + "WEST": {"action": "o", "type": "text", "text": "O", }, + "SOUTH": {"action": "p", "type": "text", "text": "P", }, + "showAllSlices": True, + }, + (1, 0): { + "CENTER": {"action": "a", "type": "text", "text": "A", }, + "EAST": {"action": "s", "type": "text", "text": "S", }, + "showAllSlices": True, + }, + (1, 1): { + "CENTER": {"action": "h", "type": "text", "text": "H", }, + "WEST": {"action": "d", "type": "text", "text": "D", }, + "NORTH": {"action": "f", "type": "text", "text": "F", }, + "EAST": {"action": "g", "type": "text", "text": "G", }, + "SOUTH": {"action": "j", "type": "text", "text": "J", }, + "showAllSlices": True, + }, + (1, 2): { + "CENTER": {"action": "l", "type": "text", "text": "L", }, + "WEST": {"action": "k", "type": "text", "text": "K", }, + "showAllSlices": True, + }, + (2, 0): { + "CENTER": {"action": "c", "type": "text", "text": "C", }, + "NORTH": {"action": "z", "type": "text", "text": "Z", }, + "EAST": {"action": "x", "type": "text", "text": "X", }, + "showAllSlices": True, + }, + (2, 1): { + "CENTER": {"action": "b", "type": "text", "text": "B", }, + "NORTH": {"action": "v", "type": "text", "text": "V", }, + "showAllSlices": True, + }, + (2, 2): { + "CENTER": {"action": "n", "type": "text", "text": "N", }, + "NORTH_WEST": {"action": "m", "type": "text", "text": "M", }, + "showAllSlices": True, + }, + }, +} diff --git a/src/plugins/alphabet.py b/src/plugins/alphabet.py new file mode 100644 index 0000000..1977dba --- /dev/null +++ b/src/plugins/alphabet.py @@ -0,0 +1,26 @@ +""" +Keyboard Origin: + +qwe rtyu iop +as dfghj kl +zxc vb nm + +e t i +a h l +c b n +""" + +from __future__ import division + +import os +import operator + +import operation + +import sys +sys.path.append("../") +import plugin_utils + + +_MAP_FILE_PATH = os.path.join(os.path.dirname(__file__), "alphabet.map") +PLUGIN = plugin_utils.PieKeyboardPluginFactory("Alphabet", _MAP_FILE_PATH) diff --git a/src/plugins/builtins.ini b/src/plugins/builtins.ini new file mode 100644 index 0000000..48acacd --- /dev/null +++ b/src/plugins/builtins.ini @@ -0,0 +1,14 @@ +[Operator] +name=Builtin +version=0.1 +description= + +[Constants] +name=Builtin +version=0.1 +description= + +[Keyboard] +name=Builtin +version=0.1 +description= diff --git a/src/plugins/builtins.map b/src/plugins/builtins.map new file mode 100644 index 0000000..17a2de7 --- /dev/null +++ b/src/plugins/builtins.map @@ -0,0 +1,64 @@ +{ + "name": "Builtins", + "dimensions": (4, 3), + "keys": { + (0, 0): { + "CENTER": {"action": "7", "type": "text", "text": "7", }, + "showAllSlices": True, + }, + (0, 1): { + "CENTER": {"action": "8", "type": "text", "text": "8", }, + "SOUTH": {"action": "[**]", "type": "text", "text": "**", }, + "showAllSlices": True, + }, + (0, 2): { + "CENTER": {"action": "9", "type": "text", "text": "9", }, + "showAllSlices": True, + }, + (1, 0): { + "CENTER": {"action": "4", "type": "text", "text": "4", }, + "showAllSlices": True, + }, + (1, 1): { + "CENTER": {"action": "5", "type": "text", "text": "5", }, + "EAST": {"action": "[+]", "type": "text", "text": "+", }, + "WEST": {"action": "[-]", "type": "text", "text": "-", }, + "NORTH": {"action": "[*]", "type": "text", "text": "*", }, + "SOUTH": {"action": "[/]", "type": "text", "text": "/", }, + "showAllSlices": True, + }, + (1, 2): { + "CENTER": {"action": "6", "type": "text", "text": "6", }, + "showAllSlices": True, + }, + (2, 0): { + "CENTER": {"action": "1", "type": "text", "text": "1", }, + "showAllSlices": True, + }, + (2, 1): { + "CENTER": {"action": "2", "type": "text", "text": "2", }, + "NORTH": {"action": "[abs]", "type": "text", "text": "abs", }, + "showAllSlices": True, + }, + (2, 2): { + "CENTER": {"action": "3", "type": "text", "text": "3", }, + "showAllSlices": True, + }, + (3, 0): { + "CENTER": {"action": "[push]", "type": "image", "path": "newline.png", }, + "NORTH": {"action": "[unpush]", "type": "text", "text": "Undo", }, + "NORTH_WEST": {"action": "[clear]", "type": "image", "path": "clear.png", }, + "WEST": {"action": "[backspace]", "type": "image", "path": "backspace.png", }, + "showAllSlices": False, + }, + (3, 1): { + "CENTER": {"action": "0", "type": "text", "text": "0", }, + "showAllSlices": True, + }, + (3, 2): { + "CENTER": {"action": ".", "type": "text", "text": ".", }, + "NORTH": {"action": "j", "type": "text", "text": "j", }, + "showAllSlices": True, + }, + }, +} diff --git a/src/plugins/builtins.py b/src/plugins/builtins.py new file mode 100644 index 0000000..5ef3b90 --- /dev/null +++ b/src/plugins/builtins.py @@ -0,0 +1,32 @@ +from __future__ import division + +import os +import operator +import math + +import operation + +import sys +sys.path.append("../") +import plugin_utils + + +_MAP_FILE_PATH = os.path.join(os.path.dirname(__file__), "builtins.map") +PLUGIN = plugin_utils.PieKeyboardPluginFactory("Builtin", _MAP_FILE_PATH) + +addition = operation.generate_function(operator.add, "+", operation.Function.REP_INFIX, 2) +subtraction = operation.generate_function(operator.sub, "-", operation.Function.REP_INFIX, 2) +multiplication = operation.generate_function(operator.mul, "*", operation.Function.REP_INFIX, 2) +trueDivision = operation.generate_function(operator.truediv, "/", operation.Function.REP_INFIX, 2) + +PLUGIN.register_operation("+", addition) +PLUGIN.register_operation("-", subtraction) +PLUGIN.register_operation("*", multiplication) +PLUGIN.register_operation("/", trueDivision) + +exponentiation = operation.generate_function(operator.pow, "**", operation.Function.REP_INFIX, 2) +abs = operation.generate_function(operator.abs, "abs", operation.Function.REP_FUNCTION, 1) +#factorial = operation.generate_function(math.factorial, "!", operation.Function.REP_POSTFIX, 1) + +PLUGIN.register_operation("**", exponentiation) +PLUGIN.register_operation("abs", abs) diff --git a/src/plugins/computer.ini b/src/plugins/computer.ini new file mode 100644 index 0000000..e0651cb --- /dev/null +++ b/src/plugins/computer.ini @@ -0,0 +1,14 @@ +[Operator] +name=Computer +version=0.1 +description= + +[Constants] +name=Computer +version=0.1 +description= + +[Keyboard] +name=Computer +version=0.1 +description= diff --git a/src/plugins/computer.map b/src/plugins/computer.map new file mode 100644 index 0000000..d0db610 --- /dev/null +++ b/src/plugins/computer.map @@ -0,0 +1,51 @@ +{ + "name": "Computer", + "dimensions": (3, 3), + "keys": { + (0, 0): { + "CENTER": {"action": "0x", "type": "text", "text": "0x", }, + "NORTH": {"action": "[hex]", "type": "text", "text": "hex", }, + "NORTH_WEST": {"action": "a", "type": "text", "text": "A", }, + "WEST": {"action": "b", "type": "text", "text": "B", }, + "SOUTH_WEST": {"action": "c", "type": "text", "text": "C", }, + "NORTH_EAST": {"action": "d", "type": "text", "text": "D", }, + "EAST": {"action": "e", "type": "text", "text": "E", }, + "SOUTH_EAST": {"action": "f", "type": "text", "text": "F", }, + "showAllSlices": True, + }, + (0, 1): { + "CENTER": {"action": "0b", "type": "text", "text": "0b", }, + "NORTH": {"action": "[oct]", "type": "text", "text": "oct", }, + "showAllSlices": True, + }, + (0, 2): { + "CENTER": {"action": "0o", "type": "text", "text": "0o", }, + "showAllSlices": True, + }, + (1, 0): { + "CENTER": {"action": "[&]", "type": "text", "text": "and", }, + "showAllSlices": True, + }, + (1, 1): { + "CENTER": {"action": "[|]", "type": "text", "text": "or", }, + "SOUTH": {"action": "[~]", "type": "text", "text": "not", }, + "showAllSlices": True, + }, + (1, 2): { + "CENTER": {"action": "[^]", "type": "text", "text": "xor", }, + "showAllSlices": True, + }, + (2, 0): { + "CENTER": {"action": "[//]", "type": "text", "text": "//", }, + "NORTH": {"action": "[%]", "type": "text", "text": "%", }, + "showAllSlices": False, + }, + (2, 1): { + "CENTER": {"action": "[dec]", "type": "text", "text": "dec", }, + "showAllSlices": True, + }, + (2, 2): { + "showAllSlices": False, + }, + }, +} diff --git a/src/plugins/computer.py b/src/plugins/computer.py new file mode 100644 index 0000000..6f338c6 --- /dev/null +++ b/src/plugins/computer.py @@ -0,0 +1,43 @@ +from __future__ import division + +import os +import operator +import math + +import operation + +import sys +sys.path.append("../") +import plugin_utils + + +_MAP_FILE_PATH = os.path.join(os.path.dirname(__file__), "computer.map") +PLUGIN = plugin_utils.PieKeyboardPluginFactory("Computer", _MAP_FILE_PATH) + +hex = operation.change_base(16, "hex") +oct = operation.change_base(8, "oct") +dec = operation.change_base(10, "dec") +ceil = operation.generate_function(math.ceil, "ceil", operation.Function.REP_FUNCTION, 1) +floor = operation.generate_function(math.floor, "floor", operation.Function.REP_FUNCTION, 1) + +PLUGIN.register_operation("hex", hex) +PLUGIN.register_operation("oct", oct) +PLUGIN.register_operation("dec", dec) +PLUGIN.register_operation("ceil", ceil) +PLUGIN.register_operation("floor", floor) + +floorDivision = operation.generate_function(operator.floordiv, "//", operation.Function.REP_INFIX, 2) +modulo = operation.generate_function(operator.mod, "%", operation.Function.REP_INFIX, 2) + +PLUGIN.register_operation("//", floorDivision) +PLUGIN.register_operation("%", modulo) + +bitAnd = operation.generate_function(operator.and_, "&", operation.Function.REP_INFIX, 2) +bitOr = operation.generate_function(operator.or_, "|", operation.Function.REP_INFIX, 2) +bitXor = operation.generate_function(operator.xor, "^", operation.Function.REP_INFIX, 2) +bitInvert = operation.generate_function(operator.invert, "~", operation.Function.REP_PREFIX, 1) + +PLUGIN.register_operation("&", bitAnd) +PLUGIN.register_operation("|", bitOr) +PLUGIN.register_operation("^", bitXor) +PLUGIN.register_operation("~", bitInvert) diff --git a/src/plugins/trig.ini b/src/plugins/trig.ini new file mode 100644 index 0000000..fb9111f --- /dev/null +++ b/src/plugins/trig.ini @@ -0,0 +1,14 @@ +[Operator] +name=Trigonometry +version=0.1 +description= + +[Constants] +name=Trigonometry +version=0.1 +description= + +[Keyboard] +name=Trigonometry +version=0.1 +description= diff --git a/src/plugins/trig.map b/src/plugins/trig.map new file mode 100644 index 0000000..3b9c91a --- /dev/null +++ b/src/plugins/trig.map @@ -0,0 +1,51 @@ +{ + "name": "Trigonometry", + "dimensions": (3, 3), + "keys": { + (0, 0): { + "CENTER": {"action": "[sin]", "type": "text", "text": "sin", }, + "NORTH": {"action": "[asin]", "type": "text", "text": "asin", }, + "showAllSlices": False, + }, + (0, 1): { + "CENTER": {"action": "[cos]", "type": "text", "text": "cos", }, + "NORTH": {"action": "[acos]", "type": "text", "text": "acos", }, + "showAllSlices": False, + }, + (0, 2): { + "CENTER": {"action": "[tan]", "type": "text", "text": "tan", }, + "NORTH": {"action": "[atan]", "type": "text", "text": "atan", }, + "showAllSlices": False, + }, + (1, 0): { + "CENTER": {"action": "[exp]", "type": "text", "text": "exp", }, + "SOUTH": {"action": "[log]", "type": "text", "text": "log", }, + "showAllSlices": True, + }, + (1, 1): { + "CENTER": {"action": "pi", "type": "text", "text": "pi", }, + "SOUTH": {"action": "e", "type": "text", "text": "e", }, + "showAllSlices": True, + }, + (1, 2): { + "CENTER": {"action": "[rad]", "type": "text", "text": "rad", }, + "SOUTH": {"action": "[deg]", "type": "text", "text": "deg", }, + "showAllSlices": True, + }, + (2, 0): { + "CENTER": {"action": "[sinh]", "type": "text", "text": "sinh", }, + "NORTH": {"action": "[asinh]", "type": "text", "text": "asinh", }, + "showAllSlices": False, + }, + (2, 1): { + "CENTER": {"action": "[cosh]", "type": "text", "text": "cosh", }, + "NORTH": {"action": "[acosh]", "type": "text", "text": "acosh", }, + "showAllSlices": False, + }, + (2, 2): { + "CENTER": {"action": "[tanh]", "type": "text", "text": "tanh", }, + "NORTH": {"action": "[atanh]", "type": "text", "text": "atanh", }, + "showAllSlices": False, + }, + }, +} diff --git a/src/plugins/trig.py b/src/plugins/trig.py new file mode 100644 index 0000000..f3f61e9 --- /dev/null +++ b/src/plugins/trig.py @@ -0,0 +1,80 @@ +from __future__ import division + +import os +import operator +import math +import cmath + +import operation + +import sys +sys.path.append("../") +import plugin_utils + + +_MAP_FILE_PATH = os.path.join(os.path.dirname(__file__), "trig.map") +PLUGIN = plugin_utils.PieKeyboardPluginFactory("Trigonometry", _MAP_FILE_PATH) + +pi = operation.Constant("pi", operation.Value(math.pi, operation.render_float_eng)) +e = operation.Constant("e", operation.Value(math.e, operation.render_float_eng)) + +def float_or_complex(float_func, complex_func): + + def switching_func(self, *args, **kwd): + if any( + isinstance(arg, complex) + for arg in args + ): + return complex_func(*args, **kwd) + else: + return float_func(*args, **kwd) + + switching_func.__name__ = complex_func.__name__ + switching_func.__doc__ = complex_func.__doc__ + return switching_func + +exp = operation.generate_function(float_or_complex(math.exp, cmath.exp), "exp", operation.Function.REP_FUNCTION, 1) +log = operation.generate_function(float_or_complex(math.exp, cmath.exp), "log", operation.Function.REP_FUNCTION, 1) + +PLUGIN.register_operation("exp", exp) +PLUGIN.register_operation("log", log) + +cos = operation.generate_function(float_or_complex(math.cos, cmath.cos), "cos", operation.Function.REP_FUNCTION, 1) +acos = operation.generate_function(float_or_complex(math.acos, cmath.acos), "acos", operation.Function.REP_FUNCTION, 1) +sin = operation.generate_function(float_or_complex(math.sin, cmath.sin), "sin", operation.Function.REP_FUNCTION, 1) +asin = operation.generate_function(float_or_complex(math.asin, cmath.asin), "asin", operation.Function.REP_FUNCTION, 1) +tan = operation.generate_function(float_or_complex(math.tan, cmath.tan), "tan", operation.Function.REP_FUNCTION, 1) +atan = operation.generate_function(float_or_complex(math.atan, cmath.atan), "atan", operation.Function.REP_FUNCTION, 1) + +PLUGIN.register_operation("cos", cos) +PLUGIN.register_operation("acos", acos) +PLUGIN.register_operation("sin", sin) +PLUGIN.register_operation("asin", asin) +PLUGIN.register_operation("tan", tan) +PLUGIN.register_operation("atan", atan) + +cosh = operation.generate_function(float_or_complex(math.cosh, cmath.cosh), "cosh", operation.Function.REP_FUNCTION, 1) +acosh = operation.generate_function(cmath.acosh, "acosh", operation.Function.REP_FUNCTION, 1) +sinh = operation.generate_function(float_or_complex(math.sinh, cmath.sinh), "sinh", operation.Function.REP_FUNCTION, 1) +asinh = operation.generate_function(cmath.asinh, "asinh", operation.Function.REP_FUNCTION, 1) +tanh = operation.generate_function(float_or_complex(math.tanh, cmath.tanh), "tanh", operation.Function.REP_FUNCTION, 1) +atanh = operation.generate_function(cmath.atanh, "atanh", operation.Function.REP_FUNCTION, 1) + +PLUGIN.register_operation("cosh", cosh) +PLUGIN.register_operation("acosh", acosh) +PLUGIN.register_operation("sinh", sinh) +PLUGIN.register_operation("asinh", asinh) +PLUGIN.register_operation("tanh", tanh) +PLUGIN.register_operation("atanh", atanh) + +deg = operation.generate_function(math.degrees, "deg", operation.Function.REP_FUNCTION, 1) +rad = operation.generate_function(math.radians, "rad", operation.Function.REP_FUNCTION, 1) + +PLUGIN.register_operation("deg", deg) +PLUGIN.register_operation("rad", rad) + +# In 2.6 +#phase = operation.generate_function(cmath.phase, "phase", operation.Function.REP_FUNCTION, 1) +#polar = operation.generate_function(cmath.polar, "polar", operation.Function.REP_FUNCTION, 1) +#rect = operation.generate_function(cmath.rect, "rect", operation.Function.REP_FUNCTION, 1) + diff --git a/support/pylint.rc b/support/pylint.rc new file mode 100644 index 0000000..b9a1464 --- /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 + +[REPORTS] + +# set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=text + +# Include message's id in output +include-ids=no + +# 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=yes + +# 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=7 + +# 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' -- 1.7.9.5