[REF] web,*: remove legacy Domain, Context and pyjs
This commit removes the legacy implementations of Domain, Context and the pyjs library. All of them exist in the new codebase, and the few pieces of code that were still using the legacy ones have been adapted. Part of task~3439226 closes odoo/odoo#130647 Related: odoo/enterprise#45129 Signed-off-by: Jorge Pinna Puissant (jpp) <jpp@odoo.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Domain } from '@web/core/domain';
|
||||
import { registry } from '@web/core/registry';
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Many2OneField, many2OneField } from '@web/views/fields/many2one/many2one_field';
|
||||
import Domain from '@web/legacy/js/core/domain';
|
||||
|
||||
const { useState, useEffect } = owl;
|
||||
|
||||
|
||||
@@ -75,8 +75,6 @@
|
||||
'web/static/src/env.js',
|
||||
|
||||
'web/static/lib/jquery.scrollTo/jquery.scrollTo.js',
|
||||
'web/static/lib/py.js/lib/py.js',
|
||||
'web/static/lib/py.js/lib/py_extras.js',
|
||||
'web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js',
|
||||
|
||||
'web/static/src/legacy/scss/fields.scss',
|
||||
@@ -97,9 +95,6 @@
|
||||
'web/static/src/legacy/root_widget.js',
|
||||
'web/static/src/legacy/backend_utils.js',
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/js/core/domain.js',
|
||||
'web/static/src/legacy/js/core/py_utils.js',
|
||||
'web/static/src/legacy/js/core/context.js',
|
||||
'web/static/src/legacy/js/core/dates.js',
|
||||
'web/static/src/legacy/js/core/misc.js',
|
||||
'web/static/src/legacy/js/services/session.js',
|
||||
|
||||
@@ -168,7 +168,6 @@
|
||||
'web/static/lib/jquery/jquery.browser.js',
|
||||
'web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js',
|
||||
'web/static/lib/qweb/qweb2.js',
|
||||
'web/static/lib/py.js/lib/py.js',
|
||||
'web/static/src/legacy/js/**/*',
|
||||
('remove', 'web/static/src/legacy/js/libs/**/*'),
|
||||
('remove', 'web/static/src/legacy/js/public/**/*'),
|
||||
|
||||
@@ -128,8 +128,6 @@
|
||||
'web/static/src/env.js',
|
||||
|
||||
'web/static/lib/jquery.scrollTo/jquery.scrollTo.js',
|
||||
'web/static/lib/py.js/lib/py.js',
|
||||
'web/static/lib/py.js/lib/py_extras.js',
|
||||
'web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js',
|
||||
|
||||
'web/static/src/legacy/scss/fields.scss',
|
||||
@@ -148,9 +146,6 @@
|
||||
'web/static/src/legacy/root_widget.js',
|
||||
'web/static/src/legacy/backend_utils.js',
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/js/core/domain.js',
|
||||
'web/static/src/legacy/js/core/py_utils.js',
|
||||
'web/static/src/legacy/js/core/context.js',
|
||||
'web/static/src/legacy/js/core/dates.js',
|
||||
'web/static/src/legacy/js/core/misc.js',
|
||||
'web/static/src/legacy/js/services/session.js',
|
||||
|
||||
@@ -129,7 +129,6 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/legacy/js/core/browser_detection.js',
|
||||
'web/static/src/legacy/js/core/bus.js',
|
||||
'web/static/src/legacy/js/core/class.js',
|
||||
'web/static/src/legacy/js/core/collections.js',
|
||||
'web/static/src/legacy/js/core/concurrency.js',
|
||||
'web/static/src/legacy/js/core/dialog.js',
|
||||
'web/static/src/legacy/xml/dialog.xml',
|
||||
@@ -194,8 +193,6 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/env.js',
|
||||
|
||||
'web/static/lib/jquery.scrollTo/jquery.scrollTo.js',
|
||||
'web/static/lib/py.js/lib/py.js',
|
||||
'web/static/lib/py.js/lib/py_extras.js',
|
||||
'web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js',
|
||||
|
||||
'web/static/src/scss/ace.scss',
|
||||
@@ -220,9 +217,6 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/legacy/root_widget.js',
|
||||
'web/static/src/legacy/backend_utils.js',
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/js/core/domain.js',
|
||||
'web/static/src/legacy/js/core/py_utils.js',
|
||||
'web/static/src/legacy/js/core/context.js',
|
||||
'web/static/src/legacy/js/core/dates.js',
|
||||
'web/static/src/legacy/js/core/misc.js',
|
||||
'web/static/src/legacy/js/services/session.js',
|
||||
@@ -339,7 +333,6 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/legacy/js/core/browser_detection.js',
|
||||
'web/static/src/legacy/js/core/bus.js',
|
||||
'web/static/src/legacy/js/core/class.js',
|
||||
'web/static/src/legacy/js/core/collections.js',
|
||||
'web/static/src/legacy/js/core/concurrency.js',
|
||||
'web/static/src/legacy/js/core/dialog.js',
|
||||
'web/static/src/legacy/xml/dialog.xml',
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
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.
|
||||
@@ -1,196 +0,0 @@
|
||||
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.
|
||||
@@ -1,46 +0,0 @@
|
||||
* 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
|
||||
@@ -1,153 +0,0 @@
|
||||
# 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."
|
||||
@@ -1,55 +0,0 @@
|
||||
.. 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
|
||||
@@ -1,247 +0,0 @@
|
||||
# -*- 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'
|
||||
@@ -1,64 +0,0 @@
|
||||
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.
|
||||
@@ -1,161 +0,0 @@
|
||||
.. 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
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
@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
|
||||
@@ -1,248 +0,0 @@
|
||||
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.
|
||||
@@ -1,248 +0,0 @@
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,998 +0,0 @@
|
||||
(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 = `${hh.toFixed(0)}:${mm.toFixed(0).padStart(2,"0")}:${ss.toFixed(0).padStart(2,"0")}`
|
||||
if (this.days) {
|
||||
s = `${this.days.toFixed(0)} day${
|
||||
this.days != 1 && this.days != -1 ? "s" : ""
|
||||
}, ${s}`;
|
||||
}
|
||||
if (this.microseconds) {
|
||||
s = `${s}.${this.microseconds.toFixed(0).padStart(6, "0")}`;
|
||||
}
|
||||
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 self.year.toFixed(0).padStart(4, "0");
|
||||
case "m":
|
||||
return self.month.toFixed(0).padStart(2, "0");
|
||||
case "d":
|
||||
return self.day.toFixed(0).padStart(2, "0");
|
||||
case "H":
|
||||
return self.hour.toFixed(0).padStart(2, "0");
|
||||
case "M":
|
||||
return self.minute.toFixed(0).padStart(2, "0");
|
||||
case "S":
|
||||
return self.second.toFixed(0).padStart(2, "0");
|
||||
}
|
||||
throw new Error('ValueError: No known conversion for ' + m);
|
||||
}));
|
||||
},
|
||||
to_utc: function () {
|
||||
var d = new Date(this.year, this.month - 1, 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 self.month.toFixed(0).padStart(2, "0");
|
||||
case 'd': return self.day.toFixed(0).padStart(2, "0");
|
||||
}
|
||||
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 = (
|
||||
'year month day hour minute second '+
|
||||
'years months weeks days hours minutes seconds '+
|
||||
'weekday leapdays yearday nlyearday'
|
||||
)
|
||||
.split(" ")
|
||||
.map( (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);
|
||||
@@ -16,8 +16,8 @@ export { formatAST } from "./py_utils";
|
||||
|
||||
/**
|
||||
* Parses an expression into a valid AST representation
|
||||
|
||||
* @param {string} expr
|
||||
|
||||
* @param {string} expr
|
||||
* @returns { AST }
|
||||
*/
|
||||
export function parseExpr(expr) {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import Class from "@web/legacy/js/core/class";
|
||||
|
||||
/**
|
||||
* Allows to build a tree representation of a data.
|
||||
*/
|
||||
var Tree = Class.extend({
|
||||
/**
|
||||
* @constructor
|
||||
* @param {*} data - the data associated to the root node
|
||||
*/
|
||||
init: function (data) {
|
||||
this._data = data;
|
||||
this._children = [];
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the root's associated data.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getData: function () {
|
||||
return this._data;
|
||||
},
|
||||
/**
|
||||
* Adds a child tree.
|
||||
*
|
||||
* @param {Tree} tree
|
||||
*/
|
||||
addChild: function (tree) {
|
||||
this._children.push(tree);
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
Tree: Tree,
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import Class from "@web/legacy/js/core/class";
|
||||
import pyUtils from "@web/legacy/js/core/py_utils";
|
||||
|
||||
var Context = Class.extend({
|
||||
init: function () {
|
||||
this.__ref = "compound_context";
|
||||
this.__contexts = [];
|
||||
this.__eval_context = null;
|
||||
var self = this;
|
||||
Object.values(arguments || {}).forEach((x) => self.add(x));
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
add: function (context) {
|
||||
this.__contexts.push(context);
|
||||
return this;
|
||||
},
|
||||
eval: function () {
|
||||
return pyUtils.eval('context', this);
|
||||
},
|
||||
/**
|
||||
* Set the evaluation context to be used when we actually eval.
|
||||
*
|
||||
* @param {Object} evalContext
|
||||
* @returns {Context}
|
||||
*/
|
||||
set_eval_context: function (evalContext) {
|
||||
// a special case needs to be done for moment objects. Dates are
|
||||
// internally represented by a moment object, but they need to be
|
||||
// converted to the server format before being sent. We call the toJSON
|
||||
// method, because it returns the date with the format required by the
|
||||
// server
|
||||
for (var key in evalContext) {
|
||||
if (evalContext[key] instanceof moment) {
|
||||
evalContext[key] = evalContext[key].toJSON();
|
||||
}
|
||||
}
|
||||
this.__eval_context = evalContext;
|
||||
return this;
|
||||
},
|
||||
});
|
||||
|
||||
export default Context;
|
||||
@@ -1,422 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { intersection } from "@web/core/utils/arrays";
|
||||
import collections from "@web/legacy/js/core/collections";
|
||||
import pyUtils from "@web/legacy/js/core/py_utils";
|
||||
var py = window.py; // look py.js
|
||||
|
||||
const TRUE_LEAF = [1, '=', 1];
|
||||
const FALSE_LEAF = [0, '=', 1];
|
||||
const TRUE_DOMAIN = [TRUE_LEAF];
|
||||
const FALSE_DOMAIN = [FALSE_LEAF];
|
||||
|
||||
function compare(a, b) {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Domain Class allows to work with a domain as a tree and provides tools
|
||||
* to manipulate array and string representations of domains.
|
||||
*/
|
||||
var Domain = collections.Tree.extend({
|
||||
/**
|
||||
* @constructor
|
||||
* @param {string|Array|boolean|undefined} domain
|
||||
* The given domain can be:
|
||||
* * a string representation of the Python prefix-array
|
||||
* representation of the domain.
|
||||
* * a JS prefix-array representation of the domain.
|
||||
* * a boolean where the "true" domain match all records and the
|
||||
* "false" domain does not match any records.
|
||||
* * undefined, considered as the false boolean.
|
||||
* * a number, considered as true except 0 considered as false.
|
||||
* @param {Object} [evalContext] - in case the given domain is a string, an
|
||||
* evaluation context might be needed
|
||||
*/
|
||||
init: function (domain, evalContext) {
|
||||
this._super.apply(this, arguments);
|
||||
if (Array.isArray(domain) || typeof domain === "string") {
|
||||
this._parse(this.normalizeArray([...this.stringToArray(domain, evalContext)]));
|
||||
} else {
|
||||
this._data = !!domain;
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Evaluates the domain with a set of values.
|
||||
*
|
||||
* @param {Object} values - a mapping {fieldName -> fieldValue} (note: all
|
||||
* the fields used in the domain should be given a
|
||||
* value otherwise the computation will break)
|
||||
* @returns {boolean}
|
||||
*/
|
||||
compute: function (values) {
|
||||
if (this._data === true || this._data === false) {
|
||||
// The domain is a always-true or a always-false domain
|
||||
return this._data;
|
||||
} else if (Array.isArray(this._data)) {
|
||||
// The domain is a [name, operator, value] entity
|
||||
// First check if we have the field value in the field values set
|
||||
// and if the first part of the domain contains 'parent.field'
|
||||
// get the value from the parent record.
|
||||
var isParentField = false;
|
||||
var fieldName = this._data[0];
|
||||
// We split the domain first part and check if it's a match
|
||||
// for the syntax 'parent.field'.
|
||||
|
||||
let fieldValue;
|
||||
if (compare(this._data, FALSE_LEAF) || compare(this._data, TRUE_LEAF)) {
|
||||
fieldValue = this._data[0];
|
||||
} else {
|
||||
var parentField = this._data[0].split('.');
|
||||
if ('parent' in values && parentField.length === 2) {
|
||||
fieldName = parentField[1];
|
||||
isParentField = parentField[0] === 'parent' &&
|
||||
fieldName in values.parent;
|
||||
}
|
||||
if (!(this._data[0] in values) && !(isParentField)) {
|
||||
throw new Error(`Unknown field ${this._data[0]} in domain`);
|
||||
}
|
||||
fieldValue = isParentField ? values.parent[fieldName] : values[fieldName];
|
||||
}
|
||||
|
||||
switch (this._data[1]) {
|
||||
case "=":
|
||||
case "==":
|
||||
return JSON.stringify(fieldValue) === JSON.stringify(this._data[2]);
|
||||
case "!=":
|
||||
case "<>":
|
||||
return JSON.stringify(fieldValue) !== JSON.stringify(this._data[2]);
|
||||
case "<":
|
||||
return (fieldValue < this._data[2]);
|
||||
case ">":
|
||||
return (fieldValue > this._data[2]);
|
||||
case "<=":
|
||||
return (fieldValue <= this._data[2]);
|
||||
case ">=":
|
||||
return (fieldValue >= this._data[2]);
|
||||
case "in":
|
||||
return intersection(
|
||||
Array.isArray(this._data[2]) ? this._data[2] : [this._data[2]],
|
||||
Array.isArray(fieldValue) ? fieldValue : [fieldValue],
|
||||
).length !== 0;
|
||||
case "not in":
|
||||
return intersection(
|
||||
Array.isArray(this._data[2]) ? this._data[2] : [this._data[2]],
|
||||
Array.isArray(fieldValue) ? fieldValue : [fieldValue],
|
||||
).length === 0;
|
||||
case "like":
|
||||
if (fieldValue === false) {
|
||||
return false;
|
||||
}
|
||||
return (fieldValue.indexOf(this._data[2]) >= 0);
|
||||
case "=like":
|
||||
if (fieldValue === false) {
|
||||
return false;
|
||||
}
|
||||
return new RegExp(this._data[2].replace(/%/g, '.*')).test(fieldValue);
|
||||
case "ilike":
|
||||
if (fieldValue === false) {
|
||||
return false;
|
||||
}
|
||||
return (fieldValue.toLowerCase().indexOf(this._data[2].toLowerCase()) >= 0);
|
||||
case "=ilike":
|
||||
if (fieldValue === false) {
|
||||
return false;
|
||||
}
|
||||
return new RegExp(this._data[2].replace(/%/g, '.*'), 'i').test(fieldValue);
|
||||
default:
|
||||
throw new Error(`Domain ${this._data} uses an unsupported operator`);
|
||||
}
|
||||
} else { // The domain is a set of [name, operator, value] entitie(s)
|
||||
switch (this._data) {
|
||||
case "&":
|
||||
return this._children.every(child => child.compute(values));
|
||||
case "|":
|
||||
return this._children.some(child => child.compute(values));
|
||||
case "!":
|
||||
return !this._children[0].compute(values);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return the JS prefix-array representation of this domain. Note that all
|
||||
* domains that use the "false" domain cannot be represented as such.
|
||||
*
|
||||
* @returns {Array} JS prefix-array representation of this domain
|
||||
*/
|
||||
toArray: function () {
|
||||
if (this._data === false) {
|
||||
throw new Error("'false' domain cannot be converted to array");
|
||||
} else if (this._data === true) {
|
||||
return [];
|
||||
} else {
|
||||
var arr = [this._data];
|
||||
return arr.concat.apply(
|
||||
arr,
|
||||
this._children.map((child) => child.toArray())
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @returns {string} representation of the Python prefix-array
|
||||
* representation of the domain
|
||||
*/
|
||||
toString: function () {
|
||||
return Domain.prototype.arrayToString(this.toArray());
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Initializes the tree representation of the domain according to its given
|
||||
* JS prefix-array representation. Note: the given array is considered
|
||||
* already normalized.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} domain - normalized JS prefix-array representation of
|
||||
* the domain
|
||||
*/
|
||||
_parse: function (domain) {
|
||||
this._data = (domain.length === 0 ? true : domain[0]);
|
||||
if (domain.length <= 1) return;
|
||||
|
||||
var expected = 1;
|
||||
for (var i = 1 ; i < domain.length ; i++) {
|
||||
if (domain[i] === "&" || domain[i] === "|") {
|
||||
expected++;
|
||||
} else if (domain[i] !== "!") {
|
||||
expected--;
|
||||
}
|
||||
|
||||
if (!expected) {
|
||||
i++;
|
||||
this._addSubdomain(domain.slice(1, i));
|
||||
this._addSubdomain(domain.slice(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a domain as a child (e.g. if the current domain is ["|", A, B],
|
||||
* using this method with a ["&", C, D] domain will result in a
|
||||
* ["|", "|", A, B, "&", C, D]).
|
||||
* Note: the internal tree representation is automatically simplified.
|
||||
*
|
||||
* @param {Array} domain - normalized JS prefix-array representation of a
|
||||
* domain to add
|
||||
*/
|
||||
_addSubdomain: function (domain) {
|
||||
if (!domain.length) return;
|
||||
var subdomain = new Domain(domain);
|
||||
|
||||
if (!subdomain._children.length || subdomain._data !== this._data) {
|
||||
this._children.push(subdomain);
|
||||
} else {
|
||||
var self = this;
|
||||
subdomain._children.forEach((childDomain) => {
|
||||
self._children.push(childDomain);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Static
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Converts JS prefix-array representation of a domain to a string
|
||||
* representation of the Python prefix-array representation of this domain.
|
||||
*
|
||||
* @static
|
||||
* @param {Array|string|undefined} domain
|
||||
* @returns {string}
|
||||
*/
|
||||
arrayToString: function (domain) {
|
||||
if (typeof domain === "string") return domain;
|
||||
|
||||
function jsToPy(p) {
|
||||
switch (p) {
|
||||
case null: return "None";
|
||||
case true: return "True";
|
||||
case false: return "False";
|
||||
default:
|
||||
if (Array.isArray(p)) {
|
||||
return `[${p.map(jsToPy)}]`;
|
||||
}
|
||||
return JSON.stringify(p);
|
||||
}
|
||||
}
|
||||
|
||||
return `[${(domain || []).map(jsToPy)}]`;
|
||||
},
|
||||
/**
|
||||
* Converts a string representation of the Python prefix-array
|
||||
* representation of a domain to a JS prefix-array representation of this
|
||||
* domain.
|
||||
*
|
||||
* @static
|
||||
* @param {string|Array} domain
|
||||
* @param {Object} [evalContext]
|
||||
* @returns {Array}
|
||||
*/
|
||||
stringToArray: function (domain, evalContext) {
|
||||
if (typeof domain !== "string") {
|
||||
return [...domain];
|
||||
}
|
||||
return pyUtils.eval("domain", domain ? domain.replace(/%%/g, '%') : "[]", evalContext);
|
||||
},
|
||||
/**
|
||||
* Makes implicit "&" operators explicit in the given JS prefix-array
|
||||
* representation of domain (e.g [A, B] -> ["&", A, B])
|
||||
*
|
||||
* @static
|
||||
* @param {Array} domain - the JS prefix-array representation of the domain
|
||||
* to normalize (! will be normalized in-place)
|
||||
* @returns {Array} the normalized JS prefix-array representation of the
|
||||
* given domain
|
||||
* @throws {Error} if the domain is invalid and can't be normalised
|
||||
*/
|
||||
normalizeArray: function (domain) {
|
||||
if (domain.length === 0) { return domain; }
|
||||
var expected = 1;
|
||||
domain.forEach(item => {
|
||||
if (item === "&" || item === "|") {
|
||||
expected++;
|
||||
} else if (item !== "!") {
|
||||
expected--;
|
||||
}
|
||||
});
|
||||
if (expected < 0) {
|
||||
domain.unshift.apply(domain, new Array(Math.abs(expected)).fill("&"));
|
||||
} else if (expected > 0) {
|
||||
throw new Error(`invalid domain ${JSON.stringify(domain)} (missing ${expected.toFixed(0)} segment(s))`);
|
||||
}
|
||||
return domain;
|
||||
},
|
||||
/**
|
||||
* Converts JS prefix-array representation of a domain to a python condition
|
||||
*
|
||||
* @static
|
||||
* @param {Array} domain
|
||||
* @returns {string}
|
||||
*/
|
||||
domainToCondition: function (domain) {
|
||||
if (!domain.length) {
|
||||
return 'True';
|
||||
}
|
||||
function consume(stack) {
|
||||
var len = stack.length;
|
||||
if (len <= 1) {
|
||||
return stack;
|
||||
} else if (stack[len-1] === '|' || stack[len-1] === '&' || stack[len-2] === '|' || stack[len-2] === '&') {
|
||||
return stack;
|
||||
} else if (len == 2) {
|
||||
stack.splice(-2, 2, stack[len-2] + ' and ' + stack[len-1]);
|
||||
} else if (stack[len-3] == '|') {
|
||||
if (len === 3) {
|
||||
stack.splice(-3, 3, stack[len-2] + ' or ' + stack[len-1]);
|
||||
} else {
|
||||
stack.splice(-3, 3, '(' + stack[len-2] + ' or ' + stack[len-1] + ')');
|
||||
}
|
||||
} else {
|
||||
stack.splice(-3, 3, stack[len-2] + ' and ' + stack[len-1]);
|
||||
}
|
||||
consume(stack);
|
||||
}
|
||||
|
||||
var stack = [];
|
||||
domain.forEach((dom) => {
|
||||
if (dom === '|' || dom === '&') {
|
||||
stack.push(dom);
|
||||
} else {
|
||||
var operator = dom[1] === '=' ? '==' : dom[1];
|
||||
if (!operator) {
|
||||
throw new Error('Wrong operator for this domain');
|
||||
}
|
||||
if (operator === '!=' && dom[2] === false) { // the field is set
|
||||
stack.push(dom[0]);
|
||||
} else if (dom[2] === null || dom[2] === true || dom[2] === false) {
|
||||
stack.push(dom[0] + ' ' + (operator === '!=' ? 'is not ' : 'is ') + (dom[2] === null ? 'None' : (dom[2] ? 'True' : 'False')));
|
||||
} else {
|
||||
stack.push(dom[0] + ' ' + operator + ' ' + JSON.stringify(dom[2]));
|
||||
}
|
||||
consume(stack);
|
||||
}
|
||||
});
|
||||
|
||||
if (stack.length !== 1) {
|
||||
throw new Error('Wrong domain');
|
||||
}
|
||||
|
||||
return stack[0];
|
||||
},
|
||||
/**
|
||||
* Converts python condition to a JS prefix-array representation of a domain
|
||||
*
|
||||
* @static
|
||||
* @param {string} condition
|
||||
* @returns {Array}
|
||||
*/
|
||||
conditionToDomain: function (condition) {
|
||||
if (!condition || condition.match(/^\s*(True)?\s*$/)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var ast = py.parse(py.tokenize(condition));
|
||||
|
||||
|
||||
function astToStackValue (node) {
|
||||
switch (node.id) {
|
||||
case '(name)': return node.value;
|
||||
case '.': return astToStackValue(node.first) + '.' + astToStackValue(node.second);
|
||||
case '(string)': return node.value;
|
||||
case '(number)': return node.value;
|
||||
case '(constant)': return node.value === 'None' ? null : node.value === 'True' ? true : false;
|
||||
case '(':
|
||||
case '[': return node.first.map((node) => astToStackValue(node));
|
||||
}
|
||||
}
|
||||
function astToStack (node) {
|
||||
switch (node.id) {
|
||||
case '(name)': return [[astToStackValue(node), '!=', false]];
|
||||
case '.': return [[astToStackValue(node.first) + '.' + astToStackValue(node.second), '!=', false]];
|
||||
case 'not': return [[astToStackValue(node.first), '=', false]];
|
||||
|
||||
case 'or': return ['|'].concat(astToStack(node.first)).concat(astToStack(node.second));
|
||||
case 'and': return ['&'].concat(astToStack(node.first)).concat(astToStack(node.second));
|
||||
case '(comparator)':
|
||||
if (node.operators.length !== 1) {
|
||||
throw new Error('Wrong condition to convert in domain');
|
||||
}
|
||||
var right = astToStackValue(node.expressions[0]);
|
||||
var left = astToStackValue(node.expressions[1]);
|
||||
var operator = node.operators[0];
|
||||
switch (operator) {
|
||||
case 'is': operator = '='; break;
|
||||
case 'is not': operator = '!='; break;
|
||||
case '==': operator = '='; break;
|
||||
}
|
||||
return [[right, operator, left]];
|
||||
default:
|
||||
throw "Condition cannot be transformed into domain";
|
||||
}
|
||||
}
|
||||
|
||||
return astToStack(ast);
|
||||
},
|
||||
});
|
||||
|
||||
Domain.TRUE_LEAF = TRUE_LEAF;
|
||||
Domain.FALSE_LEAF = FALSE_LEAF;
|
||||
Domain.TRUE_DOMAIN = TRUE_DOMAIN;
|
||||
Domain.FALSE_DOMAIN = FALSE_DOMAIN;
|
||||
|
||||
export default Domain;
|
||||
@@ -1,558 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import core from "@web/legacy/js/services/core";
|
||||
|
||||
var _t = core._t;
|
||||
var py = window.py; // to silence linters
|
||||
|
||||
// recursively wraps JS objects passed into the context to attributedicts
|
||||
// which jsonify back to JS objects
|
||||
function wrap(value) {
|
||||
if (value === null) { return py.None; }
|
||||
|
||||
switch (typeof value) {
|
||||
case 'undefined': throw new Error("No conversion for undefined");
|
||||
case 'boolean': return py.bool.fromJSON(value);
|
||||
case 'number': return py.float.fromJSON(value);
|
||||
case 'string': return py.str.fromJSON(value);
|
||||
}
|
||||
|
||||
switch(value.constructor) {
|
||||
case Object: return wrapping_dict.fromJSON(value);
|
||||
case Array: return wrapping_list.fromJSON(value);
|
||||
}
|
||||
|
||||
throw new Error("ValueError: unable to wrap " + value);
|
||||
}
|
||||
|
||||
var wrapping_dict = py.type('wrapping_dict', null, {
|
||||
__init__: function () {
|
||||
this._store = {};
|
||||
},
|
||||
__getitem__: function (key) {
|
||||
var k = key.toJSON();
|
||||
if (!(k in this._store)) {
|
||||
throw new Error("KeyError: '" + k + "'");
|
||||
}
|
||||
return wrap(this._store[k]);
|
||||
},
|
||||
__getattr__: function (key) {
|
||||
return this.__getitem__(py.str.fromJSON(key));
|
||||
},
|
||||
__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]]);
|
||||
|
||||
if (!(args.k.toJSON() in this._store)) { return args.d; }
|
||||
return this.__getitem__(args.k);
|
||||
},
|
||||
fromJSON: function (d) {
|
||||
var instance = py.PY_call(wrapping_dict);
|
||||
instance._store = d;
|
||||
return instance;
|
||||
},
|
||||
toJSON: function () {
|
||||
return this._store;
|
||||
},
|
||||
});
|
||||
|
||||
var wrapping_list = py.type('wrapping_list', null, {
|
||||
__init__: function () {
|
||||
this._store = [];
|
||||
},
|
||||
__getitem__: function (index) {
|
||||
return wrap(this._store[index.toJSON()]);
|
||||
},
|
||||
__len__: function () {
|
||||
return this._store.length;
|
||||
},
|
||||
__nonzero__: function () {
|
||||
return py.PY_size(this) > 0 ? py.True : py.False;
|
||||
},
|
||||
fromJSON: function (ar) {
|
||||
var instance = py.PY_call(wrapping_list);
|
||||
instance._store = ar;
|
||||
return instance;
|
||||
},
|
||||
toJSON: function () {
|
||||
return this._store;
|
||||
},
|
||||
});
|
||||
|
||||
function wrap_context(context) {
|
||||
for (var k in context) {
|
||||
if (!context.hasOwnProperty(k)) { continue; }
|
||||
var val = context[k];
|
||||
// Don't add a test case like ``val === undefined``
|
||||
// this is intended to prevent letting crap pass
|
||||
// on the context without even knowing it.
|
||||
// If you face an issue from here, try to sanitize
|
||||
// the context upstream instead
|
||||
if (val === null) { continue; }
|
||||
if (val.constructor === Array) {
|
||||
context[k] = wrapping_list.fromJSON(val);
|
||||
} else if (val.constructor === Object
|
||||
&& !py.PY_isInstance(val, py.object)) {
|
||||
context[k] = wrapping_dict.fromJSON(val);
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
function eval_contexts(contexts, evaluation_context) {
|
||||
evaluation_context = Object.assign(pycontext(), evaluation_context || {});
|
||||
return contexts.reduce((result_context, ctx) => {
|
||||
// __eval_context evaluations can lead to some of `contexts`'s
|
||||
// values being null, skip them as well as empty contexts
|
||||
if (Object.keys(ctx || {}).length === 0) { return result_context; }
|
||||
if (typeof ctx === "string") {
|
||||
// wrap raw strings in context
|
||||
ctx = { __ref: 'context', __debug: ctx };
|
||||
}
|
||||
var evaluated = ctx;
|
||||
switch(ctx.__ref) {
|
||||
case 'context':
|
||||
evaluation_context.context = evaluation_context;
|
||||
evaluated = py.eval(ctx.__debug, wrap_context(evaluation_context));
|
||||
break;
|
||||
case 'compound_context':
|
||||
var eval_context = eval_contexts([ctx.__eval_context]);
|
||||
evaluated = eval_contexts(
|
||||
ctx.__contexts, Object.assign({}, evaluation_context, eval_context));
|
||||
break;
|
||||
}
|
||||
// add newly evaluated context to evaluation context for following
|
||||
// siblings
|
||||
Object.assign(evaluation_context, evaluated);
|
||||
return Object.assign(result_context, evaluated);
|
||||
}, {});
|
||||
}
|
||||
|
||||
function eval_domains(domains, evaluation_context) {
|
||||
evaluation_context = Object.assign(pycontext(), evaluation_context || {});
|
||||
var result_domain = [];
|
||||
// Normalize only if the first domain is the array ["|"] or ["!"]
|
||||
var need_normalization = (
|
||||
domains &&
|
||||
domains.length > 0 &&
|
||||
domains[0].length === 1 &&
|
||||
(domains[0][0] === "|" || domains[0][0] === "!")
|
||||
);
|
||||
(domains || []).forEach(function (domain) {
|
||||
if (typeof domain === "string") {
|
||||
// wrap raw strings in domain
|
||||
domain = { __ref: 'domain', __debug: domain };
|
||||
}
|
||||
var domain_array_to_combine;
|
||||
switch(domain.__ref) {
|
||||
case 'domain':
|
||||
evaluation_context.context = evaluation_context;
|
||||
domain_array_to_combine = py.eval(domain.__debug, wrap_context(evaluation_context));
|
||||
break;
|
||||
default:
|
||||
domain_array_to_combine = domain;
|
||||
}
|
||||
if (need_normalization) {
|
||||
domain_array_to_combine = get_normalized_domain(domain_array_to_combine);
|
||||
}
|
||||
result_domain.push.apply(result_domain, domain_array_to_combine);
|
||||
});
|
||||
return result_domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a normalized copy of the given domain array. Normalization is
|
||||
* is making the implicit "&" at the start of the domain explicit, e.g.
|
||||
* [A, B, C] would become ["&", "&", A, B, C].
|
||||
*
|
||||
* @param {Array} domain_array
|
||||
* @returns {Array} normalized copy of the given array
|
||||
*/
|
||||
function get_normalized_domain(domain_array) {
|
||||
var expected = 1; // Holds the number of expected domain expressions
|
||||
domain_array.forEach((item) => {
|
||||
if (item === "&" || item === "|") {
|
||||
expected++;
|
||||
} else if (item !== "!") {
|
||||
expected--;
|
||||
}
|
||||
});
|
||||
var new_explicit_ands = new Array(-expected).fill("&");
|
||||
return new_explicit_ands.concat(domain_array);
|
||||
}
|
||||
|
||||
function eval_groupbys(contexts, evaluation_context) {
|
||||
evaluation_context = Object.assign(pycontext(), evaluation_context || {});
|
||||
var result_group = [];
|
||||
(contexts || []).forEach(function (ctx) {
|
||||
if (typeof ctx === "string") {
|
||||
// wrap raw strings in context
|
||||
ctx = { __ref: 'context', __debug: ctx };
|
||||
}
|
||||
var group;
|
||||
var evaluated = ctx;
|
||||
switch(ctx.__ref) {
|
||||
case 'context':
|
||||
evaluation_context.context = evaluation_context;
|
||||
evaluated = py.eval(ctx.__debug, wrap_context(evaluation_context));
|
||||
break;
|
||||
case 'compound_context':
|
||||
var eval_context = eval_contexts([ctx.__eval_context]);
|
||||
evaluated = eval_contexts(
|
||||
ctx.__contexts, Object.assign({}, evaluation_context, eval_context));
|
||||
break;
|
||||
}
|
||||
group = evaluated.group_by;
|
||||
if (!group) { return; }
|
||||
if (typeof group === 'string') {
|
||||
result_group.push(group);
|
||||
} else if (group instanceof Array) {
|
||||
result_group.push.apply(result_group, group);
|
||||
} else {
|
||||
throw new Error('Got invalid groupby {{'
|
||||
+ JSON.stringify(group) + '}}');
|
||||
}
|
||||
Object.assign(evaluation_context, evaluated);
|
||||
});
|
||||
return result_group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current local date, which means the date on the client (which can be different
|
||||
* compared to the date of the server).
|
||||
*
|
||||
* @return {datetime.date}
|
||||
*/
|
||||
function context_today() {
|
||||
var d = new Date();
|
||||
return py.PY_call(
|
||||
py.extras.datetime.date, [d.getFullYear(), d.getMonth() + 1, d.getDate()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timedelta object which represents the timezone offset between the
|
||||
* local timezone and the UTC time.
|
||||
*
|
||||
* This is very useful to generate datetime strings which are 'timezone'
|
||||
* dependant. For example, we can now write this to generate the correct
|
||||
* datetime string representing "this morning in the user timezone":
|
||||
*
|
||||
* "datetime.datetime.now().replace(hour=0,minute=0,second=0) + tz_offset()).strftime('%Y-%m-%d %H:%M:%S')"
|
||||
* @returns {datetime.timedelta}
|
||||
*/
|
||||
function tz_offset() {
|
||||
var offset= new Date().getTimezoneOffset();
|
||||
var kwargs = {minutes: py.float.fromJSON(offset)};
|
||||
return py.PY_call(py.extras.datetime.timedelta,[],kwargs);
|
||||
}
|
||||
|
||||
|
||||
function pycontext() {
|
||||
const d = new Date();
|
||||
const today = `${
|
||||
String(d.getFullYear()).padStart(4, "0")}-${
|
||||
String(d.getMonth() + 1).padStart(2, "0")}-${
|
||||
String(d.getDate()).padStart(2, "0")}`;
|
||||
const now = `${
|
||||
String(d.getUTCFullYear()).padStart(4, "0")}-${
|
||||
String(d.getUTCMonth() + 1).padStart(2, "0")}-${
|
||||
String(d.getUTCDate()).padStart(2, "0")} ${
|
||||
String(d.getUTCHours()).padStart(2, "0")}:${
|
||||
String(d.getUTCMinutes()).padStart(2, "0")}:${
|
||||
String(d.getUTCSeconds()).padStart(2, "0")}`;
|
||||
|
||||
const { datetime, relativedelta, time } = py.extras;
|
||||
return {
|
||||
current_date: today,
|
||||
datetime,
|
||||
time,
|
||||
now,
|
||||
today,
|
||||
relativedelta,
|
||||
context_today,
|
||||
tz_offset,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} type "domains", "contexts" or "groupbys"
|
||||
* @param {Array} object domains or contexts to evaluate
|
||||
* @param {Object} [context] evaluation context
|
||||
*/
|
||||
function pyeval(type, object, context) {
|
||||
context = Object.assign(pycontext(), context || {});
|
||||
|
||||
//noinspection FallthroughInSwitchStatementJS
|
||||
switch(type) {
|
||||
case 'context':
|
||||
case 'contexts':
|
||||
if (type === 'context') {
|
||||
object = [object];
|
||||
}
|
||||
return eval_contexts(object, context);
|
||||
case 'domain':
|
||||
case 'domains':
|
||||
if (type === 'domain')
|
||||
object = [object];
|
||||
return eval_domains(object, context);
|
||||
case 'groupbys':
|
||||
return eval_groupbys(object, context);
|
||||
}
|
||||
throw new Error("Unknow evaluation type " + type);
|
||||
}
|
||||
|
||||
function eval_arg(arg) {
|
||||
if (typeof arg !== 'object' || !arg.__ref) { return arg; }
|
||||
switch(arg.__ref) {
|
||||
case 'domain':
|
||||
return pyeval('domains', [arg]);
|
||||
case 'context': case 'compound_context':
|
||||
return pyeval('contexts', [arg]);
|
||||
default:
|
||||
throw new Error(_t("Unknown nonliteral type ") + ' ' + arg.__ref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If args or kwargs are unevaluated contexts or domains (compound or not),
|
||||
* evaluated them in-place.
|
||||
*
|
||||
* Potentially mutates both parameters.
|
||||
*
|
||||
* @param args
|
||||
* @param kwargs
|
||||
*/
|
||||
function ensure_evaluated(args, kwargs) {
|
||||
for (var i=0; i<args.length; ++i) {
|
||||
args[i] = eval_arg(args[i]);
|
||||
}
|
||||
for (var k in kwargs) {
|
||||
if (!kwargs.hasOwnProperty(k)) { continue; }
|
||||
kwargs[k] = eval_arg(kwargs[k]);
|
||||
}
|
||||
}
|
||||
|
||||
function eval_domains_and_contexts(source) {
|
||||
// see Session.eval_context in Python
|
||||
return {
|
||||
context: pyeval('contexts', source.contexts || [], source.eval_context),
|
||||
domain: pyeval('domains', source.domains, source.eval_context),
|
||||
group_by: pyeval('groupbys', source.group_by_seq || [], source.eval_context),
|
||||
};
|
||||
}
|
||||
|
||||
function py_eval(expr, context) {
|
||||
return py.eval(expr, Object.assign({}, context || {}, {"true": true, "false": false, "null": null}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble domains into a single domains using an 'OR' or an 'AND' operator.
|
||||
*
|
||||
* .. note:
|
||||
*
|
||||
* - this function does not evaluate anything inside the domain. This
|
||||
* is actually quite critical because this allows the manipulation of
|
||||
* unevaluated (dynamic) domains.
|
||||
* - this function gives a normalized domain as result,
|
||||
* - applied on a list of length 1, it returns the domain normalized.
|
||||
*
|
||||
* @param {string[]} domains list of string representing domains
|
||||
* @param {"AND" | "OR"} operator used to combine domains (default "AND")
|
||||
* @returns {string} normalized domain
|
||||
*/
|
||||
function assembleDomains(domains, operator) {
|
||||
var ASTs = domains.map(_getPyJSAST);
|
||||
if (operator === "OR") {
|
||||
operator = py.tokenize("'|'")[0];
|
||||
} else {
|
||||
operator = py.tokenize("'&'")[0];
|
||||
}
|
||||
var result = _getPyJSAST("[]");
|
||||
var normalizedDomains = ASTs
|
||||
.filter(function (AST) {
|
||||
return AST.first.length > 0;
|
||||
})
|
||||
.map(_normalizeDomainAST);
|
||||
if (normalizedDomains.length > 0) {
|
||||
result.first = normalizedDomains.reduce(function (acc, ast) {
|
||||
return acc.concat(ast.first);
|
||||
}, new Array(normalizedDomains.length - 1).fill(operator));
|
||||
}
|
||||
return _formatAST(result);
|
||||
}
|
||||
/**
|
||||
* Normalize a domain via its string representation.
|
||||
*
|
||||
* Note: this function does not evaluate anything inside the domain. This is
|
||||
* actually quite critical because this allows the manipulation of unevaluated
|
||||
* (dynamic) domains.
|
||||
*
|
||||
* @param {string} domain string representing a domain
|
||||
* @returns {string} normalized domain
|
||||
*/
|
||||
function normalizeDomain (domain) {
|
||||
return _formatAST(_normalizeDomainAST(_getPyJSAST(domain)));
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// Binding power for prefix operator is not accessible in the AST generated by
|
||||
// py.js, so we have to hardcode some values here
|
||||
var BINDING_POWERS = {
|
||||
or: 30,
|
||||
and: 40,
|
||||
not: 50,
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Convert a python AST (generated by py.js) to a string form, which should
|
||||
* represent the same AST.
|
||||
*
|
||||
* @param {Object} ast a valid AST obtained by py.js, which represent a python
|
||||
* expression
|
||||
* @param {integer} [lbp=0] a binding power. This is necessary to be able to
|
||||
* format sub expressions: the + sub expression in "3 * (a + 2)" should be
|
||||
* formatted with parenthesis, because its binding power is lower than the
|
||||
* binding power of *.
|
||||
* @returns {string}
|
||||
*/
|
||||
function _formatAST(ast, lbp) {
|
||||
lbp = lbp || 0;
|
||||
switch (ast.id) {
|
||||
// basic values
|
||||
case "(number)":
|
||||
return String(ast.value);
|
||||
case "(string)":
|
||||
return JSON.stringify(ast.value);
|
||||
case "(constant)":
|
||||
return ast.value;
|
||||
case "(name)":
|
||||
return ast.value;
|
||||
case "[":
|
||||
if (ast.second) {
|
||||
// read a value in a dictionary: d['a']
|
||||
return _formatAST(ast.first) + '[' + _formatAST(ast.second) + ']';
|
||||
} else {
|
||||
// list: [1, 2]
|
||||
var values = ast.first.map(_formatAST);
|
||||
return '[' + values.join(', ') + ']';
|
||||
}
|
||||
case "{":
|
||||
var keyValues = ast.first.map(function (kv) {
|
||||
return _formatAST(kv[0]) + ': ' + _formatAST(kv[1]);
|
||||
});
|
||||
return '{' + keyValues.join(', ') + '}';
|
||||
|
||||
// relations
|
||||
case "=":
|
||||
return _formatAST(ast.first) + ' ' + ast.id + ' ' + _formatAST(ast.second);
|
||||
// operators
|
||||
case "-":
|
||||
case "+":
|
||||
case "~":
|
||||
case "*":
|
||||
case "**":
|
||||
case "%":
|
||||
case "//":
|
||||
case "and":
|
||||
case "or":
|
||||
if (ast.second) {
|
||||
// infix
|
||||
var r = _formatAST(ast.first, ast.lbp) + ' ' + ast.id + ' ' + _formatAST(ast.second, ast.lbp);
|
||||
if (ast.lbp < lbp) {
|
||||
r = '(' + r + ')';
|
||||
}
|
||||
return r;
|
||||
}
|
||||
// prefix
|
||||
// real lbp is not accessible, it is inside a closure
|
||||
var actualBP = BINDING_POWERS[ast.id] || 130;
|
||||
return ast.id + _formatAST(ast.first, actualBP);
|
||||
case "if":
|
||||
var t = _formatAST(ast.ifTrue)
|
||||
+ ' if ' + _formatAST(ast.condition)
|
||||
+ ' else ' + _formatAST(ast.ifFalse);
|
||||
return ast.lbp < lbp ? '(' + t + ')' : t;
|
||||
case ".":
|
||||
return _formatAST(ast.first, ast.lbp) + '.' + _formatAST(ast.second);
|
||||
case "not":
|
||||
return "not " + _formatAST(ast.first);
|
||||
case "(comparator)":
|
||||
var operator = ast.operators[0];
|
||||
return _formatAST(ast.expressions[0]) + ' ' + operator + ' ' + _formatAST(ast.expressions[1]);
|
||||
|
||||
// function call
|
||||
case "(":
|
||||
if (ast.second) {
|
||||
// this is a function call: f(a, b)
|
||||
return _formatAST(ast.first) + '(' + ast.second.map(_formatAST).join(', ') + ')';
|
||||
} else {
|
||||
// this is a tuple
|
||||
return '(' + ast.first.map(_formatAST).join(', ') + ')';
|
||||
}
|
||||
}
|
||||
throw new Error("Unimplemented python construct");
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Get the PyJs AST representing a domain starting from is string representation
|
||||
*
|
||||
* @param {string} domain string representing a domain
|
||||
* @returns {PyJS AST} PyJS AST representation of domain
|
||||
*/
|
||||
function _getPyJSAST(domain) {
|
||||
return py.parse(py.tokenize(domain));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Normalize a domain, at the level of the AST.
|
||||
*
|
||||
* Note: this function does not evaluate anything inside the domain. This is
|
||||
* actually quite critical because this allows the manipulation of unevaluated
|
||||
* (dynamic) domains.
|
||||
*
|
||||
* @param {PyJS AST} domain valid AST representing a domain
|
||||
* @returns {PyJS AST} normalized domain AST
|
||||
*/
|
||||
function _normalizeDomainAST(domain) {
|
||||
var expected = 1;
|
||||
for (var i = 0; i < domain.first.length; i++) {
|
||||
var value = domain.first[i].value;
|
||||
if (value === '&' || value === '|') {
|
||||
expected++;
|
||||
} else if (value !== '!') {
|
||||
expected--;
|
||||
}
|
||||
}
|
||||
var andOperator = py.tokenize("'&'")[0];
|
||||
|
||||
if (expected < 0) {
|
||||
domain.first.unshift.apply(domain.first, new Array(Math.abs(expected)).fill(andOperator));
|
||||
}
|
||||
|
||||
return domain;
|
||||
}
|
||||
|
||||
export default {
|
||||
context: pycontext,
|
||||
ensure_evaluated: ensure_evaluated,
|
||||
eval: pyeval,
|
||||
eval_domains_and_contexts: eval_domains_and_contexts,
|
||||
py_eval: py_eval,
|
||||
normalizeDomain: normalizeDomain,
|
||||
assembleDomains: assembleDomains,
|
||||
_getPyJSAST: _getPyJSAST,
|
||||
_formatAST: _formatAST,
|
||||
_normalizeDomainAST: _normalizeDomainAST,
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { bus } from "@web/legacy/js/services/core";
|
||||
import Context from "@web/legacy/js/core/context";
|
||||
import { makeContext } from "@web/core/context";
|
||||
|
||||
import { browser } from "../core/browser/browser";
|
||||
import { registry } from "../core/registry";
|
||||
@@ -20,7 +20,7 @@ export const legacyServiceProvider = {
|
||||
browser.addEventListener("do-action", (ev) => {
|
||||
const payload = ev.detail;
|
||||
if (payload.action.context) {
|
||||
payload.action.context = new Context(payload.action.context).eval();
|
||||
payload.action.context = makeContext([payload.action.context]);
|
||||
}
|
||||
const legacyOptions = mapDoActionOptionAPI(payload.options);
|
||||
services.action.doAction(payload.action, legacyOptions);
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import Domain from "@web/legacy/js/core/domain";
|
||||
|
||||
QUnit.module('core', {}, function () {
|
||||
|
||||
QUnit.module('domain');
|
||||
|
||||
QUnit.test("empty", function (assert) {
|
||||
assert.expect(1);
|
||||
assert.ok(new Domain([]).compute({}));
|
||||
});
|
||||
|
||||
QUnit.test("basic", function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var fields = {
|
||||
a: 3,
|
||||
group_method: 'line',
|
||||
select1: 'day',
|
||||
rrule_type: 'monthly',
|
||||
};
|
||||
assert.ok(new Domain([['a', '=', 3]]).compute(fields));
|
||||
assert.ok(new Domain([['group_method','!=','count']]).compute(fields));
|
||||
assert.ok(new Domain([['select1','=','day'], ['rrule_type','=','monthly']]).compute(fields));
|
||||
});
|
||||
|
||||
QUnit.test("or", function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var web = {
|
||||
section_id: null,
|
||||
user_id: null,
|
||||
member_ids: null,
|
||||
};
|
||||
var currentDomain = [
|
||||
'|',
|
||||
['section_id', '=', 42],
|
||||
'|',
|
||||
['user_id', '=', 3],
|
||||
['member_ids', 'in', [3]]
|
||||
];
|
||||
assert.ok(new Domain(currentDomain).compute(Object.assign({}, web, {section_id: 42})));
|
||||
assert.ok(new Domain(currentDomain).compute(Object.assign({}, web, {user_id: 3})));
|
||||
assert.ok(new Domain(currentDomain).compute(Object.assign({}, web, {member_ids: 3})));
|
||||
});
|
||||
|
||||
QUnit.test("not", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var fields = {
|
||||
a: 5,
|
||||
group_method: 'line',
|
||||
};
|
||||
assert.ok(new Domain(['!', ['a', '=', 3]]).compute(fields));
|
||||
assert.ok(new Domain(['!', ['group_method','=','count']]).compute(fields));
|
||||
});
|
||||
|
||||
QUnit.test("domains initialized with a number", function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
assert.ok(new Domain(1).compute({}));
|
||||
assert.notOk(new Domain(0).compute({}));
|
||||
});
|
||||
|
||||
QUnit.test("invalid domains should not succeed", function (assert) {
|
||||
assert.expect(3);
|
||||
assert.throws(
|
||||
() => new Domain(['|', ['hr_presence_state', '=', 'absent']]),
|
||||
/invalid domain .* \(missing 1 segment/
|
||||
);
|
||||
assert.throws(
|
||||
() => new Domain(['|', '|', ['hr_presence_state', '=', 'absent'], ['attendance_state', '=', 'checked_in']]),
|
||||
/invalid domain .* \(missing 1 segment/
|
||||
);
|
||||
assert.throws(
|
||||
() => new Domain(['&', ['composition_mode', '!=', 'mass_post']]),
|
||||
/invalid domain .* \(missing 1 segment/
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("domain <=> condition", function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
var domain = [
|
||||
'|',
|
||||
'|',
|
||||
'|',
|
||||
'&', ['doc.amount', '>', 33], ['doc.toto', '!=', null],
|
||||
'&', ['doc.bidule.active', '=', true], ['truc', 'in', [2, 3]],
|
||||
['gogo', '=', 'gogo value'],
|
||||
['gogo', '!=', false]
|
||||
];
|
||||
var condition = '((doc.amount > 33 and doc.toto is not None or doc.bidule.active is True and truc in [2,3]) or gogo == "gogo value") or gogo';
|
||||
|
||||
assert.equal(Domain.prototype.domainToCondition(domain), condition);
|
||||
assert.deepEqual(Domain.prototype.conditionToDomain(condition), domain);
|
||||
assert.deepEqual(Domain.prototype.conditionToDomain(
|
||||
'doc and toto is None or not tata'),
|
||||
['|', '&', ['doc', '!=', false], ['toto', '=', null], ['tata', '=', false]]);
|
||||
assert.deepEqual(Domain.prototype.conditionToDomain(
|
||||
`field in ("foo", "bar") and display_name in ['boo','far']`),
|
||||
['&', ['field', 'in', ['foo', 'bar']], ['display_name', 'in', ['boo', 'far']]]);
|
||||
});
|
||||
|
||||
QUnit.test("condition 'a field is set' does not convert to a domain", function (assert) {
|
||||
assert.expect(1);
|
||||
var expected = [["doc.blabla","!=",false]];
|
||||
var condition = "doc.blabla";
|
||||
|
||||
var actual = Domain.prototype.conditionToDomain(condition);
|
||||
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
QUnit.test("condition with a function should fail", function (assert) {
|
||||
assert.expect(1);
|
||||
var condition = "doc.blabla()";
|
||||
|
||||
assert.throws(function() { Domain.prototype.conditionToDomain(condition); });
|
||||
});
|
||||
|
||||
QUnit.test("empty condition should not fail", function (assert) {
|
||||
assert.expect(2);
|
||||
var condition = "";
|
||||
var actual = Domain.prototype.conditionToDomain(condition);
|
||||
assert.strictEqual(typeof(actual),typeof([]));
|
||||
assert.strictEqual(actual.length, 0);
|
||||
});
|
||||
QUnit.test("undefined condition should not fail", function (assert) {
|
||||
assert.expect(2);
|
||||
var condition = undefined;
|
||||
var actual = Domain.prototype.conditionToDomain(condition);
|
||||
assert.strictEqual(typeof(actual),typeof([]));
|
||||
assert.strictEqual(actual.length, 0);
|
||||
});
|
||||
|
||||
QUnit.test("compute true domain", function (assert) {
|
||||
assert.expect(1);
|
||||
assert.ok(new Domain(Domain.TRUE_DOMAIN).compute({}));
|
||||
});
|
||||
|
||||
QUnit.test("compute false domain", function (assert) {
|
||||
assert.expect(1);
|
||||
assert.notOk(new Domain(Domain.FALSE_DOMAIN).compute({}));
|
||||
});
|
||||
|
||||
QUnit.test("arrayToString", function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
const arrayToString = Domain.prototype.arrayToString;
|
||||
|
||||
// domains containing null, false or true
|
||||
assert.strictEqual(arrayToString([['name', '=', null]]), '[["name","=",None]]');
|
||||
assert.strictEqual(arrayToString([['name', '=', false]]), '[["name","=",False]]');
|
||||
assert.strictEqual(arrayToString([['name', '=', true]]), '[["name","=",True]]');
|
||||
assert.strictEqual(arrayToString([['name', '=', 'null']]), '[["name","=","null"]]');
|
||||
assert.strictEqual(arrayToString([['name', '=', 'false']]), '[["name","=","false"]]');
|
||||
assert.strictEqual(arrayToString([['name', '=', 'true']]), '[["name","=","true"]]');
|
||||
assert.strictEqual(arrayToString([['name', 'in', [true, false]]]), '[["name","in",[True,False]]]');
|
||||
assert.strictEqual(arrayToString([['name', 'in', [null]]]), '[["name","in",[None]]]');
|
||||
|
||||
assert.strictEqual(arrayToString([['name', 'in', ["foo", "bar"]]]), '[["name","in",["foo","bar"]]]');
|
||||
assert.strictEqual(arrayToString([['name', 'in', [1, 2]]]), '[["name","in",[1,2]]]');
|
||||
assert.strictEqual(arrayToString(), '[]');
|
||||
|
||||
assert.strictEqual(arrayToString(['&', ['name', '=', 'foo'], ['type', '=', 'bar']]), '["&",["name","=","foo"],["type","=","bar"]]');
|
||||
assert.strictEqual(arrayToString(['|', ['name', '=', 'foo'], ['type', '=', 'bar']]), '["|",["name","=","foo"],["type","=","bar"]]');
|
||||
|
||||
// string domains are not processed
|
||||
assert.strictEqual(arrayToString('[["name", "ilike", "foo"]]'), '[["name", "ilike", "foo"]]');
|
||||
});
|
||||
|
||||
QUnit.test("like, =like, ilike and =ilike", function (assert) {
|
||||
assert.expect(16);
|
||||
|
||||
assert.ok(new Domain([['a', 'like', 'value']]).compute({ a: 'value' }));
|
||||
assert.ok(new Domain([['a', 'like', 'value']]).compute({ a: 'some value' }));
|
||||
assert.notOk(new Domain([['a', 'like', 'value']]).compute({ a: 'Some Value' }));
|
||||
assert.notOk(new Domain([['a', 'like', 'value']]).compute({ a: false }));
|
||||
|
||||
assert.ok(new Domain([['a', '=like', '%value']]).compute({ a: 'value' }));
|
||||
assert.ok(new Domain([['a', '=like', '%value']]).compute({ a: 'some value' }));
|
||||
assert.notOk(new Domain([['a', '=like', '%value']]).compute({ a: 'Some Value' }));
|
||||
assert.notOk(new Domain([['a', '=like', '%value']]).compute({ a: false }));
|
||||
|
||||
assert.ok(new Domain([['a', 'ilike', 'value']]).compute({ a: 'value' }));
|
||||
assert.ok(new Domain([['a', 'ilike', 'value']]).compute({ a: 'some value' }));
|
||||
assert.ok(new Domain([['a', 'ilike', 'value']]).compute({ a: 'Some Value' }));
|
||||
assert.notOk(new Domain([['a', 'ilike', 'value']]).compute({ a: false }));
|
||||
|
||||
assert.ok(new Domain([['a', '=ilike', '%value']]).compute({ a: 'value' }));
|
||||
assert.ok(new Domain([['a', '=ilike', '%value']]).compute({ a: 'some value' }));
|
||||
assert.ok(new Domain([['a', '=ilike', '%value']]).compute({ a: 'Some Value' }));
|
||||
assert.notOk(new Domain([['a', '=ilike', '%value']]).compute({ a: false }));
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { unique } from "@web/core/utils/arrays";
|
||||
import { pick } from "@web/core/utils/objects";
|
||||
import Class from "@web/legacy/js/core/class";
|
||||
import Domain from "@web/legacy/js/core/domain";
|
||||
import pyUtils from "@web/legacy/js/core/py_utils";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import {
|
||||
parseDateTime,
|
||||
serializeDate,
|
||||
@@ -277,7 +277,7 @@ var MockServer = Class.extend({
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_evaluateDomain: function (domain, fieldValues) {
|
||||
return new Domain(domain).compute(fieldValues);
|
||||
return new Domain(domain).contains(fieldValues);
|
||||
},
|
||||
/**
|
||||
* helper: read a string describing an arch, and returns a simulated
|
||||
@@ -367,7 +367,7 @@ var MockServer = Class.extend({
|
||||
// 'transfer_node_to_modifiers' simulation
|
||||
var attrs = node.getAttribute('attrs');
|
||||
if (attrs) {
|
||||
attrs = pyUtils.py_eval(attrs);
|
||||
attrs = evaluateExpr(attrs);
|
||||
Object.assign(modifiers, attrs);
|
||||
}
|
||||
|
||||
@@ -383,8 +383,7 @@ var MockServer = Class.extend({
|
||||
modifiersNames.forEach((a) => {
|
||||
var mod = node.getAttribute(a);
|
||||
if (mod) {
|
||||
var pyevalContext = window.py.dict.fromJSON(context || {});
|
||||
var v = pyUtils.py_eval(mod, {context: pyevalContext}) ? true: false;
|
||||
var v = evaluateExpr(mod, context || {}) ? true: false;
|
||||
if (inTreeView && !inListHeader && a === 'invisible') {
|
||||
modifiers.column_invisible = v;
|
||||
} else if (v || !(a in modifiers) || !Array.isArray(modifiers[a])) {
|
||||
|
||||
@@ -211,7 +211,6 @@ Featuring
|
||||
'web/static/src/legacy/js/core/browser_detection.js',
|
||||
'web/static/src/legacy/js/core/bus.js',
|
||||
'web/static/src/legacy/js/core/class.js',
|
||||
'web/static/src/legacy/js/core/collections.js',
|
||||
'web/static/src/legacy/js/core/concurrency.js',
|
||||
'web/static/src/legacy/js/core/dialog.js',
|
||||
'web/static/src/legacy/xml/dialog.xml',
|
||||
|
||||
Reference in New Issue
Block a user