summaryrefslogtreecommitdiff
path: root/addons/web/static/lib/py.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/lib/py.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/lib/py.js')
-rw-r--r--addons/web/static/lib/py.js/LICENSE13
-rw-r--r--addons/web/static/lib/py.js/README.rst196
-rw-r--r--addons/web/static/lib/py.js/TODO.rst46
-rw-r--r--addons/web/static/lib/py.js/doc/Makefile153
-rw-r--r--addons/web/static/lib/py.js/doc/builtins.rst55
-rw-r--r--addons/web/static/lib/py.js/doc/conf.py247
-rw-r--r--addons/web/static/lib/py.js/doc/differences.rst64
-rw-r--r--addons/web/static/lib/py.js/doc/index.rst161
-rw-r--r--addons/web/static/lib/py.js/doc/make.bat190
-rw-r--r--addons/web/static/lib/py.js/doc/types.rst248
-rw-r--r--addons/web/static/lib/py.js/doc/utility.rst248
-rw-r--r--addons/web/static/lib/py.js/lib/py.js1477
-rw-r--r--addons/web/static/lib/py.js/lib/py_extras.js989
13 files changed, 4087 insertions, 0 deletions
diff --git a/addons/web/static/lib/py.js/LICENSE b/addons/web/static/lib/py.js/LICENSE
new file mode 100644
index 00000000..01733dda
--- /dev/null
+++ b/addons/web/static/lib/py.js/LICENSE
@@ -0,0 +1,13 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2012
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/addons/web/static/lib/py.js/README.rst b/addons/web/static/lib/py.js/README.rst
new file mode 100644
index 00000000..5380f3e6
--- /dev/null
+++ b/addons/web/static/lib/py.js/README.rst
@@ -0,0 +1,196 @@
+What
+====
+
+
+
+Syntax
+------
+
+* Lambdas and ternaries should be parsed but are not implemented (in
+ the evaluator)
+* Only floats are implemented, ``int`` literals are parsed as floats.
+* Octal and hexadecimal literals are not implemented
+* Srings are backed by JavaScript strings and probably behave like
+ ``unicode`` more than like ``str``
+* Slices don't work
+
+Builtins
+--------
+
+``py.js`` currently implements the following builtins:
+
+``type``
+ Restricted to creating new types, can't be used to get an object's
+ type (yet)
+
+``None``
+
+``True``
+
+``False``
+
+``NotImplemented``
+ Returned from rich comparison methods when the comparison is not
+ implemented for this combination of operands. In ``py.js``, this
+ is also the default implementation for all rich comparison methods.
+
+``issubclass``
+
+``object``
+
+``bool``
+ Does not inherit from ``int``, since ``int`` is not currently
+ implemented.
+
+``float``
+
+``str``
+
+``tuple``
+ Constructor/coercer is not implemented, only handles literals
+
+``list``
+ Same as tuple (``list`` is currently an alias for ``tuple``)
+
+``dict``
+ Implements trivial getting, setting and len, nothing beyond that.
+
+Note that most methods are probably missing from all of these.
+
+Data model protocols
+--------------------
+
+``py.js`` currently implements the following protocols (or
+sub-protocols) of the `Python 2.7 data model
+<>`_:
+
+Rich comparisons
+ Pretty much complete (including operator fallbacks), although the
+ behavior is currently undefined if an operation does not return
+ either a ``py.bool`` or ``NotImplemented``.
+
+ ``__hash__`` is supported (and used), but it should return **a
+ javascript string**. ``py.js``'s dict build on javascript objects,
+ reimplementing numeral hashing is worthless complexity at this
+ point.
+
+Boolean conversion
+ Implementing ``__nonzero__`` should work.
+
+Customizing attribute access
+ Protocols for getting and setting attributes (including new-style
+ extension) fully implemented but for ``__delattr__`` (since
+ ``del`` is a statement)
+
+Descriptor protocol
+ As with attributes, ``__delete__`` is not implemented.
+
+Callable objects
+ Work, although the handling of arguments isn't exactly nailed
+ down. For now, callables get two (javascript) arguments ``args``
+ and ``kwargs``, holding (respectively) positional and keyword
+ arguments.
+
+ Conflicts are *not* handled at this point.
+
+Collections Abstract Base Classes
+ Container is the only implemented ABC protocol (ABCs themselves
+ are not currently implemented) (well technically Callable and
+ Hashable are kind-of implemented as well)
+
+Numeric type emulation
+ Operators are implemented (but not tested), ``abs``, ``divmod``
+ and ``pow`` builtins are not implemented yet. Neither are ``oct``
+ and ``hex`` but I'm not sure we care (I'm not sure we care about
+ ``pow`` or even ``divmod`` either, for that matter)
+
+Utilities
+---------
+
+``py.js`` also provides (and exposes) a few utilities for "userland"
+implementation:
+
+``def``
+ Wraps a native javascript function into a ``py.js`` function, so
+ that it can be called from native expressions.
+
+ Does not ensure the return types are type-compatible with
+ ``py.js`` types.
+
+ When accessing instance methods, ``py.js`` automatically wraps
+ these in a variant of ``py.def``, to behave as Python's (bound)
+ methods.
+
+Why
+===
+
+Originally, to learn about Pratt parsers (which are very, very good at
+parsing expressions with lots of infix or mixfix symbols). The
+evaluator part came because "why not" and because I work on a product
+with the "feature" of transmitting Python expressions (over the wire)
+which the client is supposed to evaluate.
+
+How
+===
+
+At this point, only three steps exist in ``py.js``: tokenizing,
+parsing and evaluation. It is possible that a compilation step be
+added later (for performance reasons).
+
+To evaluate a Python expression, the caller merely needs to call
+`py.eval`_. `py.eval`_ takes a mandatory Python
+expression to evaluate (as a string) and an optional context, for the
+substitution of the free variables in the expression::
+
+ > py.eval("type in ('a', 'b', 'c') and foo", {type: 'c', foo: true});
+ true
+
+This is great for one-shot evaluation of expressions. If the
+expression will need to be repeatedly evaluated with the same
+parameters, the various parsing and evaluation steps can be performed
+separately: `py.eval`_ is really a shortcut for sequentially calling
+`py.tokenize`_, `py.parse`_ and `py.evaluate`_.
+
+API
+===
+
+.. _py.eval:
+
+``py.eval(expr[, context])``
+ "Do everything" function, to use for one-shot evaluation of a
+ Python expression: it will internally handle the tokenizing,
+ parsing and actual evaluation of the Python expression without
+ having to perform these separately.
+
+ ``expr``
+ Python expression to evaluate
+ ``context``
+ context dictionary holding the substitutions for the free
+ variables in the expression
+
+.. _py.tokenize:
+
+``py.tokenize(expr)``
+ ``expr``
+ Python expression to tokenize
+
+.. _py.parse:
+
+``py.parse(tokens)``
+ Parses a token stream and returns an abstract syntax tree of the
+ expression (if the token stream represents a valid Python
+ expression).
+
+ A parse tree is stateless and can be memoized and used multiple
+ times in separate evaluations.
+
+ ``tokens``
+ stream of tokens returned by `py.tokenize`_
+
+.. _py.evaluate:
+
+``py.evaluate(ast[, context])``
+ ``ast``
+ The output of `py.parse`_
+ ``context``
+ The evaluation context for the Python expression.
diff --git a/addons/web/static/lib/py.js/TODO.rst b/addons/web/static/lib/py.js/TODO.rst
new file mode 100644
index 00000000..dd3237e3
--- /dev/null
+++ b/addons/web/static/lib/py.js/TODO.rst
@@ -0,0 +1,46 @@
+* Parser
+ since parsing expressions, try with a pratt parser
+ http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
+ http://effbot.org/zone/simple-top-down-parsing.htm
+
+Evaluator
+---------
+
+* Builtins should be built-in, there should be no need to add e.g. ``py.bool`` to the evaluation context (?)
+* Stop busyworking trivial binary operator
+* Make it *trivial* to build Python type-wrappers
+* Implement Python's `data model protocols
+ <http://docs.python.org/reference/datamodel.html#basic-customization>`_
+ for *all* supported operations, optimizations can come later
+* Automatically type-wrap everything (for now anyway)
+
+Base type requirements:
+***********************
+
+* int
+* float
+* --str-- unicode
+* bool
+* dict
+* tuple
+* list
+* ?module
+* ?object
+* datetime.time
+* datetime.timedelta
+* NotImplementedType
+
+Base methods requirement
+************************
+
+* ``__getattr__``
+* ``dict.get``
+
+In datamodel, not implemented in any type, untested
+***************************************************
+
+* a[b]
+
+* a + b, a - b, a * b, ...
+
+* +a, ~a
diff --git a/addons/web/static/lib/py.js/doc/Makefile b/addons/web/static/lib/py.js/doc/Makefile
new file mode 100644
index 00000000..cb7d658e
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyjs.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyjs.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/pyjs"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyjs"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/addons/web/static/lib/py.js/doc/builtins.rst b/addons/web/static/lib/py.js/doc/builtins.rst
new file mode 100644
index 00000000..6958d83b
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/builtins.rst
@@ -0,0 +1,55 @@
+.. default-domain: python
+
+.. _builtins:
+
+Supported Python builtins
+=========================
+
+.. function:: py.type(object)
+
+ Gets the class of a provided object, if possible.
+
+ .. note:: currently doesn't work correctly when called on a class
+ object, will return the class itself (also, classes
+ don't currently have a type).
+
+.. js:function:: py.type(name, bases, dict)
+
+ Not exactly a builtin as this form is solely javascript-level
+ (currently). Used to create new ``py.js`` types. See :doc:`types`
+ for its usage.
+
+.. data:: py.None
+
+.. data:: py.True
+
+.. data:: py.False
+
+.. data:: py.NotImplemented
+
+.. class:: py.object
+
+ Base class for all types, even implicitly (if no bases are
+ provided to :js:func:`py.type`)
+
+.. class:: py.bool([object])
+
+.. class:: py.float([object])
+
+.. class:: py.str([object])
+
+.. class:: py.unicode([object])
+
+.. class:: py.tuple()
+
+.. class:: py.list()
+
+.. class:: py.dict()
+
+.. function:: py.len(object)
+
+.. function:: py.isinstance(object, type)
+
+.. function:: py.issubclass(type, other_type)
+
+.. class:: py.classmethod
diff --git a/addons/web/static/lib/py.js/doc/conf.py b/addons/web/static/lib/py.js/doc/conf.py
new file mode 100644
index 00000000..287f21af
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/conf.py
@@ -0,0 +1,247 @@
+# -*- coding: utf-8 -*-
+#
+# py.js documentation build configuration file, created by
+# sphinx-quickstart on Sun Sep 9 19:36:23 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.todo']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'py.js'
+copyright = u'2012, Xavier Morel'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.6'
+# The full version, including alpha/beta/rc tags.
+release = '0.6'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# Default sphinx domain
+default_domain = 'js'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+# default code-block highlighting
+highlight_language = 'javascript'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'pyjsdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'pyjs.tex', u'py.js Documentation',
+ u'Xavier Morel', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'pyjs', u'py.js Documentation',
+ [u'Xavier Morel'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'pyjs', u'py.js Documentation',
+ u'Xavier Morel', 'pyjs', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
diff --git a/addons/web/static/lib/py.js/doc/differences.rst b/addons/web/static/lib/py.js/doc/differences.rst
new file mode 100644
index 00000000..d81acd24
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/differences.rst
@@ -0,0 +1,64 @@
+Differences with Python
+=======================
+
+* ``py.js`` completely ignores old-style classes as well as their
+ lookup details. All ``py.js`` types should be considered matching
+ the behavior of new-style classes
+
+* New types can only have a single base. This is due to ``py.js``
+ implementing its types on top of Javascript's, and javascript being
+ a single-inheritance language.
+
+ This may change if ``py.js`` ever reimplements its object model from
+ scratch.
+
+* Piggybacking on javascript's object model also means metaclasses are
+ not available (:js:func:`py.type` is a function)
+
+* A python-level function (created through :js:class:`py.PY_def`) set
+ on a new type will not become a method, it'll remain a function.
+
+* :js:func:`py.PY_parseArgs` supports keyword-only arguments (though
+ it's a Python 3 feature)
+
+* Because the underlying type is a javascript ``String``, there
+ currently is no difference between :js:class:`py.str` and
+ :js:class:`py.unicode`. As a result, there also is no difference
+ between :js:func:`__str__` and :js:func:`__unicode__`.
+
+Unsupported features
+--------------------
+
+These are Python features which are not supported at all in ``py.js``,
+usually because they don't make sense or there is no way to support them
+
+* The ``__delattr__``, ``__delete__`` and ``__delitem__``: as
+ ``py.js`` only handles expressions and these are accessed via the
+ ``del`` statement, there would be no way to call them.
+
+* ``__del__`` the lack of cross-platform GC hook means there is no way
+ to know when an object is deallocated.
+
+* ``__slots__`` are not handled
+
+* Dedicated (and deprecated) slicing special methods are unsupported
+
+Missing features
+----------------
+
+These are Python features which are missing because they haven't been
+implemented yet:
+
+* Class-binding of descriptors doesn't currently work.
+
+* Instance and subclass checks can't be customized
+
+* "poor" comparison methods (``__cmp__`` and ``__rcmp__``) are not
+ supported and won't be falled-back to.
+
+* ``__coerce__`` is currently supported
+
+* Context managers are not currently supported
+
+* Unbound methods are not supported, instance methods can only be
+ accessed from instances.
diff --git a/addons/web/static/lib/py.js/doc/index.rst b/addons/web/static/lib/py.js/doc/index.rst
new file mode 100644
index 00000000..1b916752
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/index.rst
@@ -0,0 +1,161 @@
+.. py.js documentation master file, created by
+ sphinx-quickstart on Sun Sep 9 19:36:23 2012.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+py.js, a Python expressions parser and evaluator
+================================================
+
+``py.js`` is a parser and evaluator of Python expressions, written in
+pure javascript.
+
+``py.js`` is not intended to implement a full Python interpreter, its
+specification document is the `Python 2.7 Expressions spec
+<http://docs.python.org/reference/expressions.html>`_ (along with the
+lexical analysis part) as well as the Python builtins.
+
+
+.. toctree::
+ :maxdepth: 2
+
+ builtins
+ types
+ utility
+ differences
+
+Usage
+-----
+
+To evaluate a Python expression, simply call
+:func:`py.eval`. :func:`py.eval` takes a mandatory Python expression
+parameter, as a string, and an optional evaluation context (namespace
+for the expression's free variables), and returns a javascript value::
+
+ > py.eval("t in ('a', 'b', 'c') and foo", {t: 'c', foo: true});
+ true
+
+If the expression needs to be repeatedly evaluated, or the result of
+the expression is needed in its "python" form without being converted
+back to javascript, you can use the underlying triplet of functions
+:func:`py.tokenize`, :func:`py.parse` and :func:`py.evaluate`
+directly.
+
+API
+---
+
+Core functions
+++++++++++++++
+
+.. function:: py.eval(expr[, context])
+
+ "Do everything" function, to use for one-shot evaluation of Python
+ expressions. Chains tokenizing, parsing and evaluating the
+ expression then :ref:`converts the result back to javascript
+ <convert-js>`
+
+ :param expr: Python expression to evaluate
+ :type expr: String
+ :param context: evaluation context for the expression's free
+ variables
+ :type context: Object
+ :returns: the expression's result, converted back to javascript
+
+.. function:: py.tokenize(expr)
+
+ Expression tokenizer
+
+ :param expr: Python expression to tokenize
+ :type expr: String
+ :returns: token stream
+
+.. function:: py.parse(tokens)
+
+ Parses a token stream and returns the corresponding parse tree.
+
+ The parse tree is stateless and can be memoized and reused for
+ frequently evaluated expressions.
+
+ :param tokens: token stream from :func:`py.tokenize`
+ :returns: parse tree
+
+.. function:: py.evaluate(tree[, context])
+
+ Evaluates the expression represented by the provided parse tree,
+ using the provided context for the exprssion's free variables.
+
+ :param tree: parse tree returned by :func:`py.parse`
+ :param context: evaluation context
+ :returns: the "python object" resulting from the expression's
+ evaluation
+ :rtype: :class:`py.object`
+
+.. _convert-py:
+
+Conversions from Javascript to Python
++++++++++++++++++++++++++++++++++++++
+
+``py.js`` will automatically attempt to convert non-:class:`py.object`
+values into their ``py.js`` equivalent in the following situations:
+
+* Values passed through the context of :func:`py.eval` or
+ :func:`py.evaluate`
+
+* Attributes accessed directly on objects
+
+* Values of mappings passed to :class:`py.dict`
+
+Notably, ``py.js`` will *not* attempt an automatic conversion of
+values returned by functions or methods, these must be
+:class:`py.object` instances.
+
+The automatic conversions performed by ``py.js`` are the following:
+
+* ``null`` is converted to :data:`py.None`
+
+* ``true`` is converted to :data:`py.True`
+
+* ``false`` is converted to :data:`py.False`
+
+* numbers are converted to :class:`py.float`
+
+* strings are converted to :class:`py.str`
+
+* functions are wrapped into :class:`py.PY_dev`
+
+* ``Array`` instances are converted to :class:`py.list`
+
+The rest generates an error, except for ``undefined`` which
+specifically generates a ``NameError``.
+
+.. _convert-js:
+
+Conversions from Python to Javascript
++++++++++++++++++++++++++++++++++++++
+
+py.js types (extensions of :js:class:`py.object`) can be converted
+back to javascript by calling their :js:func:`py.object.toJSON`
+method.
+
+The default implementation raises an error, as arbitrary objects can
+not be converted back to javascript.
+
+Most built-in objects provide a :js:func:`py.object.toJSON`
+implementation out of the box.
+
+Javascript-level exceptions
++++++++++++++++++++++++++++
+
+Javascript allows throwing arbitrary things, but runtimes don't seem
+to provide any useful information (when they ever do) if what is
+thrown isn't a direct instance of ``Error``. As a result, while
+``py.js`` tries to match the exception-throwing semantics of Python it
+only ever throws bare ``Error`` at the javascript-level. Instead, it
+prefixes the error message with the name of the Python expression, a
+colon, a space, and the actual message.
+
+For instance, where Python would throw ``KeyError("'foo'")`` when
+accessing an invalid key on a ``dict``, ``py.js`` will throw
+``Error("KeyError: 'foo'")``.
+
+.. _Python Data Model: http://docs.python.org/reference/datamodel.html
+
diff --git a/addons/web/static/lib/py.js/doc/make.bat b/addons/web/static/lib/py.js/doc/make.bat
new file mode 100644
index 00000000..c3db032e
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/make.bat
@@ -0,0 +1,190 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^<target^>` where ^<target^> is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyjs.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyjs.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
diff --git a/addons/web/static/lib/py.js/doc/types.rst b/addons/web/static/lib/py.js/doc/types.rst
new file mode 100644
index 00000000..61d950c6
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/types.rst
@@ -0,0 +1,248 @@
+Implementing a custom type
+==========================
+
+To implement a custom python-level type, one can use the
+:func:`py.type` builtin. At the JS-level, it is a function with the
+same signature as the :py:class:`type` builtin [#bases]_. It returns a
+child type of its one base (or :py:class:`py.object` if no base is
+provided).
+
+The ``dict`` parameter to :func:`py.type` can contain any
+attribute, javascript-level or python-level: the default
+``__getattribute__`` implementation will ensure they are converted to
+Python-level attributes if needed. Most methods are also wrapped and
+converted to :ref:`types-methods-python`, although there are a number
+of special cases:
+
+* Most "magic methods" of the data model ("dunder" methods) remain
+ javascript-level. See :ref:`the listing of magic methods and their
+ signatures <types-methods-dunder>`. As a result, they do not respect
+ the :ref:`types-methods-python-call`
+
+* The ``toJSON`` and ``fromJSON`` methods are special-cased to remain
+ javascript-level and don't follow the
+ :ref:`types-methods-python-call`
+
+* Functions which have been wrapped explicitly (via
+ :class:`py.PY_def`, :py:class:`py.classmethod` or
+ :py:class:`py.staticmethod`) are associated to the class
+ untouched. But due to their wrapper, they will use the
+ :ref:`types-methods-python-call` anyway
+
+.. _types-methods-python:
+
+Python-level callable
+---------------------
+
+Wrapped javascript function *or* the :func:`__call__` method itself
+follow the :ref:`types-methods-python-call`. As a result, they can't
+(easily) be called directly from javascript code. Because
+:func:`__new__` and :func:`__init__` follow from :func:`__call__`,
+they also follow the :ref:`types-methods-python-call`.
+
+:func:`py.PY_call` should be used when interacting with them from
+javascript is necessary.
+
+Because ``__call__`` follows the :ref:`types-methods-python-call`,
+instantiating a ``py.js`` type from javascript requires using
+:func:`py.PY_call`.
+
+.. _types-methods-python-call:
+
+Python calling conventions
+++++++++++++++++++++++++++
+
+The python-level arguments should be considered completely opaque,
+they should be interacted with through :func:`py.PY_parseArgs` (to
+extract python-level arguments to javascript implementation code) and
+:func:`py.PY_call` (to call :ref:`types-methods-python` from
+javascript code).
+
+A callable following the :ref:`types-methods-python-call` *must*
+return a ``py.js`` object, an error will be generated when failing to
+do so.
+
+.. todo:: arguments forwarding when e.g. overriding methods?
+
+.. _types-methods-dunder:
+
+Magic methods
+-------------
+
+``py.js`` doesn't support calling magic ("dunder") methods of the
+datamodel from Python code, and these methods remain javascript-level
+(they don't follow the :ref:`types-methods-python-call`).
+
+Here is a list of the understood datamodel methods, refer to `the
+relevant Python documentation
+<http://docs.python.org/reference/datamodel.html?highlight=data%20model#special-method-names>`_
+for their roles.
+
+Basic customization
++++++++++++++++++++
+
+.. function:: __hash__()
+
+ :returns: String
+
+.. function:: __eq__(other)
+
+ The default implementation tests for identity
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+.. function:: __ne__(other)
+
+ The default implementation calls :func:`__eq__` and reverses
+ its result.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+.. function:: __lt__(other)
+
+ The default implementation simply returns
+ :data:`py.NotImplemented`.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+
+.. function:: __le__(other)
+
+ The default implementation simply returns
+ :data:`py.NotImplemented`.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+
+.. function:: __ge__(other)
+
+ The default implementation simply returns
+ :data:`py.NotImplemented`.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+
+.. function:: __gt__(other)
+
+ The default implementation simply returns
+ :data:`py.NotImplemented`.
+
+ :param other: :py:class:`py.object` to compare this object with
+ :returns: :py:class:`py.bool`
+
+.. function:: __str__()
+
+ Simply calls :func:`__unicode__`. This method should not be
+ overridden, :func:`__unicode__` should be overridden instead.
+
+ :returns: :py:class:`py.str`
+
+.. function:: __unicode__()
+
+ :returns: :py:class:`py.unicode`
+
+.. function:: __nonzero__()
+
+ The default implementation always returns :data:`py.True`
+
+ :returns: :py:class:`py.bool`
+
+Customizing attribute access
+++++++++++++++++++++++++++++
+
+.. function:: __getattribute__(name)
+
+ :param String name: name of the attribute, as a javascript string
+ :returns: :py:class:`py.object`
+
+.. function:: __getattr__(name)
+
+ :param String name: name of the attribute, as a javascript string
+ :returns: :py:class:`py.object`
+
+.. function:: __setattr__(name, value)
+
+ :param String name: name of the attribute, as a javascript string
+ :param value: :py:class:`py.object`
+
+Implementing descriptors
+++++++++++++++++++++++++
+
+.. function:: __get__(instance)
+
+ .. note:: readable descriptors don't currently handle "owner
+ classes"
+
+ :param instance: :py:class:`py.object`
+ :returns: :py:class:`py.object`
+
+.. function:: __set__(instance, value)
+
+ :param instance: :py:class:`py.object`
+ :param value: :py:class:`py.object`
+
+Emulating Numeric Types
++++++++++++++++++++++++
+
+* Non-in-place binary numeric methods (e.g. ``__add__``, ``__mul__``,
+ ...) should all be supported including reversed calls (in case the
+ primary call is not available or returns
+ :py:data:`py.NotImplemented`). They take a single
+ :py:class:`py.object` parameter and return a single
+ :py:class:`py.object` parameter.
+
+* Unary operator numeric methods are all supported:
+
+ .. function:: __pos__()
+
+ :returns: :py:class:`py.object`
+
+ .. function:: __neg__()
+
+ :returns: :py:class:`py.object`
+
+ .. function:: __invert__()
+
+ :returns: :py:class:`py.object`
+
+* For non-operator numeric methods, support is contingent on the
+ corresponding :ref:`builtins <builtins>` being implemented
+
+Emulating container types
++++++++++++++++++++++++++
+
+.. function:: __len__()
+
+ :returns: :py:class:`py.int`
+
+.. function:: __getitem__(name)
+
+ :param name: :py:class:`py.object`
+ :returns: :py:class:`py.object`
+
+.. function:: __setitem__(name, value)
+
+ :param name: :py:class:`py.object`
+ :param value: :py:class:`py.object`
+
+.. function:: __iter__()
+
+ :returns: :py:class:`py.object`
+
+.. function:: __reversed__()
+
+ :returns: :py:class:`py.object`
+
+.. function:: __contains__(other)
+
+ :param other: :py:class:`py.object`
+ :returns: :py:class:`py.bool`
+
+.. [#bases] with the limitation that, because :ref:`py.js builds its
+ object model on top of javascript's
+ <details-object-model>`, only one base is allowed.
diff --git a/addons/web/static/lib/py.js/doc/utility.rst b/addons/web/static/lib/py.js/doc/utility.rst
new file mode 100644
index 00000000..8bb91648
--- /dev/null
+++ b/addons/web/static/lib/py.js/doc/utility.rst
@@ -0,0 +1,248 @@
+Utility functions for interacting with ``py.js`` objects
+========================================================
+
+Essentially the ``py.js`` version of the Python C API, these functions
+are used to implement new ``py.js`` types or to interact with existing
+ones.
+
+They are prefixed with ``PY_``.
+
+.. function:: py.PY_parseArgs(arguments, format)
+
+ Arguments parser converting from the :ref:`user-defined calling
+ conventions <types-methods-python-call>` to a JS object mapping
+ argument names to values. It serves the same role as
+ `PyArg_ParseTupleAndKeywords`_.
+
+ ::
+
+ var args = py.PY_parseArgs(
+ arguments, ['foo', 'bar', ['baz', 3], ['qux', "foo"]]);
+
+ roughly corresponds to the argument spec:
+
+ .. code-block:: python
+
+ def func(foo, bar, baz=3, qux="foo"):
+ pass
+
+ .. note:: a significant difference is that "default values" will
+ be re-evaluated at each call, since they are within the
+ function.
+
+ :param arguments: array-like objects holding the args and kwargs
+ passed to the callable, generally the
+ ``arguments`` of the caller.
+
+ :param format: mapping declaration to the actual arguments of the
+ function. A javascript array composed of five
+ possible types of elements:
+
+ * The literal string ``'*'`` marks all following
+ parameters as keyword-only, regardless of them
+ having a default value or not [#kwonly]_. Can
+ only be present once in the parameters list.
+
+ * A string prefixed by ``*``, marks the positional
+ variadic parameter for the function: gathers all
+ provided positional arguments left and makes all
+ following parameters keyword-only
+ [#star-args]_. ``*args`` is incompatible with
+ ``*``.
+
+ * A string prefixed with ``**``, marks the
+ positional keyword variadic parameter for the
+ function: gathers all provided keyword arguments
+ left and closes the argslist. If present, this
+ must be the last parameter of the format list.
+
+ * A string defines a required parameter, accessible
+ positionally or through keyword
+
+ * A pair of ``[String, py.object]`` defines an
+ optional parameter and its default value.
+
+ For simplicity, when not using optional parameters
+ it is possible to use a simple string as the format
+ (using space-separated elements). The string will
+ be split on whitespace and processed as a normal
+ format array.
+
+ :returns: a javascript object mapping argument names to values
+
+ :raises: ``TypeError`` if the provided arguments don't match the
+ format
+
+.. class:: py.PY_def(fn)
+
+ Type wrapping javascript functions into py.js callables. The
+ wrapped function follows :ref:`the py.js calling conventions
+ <types-methods-python-call>`
+
+ :param Function fn: the javascript function to wrap
+ :returns: a callable py.js object
+
+Object Protocol
+---------------
+
+.. function:: py.PY_hasAttr(o, attr_name)
+
+ Returns ``true`` if ``o`` has the attribute ``attr_name``,
+ otherwise returns ``false``. Equivalent to Python's ``hasattr(o,
+ attr_name)``
+
+ :param o: A :class:`py.object`
+ :param attr_name: a javascript ``String``
+ :rtype: ``Boolean``
+
+.. function:: py.PY_getAttr(o, attr_name)
+
+ Retrieve an attribute ``attr_name`` from the object ``o``. Returns
+ the attribute value on success, raises ``AttributeError`` on
+ failure. Equivalent to the python expression ``o.attr_name``.
+
+ :param o: A :class:`py.object`
+ :param attr_name: a javascript ``String``
+ :returns: A :class:`py.object`
+ :raises: ``AttributeError``
+
+.. function:: py.PY_str(o)
+
+ Computes a string representation of ``o``, returns the string
+ representation. Equivalent to ``str(o)``
+
+ :param o: A :class:`py.object`
+ :returns: :class:`py.str`
+
+.. function:: py.PY_isInstance(inst, cls)
+
+ Returns ``true`` if ``inst`` is an instance of ``cls``, ``false``
+ otherwise.
+
+.. function:: py.PY_isSubclass(derived, cls)
+
+ Returns ``true`` if ``derived`` is ``cls`` or a subclass thereof.
+
+.. function:: py.PY_call(callable[, args][, kwargs])
+
+ Call an arbitrary python-level callable from javascript.
+
+ :param callable: A ``py.js`` callable object (broadly speaking,
+ either a class or an object with a ``__call__``
+ method)
+
+ :param args: javascript Array of :class:`py.object`, used as
+ positional arguments to ``callable``
+
+ :param kwargs: javascript Object mapping names to
+ :class:`py.object`, used as named arguments to
+ ``callable``
+
+ :returns: nothing or :class:`py.object`
+
+.. function:: py.PY_isTrue(o)
+
+ Returns ``true`` if the object is considered truthy, ``false``
+ otherwise. Equivalent to ``bool(o)``.
+
+ :param o: A :class:`py.object`
+ :rtype: Boolean
+
+.. function:: py.PY_not(o)
+
+ Inverse of :func:`py.PY_isTrue`.
+
+.. function:: py.PY_size(o)
+
+ If ``o`` is a sequence or mapping, returns its length. Otherwise,
+ raises ``TypeError``.
+
+ :param o: A :class:`py.object`
+ :returns: ``Number``
+ :raises: ``TypeError`` if the object doesn't have a length
+
+.. function:: py.PY_getItem(o, key)
+
+ Returns the element of ``o`` corresponding to the object
+ ``key``. This is equivalent to ``o[key]``.
+
+ :param o: :class:`py.object`
+ :param key: :class:`py.object`
+ :returns: :class:`py.object`
+ :raises: ``TypeError`` if ``o`` does not support the operation, if
+ ``key`` or the return value is not a :class:`py.object`
+
+.. function:: py.PY_setItem(o, key, v)
+
+ Maps the object ``key`` to the value ``v`` in ``o``. Equivalent to
+ ``o[key] = v``.
+
+ :param o: :class:`py.object`
+ :param key: :class:`py.object`
+ :param v: :class:`py.object`
+ :raises: ``TypeError`` if ``o`` does not support the operation, or
+ if ``key`` or ``v`` are not :class:`py.object`
+
+Number Protocol
+---------------
+
+.. function:: py.PY_add(o1, o2)
+
+ Returns the result of adding ``o1`` and ``o2``, equivalent to
+ ``o1 + o2``.
+
+ :param o1: :class:`py.object`
+ :param o2: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_subtract(o1, o2)
+
+ Returns the result of subtracting ``o2`` from ``o1``, equivalent
+ to ``o1 - o2``.
+
+ :param o1: :class:`py.object`
+ :param o2: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_multiply(o1, o2)
+
+ Returns the result of multiplying ``o1`` by ``o2``, equivalent to
+ ``o1 * o2``.
+
+ :param o1: :class:`py.object`
+ :param o2: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_divide(o1, o2)
+
+ Returns the result of dividing ``o1`` by ``o2``, equivalent to
+ ``o1 / o2``.
+
+ :param o1: :class:`py.object`
+ :param o2: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_negative(o)
+
+ Returns the negation of ``o``, equivalent to ``-o``.
+
+ :param o: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. function:: py.PY_positive(o)
+
+ Returns the "positive" of ``o``, equivalent to ``+o``.
+
+ :param o: :class:`py.object`
+ :returns: :class:`py.object`
+
+.. [#kwonly] Python 2, which py.js currently implements, does not
+ support Python-level keyword-only parameters (it can be
+ done through the C-API), but it seemed neat and easy
+ enough so there.
+
+.. [#star-args] due to this and contrary to Python 2, py.js allows
+ arguments other than ``**kwargs`` to follow ``*args``.
+
+.. _PyArg_ParseTupleAndKeywords:
+ http://docs.python.org/c-api/arg.html#PyArg_ParseTupleAndKeywords
diff --git a/addons/web/static/lib/py.js/lib/py.js b/addons/web/static/lib/py.js/lib/py.js
new file mode 100644
index 00000000..7ba81079
--- /dev/null
+++ b/addons/web/static/lib/py.js/lib/py.js
@@ -0,0 +1,1477 @@
+var py = {};
+(function (py) {
+ var create = function (o, props) {
+ function F() {}
+ F.prototype = o;
+ var inst = new F;
+ if (props) {
+ for(var name in props) {
+ if(!props.hasOwnProperty(name)) { continue; }
+ inst[name] = props[name];
+ }
+ }
+ return inst;
+ };
+
+ var symbols = {};
+ var comparators = {};
+ var Base = {
+ nud: function () { throw new Error(this.id + " undefined as prefix"); },
+ led: function (led) { throw new Error(this.id + " undefined as infix"); },
+ toString: function () {
+ if (this.id === '(constant)' || this.id === '(number)' || this.id === '(name)' || this.id === '(string)') {
+ return [this.id.slice(0, this.id.length-1), ' ', this.value, ')'].join('');
+ } else if (this.id === '(end)') {
+ return '(end)';
+ } else if (this.id === '(comparator)' ) {
+ var repr = ['(comparator', this.expressions[0]];
+ for (var i=0;i<this.operators.length; ++i) {
+ repr.push(this.operators[i], this.expressions[i+1]);
+ }
+ return repr.join(' ') + ')';
+ }
+ var out = [this.id, this.first, this.second, this.third]
+ .filter(function (r){return r}).join(' ');
+ return '(' + out + ')';
+ }
+ };
+ function symbol(id, bp) {
+ bp = bp || 0;
+ var s = symbols[id];
+ if (s) {
+ if (bp > s.lbp) {
+ s.lbp = bp;
+ }
+ return s;
+ }
+ return symbols[id] = create(Base, {
+ id: id,
+ lbp: bp
+ });
+ }
+ function constant(id) {
+ var s = symbol(id);
+ s.id = '(constant)';
+ s.value = id;
+ s.nud = function () {
+ return this;
+ };
+ }
+ function prefix(id, bp, nud) {
+ symbol(id).nud = nud || function () {
+ this.first = expression(bp);
+ return this
+ }
+ }
+ function infix(id, bp, led) {
+ symbol(id, bp).led = led || function (left) {
+ this.first = left;
+ this.second = expression(bp);
+ return this;
+ }
+ }
+ function infixr(id, bp) {
+ symbol(id, bp).led = function (left) {
+ this.first = left;
+ this.second = expression(bp - 1);
+ return this;
+ }
+ }
+ function comparator(id) {
+ comparators[id] = true;
+ var bp = 60;
+ infix(id, bp, function (left) {
+ this.id = '(comparator)';
+ this.operators = [id];
+ this.expressions = [left, expression(bp)];
+ while (token.id in comparators) {
+ this.operators.push(token.id);
+ advance();
+ this.expressions.push(
+ expression(bp));
+ }
+ return this;
+ });
+ }
+
+ constant('None'); constant('False'); constant('True');
+
+ symbol('(number)').nud = function () { return this; };
+ symbol('(name)').nud = function () { return this; };
+ symbol('(string)').nud = function () { return this; };
+ symbol('(end)');
+
+ symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(',');
+ symbol('else');
+
+ infix('=', 10, function (left) {
+ if (left.id !== '(name)') {
+ throw new Error("Expected keyword argument name, got " + token.id);
+ }
+ this.first = left;
+ this.second = expression();
+ return this;
+ });
+
+ symbol('lambda', 20).nud = function () {
+ this.first = [];
+ if (token.id !== ':') {
+ for(;;) {
+ if (token.id !== '(name)') {
+ throw new Error('Excepted an argument name');
+ }
+ this.first.push(token);
+ advance();
+ if (token.id !== ',') {
+ break;
+ }
+ advance(',');
+ }
+ }
+ advance(':');
+ this.second = expression();
+ return this;
+ };
+ infix('if', 20, function (left) {
+ this.ifTrue = left;
+ this.condition = expression();
+ advance('else');
+ this.ifFalse = expression();
+ return this;
+ });
+
+ infixr('or', 30); infixr('and', 40); prefix('not', 50);
+
+ comparator('in'); comparator('not in');
+ comparator('is'); comparator('is not');
+ comparator('<'); comparator('<=');
+ comparator('>'); comparator('>=');
+ comparator('<>'); comparator('!='); comparator('==');
+
+ infix('|', 70); infix('^', 80); infix('&', 90);
+
+ infix('<<', 100); infix('>>', 100);
+
+ infix('+', 110); infix('-', 110);
+
+ infix('*', 120); infix('/', 120);
+ infix('//', 120); infix('%', 120);
+
+ prefix('-', 130); prefix('+', 130); prefix('~', 130);
+
+ infixr('**', 140);
+
+ infix('.', 150, function (left) {
+ if (token.id !== '(name)') {
+ throw new Error('Expected attribute name, got ' + token.id);
+ }
+ this.first = left;
+ this.second = token;
+ advance();
+ return this;
+ });
+ symbol('(', 150).nud = function () {
+ this.first = [];
+ var comma = false;
+ if (token.id !== ')') {
+ while (true) {
+ if (token.id === ')') {
+ break;
+ }
+ this.first.push(expression());
+ if (token.id !== ',') {
+ break;
+ }
+ comma = true;
+ advance(',');
+ }
+ }
+ advance(')');
+ if (!this.first.length || comma) {
+ return this;
+ } else {
+ return this.first[0];
+ }
+ };
+ symbol('(').led = function (left) {
+ this.first = left;
+ this.second = [];
+ if (token.id !== ")") {
+ for(;;) {
+ this.second.push(expression());
+ if (token.id !== ',') {
+ break;
+ }
+ advance(',');
+ }
+ }
+ advance(")");
+ return this;
+
+ };
+ infix('[', 150, function (left) {
+ this.first = left;
+ this.second = expression();
+ advance("]");
+ return this;
+ });
+ symbol('[').nud = function () {
+ this.first = [];
+ if (token.id !== ']') {
+ for (;;) {
+ if (token.id === ']') {
+ break;
+ }
+ this.first.push(expression());
+ if (token.id !== ',') {
+ break;
+ }
+ advance(',');
+ }
+ }
+ advance(']');
+ return this;
+ };
+
+ symbol('{').nud = function () {
+ this.first = [];
+ if (token.id !== '}') {
+ for(;;) {
+ if (token.id === '}') {
+ break;
+ }
+ var key = expression();
+ advance(':');
+ var value = expression();
+ this.first.push([key, value]);
+ if (token.id !== ',') {
+ break;
+ }
+ advance(',');
+ }
+ }
+ advance('}');
+ return this;
+ };
+
+ py.tokenize = (function () {
+ function group() { return '(' + Array.prototype.join.call(arguments, '|') + ')'; }
+
+ var Whitespace = '[ \\f\\t]*';
+
+ var Name = '[a-zA-Z_]\\w*';
+
+ var DecNumber = '\\d+(L|l)?';
+ var IntNumber = DecNumber;
+ var PointFloat = group('\\d+\\.\\d*', '\\.\\d+');
+ var FloatNumber = PointFloat;
+ var Number = group(FloatNumber, IntNumber);
+
+ var Operator = group("\\*\\*=?", ">>=?", "<<=?", "<>", "!=",
+ "//=?", "[+\\-*/%&|^=<>]=?", "~");
+ var Bracket = '[\\[\\]\\(\\)\\{\\}]';
+ var Special = '[:;.,`@]';
+ var Funny = group(Operator, Bracket, Special);
+
+ var ContStr = group("([uU])?'([^\n'\\\\]*(?:\\\\.[^\n'\\\\]*)*)'", '([uU])?"([^\n"\\\\]*(?:\\\\.[^\n"\\\\]*)*)"');
+ var PseudoToken = Whitespace + group(Number, Funny, ContStr, Name);
+
+ var number_pattern = new RegExp('^' + Number + '$');
+ var string_pattern = new RegExp('^' + ContStr + '$');
+ var name_pattern = new RegExp('^' + Name + '$');
+ var strip = new RegExp('^' + Whitespace);
+ return function tokenize(s) {
+ var max=s.length, tokens = [], start, end;
+ // /g flag makes repeated exec() have memory
+ var pseudoprog = new RegExp(PseudoToken, 'g');
+
+ while(pseudoprog.lastIndex < max) {
+ var pseudomatch = pseudoprog.exec(s);
+ if (!pseudomatch) {
+ // if match failed on trailing whitespace, end tokenizing
+ if (/^\s+$/.test(s.slice(end))) {
+ break;
+ }
+ throw new Error('Failed to tokenize <<' + s
+ + '>> at index ' + (end || 0)
+ + '; parsed so far: ' + tokens);
+ }
+
+ start = pseudomatch.index;
+ end = pseudoprog.lastIndex;
+ // strip leading space caught by Whitespace
+ var token = s.slice(start, end).replace(strip, '');
+
+ if (number_pattern.test(token)) {
+ tokens.push(create(symbols['(number)'], {
+ value: parseFloat(token)
+ }));
+ } else if (string_pattern.test(token)) {
+ var m = string_pattern.exec(token);
+ tokens.push(create(symbols['(string)'], {
+ value: PY_decode_string_literal(
+ m[3] !== undefined ? m[3] : m[5],
+ !!(m[2] || m[4])
+ )
+ }));
+ } else if (token in symbols) {
+ var symbol;
+ // transform 'not in' and 'is not' in a single token
+ if (token === 'in' && tokens.length > 1 && tokens[tokens.length-1].id === 'not') {
+ symbol = symbols['not in'];
+ tokens.pop();
+ } else if (token === 'not' && tokens.length > 1 && tokens[tokens.length-1].id === 'is') {
+ symbol = symbols['is not'];
+ tokens.pop();
+ } else {
+ symbol = symbols[token];
+ }
+ tokens.push(create(symbol));
+ } else if (name_pattern.test(token)) {
+ tokens.push(create(symbols['(name)'], {
+ value: token
+ }));
+ } else {
+ throw new Error("Tokenizing failure of <<" + s + ">> at index " + start
+ + " for token [[" + token + "]]"
+ + "; parsed so far: " + tokens);
+
+ }
+ }
+ tokens.push(create(symbols['(end)']));
+ return tokens;
+ }
+ })();
+
+ var token, next;
+ function expression(rbp) {
+ rbp = rbp || 0;
+ var t = token;
+ token = next();
+ var left = t.nud();
+ while (rbp < token.lbp) {
+ t = token;
+ token = next();
+ left = t.led(left);
+ }
+ return left;
+ }
+ function advance(id) {
+ if (id && token.id !== id) {
+ throw new Error(
+ 'Expected "' + id + '", got "' + token.id + '"');
+ }
+ token = next();
+ }
+
+ function PY_ensurepy(val, name) {
+ switch (val) {
+ case undefined:
+ throw new Error("NameError: name '" + name + "' is not defined");
+ case null:
+ return py.None;
+ case true:
+ return py.True;
+ case false:
+ return py.False;
+ }
+
+ var fn = function () {};
+ fn.prototype = py.object;
+ if (py.PY_isInstance(val, py.object)
+ || py.PY_isSubclass(val, py.object)) {
+ return val;
+ }
+
+ switch (typeof val) {
+ case 'number':
+ return py.float.fromJSON(val);
+ case 'string':
+ return py.str.fromJSON(val);
+ case 'function':
+ return py.PY_def.fromJSON(val);
+ }
+
+ switch(val.constructor) {
+ case Object:
+ // TODO: why py.object instead of py.dict?
+ var o = py.PY_call(py.object);
+ for (var prop in val) {
+ if (val.hasOwnProperty(prop)) {
+ o[prop] = val[prop];
+ }
+ }
+ return o;
+ case Array:
+ return py.list.fromJSON(val);
+ }
+
+ throw new Error("Could not convert " + val + " to a pyval");
+ }
+
+ var typename = function (obj) {
+ if (obj.__class__) { // py type
+ return obj.__class__.__name__;
+ } else if(typeof obj !== 'object') { // JS primitive
+ return typeof obj;
+ } else { // JS object
+ return obj.constructor.name;
+ }
+ };
+ // JSAPI, JS-level utility functions for implementing new py.js
+ // types
+ py.py = {};
+
+ py.PY_parseArgs = function PY_parseArgs(argument, format) {
+ var out = {};
+ var args = argument[0];
+ var kwargs = {};
+ for (var k in argument[1]) {
+ if (!argument[1].hasOwnProperty(k)) { continue; }
+ kwargs[k] = argument[1][k];
+ }
+ if (typeof format === 'string') {
+ format = format.split(/\s+/);
+ }
+ var name = function (spec) {
+ if (typeof spec === 'string') {
+ return spec;
+ } else if (spec instanceof Array && spec.length === 2) {
+ return spec[0];
+ }
+ throw new Error(
+ "TypeError: unknown format specification " +
+ JSON.stringify(spec));
+ };
+ var spec;
+ // TODO: ensure all format arg names are actual names?
+ for(var i=0; i<args.length; ++i) {
+ spec = format[i];
+ // spec list ended, or specs switching to keyword-only
+ if (!spec || spec === '*') {
+ throw new Error(
+ "TypeError: function takes exactly " + (i-1) +
+ " positional arguments (" + args.length +
+ " given")
+ } else if(/^\*\w/.test(spec)) {
+ // *args, final
+ out[name(spec.slice(1))] = args.slice(i);
+ break;
+ }
+
+ out[name(spec)] = args[i];
+ }
+ for(var j=i; j<format.length; ++j) {
+ spec = format[j];
+ var n = name(spec);
+
+ if (n in out) {
+ throw new Error(
+ "TypeError: function got multiple values " +
+ "for keyword argument '" + kwarg + "'");
+ }
+ if (/^\*\*\w/.test(n)) {
+ // **kwarg
+ out[n.slice(2)] = kwargs;
+ kwargs = {};
+ break;
+ }
+ if (n in kwargs) {
+ out[n] = kwargs[n];
+ // Remove from args map
+ delete kwargs[n];
+ }
+ }
+ // Ensure all keyword arguments were consumed
+ for (var key in kwargs) {
+ throw new Error(
+ "TypeError: function got an unexpected keyword argument '"
+ + key + "'");
+ }
+
+ // Fixup args count if there's a kwonly flag (or an *args)
+ var kwonly = 0;
+ for(var k = 0; k < format.length; ++k) {
+ if (/^\*/.test(format[k])) { kwonly = 1; break; }
+ }
+ // Check that all required arguments have been matched, add
+ // optional values
+ for(var k = 0; k < format.length; ++k) {
+ spec = format[k];
+ var n = name(spec);
+ // kwonly, va_arg or matched argument
+ if (/^\*/.test(n) || n in out) { continue; }
+ // Unmatched required argument
+ if (!(spec instanceof Array)) {
+ throw new Error(
+ "TypeError: function takes exactly " + (format.length - kwonly)
+ + " arguments");
+ }
+ // Set default value
+ out[n] = spec[1];
+ }
+
+ return out;
+ };
+
+ py.PY_hasAttr = function (o, attr_name) {
+ try {
+ py.PY_getAttr(o, attr_name);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ };
+ py.PY_getAttr = function (o, attr_name) {
+ return PY_ensurepy(o.__getattribute__(attr_name));
+ };
+ py.PY_str = function (o) {
+ var v = o.__str__();
+ if (py.PY_isInstance(v, py.str)) {
+ return v;
+ }
+ throw new Error(
+ 'TypeError: __str__ returned non-string (type '
+ + typename(v)
+ +')');
+ };
+ py.PY_isInstance = function (inst, cls) {
+ var fn = function () {};
+ fn.prototype = cls;
+ return inst instanceof fn;
+ };
+ py.PY_isSubclass = function (derived, cls) {
+ var fn = function () {};
+ fn.prototype = cls;
+ return derived === cls || derived instanceof fn;
+ };
+ py.PY_call = function (callable, args, kwargs) {
+ if (!args) {
+ args = [];
+ }
+ if (typeof args === 'object' && !(args instanceof Array)) {
+ kwargs = args;
+ args = [];
+ }
+ if (!kwargs) {
+ kwargs = {};
+ }
+ if (callable.__is_type) {
+ // class hack
+ var instance = callable.__new__.call(callable, args, kwargs);
+ var typ = function () {};
+ typ.prototype = callable;
+ if (instance instanceof typ) {
+ instance.__init__.call(instance, args, kwargs);
+ }
+ return instance
+ }
+ return callable.__call__(args, kwargs);
+ };
+ py.PY_isTrue = function (o) {
+ var res = o.__nonzero__();
+ if (res === py.True) {
+ return true;
+ }
+ if (res === py.False) {
+ return false;
+ }
+ throw new Error(
+ "TypeError: __nonzero__ should return bool, returned "
+ + typename(res));
+ };
+ py.PY_not = function (o) {
+ return !py.PY_isTrue(o);
+ };
+ py.PY_size = function (o) {
+ if (!o.__len__) {
+ throw new Error(
+ "TypeError: object of type '" +
+ typename(o) +
+ "' has no len()");
+ }
+ var v = o.__len__();
+ if (typeof v !== 'number') {
+ throw new Error("TypeError: a number is required");
+ }
+ return v;
+ };
+ py.PY_getItem = function (o, key) {
+ if (!('__getitem__' in o)) {
+ throw new Error(
+ "TypeError: '" + typename(o) +
+ "' object is unsubscriptable")
+ }
+ if (!py.PY_isInstance(key, py.object)) {
+ throw new Error(
+ "TypeError: '" + typename(key) +
+ "' is not a py.js object");
+ }
+ var res = o.__getitem__(key);
+ if (!py.PY_isInstance(key, py.object)) {
+ throw new Error(
+ "TypeError: __getitem__ must return a py.js object, got "
+ + typename(res));
+ }
+ return res;
+ };
+ py.PY_setItem = function (o, key, v) {
+ if (!('__setitem__' in o)) {
+ throw new Error(
+ "TypeError: '" + typename(o) +
+ "' object does not support item assignment");
+ }
+ if (!py.PY_isInstance(key, py.object)) {
+ throw new Error(
+ "TypeError: '" + typename(key) +
+ "' is not a py.js object");
+ }
+ if (!py.PY_isInstance(v, py.object)) {
+ throw new Error(
+ "TypeError: '" + typename(v) +
+ "' is not a py.js object");
+ }
+ o.__setitem__(key, v);
+ };
+
+ py.PY_add = function (o1, o2) {
+ return PY_op(o1, o2, '+');
+ };
+ py.PY_subtract = function (o1, o2) {
+ return PY_op(o1, o2, '-');
+ };
+ py.PY_multiply = function (o1, o2) {
+ return PY_op(o1, o2, '*');
+ };
+ py.PY_divide = function (o1, o2) {
+ return PY_op(o1, o2, '/');
+ };
+ py.PY_negative = function (o) {
+ if (!o.__neg__) {
+ throw new Error(
+ "TypeError: bad operand for unary -: '"
+ + typename(o)
+ + "'");
+ }
+ return o.__neg__();
+ };
+ py.PY_positive = function (o) {
+ if (!o.__pos__) {
+ throw new Error(
+ "TypeError: bad operand for unary +: '"
+ + typename(o)
+ + "'");
+ }
+ return o.__pos__();
+ };
+
+ // Builtins
+ py.type = function type(name, bases, dict) {
+ if (typeof name !== 'string') {
+ throw new Error("ValueError: a class name should be a string");
+ }
+ if (!bases || bases.length === 0) {
+ bases = [py.object];
+ } else if (bases.length > 1) {
+ throw new Error("ValueError: can't provide multiple bases for a "
+ + "new type");
+ }
+ var base = bases[0];
+ var ClassObj = create(base);
+ if (dict) {
+ for (var k in dict) {
+ if (!dict.hasOwnProperty(k)) { continue; }
+ ClassObj[k] = dict[k];
+ }
+ }
+ ClassObj.__class__ = ClassObj;
+ ClassObj.__name__ = name;
+ ClassObj.__bases__ = bases;
+ ClassObj.__is_type = true;
+
+ return ClassObj;
+ };
+ py.type.__call__ = function () {
+ var args = py.PY_parseArgs(arguments, ['object']);
+ return args.object.__class__;
+ };
+
+ var hash_counter = 0;
+ py.object = py.type('object', [{}], {
+ __new__: function () {
+ // If ``this`` isn't the class object, this is going to be
+ // beyond fucked up
+ var inst = create(this);
+ inst.__is_type = false;
+ return inst;
+ },
+ __init__: function () {},
+ // Basic customization
+ __hash__: function () {
+ if (this._hash) {
+ return this._hash;
+ }
+ // tagged counter, to avoid collisions with e.g. number hashes
+ return this._hash = '\0\0\0' + String(hash_counter++);
+ },
+ __eq__: function (other) {
+ return (this === other) ? py.True : py.False;
+ },
+ __ne__: function (other) {
+ if (py.PY_isTrue(this.__eq__(other))) {
+ return py.False;
+ } else {
+ return py.True;
+ }
+ },
+ __lt__: function () { return py.NotImplemented; },
+ __le__: function () { return py.NotImplemented; },
+ __ge__: function () { return py.NotImplemented; },
+ __gt__: function () { return py.NotImplemented; },
+ __str__: function () {
+ return this.__unicode__();
+ },
+ __unicode__: function () {
+ return py.str.fromJSON('<' + typename(this) + ' object>');
+ },
+ __nonzero__: function () {
+ return py.True;
+ },
+ // Attribute access
+ __getattribute__: function (name) {
+ if (name in this) {
+ var val = this[name];
+ if (typeof val === 'object' && '__get__' in val) {
+ // TODO: second argument should be class
+ return val.__get__(this, py.PY_call(py.type, [this]));
+ }
+ if (typeof val === 'function' && !this.hasOwnProperty(name)) {
+ // val is a method from the class
+ return PY_instancemethod.fromJSON(val, this);
+ }
+ return val;
+ }
+ if ('__getattr__' in this) {
+ return this.__getattr__(name);
+ }
+ throw new Error("AttributeError: object has no attribute '" + name +"'");
+ },
+ __setattr__: function (name, value) {
+ if (name in this && '__set__' in this[name]) {
+ this[name].__set__(this, value);
+ }
+ this[name] = value;
+ },
+ // no delattr, because no 'del' statement
+
+ // Conversion
+ toJSON: function () {
+ throw new Error(this.constructor.name + ' can not be converted to JSON');
+ }
+ });
+ var NoneType = py.type('NoneType', null, {
+ __nonzero__: function () { return py.False; },
+ toJSON: function () { return null; }
+ });
+ py.None = py.PY_call(NoneType);
+ var NotImplementedType = py.type('NotImplementedType', null, {});
+ py.NotImplemented = py.PY_call(NotImplementedType);
+ var booleans_initialized = false;
+ py.bool = py.type('bool', null, {
+ __new__: function () {
+ if (!booleans_initialized) {
+ return py.object.__new__.apply(this);
+ }
+
+ var ph = {};
+ var args = py.PY_parseArgs(arguments, [['value', ph]]);
+ if (args.value === ph) {
+ return py.False;
+ }
+ return py.PY_isTrue(args.value) ? py.True : py.False;
+ },
+ __str__: function () {
+ return py.str.fromJSON((this === py.True) ? "True" : "False");
+ },
+ __nonzero__: function () { return this; },
+ fromJSON: function (val) { return val ? py.True : py.False },
+ toJSON: function () { return this === py.True; }
+ });
+ py.True = py.PY_call(py.bool);
+ py.False = py.PY_call(py.bool);
+ booleans_initialized = true;
+ py.float = py.type('float', null, {
+ __init__: function () {
+ var placeholder = {};
+ var args = py.PY_parseArgs(arguments, [['value', placeholder]]);
+ var value = args.value;
+ if (value === placeholder) {
+ this._value = 0; return;
+ }
+ if (py.PY_isInstance(value, py.float)) {
+ this._value = value._value;
+ }
+ if (py.PY_isInstance(value, py.object) && '__float__' in value) {
+ var res = value.__float__();
+ if (py.PY_isInstance(res, py.float)) {
+ this._value = res._value;
+ return;
+ }
+ throw new Error('TypeError: __float__ returned non-float (type ' +
+ typename(res) + ')');
+ }
+ throw new Error('TypeError: float() argument must be a string or a number');
+ },
+ __str__: function () {
+ return py.str.fromJSON(String(this._value));
+ },
+ __eq__: function (other) {
+ return this._value === other._value ? py.True : py.False;
+ },
+ __lt__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return this._value < other._value ? py.True : py.False;
+ },
+ __le__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return this._value <= other._value ? py.True : py.False;
+ },
+ __gt__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return this._value > other._value ? py.True : py.False;
+ },
+ __ge__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return this._value >= other._value ? py.True : py.False;
+ },
+ __abs__: function () {
+ return py.float.fromJSON(
+ Math.abs(this._value));
+ },
+ __add__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value + other._value);
+ },
+ __mod__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value % other._value);
+ },
+ __neg__: function () {
+ return py.float.fromJSON(-this._value);
+ },
+ __sub__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value - other._value);
+ },
+ __mul__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value * other._value);
+ },
+ __pow__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value ** other._value);
+ },
+ __div__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ return py.float.fromJSON(this._value / other._value);
+ },
+ __nonzero__: function () {
+ return this._value ? py.True : py.False;
+ },
+ fromJSON: function (v) {
+ if (!(typeof v === 'number')) {
+ throw new Error('py.float.fromJSON can only take numbers');
+ }
+ var instance = py.PY_call(py.float);
+ instance._value = v;
+ return instance;
+ },
+ toJSON: function () {
+ return this._value;
+ }
+ });
+ py.str = py.type('str', null, {
+ __init__: function () {
+ var placeholder = {};
+ var args = py.PY_parseArgs(arguments, [['value', placeholder]]);
+ var s = args.value;
+ if (s === placeholder) { this._value = ''; return; }
+ this._value = py.PY_str(s)._value;
+ },
+ __hash__: function () {
+ return '\1\0\1' + this._value;
+ },
+ __str__: function () {
+ return this;
+ },
+ __eq__: function (other) {
+ if (py.PY_isInstance(other, py.str)
+ && this._value === other._value) {
+ return py.True;
+ }
+ return py.False;
+ },
+ __lt__: function (other) {
+ if (py.PY_not(py.PY_call(py.isinstance, [other, py.str]))) {
+ return py.NotImplemented;
+ }
+ return this._value < other._value ? py.True : py.False;
+ },
+ __le__: function (other) {
+ if (!py.PY_isInstance(other, py.str)) {
+ return py.NotImplemented;
+ }
+ return this._value <= other._value ? py.True : py.False;
+ },
+ __gt__: function (other) {
+ if (!py.PY_isInstance(other, py.str)) {
+ return py.NotImplemented;
+ }
+ return this._value > other._value ? py.True : py.False;
+ },
+ __ge__: function (other) {
+ if (!py.PY_isInstance(other, py.str)) {
+ return py.NotImplemented;
+ }
+ return this._value >= other._value ? py.True : py.False;
+ },
+ __add__: function (other) {
+ if (!py.PY_isInstance(other, py.str)) {
+ return py.NotImplemented;
+ }
+ return py.str.fromJSON(this._value + other._value);
+ },
+ __nonzero__: function () {
+ return this._value.length ? py.True : py.False;
+ },
+ __contains__: function (s) {
+ return (this._value.indexOf(s._value) !== -1) ? py.True : py.False;
+ },
+ fromJSON: function (s) {
+ if (typeof s === 'string') {
+ var instance = py.PY_call(py.str);
+ instance._value = s;
+ return instance;
+ }
+ throw new Error("str.fromJSON can only take strings");
+ },
+ toJSON: function () {
+ return this._value;
+ }
+ });
+ py.tuple = py.type('tuple', null, {
+ __init__: function () {
+ this._values = [];
+ },
+ __len__: function () {
+ return this._values.length;
+ },
+ __nonzero__: function () {
+ return py.PY_size(this) > 0 ? py.True : py.False;
+ },
+ __contains__: function (value) {
+ for(var i=0, len=this._values.length; i<len; ++i) {
+ if (py.PY_isTrue(this._values[i].__eq__(value))) {
+ return py.True;
+ }
+ }
+ return py.False;
+ },
+ __getitem__: function (index) {
+ return this._values[index.toJSON()];
+ },
+ toJSON: function () {
+ var out = [];
+ for (var i=0; i<this._values.length; ++i) {
+ out.push(this._values[i].toJSON());
+ }
+ return out;
+ },
+ fromJSON: function (ar) {
+ if (!(ar instanceof Array)) {
+ throw new Error("Can only create a py.tuple from an Array");
+ }
+ var t = py.PY_call(py.tuple);
+ for(var i=0; i<ar.length; ++i) {
+ t._values.push(PY_ensurepy(ar[i]));
+ }
+ return t;
+ }
+ });
+ py.list = py.type('list', null, {
+ __nonzero__: function () {
+ return this.__len__ > 0 ? py.True : py.False;
+ },
+ });
+ _.defaults(py.list, py.tuple) // Copy attributes not redefined in type list
+ py.dict = py.type('dict', null, {
+ __init__: function () {
+ this._store = {};
+ },
+ __getitem__: function (key) {
+ var h = key.__hash__();
+ if (!(h in this._store)) {
+ throw new Error("KeyError: '" + key.toJSON() + "'");
+ }
+ return this._store[h][1];
+ },
+ __setitem__: function (key, value) {
+ this._store[key.__hash__()] = [key, value];
+ },
+ __len__: function () {
+ return Object.keys(this._store).length
+ },
+ __nonzero__: function () {
+ return py.PY_size(this) > 0 ? py.True : py.False;
+ },
+ get: function () {
+ var args = py.PY_parseArgs(arguments, ['k', ['d', py.None]]);
+ var h = args.k.__hash__();
+ if (!(h in this._store)) {
+ return args.d;
+ }
+ return this._store[h][1];
+ },
+ fromJSON: function (d) {
+ var instance = py.PY_call(py.dict);
+ for (var k in (d || {})) {
+ if (!d.hasOwnProperty(k)) { continue; }
+ instance.__setitem__(
+ py.str.fromJSON(k),
+ PY_ensurepy(d[k]));
+ }
+ return instance;
+ },
+ toJSON: function () {
+ var out = {};
+ for(var k in this._store) {
+ var item = this._store[k];
+ out[item[0].toJSON()] = item[1].toJSON();
+ }
+ return out;
+ }
+ });
+ py.PY_def = py.type('function', null, {
+ __call__: function () {
+ // don't want to rewrite __call__ for instancemethod
+ return this._func.apply(this._inst, arguments);
+ },
+ fromJSON: function (nativefunc) {
+ var instance = py.PY_call(py.PY_def);
+ instance._inst = null;
+ instance._func = nativefunc;
+ return instance;
+ },
+ toJSON: function () {
+ return this._func;
+ }
+ });
+ py.classmethod = py.type('classmethod', null, {
+ __init__: function () {
+ var args = py.PY_parseArgs(arguments, 'function');
+ this._func = args['function'];
+ },
+ __get__: function (obj, type) {
+ return PY_instancemethod.fromJSON(this._func, type);
+ },
+ fromJSON: function (func) {
+ return py.PY_call(py.classmethod, [func]);
+ }
+ });
+ var PY_instancemethod = py.type('instancemethod', [py.PY_def], {
+ fromJSON: function (nativefunc, instance) {
+ var inst = py.PY_call(PY_instancemethod);
+ // could also use bind?
+ inst._inst = instance;
+ inst._func = nativefunc;
+ return inst;
+ }
+ });
+
+ py.abs = new py.PY_def.fromJSON(function abs() {
+ var args = py.PY_parseArgs(arguments, ['number']);
+ if (!args.number.__abs__) {
+ throw new Error(
+ "TypeError: bad operand type for abs(): '"
+ + typename(args.number)
+ + "'");
+ }
+ return args.number.__abs__();
+ });
+ py.len = new py.PY_def.fromJSON(function len() {
+ var args = py.PY_parseArgs(arguments, ['object']);
+ return py.float.fromJSON(py.PY_size(args.object));
+ });
+ py.isinstance = new py.PY_def.fromJSON(function isinstance() {
+ var args = py.PY_parseArgs(arguments, ['object', 'class']);
+ return py.PY_isInstance(args.object, args['class'])
+ ? py.True : py.False;
+ });
+ py.issubclass = new py.PY_def.fromJSON(function issubclass() {
+ var args = py.PY_parseArgs(arguments, ['C', 'B']);
+ return py.PY_isSubclass(args.C, args.B)
+ ? py.True : py.False;
+ });
+
+
+ /**
+ * Implements the decoding of Python string literals (embedded in
+ * JS strings) into actual JS strings. This includes the decoding
+ * of escapes into their corresponding JS
+ * characters/codepoints/whatever.
+ *
+ * The ``unicode`` flags notes whether the literal should be
+ * decoded as a bytestring literal or a unicode literal, which
+ * pretty much only impacts decoding (or not) of unicode escapes
+ * at this point since bytestrings are not technically handled
+ * (everything is decoded to JS "unicode" strings)
+ *
+ * Eventurally, ``str`` could eventually use typed arrays, that'd
+ * be interesting...
+ */
+ var PY_decode_string_literal = function (str, unicode) {
+ var out = [], code;
+ // Directly maps a single escape code to an output
+ // character
+ var direct_map = {
+ '\\': '\\',
+ '"': '"',
+ "'": "'",
+ 'a': '\x07',
+ 'b': '\x08',
+ 'f': '\x0c',
+ 'n': '\n',
+ 'r': '\r',
+ 't': '\t',
+ 'v': '\v'
+ };
+
+ for (var i=0; i<str.length; ++i) {
+ if (str[i] !== '\\') {
+ out.push(str[i]);
+ continue;
+ }
+ var escape = str[i+1];
+ if (escape in direct_map) {
+ out.push(direct_map[escape]);
+ ++i;
+ continue;
+ }
+
+ switch (escape) {
+ // Ignored
+ case '\n': ++i; continue;
+ // Character named name in the Unicode database (Unicode only)
+ case 'N':
+ if (!unicode) { break; }
+ throw Error("SyntaxError: \\N{} escape not implemented");
+ case 'u':
+ if (!unicode) { break; }
+ var uni = str.slice(i+2, i+6);
+ if (!/[0-9a-f]{4}/i.test(uni)) {
+ throw new Error([
+ "SyntaxError: (unicode error) 'unicodeescape' codec",
+ " can't decode bytes in position ",
+ i, "-", i+4,
+ ": truncated \\uXXXX escape"
+ ].join(''));
+ }
+ code = parseInt(uni, 16);
+ out.push(String.fromCharCode(code));
+ // escape + 4 hex digits
+ i += 5;
+ continue;
+ case 'U':
+ if (!unicode) { break; }
+ // TODO: String.fromCodePoint
+ throw Error("SyntaxError: \\U escape not implemented");
+ case 'x':
+ // get 2 hex digits
+ var hex = str.slice(i+2, i+4);
+ if (!/[0-9a-f]{2}/i.test(hex)) {
+ if (!unicode) {
+ throw new Error('ValueError: invalid \\x escape');
+ }
+ throw new Error([
+ "SyntaxError: (unicode error) 'unicodeescape'",
+ " codec can't decode bytes in position ",
+ i, '-', i+2,
+ ": truncated \\xXX escape"
+ ].join(''))
+ }
+ code = parseInt(hex, 16);
+ out.push(String.fromCharCode(code));
+ // skip escape + 2 hex digits
+ i += 3;
+ continue;
+ default:
+ // Check if octal
+ if (!/[0-8]/.test(escape)) { break; }
+ var r = /[0-8]{1,3}/g;
+ r.lastIndex = i+1;
+ var m = r.exec(str);
+ var oct = m[0];
+ code = parseInt(oct, 8);
+ out.push(String.fromCharCode(code));
+ // skip matchlength
+ i += oct.length;
+ continue;
+ }
+ out.push('\\');
+ }
+
+ return out.join('');
+ };
+ // All binary operators with fallbacks, so they can be applied generically
+ var PY_operators = {
+ '==': ['eq', 'eq', function (a, b) { return a === b; }],
+ '!=': ['ne', 'ne', function (a, b) { return a !== b; }],
+ '<>': ['ne', 'ne', function (a, b) { return a !== b; }],
+ '<': ['lt', 'gt', function (a, b) {return a.__class__.__name__ < b.__class__.__name__;}],
+ '<=': ['le', 'ge', function (a, b) {return a.__class__.__name__ <= b.__class__.__name__;}],
+ '>': ['gt', 'lt', function (a, b) {return a.__class__.__name__ > b.__class__.__name__;}],
+ '>=': ['ge', 'le', function (a, b) {return a.__class__.__name__ >= b.__class__.__name__;}],
+
+ '+': ['add', 'radd'],
+ '-': ['sub', 'rsub'],
+ '*': ['mul', 'rmul'],
+ '/': ['div', 'rdiv'],
+ '//': ['floordiv', 'rfloordiv'],
+ '%': ['mod', 'rmod'],
+ '**': ['pow', 'rpow'],
+ '<<': ['lshift', 'rlshift'],
+ '>>': ['rshift', 'rrshift'],
+ '&': ['and', 'rand'],
+ '^': ['xor', 'rxor'],
+ '|': ['or', 'ror']
+ };
+ /**
+ * Implements operator fallback/reflection.
+ *
+ * First two arguments are the objects to apply the operator on,
+ * in their actual order (ltr).
+ *
+ * Third argument is the actual operator.
+ *
+ * If the operator methods raise exceptions, those exceptions are
+ * not intercepted.
+ */
+ var PY_op = function (o1, o2, op) {
+ var r;
+ var methods = PY_operators[op];
+ var forward = '__' + methods[0] + '__', reverse = '__' + methods[1] + '__';
+ var otherwise = methods[2];
+
+ if (forward in o1 && (r = o1[forward](o2)) !== py.NotImplemented) {
+ return r;
+ }
+ if (reverse in o2 && (r = o2[reverse](o1)) !== py.NotImplemented) {
+ return r;
+ }
+ if (otherwise) {
+ return PY_ensurepy(otherwise(o1, o2));
+ }
+ throw new Error(
+ "TypeError: unsupported operand type(s) for " + op + ": '"
+ + typename(o1) + "' and '" + typename(o2) + "'");
+ };
+
+ var PY_builtins = {
+ type: py.type,
+
+ None: py.None,
+ True: py.True,
+ False: py.False,
+ NotImplemented: py.NotImplemented,
+
+ object: py.object,
+ bool: py.bool,
+ float: py.float,
+ str: py.str,
+ unicode: py.unicode,
+ tuple: py.tuple,
+ list: py.list,
+ dict: py.dict,
+
+ abs: py.abs,
+ len: py.len,
+ isinstance: py.isinstance,
+ issubclass: py.issubclass,
+ classmethod: py.classmethod,
+ };
+
+ py.parse = function (toks) {
+ var index = 0;
+ token = toks[0];
+ next = function () { return toks[++index]; };
+ return expression();
+ };
+ var evaluate_operator = function (operator, a, b) {
+ switch (operator) {
+ case 'is': return a === b ? py.True : py.False;
+ case 'is not': return a !== b ? py.True : py.False;
+ case 'in':
+ return b.__contains__(a);
+ case 'not in':
+ return py.PY_isTrue(b.__contains__(a)) ? py.False : py.True;
+ case '==': case '!=': case '<>':
+ case '<': case '<=':
+ case '>': case '>=':
+ return PY_op(a, b, operator);
+ }
+ throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
+ };
+ py.evaluate = function (expr, context) {
+ context = context || {};
+ switch (expr.id) {
+ case '(name)':
+ var val = context[expr.value];
+ if (val === undefined && expr.value in PY_builtins) {
+ return PY_builtins[expr.value];
+ }
+ return PY_ensurepy(val, expr.value);
+ case '(string)':
+ return py.str.fromJSON(expr.value);
+ case '(number)':
+ return py.float.fromJSON(expr.value);
+ case '(constant)':
+ switch (expr.value) {
+ case 'None': return py.None;
+ case 'False': return py.False;
+ case 'True': return py.True;
+ }
+ throw new Error("SyntaxError: unknown constant '" + expr.value + "'");
+ case '(comparator)':
+ var result, left = py.evaluate(expr.expressions[0], context);
+ for(var i=0; i<expr.operators.length; ++i) {
+ result = evaluate_operator(
+ expr.operators[i],
+ left,
+ left = py.evaluate(expr.expressions[i+1], context));
+ if (py.PY_not(result)) { return py.False; }
+ }
+ return py.True;
+ case 'not':
+ return py.PY_isTrue(py.evaluate(expr.first, context)) ? py.False : py.True;
+ case 'and':
+ var and_first = py.evaluate(expr.first, context);
+ if (py.PY_isTrue(and_first.__nonzero__())) {
+ return py.evaluate(expr.second, context);
+ }
+ return and_first;
+ case 'or':
+ var or_first = py.evaluate(expr.first, context);
+ if (py.PY_isTrue(or_first.__nonzero__())) {
+ return or_first
+ }
+ return py.evaluate(expr.second, context);
+ case 'if':
+ var cond = py.evaluate(expr.condition, context);
+ if (py.PY_isTrue(cond)) {
+ return py.evaluate(expr.ifTrue, context);
+ } else {
+ return py.evaluate(expr.ifFalse, context);
+ }
+ case '(':
+ if (expr.second) {
+ var callable = py.evaluate(expr.first, context);
+ var args = [], kwargs = {};
+ for (var jj=0; jj<expr.second.length; ++jj) {
+ var arg = expr.second[jj];
+ if (arg.id !== '=') {
+ // arg
+ args.push(py.evaluate(arg, context));
+ } else {
+ // kwarg
+ kwargs[arg.first.value] =
+ py.evaluate(arg.second, context);
+ }
+ }
+ return py.PY_call(callable, args, kwargs);
+ }
+ var tuple_exprs = expr.first,
+ tuple_values = [];
+ for (var j=0, len=tuple_exprs.length; j<len; ++j) {
+ tuple_values.push(py.evaluate(
+ tuple_exprs[j], context));
+ }
+ return py.tuple.fromJSON(tuple_values);
+ case '[':
+ if (expr.second) {
+ return py.PY_getItem(
+ py.evaluate(expr.first, context),
+ py.evaluate(expr.second, context));
+ }
+ var list_exprs = expr.first, list_values = [];
+ for (var k=0; k<list_exprs.length; ++k) {
+ list_values.push(py.evaluate(
+ list_exprs[k], context));
+ }
+ return py.list.fromJSON(list_values);
+ case '{':
+ var dict_exprs = expr.first, dict = py.PY_call(py.dict);
+ for(var l=0; l<dict_exprs.length; ++l) {
+ py.PY_setItem(dict,
+ py.evaluate(dict_exprs[l][0], context),
+ py.evaluate(dict_exprs[l][1], context));
+ }
+ return dict;
+ case '.':
+ if (expr.second.id !== '(name)') {
+ throw new Error('SyntaxError: ' + expr);
+ }
+ return py.PY_getAttr(py.evaluate(expr.first, context),
+ expr.second.value);
+ // numerical operators
+ case '~':
+ return (py.evaluate(expr.first, context)).__invert__();
+ case '+':
+ if (!expr.second) {
+ return py.PY_positive(py.evaluate(expr.first, context));
+ }
+ case '-':
+ if (!expr.second) {
+ return py.PY_negative(py.evaluate(expr.first, context));
+ }
+ case '*': case '/': case '//':
+ case '%':
+ case '**':
+ case '<<': case '>>':
+ case '&': case '^': case '|':
+ return PY_op(
+ py.evaluate(expr.first, context),
+ py.evaluate(expr.second, context),
+ expr.id);
+
+ default:
+ throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
+ }
+ };
+ py.eval = function (str, context) {
+ return py.evaluate(
+ py.parse(
+ py.tokenize(
+ str)),
+ context).toJSON();
+ }
+})(typeof exports === 'undefined' ? py : exports);
diff --git a/addons/web/static/lib/py.js/lib/py_extras.js b/addons/web/static/lib/py.js/lib/py_extras.js
new file mode 100644
index 00000000..402acb37
--- /dev/null
+++ b/addons/web/static/lib/py.js/lib/py_extras.js
@@ -0,0 +1,989 @@
+(function (py) {
+"use strict";
+
+/**
+ * This file add extra functionality to the python interpreter exported by py.js
+ *
+ * Main extra functionality is about time management, more precisely:
+ * - date
+ * - datetime
+ * - relativedelta
+ *
+ * These python modules are exported in the py.extras object, and can be added
+ * to the evaluation context. For example,
+ *
+ * var context = {
+ * datetime: py.extras.datetime,
+ * date: py.extras.date,
+ * time: py.extras.time,
+ * };
+ * var result = py.eval(some_python_expression, context);
+ */
+
+/*
+ * py.js helpers and setup
+ */
+
+/**
+ * computes (Math.floor(a/b), a%b and passes that to the callback.
+ *
+ * returns the callback's result
+ */
+function divmod (a, b, fn) {
+ var mod = a%b;
+ // in python, sign(a % b) === sign(b). Not in JS. If wrong side, add a
+ // round of b
+ if (mod > 0 && b < 0 || mod < 0 && b > 0) {
+ mod += b;
+ }
+ return fn(Math.floor(a/b), mod);
+}
+
+/**
+ * Passes the fractional and integer parts of x to the callback, returns
+ * the callback's result
+ */
+function modf(x, fn) {
+ var mod = x%1;
+ if (mod < 0) {
+ mod += 1;
+ }
+ return fn(mod, Math.floor(x));
+}
+
+function assert(bool) {
+ if (!bool) {
+ throw new Error("AssertionError");
+ }
+}
+
+
+var obj = function () {};
+obj.prototype = py.object;
+var asJS = function (arg) {
+ if (arg instanceof obj) {
+ return arg.toJSON();
+ }
+ return arg;
+};
+
+var datetime = py.PY_call(py.object);
+
+var zero = py.float.fromJSON(0);
+
+// Port from pypy/lib_pypy/datetime.py
+var DAYS_IN_MONTH = [null, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+var DAYS_BEFORE_MONTH = [null];
+var dbm = 0;
+
+for (var i=1; i<DAYS_IN_MONTH.length; ++i) {
+ DAYS_BEFORE_MONTH.push(dbm);
+ dbm += DAYS_IN_MONTH[i];
+}
+
+function is_leap(year) {
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
+}
+
+function days_before_year(year) {
+ var y = year - 1;
+ return y*365 + Math.floor(y/4) - Math.floor(y/100) + Math.floor(y/400);
+}
+
+function days_in_month(year, month) {
+ if (month === 2 && is_leap(year)) {
+ return 29;
+ }
+ return DAYS_IN_MONTH[month];
+}
+
+function days_before_month(year, month) {
+ var post_leap_feb = month > 2 && is_leap(year);
+ return DAYS_BEFORE_MONTH[month] + (post_leap_feb ? 1 : 0);
+}
+
+function ymd2ord(year, month, day) {
+ var dim = days_in_month(year, month);
+ if (!(1 <= day && day <= dim)) {
+ throw new Error("ValueError: day must be in 1.." + dim);
+ }
+ return days_before_year(year) +
+ days_before_month(year, month) +
+ day;
+}
+
+function get_quarter_number(month) {
+ return Math.ceil(month / 3);
+}
+
+function get_quarter(year, month) {
+ var quarter_number = get_quarter_number(month);
+ var month_from = ((quarter_number - 1) * 3) + 1
+ var date_from = {year: year, month: month_from, day: 1}
+ var date_to = {year: year, month: month_from + 2, day: days_in_month(year, month)}
+ return [date_from, date_to];
+}
+
+function get_day_of_week(year, month, day) {
+ // Since JavaScript is a piece of garbage, months start at 0
+ var d = new Date(year, month - 1, day);
+ // Convert to ISO8601: Monday = 0 ... Sunday = 6
+ return (d.getDay() + 6) % 7;
+}
+
+var DI400Y = days_before_year(401);
+var DI100Y = days_before_year(101);
+var DI4Y = days_before_year(5);
+
+function ord2ymd(n) {
+ --n;
+ var n400, n100, n4, n1, n0;
+ divmod(n, DI400Y, function (_n400, n) {
+ n400 = _n400;
+ divmod(n, DI100Y, function (_n100, n) {
+ n100 = _n100;
+ divmod(n, DI4Y, function (_n4, n) {
+ n4 = _n4;
+ divmod(n, 365, function (_n1, n) {
+ n1 = _n1;
+ n0 = n;
+ });
+ });
+ });
+ });
+
+ n = n0;
+ var year = n400 * 400 + 1 + n100 * 100 + n4 * 4 + n1;
+ if (n1 == 4 || n100 == 100) {
+ assert(n0 === 0);
+ return {
+ year: year - 1,
+ month: 12,
+ day: 31
+ };
+ }
+
+ var leapyear = n1 === 3 && (n4 !== 24 || n100 == 3);
+ assert(leapyear == is_leap(year));
+ var month = (n + 50) >> 5;
+ var preceding = DAYS_BEFORE_MONTH[month] + ((month > 2 && leapyear) ? 1 : 0);
+ if (preceding > n) {
+ --month;
+ preceding -= DAYS_IN_MONTH[month] + ((month === 2 && leapyear) ? 1 : 0);
+ }
+ n -= preceding;
+ return {
+ year: year,
+ month: month,
+ day: n+1
+ };
+}
+
+/**
+ * Converts the stuff passed in into a valid date, applying overflows as needed
+ */
+function tmxxx(year, month, day, hour, minute, second, microsecond) {
+ hour = hour || 0; minute = minute || 0; second = second || 0;
+ microsecond = microsecond || 0;
+
+ if (microsecond < 0 || microsecond > 999999) {
+ divmod(microsecond, 1000000, function (carry, ms) {
+ microsecond = ms;
+ second += carry;
+ });
+ }
+ if (second < 0 || second > 59) {
+ divmod(second, 60, function (carry, s) {
+ second = s;
+ minute += carry;
+ });
+ }
+ if (minute < 0 || minute > 59) {
+ divmod(minute, 60, function (carry, m) {
+ minute = m;
+ hour += carry;
+ });
+ }
+ if (hour < 0 || hour > 23) {
+ divmod(hour, 24, function (carry, h) {
+ hour = h;
+ day += carry;
+ });
+ }
+ // That was easy. Now it gets muddy: the proper range for day
+ // can't be determined without knowing the correct month and year,
+ // but if day is, e.g., plus or minus a million, the current month
+ // and year values make no sense (and may also be out of bounds
+ // themselves).
+ // Saying 12 months == 1 year should be non-controversial.
+ if (month < 1 || month > 12) {
+ divmod(month-1, 12, function (carry, m) {
+ month = m + 1;
+ year += carry;
+ });
+ }
+ // Now only day can be out of bounds (year may also be out of bounds
+ // for a datetime object, but we don't care about that here).
+ // If day is out of bounds, what to do is arguable, but at least the
+ // method here is principled and explainable.
+ var dim = days_in_month(year, month);
+ if (day < 1 || day > dim) {
+ // Move day-1 days from the first of the month. First try to
+ // get off cheap if we're only one day out of range (adjustments
+ // for timezone alone can't be worse than that).
+ if (day === 0) {
+ --month;
+ if (month > 0) {
+ day = days_in_month(year, month);
+ } else {
+ --year; month=12; day=31;
+ }
+ } else if (day == dim + 1) {
+ ++month;
+ day = 1;
+ if (month > 12) {
+ month = 1;
+ ++year;
+ }
+ } else {
+ var r = ord2ymd(ymd2ord(year, month, 1) + (day - 1));
+ year = r.year;
+ month = r.month;
+ day = r.day;
+ }
+ }
+ return {
+ year: year,
+ month: month,
+ day: day,
+ hour: hour,
+ minute: minute,
+ second: second,
+ microsecond: microsecond
+ };
+}
+
+datetime.timedelta = py.type('timedelta', null, {
+ __init__: function () {
+ var args = py.PY_parseArgs(arguments, [
+ ['days', zero], ['seconds', zero], ['microseconds', zero],
+ ['milliseconds', zero], ['minutes', zero], ['hours', zero],
+ ['weeks', zero]
+ ]);
+
+ var d = 0, s = 0, m = 0;
+ var days = args.days.toJSON() + args.weeks.toJSON() * 7;
+ var seconds = args.seconds.toJSON()
+ + args.minutes.toJSON() * 60
+ + args.hours.toJSON() * 3600;
+ var microseconds = args.microseconds.toJSON()
+ + args.milliseconds.toJSON() * 1000;
+
+ // Get rid of all fractions, and normalize s and us.
+ // Take a deep breath <wink>.
+ var daysecondsfrac = modf(days, function (dayfrac, days) {
+ d = days;
+ if (dayfrac) {
+ return modf(dayfrac * 24 * 3600, function (dsf, dsw) {
+ s = dsw;
+ return dsf;
+ });
+ }
+ return 0;
+ });
+
+ var secondsfrac = modf(seconds, function (sf, s) {
+ seconds = s;
+ return sf + daysecondsfrac;
+ });
+ divmod(seconds, 24*3600, function (days, seconds) {
+ d += days;
+ s += seconds;
+ });
+ // seconds isn't referenced again before redefinition
+
+ microseconds += secondsfrac * 1e6;
+ divmod(microseconds, 1000000, function (seconds, microseconds) {
+ divmod(seconds, 24*3600, function (days, seconds) {
+ d += days;
+ s += seconds;
+ m += Math.round(microseconds);
+ });
+ });
+
+ // Carrying still possible here?
+
+ this.days = d;
+ this.seconds = s;
+ this.microseconds = m;
+ },
+ __str__: function () {
+ var hh, mm, ss;
+ divmod(this.seconds, 60, function (m, s) {
+ divmod(m, 60, function (h, m) {
+ hh = h;
+ mm = m;
+ ss = s;
+ });
+ });
+ var s = _.str.sprintf("%d:%02d:%02d", hh, mm, ss);
+ if (this.days) {
+ s = _.str.sprintf("%d day%s, %s",
+ this.days,
+ (this.days != 1 && this.days != -1) ? 's' : '',
+ s);
+ }
+ if (this.microseconds) {
+ s = _.str.sprintf("%s.%06d", s, this.microseconds);
+ }
+ return py.str.fromJSON(s);
+ },
+ __eq__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.False;
+ }
+
+ return (this.days === other.days
+ && this.seconds === other.seconds
+ && this.microseconds === other.microseconds)
+ ? py.True : py.False;
+ },
+ __add__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.NotImplemented;
+ }
+ return py.PY_call(datetime.timedelta, [
+ py.float.fromJSON(this.days + other.days),
+ py.float.fromJSON(this.seconds + other.seconds),
+ py.float.fromJSON(this.microseconds + other.microseconds)
+ ]);
+ },
+ __radd__: function (other) { return this.__add__(other); },
+ __sub__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.NotImplemented;
+ }
+ return py.PY_call(datetime.timedelta, [
+ py.float.fromJSON(this.days - other.days),
+ py.float.fromJSON(this.seconds - other.seconds),
+ py.float.fromJSON(this.microseconds - other.microseconds)
+ ]);
+ },
+ __rsub__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.NotImplemented;
+ }
+ return this.__neg__().__add__(other);
+ },
+ __neg__: function () {
+ return py.PY_call(datetime.timedelta, [
+ py.float.fromJSON(-this.days),
+ py.float.fromJSON(-this.seconds),
+ py.float.fromJSON(-this.microseconds)
+ ]);
+ },
+ __pos__: function () { return this; },
+ __mul__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ var n = other.toJSON();
+ return py.PY_call(datetime.timedelta, [
+ py.float.fromJSON(this.days * n),
+ py.float.fromJSON(this.seconds * n),
+ py.float.fromJSON(this.microseconds * n)
+ ]);
+ },
+ __rmul__: function (other) { return this.__mul__(other); },
+ __div__: function (other) {
+ if (!py.PY_isInstance(other, py.float)) {
+ return py.NotImplemented;
+ }
+ var usec = ((this.days * 24 * 3600) + this.seconds) * 1000000
+ + this.microseconds;
+ return py.PY_call(
+ datetime.timedelta, [
+ zero, zero, py.float.fromJSON(usec / other.toJSON())]);
+ },
+ __floordiv__: function (other) { return this.__div__(other); },
+ total_seconds: function () {
+ return py.float.fromJSON(
+ this.days * 86400
+ + this.seconds
+ + this.microseconds / 1000000);
+ },
+ __nonzero__: function () {
+ return (!!this.days || !!this.seconds || !!this.microseconds)
+ ? py.True
+ : py.False;
+ }
+});
+
+datetime.datetime = py.type('datetime', null, {
+ __init__: function () {
+ var zero = py.float.fromJSON(0);
+ var args = py.PY_parseArgs(arguments, [
+ 'year', 'month', 'day',
+ ['hour', zero], ['minute', zero], ['second', zero],
+ ['microsecond', zero], ['tzinfo', py.None]
+ ]);
+ for(var key in args) {
+ if (!args.hasOwnProperty(key)) { continue; }
+ this[key] = asJS(args[key]);
+ }
+ },
+ __eq__: function (other) {
+ return (this.year === other.year
+ && this.month === other.month
+ && this.day === other.day
+ && this.hour === other.hour
+ && this.minute === other.minute
+ && this.second === other.second
+ && this.microsecond === other.microsecond
+ && this.tzinfo === other.tzinfo)
+ ? py.True : py.False;
+ },
+ replace: function () {
+ var args = py.PY_parseArgs(arguments, [
+ ['year', py.None], ['month', py.None], ['day', py.None],
+ ['hour', py.None], ['minute', py.None], ['second', py.None],
+ ['microsecond', py.None] // FIXME: tzinfo, can't use None as valid input
+ ]);
+ var params = {};
+ for(var key in args) {
+ if (!args.hasOwnProperty(key)) { continue; }
+
+ var arg = args[key];
+ params[key] = (arg === py.None ? this[key] : asJS(arg));
+ }
+ return py.PY_call(datetime.datetime, params);
+ },
+ start_of: function() {
+ var args = py.PY_parseArgs(arguments, 'granularity');
+ var granularity = args.granularity.toJSON();
+ if (granularity === 'year') {
+ return py.PY_call(datetime.datetime, [this.year, 1, 1]);
+ } else if (granularity === 'quarter') {
+ var quarter = get_quarter(this.year, this.month)[0];
+ return py.PY_call(datetime.datetime, [quarter.year, quarter.month, quarter.day]);
+ } else if (granularity === 'month') {
+ return py.PY_call(datetime.datetime, [this.year, this.month, 1]);
+ } else if (granularity === 'week') {
+ var dow = get_day_of_week(this.year, this.month, this.day);
+ return py.PY_call(datetime.datetime, [this.year, this.month, this.day - dow]);
+ } else if (granularity === 'day') {
+ return py.PY_call(datetime.datetime, [this.year, this.month, this.day]);
+ } else if (granularity === 'hour') {
+ return py.PY_call(datetime.datetime, [this.year, this.month, this.day, this.hour]);
+ } else {
+ throw new Error(
+ 'ValueError: ' + granularity + ' is not a supported granularity, supported ' +
+ ' granularities are: year, quarter, month, week, day and hour.'
+ )
+ }
+ },
+ end_of: function () {
+ var args = py.PY_parseArgs(arguments, 'granularity');
+ var granularity = args.granularity.toJSON();
+ var min = [23, 59, 59];
+ if (granularity === 'year') {
+ return py.PY_call(datetime.datetime, [this.year, 12, 31].concat(min));
+ } else if (granularity === 'quarter') {
+ var quarter = get_quarter(this.year, this.month)[1];
+ return py.PY_call(
+ datetime.datetime, [quarter.year, quarter.month, quarter.day].concat(min)
+ );
+ } else if (granularity === 'month') {
+ var dom = days_in_month(this.year, this.month);
+ return py.PY_call(datetime.datetime, [this.year, this.month, dom].concat(min));
+ } else if (granularity === 'week') {
+ var dow = get_day_of_week(this.year, this.month, this.day);
+ return py.PY_call(
+ datetime.datetime, [this.year, this.month, this.day + (6 - dow)].concat(min)
+ );
+ } else if (granularity === 'day') {
+ return py.PY_call(datetime.datetime, [this.year, this.month, this.day].concat(min));
+ } else if (granularity === 'hour') {
+ return py.PY_call(
+ datetime.datetime, [this.year, this.month, this.day, this.hour, 59, 59]
+ );
+ } else {
+ throw new Error(
+ 'ValueError: ' + granularity + ' is not a supported granularity, supported ' +
+ ' granularities are: year, quarter, month, week, day and hour.'
+ )
+ }
+ },
+ add: function() {
+ var args = py.PY_parseArgs(arguments, [
+ ['years', py.None], ['months', py.None], ['days', py.None],
+ ['hours', py.None], ['minutes', py.None], ['seconds', py.None],
+ ]);
+ return py.PY_add(this, py.PY_call(relativedelta, {
+ 'years': args.years,
+ 'months': args.months,
+ 'days': args.days,
+ 'hours': args.hours,
+ 'minutes': args.minutes,
+ 'seconds': args.seconds,
+ }));
+ },
+ subtract: function() {
+ var args = py.PY_parseArgs(arguments, [
+ ['years', py.None], ['months', py.None], ['days', py.None],
+ ['hours', py.None], ['minutes', py.None], ['seconds', py.None],
+ ]);
+ var params = {};
+ for (var key in args) {
+ params[key] = (args[key] === py.None ? args[key] : py.float.fromJSON(-asJS(args[key])));
+ }
+ return py.PY_add(this, py.PY_call(relativedelta, params));
+ },
+ strftime: function () {
+ var self = this;
+ var args = py.PY_parseArgs(arguments, 'format');
+ return py.str.fromJSON(args.format.toJSON()
+ .replace(/%([A-Za-z])/g, function (m, c) {
+ switch (c) {
+ case 'Y': return _.str.sprintf('%04d', self.year);
+ case 'm': return _.str.sprintf('%02d', self.month);
+ case 'd': return _.str.sprintf('%02d', self.day);
+ case 'H': return _.str.sprintf('%02d', self.hour);
+ case 'M': return _.str.sprintf('%02d', self.minute);
+ case 'S': return _.str.sprintf('%02d', self.second);
+ }
+ throw new Error('ValueError: No known conversion for ' + m);
+ }));
+ },
+ to_utc: function () {
+ var d = new Date(this.year, this.month, this.day, this.hour, this.minute, this.second);
+ var offset = d.getTimezoneOffset();
+ var kwargs = {minutes: py.float.fromJSON(offset)};
+ var timedelta = py.PY_call(py.extras.datetime.timedelta,[],kwargs);
+ var s = tmxxx(this.year, this.month, this.day + timedelta.days, this.hour, this.minute, this.second + timedelta.seconds);
+ return datetime.datetime.fromJSON(s.year, s.month, s.day, s.hour, s.minute, s.second);
+ },
+ now: py.classmethod.fromJSON(function () {
+ var d = new Date();
+ return py.PY_call(datetime.datetime, [
+ d.getFullYear(), d.getMonth() + 1, d.getDate(),
+ d.getHours(), d.getMinutes(), d.getSeconds(),
+ d.getMilliseconds() * 1000]);
+ }),
+ today: py.classmethod.fromJSON(function () {
+ var dt_class = py.PY_getAttr(datetime, 'datetime');
+ return py.PY_call(py.PY_getAttr(dt_class, 'now'));
+ }),
+ utcnow: py.classmethod.fromJSON(function () {
+ var d = new Date();
+ return py.PY_call(datetime.datetime,
+ [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(),
+ d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(),
+ d.getUTCMilliseconds() * 1000]);
+ }),
+
+ combine: py.classmethod.fromJSON(function () {
+ var args = py.PY_parseArgs(arguments, 'date time');
+ return py.PY_call(datetime.datetime, [
+ py.PY_getAttr(args.date, 'year'),
+ py.PY_getAttr(args.date, 'month'),
+ py.PY_getAttr(args.date, 'day'),
+ py.PY_getAttr(args.time, 'hour'),
+ py.PY_getAttr(args.time, 'minute'),
+ py.PY_getAttr(args.time, 'second')
+ ]);
+ }),
+ toJSON: function () {
+ return new Date(
+ this.year,
+ this.month - 1,
+ this.day,
+ this.hour,
+ this.minute,
+ this.second,
+ this.microsecond / 1000);
+ },
+ __add__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.NotImplemented;
+ }
+ var s = tmxxx(this.year, this.month, this.day + other.days, this.hour, this.minute, this.second + other.seconds);
+ return datetime.datetime.fromJSON(s.year, s.month, s.day, s.hour, s.minute, s.second);
+ },
+ __sub__: function (other) {
+ if (py.PY_isInstance(other, datetime.timedelta)) {
+ return py.PY_add(this, py.PY_negative(other));
+ }
+ return py.NotImplemented;
+ },
+ fromJSON: function (year, month, day, hour, minute, second) {
+ return py.PY_call(datetime.datetime, [year, month, day, hour, minute, second]);
+ },
+});
+
+datetime.date = py.type('date', null, {
+ __init__: function () {
+ var args = py.PY_parseArgs(arguments, 'year month day');
+ this.year = asJS(args.year);
+ this.month = asJS(args.month);
+ this.day = asJS(args.day);
+ },
+ strftime: function () {
+ var self = this;
+ var args = py.PY_parseArgs(arguments, 'format');
+ return py.str.fromJSON(args.format.toJSON()
+ .replace(/%([A-Za-z])/g, function (m, c) {
+ switch (c) {
+ case 'Y': return self.year;
+ case 'm': return _.str.sprintf('%02d', self.month);
+ case 'd': return _.str.sprintf('%02d', self.day);
+ }
+ throw new Error('ValueError: No known conversion for ' + m);
+ }));
+ },
+ __eq__: function (other) {
+ return (this.year === other.year
+ && this.month === other.month
+ && this.day === other.day)
+ ? py.True : py.False;
+ },
+ replace: function () {
+ var args = py.PY_parseArgs(arguments, [
+ ['year', py.None], ['month', py.None], ['day', py.None]
+ ]);
+ var params = {};
+ for(var key in args) {
+ if (!args.hasOwnProperty(key)) { continue; }
+
+ var arg = args[key];
+ params[key] = (arg === py.None ? this[key] : asJS(arg));
+ }
+ return py.PY_call(datetime.date, params);
+ },
+ start_of: function() {
+ var args = py.PY_parseArgs(arguments, 'granularity');
+ var granularity = args.granularity.toJSON();
+ if (granularity === 'year') {
+ return py.PY_call(datetime.date, [this.year, 1, 1]);
+ } else if (granularity === 'quarter') {
+ var quarter = get_quarter(this.year, this.month)[0];
+ return py.PY_call(datetime.date, [quarter.year, quarter.month, quarter.day]);
+ } else if (granularity === 'month') {
+ return py.PY_call(datetime.date, [this.year, this.month, 1]);
+ } else if (granularity === 'week') {
+ var dow = get_day_of_week(this.year, this.month, this.day);
+ return py.PY_call(datetime.date, [this.year, this.month, this.day - dow]);
+ } else if (granularity === 'day') {
+ return py.PY_call(datetime.date, [this.year, this.month, this.day]);
+ } else {
+ throw new Error(
+ 'ValueError: ' + granularity + ' is not a supported granularity, supported ' +
+ ' granularities are: year, quarter, month, week and day.'
+ )
+ }
+ },
+ end_of: function () {
+ var args = py.PY_parseArgs(arguments, 'granularity');
+ var granularity = args.granularity.toJSON();
+ if (granularity === 'year') {
+ return py.PY_call(datetime.date, [this.year, 12, 31]);
+ } else if (granularity === 'quarter') {
+ var quarter = get_quarter(this.year, this.month)[1];
+ return py.PY_call(datetime.date, [quarter.year, quarter.month, quarter.day]);
+ } else if (granularity === 'month') {
+ var dom = days_in_month(this.year, this.month);
+ return py.PY_call(datetime.date, [this.year, this.month, dom]);
+ } else if (granularity === 'week') {
+ var dow = get_day_of_week(this.year, this.month, this.day);
+ return py.PY_call(datetime.date, [this.year, this.month, this.day + (6 - dow)]);
+ } else if (granularity === 'day') {
+ return py.PY_call(datetime.date, [this.year, this.month, this.day]);
+ } else {
+ throw new Error(
+ 'ValueError: ' + granularity + ' is not a supported granularity, supported ' +
+ ' granularities are: year, quarter, month, week and day.'
+ )
+ }
+ },
+ add: function() {
+ var args = py.PY_parseArgs(arguments, [
+ ['years', py.None], ['months', py.None], ['days', py.None],
+ ]);
+ return py.PY_add(this, py.PY_call(relativedelta, {
+ 'years': args.years,
+ 'months': args.months,
+ 'days': args.days,
+ }));
+ },
+ subtract: function() {
+ var args = py.PY_parseArgs(arguments, [
+ ['years', py.None], ['months', py.None], ['days', py.None],
+ ]);
+ var params = {};
+ for (var key in args) {
+ params[key] = (args[key] === py.None ? args[key] : py.float.fromJSON(-asJS(args[key])));
+ }
+ return py.PY_add(this, py.PY_call(relativedelta, params));
+ },
+ __add__: function (other) {
+ if (!py.PY_isInstance(other, datetime.timedelta)) {
+ return py.NotImplemented;
+ }
+ var s = tmxxx(this.year, this.month, this.day + other.days);
+ return datetime.date.fromJSON(s.year, s.month, s.day);
+ },
+ __radd__: function (other) { return this.__add__(other); },
+ __sub__: function (other) {
+ if (py.PY_isInstance(other, datetime.timedelta)) {
+ return py.PY_add(this, py.PY_negative(other));
+ }
+ if (py.PY_isInstance(other, datetime.date)) {
+ // FIXME: getattr and sub API methods
+ return py.PY_call(datetime.timedelta, [
+ py.PY_subtract(
+ py.PY_call(py.PY_getAttr(this, 'toordinal')),
+ py.PY_call(py.PY_getAttr(other, 'toordinal')))
+ ]);
+ }
+ return py.NotImplemented;
+ },
+ toordinal: function () {
+ return py.float.fromJSON(ymd2ord(this.year, this.month, this.day));
+ },
+ weekday: function () {
+ return py.float.fromJSON((this.toordinal().toJSON()+6)%7);
+ },
+ fromJSON: function (year, month, day) {
+ return py.PY_call(datetime.date, [year, month, day]);
+ },
+ today: py.classmethod.fromJSON(function () {
+ var d = new Date ();
+ return py.PY_call(datetime.date, [
+ d.getFullYear(), d.getMonth() + 1, d.getDate()]);
+ }),
+});
+
+datetime.time = py.type('time', null, {
+ __init__: function () {
+ var zero = py.float.fromJSON(0);
+ var args = py.PY_parseArgs(arguments, [
+ ['hour', zero], ['minute', zero], ['second', zero], ['microsecond', zero],
+ ['tzinfo', py.None]
+ ]);
+
+ for(var k in args) {
+ if (!args.hasOwnProperty(k)) { continue; }
+ this[k] = asJS(args[k]);
+ }
+ }
+});
+
+var time = py.PY_call(py.object);
+time.strftime = py.PY_def.fromJSON(function () {
+ var args = py.PY_parseArgs(arguments, 'format');
+ var dt_class = py.PY_getAttr(datetime, 'datetime');
+ var d = py.PY_call(py.PY_getAttr(dt_class, 'utcnow'));
+ return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
+});
+
+var args = _.map(('year month day hour minute second '
+ + 'years months weeks days hours minutes seconds '
+ + 'weekday leapdays yearday nlyearday').split(' '), function (arg) {
+ switch (arg) {
+ case 'years':case 'months':case 'days':case 'leapdays':case 'weeks':
+ case 'hours':case 'minutes':case 'seconds':
+ return [arg, zero];
+ case 'year':case 'month':case 'day':case 'weekday':
+ case 'hour':case 'minute':case 'second':
+ case 'yearday':case 'nlyearday':
+ return [arg, null];
+ default:
+ throw new Error("Unknown relativedelta argument " + arg);
+ }
+});
+args.unshift('*');
+
+var _utils = {
+ monthrange: function (year, month) {
+ if (month < 1 || month > 12) {
+ throw new Error("Illegal month " + month);
+ }
+
+ var day1 = this.weekday(year, month, 1);
+ var ndays = this.mdays[month] + (month == this.February && this.isleap(year));
+ return [day1, ndays];
+ },
+ weekday: function (year, month, day) {
+ var date = py.PY_call(datetime.date, [year, month, day]);
+ return py.PY_call(py.PY_getAttr(date, 'weekday'));
+ },
+ isleap: function (year) {
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
+ },
+ mdays: [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+ January: 1,
+ February: 2
+};
+
+var relativedelta = py.type('relativedelta', null, {
+ __init__: function () {
+ this.ops = py.PY_parseArgs(arguments, args);
+ this.ops.days = py.float.fromJSON(
+ asJS(this.ops.days) + asJS(this.ops.weeks) * 7
+ );
+
+ var yday = zero;
+ if (this.ops.nlyearday) {
+ yday = this.ops.nlyearday;
+ } else if (this.ops.yearday) {
+ yday = this.ops.yearday;
+ if (asJS(this.ops.yearday) > 59) {
+ this.ops.leapdays = py.float.fromJS(-1);
+ }
+ }
+ if (py.PY_isTrue(yday)) {
+ var ydayidx = [31, 59, 90, 120, 151, 181, 212,
+ 243, 273, 304, 334, 366];
+ for(var idx=0; idx<ydayidx.length; ++idx) {
+ var ydays = ydayidx[idx];
+ if (asJS(yday) <= ydays) {
+ this.ops.month = py.float.fromJSON(idx+1);
+ if (!idx) {
+ this.ops.day = yday;
+ } else {
+ this.ops.day = py.PY_subtract(
+ yday,
+ py.float.fromJSON(ydayidx[idx-1])
+ );
+ }
+ break;
+ }
+ }
+ if (idx === ydayidx.length) {
+ throw new Error("Invalid year day (" + asJS(yday) + ")");
+ }
+ }
+ this._fix();
+ },
+ _fix: function () {
+ var self = this;
+ var months = asJS(this.ops.months);
+ if (Math.abs(months) > 11) {
+ var s = months > 0 ? 1 : -1;
+ divmod(months * s, 12, function (years, months) {
+ self.ops.months = py.float.fromJSON(months*s);
+ self.ops.years = py.float.fromJSON(
+ asJS(self.ops.years) + years*s);
+ });
+ }
+ this._has_time = 0;
+ },
+ __add__: function (other) {
+ if (!(py.PY_isInstance(other, datetime.date) ||
+ py.PY_isInstance(other, datetime.datetime))) {
+ return py.NotImplemented;
+ }
+ // TODO: test this whole mess
+ var year = (asJS(this.ops.year) || asJS(other.year)) + asJS(this.ops.years);
+ var month = asJS(this.ops.month) || asJS(other.month);
+ var months;
+ if (months = asJS(this.ops.months)) {
+ if (Math.abs(months) < 1 || Math.abs(months) > 12) {
+ throw new Error("Can only use relative months between -12 and +12");
+ }
+ month += months;
+ if (month > 12) {
+ year += 1;
+ month -= 12;
+ }
+ if (month < 1) {
+ year -= 1;
+ month += 12;
+ }
+ }
+
+ var day = Math.min(_utils.monthrange(year, month)[1],
+ asJS(this.ops.day) || asJS(other.day));
+
+ var repl = {
+ year: py.float.fromJSON(year),
+ month: py.float.fromJSON(month),
+ day: py.float.fromJSON(day)
+ };
+
+ if (py.PY_isInstance(other, datetime.datetime)) {
+ repl.hour = py.float.fromJSON(asJS(this.ops.hour) || asJS(other.hour));
+ repl.minute = py.float.fromJSON(asJS(this.ops.minute) || asJS(other.minute));
+ repl.second = py.float.fromJSON(asJS(this.ops.second) || asJS(other.second));
+ }
+
+ var days = asJS(this.ops.days);
+ if (py.PY_isTrue(this.ops.leapdays) && month > 2 && _utils.isleap(year)) {
+ days += asJS(this.ops.leapdays);
+ }
+
+ var ret = py.PY_add(
+ py.PY_call(py.PY_getAttr(other, 'replace'), repl),
+ py.PY_call(datetime.timedelta, {
+ days: py.float.fromJSON(days),
+ hours: py.float.fromJSON(asJS(this.ops.hours)),
+ minutes: py.float.fromJSON(asJS(this.ops.minutes)),
+ seconds: py.float.fromJSON(asJS(this.ops.seconds))
+ })
+ );
+
+ if (this.ops.weekday) {
+ // FIXME: only handles numeric weekdays, not decorated
+ var weekday = asJS(this.ops.weekday), nth = 1;
+ var jumpdays = (Math.abs(nth) - 1) * 7;
+
+ var ret_weekday = asJS(py.PY_call(py.PY_getAttr(ret, 'weekday')));
+ if (nth > 0) {
+ jumpdays += (7-ret_weekday+weekday) % 7;
+ } else {
+ jumpdays += (ret_weekday - weekday) % 7;
+ jumpdays *= -1;
+ }
+ ret = py.PY_add(
+ ret,
+ py.PY_call(datetime.timedelta, {
+ days: py.float.fromJSON(jumpdays)
+ })
+ );
+ }
+
+ return ret;
+ },
+ __radd__: function (other) {
+ return this.__add__(other);
+ },
+ __rsub__: function (other) {
+ return this.__neg__().__radd__(other);
+ },
+ __neg__: function () {
+ return py.PY_call(relativedelta, {
+ years: py.PY_negative(this.ops.years),
+ months: py.PY_negative(this.ops.months),
+ days: py.PY_negative(this.ops.days),
+ leapdays: this.ops.leapdays,
+ hours: py.PY_negative(this.ops.hours),
+ minutes: py.PY_negative(this.ops.minutes),
+ seconds: py.PY_negative(this.ops.seconds),
+ year: this.ops.year,
+ month: this.ops.month,
+ day: this.ops.day,
+ weekday: this.ops.weekday,
+ hour: this.ops.hour,
+ minute: this.ops.minute,
+ second: this.ops.second
+ });
+ }
+});
+
+py.extras = {
+ datetime: datetime,
+ time: time,
+ relativedelta: relativedelta,
+};
+
+})(typeof exports === 'undefined' ? py : exports);