Initial checkin
authorepage <eopage@byu.net>
Fri, 30 Jan 2009 01:14:15 +0000 (01:14 +0000)
committerepage <eopage@byu.net>
Fri, 30 Jan 2009 01:14:15 +0000 (01:14 +0000)
git-svn-id: file:///svnroot/ejpi/trunk@1 df6cc7de-23d0-4ae0-bb86-c17aa67b2a9d

49 files changed:
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
src/__init__.py [new file with mode: 0644]
src/calc.glade [new file with mode: 0644]
src/ejpi_cli.py [new file with mode: 0755]
src/ejpi_glade.py [new file with mode: 0755]
src/gtkhistory.py [new file with mode: 0755]
src/history.py [new file with mode: 0644]
src/libraries/__init__.py [new file with mode: 0644]
src/libraries/gtkpie.py [new file with mode: 0755]
src/libraries/gtkpieboard.py [new file with mode: 0755]
src/libraries/images/alt.png [new file with mode: 0644]
src/libraries/images/arrows.png [new file with mode: 0644]
src/libraries/images/backspace.png [new file with mode: 0644]
src/libraries/images/clear.png [new file with mode: 0644]
src/libraries/images/control.png [new file with mode: 0644]
src/libraries/images/newline.png [new file with mode: 0644]
src/libraries/images/shift.png [new file with mode: 0644]
src/libraries/images/space.png [new file with mode: 0644]
src/libraries/images/super.png [new file with mode: 0644]
src/libraries/images/symbols.dia [new file with mode: 0644]
src/libraries/images/tab.png [new file with mode: 0644]
src/libraries/recipes/__init__.py [new file with mode: 0644]
src/libraries/recipes/algorithms.py [new file with mode: 0644]
src/libraries/recipes/concurrent.py [new file with mode: 0644]
src/libraries/recipes/datatypes.py [new file with mode: 0644]
src/libraries/recipes/gtk_utils.py [new file with mode: 0644]
src/libraries/recipes/io.py [new file with mode: 0644]
src/libraries/recipes/misc.py [new file with mode: 0644]
src/libraries/recipes/operators.py [new file with mode: 0644]
src/libraries/recipes/overloading.py [new file with mode: 0644]
src/libraries/recipes/test_utils.py [new file with mode: 0644]
src/libraries/recipes/xml_utils.py [new file with mode: 0755]
src/operation.py [new file with mode: 0644]
src/plugin_utils.py [new file with mode: 0644]
src/plugins/__init__.py [new file with mode: 0644]
src/plugins/alphabet.ini [new file with mode: 0644]
src/plugins/alphabet.map [new file with mode: 0644]
src/plugins/alphabet.py [new file with mode: 0644]
src/plugins/builtins.ini [new file with mode: 0644]
src/plugins/builtins.map [new file with mode: 0644]
src/plugins/builtins.py [new file with mode: 0644]
src/plugins/computer.ini [new file with mode: 0644]
src/plugins/computer.map [new file with mode: 0644]
src/plugins/computer.py [new file with mode: 0644]
src/plugins/trig.ini [new file with mode: 0644]
src/plugins/trig.map [new file with mode: 0644]
src/plugins/trig.py [new file with mode: 0644]
support/pylint.rc [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
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.
+\f
+  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.
+\f
+                 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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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
+\f
+           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.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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 (file)
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 (file)
index 0000000..4265cc3
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/src/calc.glade b/src/calc.glade
new file mode 100644 (file)
index 0000000..9e99d79
--- /dev/null
@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.5 on Tue Jan 27 20:26:23 2009 -->
+<glade-interface>
+  <widget class="GtkWindow" id="mainWindow">
+    <property name="title" translatable="yes">Cluttered Calc</property>
+    <property name="default_width">800</property>
+    <property name="default_height">480</property>
+    <child>
+      <widget class="GtkVBox" id="mainLayout">
+        <property name="visible">True</property>
+        <child>
+          <widget class="GtkMenuBar" id="mainMenubar">
+            <property name="visible">True</property>
+            <child>
+              <widget class="GtkMenuItem" id="fileMenuItem">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">_File</property>
+                <property name="use_underline">True</property>
+                <child>
+                  <widget class="GtkMenu" id="fileMenu">
+                    <property name="visible">True</property>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="quitMenuItem">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">gtk-quit</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="on_calculator_quit"/>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkMenuItem" id="editMenuItem">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">_Edit</property>
+                <property name="use_underline">True</property>
+                <child>
+                  <widget class="GtkMenu" id="editMenuIte">
+                    <property name="visible">True</property>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="pasteMenuItem">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">gtk-paste</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="on_paste"/>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="deleteMenuItem">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">gtk-delete</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="on_clear_entry"/>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkMenuItem" id="helpMenuItem">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">_Help</property>
+                <property name="use_underline">True</property>
+                <child>
+                  <widget class="GtkMenu" id="helpMenu">
+                    <property name="visible">True</property>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="aboutMenuItem">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">gtk-about</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="on_about"/>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkHBox" id="calculatorLayout">
+            <property name="visible">True</property>
+            <child>
+              <widget class="GtkVBox" id="historyLayout">
+                <property name="visible">True</property>
+                <child>
+                  <widget class="GtkEntry" id="entryView">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="editable">False</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkEventBox" id="errorEventBox">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
+                    <child>
+                      <widget class="GtkHBox" id="errorBox">
+                        <property name="visible">True</property>
+                        <child>
+                          <widget class="GtkImage" id="errorImage">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-dialog-error</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="errorDescription">
+                            <property name="visible">True</property>
+                            <property name="use_markup">True</property>
+                          </widget>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkImage" id="errorClose">
+                            <property name="visible">True</property>
+                            <property name="tooltip_markup">Close Error Message</property>
+                            <property name="stock">gtk-close</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkScrolledWindow" id="scrollingHistory">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <child>
+                      <widget class="GtkTreeView" id="historyView">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="headers_visible">False</property>
+                        <property name="headers_clickable">False</property>
+                        <property name="reorderable">True</property>
+                        <property name="rules_hint">True</property>
+                        <property name="enable_search">False</property>
+                        <property name="hover_selection">True</property>
+                        <property name="hover_expand">True</property>
+                        <property name="rubber_banding">True</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkVBox" id="functionLayout">
+                <property name="visible">True</property>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <widget class="GtkHSeparator" id="hseparator1">
+                    <property name="visible">True</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkNotebook" id="pluginKeyboards">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="tab_pos">GTK_POS_BOTTOM</property>
+                    <property name="scrollable">True</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="NullFunctionTab">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">None</property>
+                      </widget>
+                      <packing>
+                        <property name="type">tab</property>
+                        <property name="tab_fill">False</property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
diff --git a/src/ejpi_cli.py b/src/ejpi_cli.py
new file mode 100755 (executable)
index 0000000..8fc34c3
--- /dev/null
@@ -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 (executable)
index 0000000..d2cdbd8
--- /dev/null
@@ -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 (executable)
index 0000000..d61739a
--- /dev/null
@@ -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 (file)
index 0000000..bf0014d
--- /dev/null
@@ -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 (file)
index 0000000..3e621cd
--- /dev/null
@@ -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 (executable)
index 0000000..57dbdd6
--- /dev/null
@@ -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 (executable)
index 0000000..c2c7175
--- /dev/null
@@ -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 (file)
index 0000000..8ccd110
Binary files /dev/null and b/src/libraries/images/alt.png differ
diff --git a/src/libraries/images/arrows.png b/src/libraries/images/arrows.png
new file mode 100644 (file)
index 0000000..6ab51c0
Binary files /dev/null and b/src/libraries/images/arrows.png differ
diff --git a/src/libraries/images/backspace.png b/src/libraries/images/backspace.png
new file mode 100644 (file)
index 0000000..c8d5506
Binary files /dev/null and b/src/libraries/images/backspace.png differ
diff --git a/src/libraries/images/clear.png b/src/libraries/images/clear.png
new file mode 100644 (file)
index 0000000..e18eed4
Binary files /dev/null and b/src/libraries/images/clear.png differ
diff --git a/src/libraries/images/control.png b/src/libraries/images/control.png
new file mode 100644 (file)
index 0000000..f24980e
Binary files /dev/null and b/src/libraries/images/control.png differ
diff --git a/src/libraries/images/newline.png b/src/libraries/images/newline.png
new file mode 100644 (file)
index 0000000..8b242f2
Binary files /dev/null and b/src/libraries/images/newline.png differ
diff --git a/src/libraries/images/shift.png b/src/libraries/images/shift.png
new file mode 100644 (file)
index 0000000..789d7e3
Binary files /dev/null and b/src/libraries/images/shift.png differ
diff --git a/src/libraries/images/space.png b/src/libraries/images/space.png
new file mode 100644 (file)
index 0000000..71207c3
Binary files /dev/null and b/src/libraries/images/space.png differ
diff --git a/src/libraries/images/super.png b/src/libraries/images/super.png
new file mode 100644 (file)
index 0000000..6461770
Binary files /dev/null and b/src/libraries/images/super.png differ
diff --git a/src/libraries/images/symbols.dia b/src/libraries/images/symbols.dia
new file mode 100644 (file)
index 0000000..a04fe5a
Binary files /dev/null and b/src/libraries/images/symbols.dia differ
diff --git a/src/libraries/images/tab.png b/src/libraries/images/tab.png
new file mode 100644 (file)
index 0000000..1040cac
Binary files /dev/null and b/src/libraries/images/tab.png differ
diff --git a/src/libraries/recipes/__init__.py b/src/libraries/recipes/__init__.py
new file mode 100644 (file)
index 0000000..4265cc3
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/src/libraries/recipes/algorithms.py b/src/libraries/recipes/algorithms.py
new file mode 100644 (file)
index 0000000..a2dc356
--- /dev/null
@@ -0,0 +1,504 @@
+#!/usr/bin/env python
+
+"""
+@note Source http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66448
+"""
+
+import itertools
+import functools
+import datetime
+import types
+
+
+def ordered_itr(collection):
+       """
+       >>> [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 (file)
index 0000000..3961723
--- /dev/null
@@ -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 (file)
index 0000000..2993cb3
--- /dev/null
@@ -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 (file)
index 0000000..7f83aec
--- /dev/null
@@ -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 (file)
index 0000000..6470cb6
--- /dev/null
@@ -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 (file)
index 0000000..2104c84
--- /dev/null
@@ -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 <Foo> <Bar> <Language> [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
+       (<class '...Foo'>, <...Foo object at ...>)
+       >>> b.foo() # doctest: +ELLIPSIS
+       (<class '...Foo'>, <...Bar object at ...>)
+       >>> b.bar() # doctest: +ELLIPSIS
+       (<class '...Bar'>, <...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 (file)
index 0000000..a6f7618
--- /dev/null
@@ -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 (file)
index 0000000..89cb738
--- /dev/null
@@ -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 (<type 'int'>):\\n\\tprints the hex representation of x\\n\\n...overloading.foo_dict (<type '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 (<type 'int'>, <type 'int'>):\\n\\n...overloading.two_arg_float_int (<type 'float'>, <type 'int'>):\\n\\n...overloading.two_arg_int_float (<type 'int'>, <type '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 (file)
index 0000000..349eded
--- /dev/null
@@ -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 "<doctest libraries.recipes.context.expected[1]>", line 3, in <module>
+                       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 (executable)
index 0000000..55be820
--- /dev/null
@@ -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("<xml><tree><bird /></tree></xml>")
+       >>> indent(xmlRoot)
+       >>> ElementTree.dump(xmlRoot)
+       <xml>
+           <tree>
+               <bird />
+           </tree>
+       </xml>
+       """
+
+       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 (file)
index 0000000..165cac3
--- /dev/null
@@ -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 (file)
index 0000000..d01b4dc
--- /dev/null
@@ -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 (file)
index 0000000..4265cc3
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/src/plugins/alphabet.ini b/src/plugins/alphabet.ini
new file mode 100644 (file)
index 0000000..cb845d1
--- /dev/null
@@ -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 (file)
index 0000000..eae3549
--- /dev/null
@@ -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 (file)
index 0000000..1977dba
--- /dev/null
@@ -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 (file)
index 0000000..48acacd
--- /dev/null
@@ -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 (file)
index 0000000..17a2de7
--- /dev/null
@@ -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 (file)
index 0000000..5ef3b90
--- /dev/null
@@ -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 (file)
index 0000000..e0651cb
--- /dev/null
@@ -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 (file)
index 0000000..d0db610
--- /dev/null
@@ -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 (file)
index 0000000..6f338c6
--- /dev/null
@@ -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 (file)
index 0000000..fb9111f
--- /dev/null
@@ -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 (file)
index 0000000..3b9c91a
--- /dev/null
@@ -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 (file)
index 0000000..f3f61e9
--- /dev/null
@@ -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 (file)
index 0000000..b9a1464
--- /dev/null
@@ -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 <file or directory> 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'