diff --git a/addons/mass_mailing/static/src/js/mailing_m2o_filter.js b/addons/mass_mailing/static/src/js/mailing_m2o_filter.js index 01905863c80..9b7dfc9ea09 100644 --- a/addons/mass_mailing/static/src/js/mailing_m2o_filter.js +++ b/addons/mass_mailing/static/src/js/mailing_m2o_filter.js @@ -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; diff --git a/addons/mrp_subcontracting/__manifest__.py b/addons/mrp_subcontracting/__manifest__.py index c3fb515b21e..f396baf8ff0 100644 --- a/addons/mrp_subcontracting/__manifest__.py +++ b/addons/mrp_subcontracting/__manifest__.py @@ -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', diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py index e3edbd59b12..04d0d82b715 100644 --- a/addons/point_of_sale/__manifest__.py +++ b/addons/point_of_sale/__manifest__.py @@ -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/**/*'), diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index e9372264aa3..3e41a747514 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -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', diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index 59476b86ae7..6b2726f539c 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -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', diff --git a/addons/web/static/lib/py.js/LICENSE b/addons/web/static/lib/py.js/LICENSE deleted file mode 100644 index 01733ddac98..00000000000 --- a/addons/web/static/lib/py.js/LICENSE +++ /dev/null @@ -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. diff --git a/addons/web/static/lib/py.js/README.rst b/addons/web/static/lib/py.js/README.rst deleted file mode 100644 index 5380f3e6c8f..00000000000 --- a/addons/web/static/lib/py.js/README.rst +++ /dev/null @@ -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. diff --git a/addons/web/static/lib/py.js/TODO.rst b/addons/web/static/lib/py.js/TODO.rst deleted file mode 100644 index dd3237e3572..00000000000 --- a/addons/web/static/lib/py.js/TODO.rst +++ /dev/null @@ -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 - `_ - for *all* supported operations, optimizations can come later -* Automatically type-wrap everything (for now anyway) - -Base type requirements: -*********************** - -* int -* float -* --str-- unicode -* bool -* dict -* tuple -* list -* ?module -* ?object -* datetime.time -* datetime.timedelta -* NotImplementedType - -Base methods requirement -************************ - -* ``__getattr__`` -* ``dict.get`` - -In datamodel, not implemented in any type, untested -*************************************************** - -* a[b] - -* a + b, a - b, a * b, ... - -* +a, ~a diff --git a/addons/web/static/lib/py.js/doc/Makefile b/addons/web/static/lib/py.js/doc/Makefile deleted file mode 100644 index cb7d658e796..00000000000 --- a/addons/web/static/lib/py.js/doc/Makefile +++ /dev/null @@ -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 ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyjs.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyjs.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/pyjs" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyjs" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/addons/web/static/lib/py.js/doc/builtins.rst b/addons/web/static/lib/py.js/doc/builtins.rst deleted file mode 100644 index 6958d83b884..00000000000 --- a/addons/web/static/lib/py.js/doc/builtins.rst +++ /dev/null @@ -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 diff --git a/addons/web/static/lib/py.js/doc/conf.py b/addons/web/static/lib/py.js/doc/conf.py deleted file mode 100644 index 287f21af577..00000000000 --- a/addons/web/static/lib/py.js/doc/conf.py +++ /dev/null @@ -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 -# " v 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 tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'pyjsdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'pyjs.tex', u'py.js Documentation', - u'Xavier Morel', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'pyjs', u'py.js Documentation', - [u'Xavier Morel'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'pyjs', u'py.js Documentation', - u'Xavier Morel', 'pyjs', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' diff --git a/addons/web/static/lib/py.js/doc/differences.rst b/addons/web/static/lib/py.js/doc/differences.rst deleted file mode 100644 index d81acd240a2..00000000000 --- a/addons/web/static/lib/py.js/doc/differences.rst +++ /dev/null @@ -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. diff --git a/addons/web/static/lib/py.js/doc/index.rst b/addons/web/static/lib/py.js/doc/index.rst deleted file mode 100644 index 1b9167529b8..00000000000 --- a/addons/web/static/lib/py.js/doc/index.rst +++ /dev/null @@ -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 -`_ (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 - ` - - :param expr: Python expression to evaluate - :type expr: String - :param context: evaluation context for the expression's free - variables - :type context: Object - :returns: the expression's result, converted back to javascript - -.. function:: py.tokenize(expr) - - Expression tokenizer - - :param expr: Python expression to tokenize - :type expr: String - :returns: token stream - -.. function:: py.parse(tokens) - - Parses a token stream and returns the corresponding parse tree. - - The parse tree is stateless and can be memoized and reused for - frequently evaluated expressions. - - :param tokens: token stream from :func:`py.tokenize` - :returns: parse tree - -.. function:: py.evaluate(tree[, context]) - - Evaluates the expression represented by the provided parse tree, - using the provided context for the exprssion's free variables. - - :param tree: parse tree returned by :func:`py.parse` - :param context: evaluation context - :returns: the "python object" resulting from the expression's - evaluation - :rtype: :class:`py.object` - -.. _convert-py: - -Conversions from Javascript to Python -+++++++++++++++++++++++++++++++++++++ - -``py.js`` will automatically attempt to convert non-:class:`py.object` -values into their ``py.js`` equivalent in the following situations: - -* Values passed through the context of :func:`py.eval` or - :func:`py.evaluate` - -* Attributes accessed directly on objects - -* Values of mappings passed to :class:`py.dict` - -Notably, ``py.js`` will *not* attempt an automatic conversion of -values returned by functions or methods, these must be -:class:`py.object` instances. - -The automatic conversions performed by ``py.js`` are the following: - -* ``null`` is converted to :data:`py.None` - -* ``true`` is converted to :data:`py.True` - -* ``false`` is converted to :data:`py.False` - -* numbers are converted to :class:`py.float` - -* strings are converted to :class:`py.str` - -* functions are wrapped into :class:`py.PY_dev` - -* ``Array`` instances are converted to :class:`py.list` - -The rest generates an error, except for ``undefined`` which -specifically generates a ``NameError``. - -.. _convert-js: - -Conversions from Python to Javascript -+++++++++++++++++++++++++++++++++++++ - -py.js types (extensions of :js:class:`py.object`) can be converted -back to javascript by calling their :js:func:`py.object.toJSON` -method. - -The default implementation raises an error, as arbitrary objects can -not be converted back to javascript. - -Most built-in objects provide a :js:func:`py.object.toJSON` -implementation out of the box. - -Javascript-level exceptions -+++++++++++++++++++++++++++ - -Javascript allows throwing arbitrary things, but runtimes don't seem -to provide any useful information (when they ever do) if what is -thrown isn't a direct instance of ``Error``. As a result, while -``py.js`` tries to match the exception-throwing semantics of Python it -only ever throws bare ``Error`` at the javascript-level. Instead, it -prefixes the error message with the name of the Python expression, a -colon, a space, and the actual message. - -For instance, where Python would throw ``KeyError("'foo'")`` when -accessing an invalid key on a ``dict``, ``py.js`` will throw -``Error("KeyError: 'foo'")``. - -.. _Python Data Model: http://docs.python.org/reference/datamodel.html - diff --git a/addons/web/static/lib/py.js/doc/make.bat b/addons/web/static/lib/py.js/doc/make.bat deleted file mode 100644 index c3db032e01a..00000000000 --- a/addons/web/static/lib/py.js/doc/make.bat +++ /dev/null @@ -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 ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyjs.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyjs.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/addons/web/static/lib/py.js/doc/types.rst b/addons/web/static/lib/py.js/doc/types.rst deleted file mode 100644 index 61d950c6ec6..00000000000 --- a/addons/web/static/lib/py.js/doc/types.rst +++ /dev/null @@ -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 `. 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 -`_ -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 ` 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 - `, only one base is allowed. diff --git a/addons/web/static/lib/py.js/doc/utility.rst b/addons/web/static/lib/py.js/doc/utility.rst deleted file mode 100644 index 8bb9164885f..00000000000 --- a/addons/web/static/lib/py.js/doc/utility.rst +++ /dev/null @@ -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 ` 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 - ` - - :param Function fn: the javascript function to wrap - :returns: a callable py.js object - -Object Protocol ---------------- - -.. function:: py.PY_hasAttr(o, attr_name) - - Returns ``true`` if ``o`` has the attribute ``attr_name``, - otherwise returns ``false``. Equivalent to Python's ``hasattr(o, - attr_name)`` - - :param o: A :class:`py.object` - :param attr_name: a javascript ``String`` - :rtype: ``Boolean`` - -.. function:: py.PY_getAttr(o, attr_name) - - Retrieve an attribute ``attr_name`` from the object ``o``. Returns - the attribute value on success, raises ``AttributeError`` on - failure. Equivalent to the python expression ``o.attr_name``. - - :param o: A :class:`py.object` - :param attr_name: a javascript ``String`` - :returns: A :class:`py.object` - :raises: ``AttributeError`` - -.. function:: py.PY_str(o) - - Computes a string representation of ``o``, returns the string - representation. Equivalent to ``str(o)`` - - :param o: A :class:`py.object` - :returns: :class:`py.str` - -.. function:: py.PY_isInstance(inst, cls) - - Returns ``true`` if ``inst`` is an instance of ``cls``, ``false`` - otherwise. - -.. function:: py.PY_isSubclass(derived, cls) - - Returns ``true`` if ``derived`` is ``cls`` or a subclass thereof. - -.. function:: py.PY_call(callable[, args][, kwargs]) - - Call an arbitrary python-level callable from javascript. - - :param callable: A ``py.js`` callable object (broadly speaking, - either a class or an object with a ``__call__`` - method) - - :param args: javascript Array of :class:`py.object`, used as - positional arguments to ``callable`` - - :param kwargs: javascript Object mapping names to - :class:`py.object`, used as named arguments to - ``callable`` - - :returns: nothing or :class:`py.object` - -.. function:: py.PY_isTrue(o) - - Returns ``true`` if the object is considered truthy, ``false`` - otherwise. Equivalent to ``bool(o)``. - - :param o: A :class:`py.object` - :rtype: Boolean - -.. function:: py.PY_not(o) - - Inverse of :func:`py.PY_isTrue`. - -.. function:: py.PY_size(o) - - If ``o`` is a sequence or mapping, returns its length. Otherwise, - raises ``TypeError``. - - :param o: A :class:`py.object` - :returns: ``Number`` - :raises: ``TypeError`` if the object doesn't have a length - -.. function:: py.PY_getItem(o, key) - - Returns the element of ``o`` corresponding to the object - ``key``. This is equivalent to ``o[key]``. - - :param o: :class:`py.object` - :param key: :class:`py.object` - :returns: :class:`py.object` - :raises: ``TypeError`` if ``o`` does not support the operation, if - ``key`` or the return value is not a :class:`py.object` - -.. function:: py.PY_setItem(o, key, v) - - Maps the object ``key`` to the value ``v`` in ``o``. Equivalent to - ``o[key] = v``. - - :param o: :class:`py.object` - :param key: :class:`py.object` - :param v: :class:`py.object` - :raises: ``TypeError`` if ``o`` does not support the operation, or - if ``key`` or ``v`` are not :class:`py.object` - -Number Protocol ---------------- - -.. function:: py.PY_add(o1, o2) - - Returns the result of adding ``o1`` and ``o2``, equivalent to - ``o1 + o2``. - - :param o1: :class:`py.object` - :param o2: :class:`py.object` - :returns: :class:`py.object` - -.. function:: py.PY_subtract(o1, o2) - - Returns the result of subtracting ``o2`` from ``o1``, equivalent - to ``o1 - o2``. - - :param o1: :class:`py.object` - :param o2: :class:`py.object` - :returns: :class:`py.object` - -.. function:: py.PY_multiply(o1, o2) - - Returns the result of multiplying ``o1`` by ``o2``, equivalent to - ``o1 * o2``. - - :param o1: :class:`py.object` - :param o2: :class:`py.object` - :returns: :class:`py.object` - -.. function:: py.PY_divide(o1, o2) - - Returns the result of dividing ``o1`` by ``o2``, equivalent to - ``o1 / o2``. - - :param o1: :class:`py.object` - :param o2: :class:`py.object` - :returns: :class:`py.object` - -.. function:: py.PY_negative(o) - - Returns the negation of ``o``, equivalent to ``-o``. - - :param o: :class:`py.object` - :returns: :class:`py.object` - -.. function:: py.PY_positive(o) - - Returns the "positive" of ``o``, equivalent to ``+o``. - - :param o: :class:`py.object` - :returns: :class:`py.object` - -.. [#kwonly] Python 2, which py.js currently implements, does not - support Python-level keyword-only parameters (it can be - done through the C-API), but it seemed neat and easy - enough so there. - -.. [#star-args] due to this and contrary to Python 2, py.js allows - arguments other than ``**kwargs`` to follow ``*args``. - -.. _PyArg_ParseTupleAndKeywords: - http://docs.python.org/c-api/arg.html#PyArg_ParseTupleAndKeywords diff --git a/addons/web/static/lib/py.js/lib/py.js b/addons/web/static/lib/py.js/lib/py.js deleted file mode 100644 index 46eff409a5f..00000000000 --- a/addons/web/static/lib/py.js/lib/py.js +++ /dev/null @@ -1,1481 +0,0 @@ -var py = {}; -(function (py) { - var create = function (o, props) { - function F() {} - F.prototype = o; - var inst = new F; - if (props) { - for(var name in props) { - if(!props.hasOwnProperty(name)) { continue; } - inst[name] = props[name]; - } - } - return inst; - }; - - var symbols = {}; - var comparators = {}; - var Base = { - nud: function () { throw new Error(this.id + " undefined as prefix"); }, - led: function (led) { throw new Error(this.id + " undefined as infix"); }, - toString: function () { - if (this.id === '(constant)' || this.id === '(number)' || this.id === '(name)' || this.id === '(string)') { - return [this.id.slice(0, this.id.length-1), ' ', this.value, ')'].join(''); - } else if (this.id === '(end)') { - return '(end)'; - } else if (this.id === '(comparator)' ) { - var repr = ['(comparator', this.expressions[0]]; - for (var i=0;i s.lbp) { - s.lbp = bp; - } - return s; - } - return symbols[id] = create(Base, { - id: id, - lbp: bp - }); - } - function constant(id) { - var s = symbol(id); - s.id = '(constant)'; - s.value = id; - s.nud = function () { - return this; - }; - } - function prefix(id, bp, nud) { - symbol(id).nud = nud || function () { - this.first = expression(bp); - return this - } - } - function infix(id, bp, led) { - symbol(id, bp).led = led || function (left) { - this.first = left; - this.second = expression(bp); - return this; - } - } - function infixr(id, bp) { - symbol(id, bp).led = function (left) { - this.first = left; - this.second = expression(bp - 1); - return this; - } - } - function comparator(id) { - comparators[id] = true; - var bp = 60; - infix(id, bp, function (left) { - this.id = '(comparator)'; - this.operators = [id]; - this.expressions = [left, expression(bp)]; - while (token.id in comparators) { - this.operators.push(token.id); - advance(); - this.expressions.push( - expression(bp)); - } - return this; - }); - } - - constant('None'); constant('False'); constant('True'); - - symbol('(number)').nud = function () { return this; }; - symbol('(name)').nud = function () { return this; }; - symbol('(string)').nud = function () { return this; }; - symbol('(end)'); - - symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(','); - symbol('else'); - - infix('=', 10, function (left) { - if (left.id !== '(name)') { - throw new Error("Expected keyword argument name, got " + token.id); - } - this.first = left; - this.second = expression(); - return this; - }); - - symbol('lambda', 20).nud = function () { - this.first = []; - if (token.id !== ':') { - for(;;) { - if (token.id !== '(name)') { - throw new Error('Excepted an argument name'); - } - this.first.push(token); - advance(); - if (token.id !== ',') { - break; - } - advance(','); - } - } - advance(':'); - this.second = expression(); - return this; - }; - infix('if', 20, function (left) { - this.ifTrue = left; - this.condition = expression(); - advance('else'); - this.ifFalse = expression(); - return this; - }); - - infixr('or', 30); infixr('and', 40); prefix('not', 50); - - comparator('in'); comparator('not in'); - comparator('is'); comparator('is not'); - comparator('<'); comparator('<='); - comparator('>'); comparator('>='); - comparator('<>'); comparator('!='); comparator('=='); - - infix('|', 70); infix('^', 80); infix('&', 90); - - infix('<<', 100); infix('>>', 100); - - infix('+', 110); infix('-', 110); - - infix('*', 120); infix('/', 120); - infix('//', 120); infix('%', 120); - - prefix('-', 130); prefix('+', 130); prefix('~', 130); - - infixr('**', 140); - - infix('.', 150, function (left) { - if (token.id !== '(name)') { - throw new Error('Expected attribute name, got ' + token.id); - } - this.first = left; - this.second = token; - advance(); - return this; - }); - symbol('(', 150).nud = function () { - this.first = []; - var comma = false; - if (token.id !== ')') { - while (true) { - if (token.id === ')') { - break; - } - this.first.push(expression()); - if (token.id !== ',') { - break; - } - comma = true; - advance(','); - } - } - advance(')'); - if (!this.first.length || comma) { - return this; - } else { - return this.first[0]; - } - }; - symbol('(').led = function (left) { - this.first = left; - this.second = []; - if (token.id !== ")") { - for(;;) { - this.second.push(expression()); - if (token.id !== ',') { - break; - } - advance(','); - } - } - advance(")"); - return this; - - }; - infix('[', 150, function (left) { - this.first = left; - this.second = expression(); - advance("]"); - return this; - }); - symbol('[').nud = function () { - this.first = []; - if (token.id !== ']') { - for (;;) { - if (token.id === ']') { - break; - } - this.first.push(expression()); - if (token.id !== ',') { - break; - } - advance(','); - } - } - advance(']'); - return this; - }; - - symbol('{').nud = function () { - this.first = []; - if (token.id !== '}') { - for(;;) { - if (token.id === '}') { - break; - } - var key = expression(); - advance(':'); - var value = expression(); - this.first.push([key, value]); - if (token.id !== ',') { - break; - } - advance(','); - } - } - advance('}'); - return this; - }; - - py.tokenize = (function () { - function group() { return '(' + Array.prototype.join.call(arguments, '|') + ')'; } - - var Whitespace = '[ \\f\\t]*'; - - var Name = '[a-zA-Z_]\\w*'; - - var DecNumber = '\\d+(L|l)?'; - var IntNumber = DecNumber; - - var Exponent = '[eE][+-]?\\d+'; - var PointFloat = group(`\\d+\\.\\d*(${Exponent})?`, `\\.\\d+(${Exponent})?`); - // Exponent not optional when no decimal point - var FloatNumber = group(PointFloat, `\\d+${Exponent}`); - - var Number = group(FloatNumber, IntNumber); - - var Operator = group("\\*\\*=?", ">>=?", "<<=?", "<>", "!=", - "//=?", "[+\\-*/%&|^=<>]=?", "~"); - var Bracket = '[\\[\\]\\(\\)\\{\\}]'; - var Special = '[:;.,`@]'; - var Funny = group(Operator, Bracket, Special); - - var ContStr = group("([uU])?'([^\n'\\\\]*(?:\\\\.[^\n'\\\\]*)*)'", '([uU])?"([^\n"\\\\]*(?:\\\\.[^\n"\\\\]*)*)"'); - var PseudoToken = Whitespace + group(Number, Funny, ContStr, Name); - - var number_pattern = new RegExp('^' + Number + '$'); - var string_pattern = new RegExp('^' + ContStr + '$'); - var name_pattern = new RegExp('^' + Name + '$'); - var strip = new RegExp('^' + Whitespace); - return function tokenize(s) { - var max=s.length, tokens = [], start, end; - // /g flag makes repeated exec() have memory - var pseudoprog = new RegExp(PseudoToken, 'g'); - - while(pseudoprog.lastIndex < max) { - var pseudomatch = pseudoprog.exec(s); - if (!pseudomatch) { - // if match failed on trailing whitespace, end tokenizing - if (/^\s+$/.test(s.slice(end))) { - break; - } - throw new Error('Failed to tokenize <<' + s - + '>> at index ' + (end || 0) - + '; parsed so far: ' + tokens); - } - - start = pseudomatch.index; - end = pseudoprog.lastIndex; - // strip leading space caught by Whitespace - var token = s.slice(start, end).replace(strip, ''); - - if (number_pattern.test(token)) { - tokens.push(create(symbols['(number)'], { - value: parseFloat(token) - })); - } else if (string_pattern.test(token)) { - var m = string_pattern.exec(token); - tokens.push(create(symbols['(string)'], { - value: PY_decode_string_literal( - m[3] !== undefined ? m[3] : m[5], - !!(m[2] || m[4]) - ) - })); - } else if (token in symbols) { - var symbol; - // transform 'not in' and 'is not' in a single token - if (token === 'in' && tokens.length > 1 && tokens[tokens.length-1].id === 'not') { - symbol = symbols['not in']; - tokens.pop(); - } else if (token === 'not' && tokens.length > 1 && tokens[tokens.length-1].id === 'is') { - symbol = symbols['is not']; - tokens.pop(); - } else { - symbol = symbols[token]; - } - tokens.push(create(symbol)); - } else if (name_pattern.test(token)) { - tokens.push(create(symbols['(name)'], { - value: token - })); - } else { - throw new Error("Tokenizing failure of <<" + s + ">> at index " + start - + " for token [[" + token + "]]" - + "; parsed so far: " + tokens); - - } - } - tokens.push(create(symbols['(end)'])); - return tokens; - } - })(); - - var token, next; - function expression(rbp) { - rbp = rbp || 0; - var t = token; - token = next(); - var left = t.nud(); - while (rbp < token.lbp) { - t = token; - token = next(); - left = t.led(left); - } - return left; - } - function advance(id) { - if (id && token.id !== id) { - throw new Error( - 'Expected "' + id + '", got "' + token.id + '"'); - } - token = next(); - } - - function PY_ensurepy(val, name) { - switch (val) { - case undefined: - throw new Error("NameError: name '" + name + "' is not defined"); - case null: - return py.None; - case true: - return py.True; - case false: - return py.False; - } - - var fn = function () {}; - fn.prototype = py.object; - if (py.PY_isInstance(val, py.object) - || py.PY_isSubclass(val, py.object)) { - return val; - } - - switch (typeof val) { - case 'number': - return py.float.fromJSON(val); - case 'string': - return py.str.fromJSON(val); - case 'function': - return py.PY_def.fromJSON(val); - } - - switch(val.constructor) { - case Object: - // TODO: why py.object instead of py.dict? - var o = py.PY_call(py.object); - for (var prop in val) { - if (val.hasOwnProperty(prop)) { - o[prop] = val[prop]; - } - } - return o; - case Array: - return py.list.fromJSON(val); - } - - throw new Error("Could not convert " + val + " to a pyval"); - } - - var typename = function (obj) { - if (obj.__class__) { // py type - return obj.__class__.__name__; - } else if(typeof obj !== 'object') { // JS primitive - return typeof obj; - } else { // JS object - return obj.constructor.name; - } - }; - // JSAPI, JS-level utility functions for implementing new py.js - // types - py.py = {}; - - py.PY_parseArgs = function PY_parseArgs(argument, format) { - var out = {}; - var args = argument[0]; - var kwargs = {}; - for (var k in argument[1]) { - if (!argument[1].hasOwnProperty(k)) { continue; } - kwargs[k] = argument[1][k]; - } - if (typeof format === 'string') { - format = format.split(/\s+/); - } - var name = function (spec) { - if (typeof spec === 'string') { - return spec; - } else if (spec instanceof Array && spec.length === 2) { - return spec[0]; - } - throw new Error( - "TypeError: unknown format specification " + - JSON.stringify(spec)); - }; - var spec; - // TODO: ensure all format arg names are actual names? - for(var i=0; i 1) { - throw new Error("ValueError: can't provide multiple bases for a " - + "new type"); - } - var base = bases[0]; - var ClassObj = create(base); - if (dict) { - for (var k in dict) { - if (!dict.hasOwnProperty(k)) { continue; } - ClassObj[k] = dict[k]; - } - } - ClassObj.__class__ = ClassObj; - ClassObj.__name__ = name; - ClassObj.__bases__ = bases; - ClassObj.__is_type = true; - - return ClassObj; - }; - py.type.__call__ = function () { - var args = py.PY_parseArgs(arguments, ['object']); - return args.object.__class__; - }; - - var hash_counter = 0; - py.object = py.type('object', [{}], { - __new__: function () { - // If ``this`` isn't the class object, this is going to be - // beyond fucked up - var inst = create(this); - inst.__is_type = false; - return inst; - }, - __init__: function () {}, - // Basic customization - __hash__: function () { - if (this._hash) { - return this._hash; - } - // tagged counter, to avoid collisions with e.g. number hashes - return this._hash = '\0\0\0' + String(hash_counter++); - }, - __eq__: function (other) { - return (this === other) ? py.True : py.False; - }, - __ne__: function (other) { - if (py.PY_isTrue(this.__eq__(other))) { - return py.False; - } else { - return py.True; - } - }, - __lt__: function () { return py.NotImplemented; }, - __le__: function () { return py.NotImplemented; }, - __ge__: function () { return py.NotImplemented; }, - __gt__: function () { return py.NotImplemented; }, - __str__: function () { - return this.__unicode__(); - }, - __unicode__: function () { - return py.str.fromJSON('<' + typename(this) + ' object>'); - }, - __nonzero__: function () { - return py.True; - }, - // Attribute access - __getattribute__: function (name) { - if (name in this) { - var val = this[name]; - if (typeof val === 'object' && '__get__' in val) { - // TODO: second argument should be class - return val.__get__(this, py.PY_call(py.type, [this])); - } - if (typeof val === 'function' && !this.hasOwnProperty(name)) { - // val is a method from the class - return PY_instancemethod.fromJSON(val, this); - } - return val; - } - if ('__getattr__' in this) { - return this.__getattr__(name); - } - throw new Error("AttributeError: object has no attribute '" + name +"'"); - }, - __setattr__: function (name, value) { - if (name in this && '__set__' in this[name]) { - this[name].__set__(this, value); - } - this[name] = value; - }, - // no delattr, because no 'del' statement - - // Conversion - toJSON: function () { - throw new Error(this.constructor.name + ' can not be converted to JSON'); - } - }); - var NoneType = py.type('NoneType', null, { - __nonzero__: function () { return py.False; }, - toJSON: function () { return null; } - }); - py.None = py.PY_call(NoneType); - var NotImplementedType = py.type('NotImplementedType', null, {}); - py.NotImplemented = py.PY_call(NotImplementedType); - var booleans_initialized = false; - py.bool = py.type('bool', null, { - __new__: function () { - if (!booleans_initialized) { - return py.object.__new__.apply(this); - } - - var ph = {}; - var args = py.PY_parseArgs(arguments, [['value', ph]]); - if (args.value === ph) { - return py.False; - } - return py.PY_isTrue(args.value) ? py.True : py.False; - }, - __str__: function () { - return py.str.fromJSON((this === py.True) ? "True" : "False"); - }, - __nonzero__: function () { return this; }, - fromJSON: function (val) { return val ? py.True : py.False }, - toJSON: function () { return this === py.True; } - }); - py.True = py.PY_call(py.bool); - py.False = py.PY_call(py.bool); - booleans_initialized = true; - py.float = py.type('float', null, { - __init__: function () { - var placeholder = {}; - var args = py.PY_parseArgs(arguments, [['value', placeholder]]); - var value = args.value; - if (value === placeholder) { - this._value = 0; return; - } - if (py.PY_isInstance(value, py.float)) { - this._value = value._value; - } - if (py.PY_isInstance(value, py.object) && '__float__' in value) { - var res = value.__float__(); - if (py.PY_isInstance(res, py.float)) { - this._value = res._value; - return; - } - throw new Error('TypeError: __float__ returned non-float (type ' + - typename(res) + ')'); - } - throw new Error('TypeError: float() argument must be a string or a number'); - }, - __str__: function () { - return py.str.fromJSON(String(this._value)); - }, - __eq__: function (other) { - return this._value === other._value ? py.True : py.False; - }, - __lt__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return this._value < other._value ? py.True : py.False; - }, - __le__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return this._value <= other._value ? py.True : py.False; - }, - __gt__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return this._value > other._value ? py.True : py.False; - }, - __ge__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return this._value >= other._value ? py.True : py.False; - }, - __abs__: function () { - return py.float.fromJSON( - Math.abs(this._value)); - }, - __add__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return py.float.fromJSON(this._value + other._value); - }, - __mod__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return py.float.fromJSON(this._value % other._value); - }, - __neg__: function () { - return py.float.fromJSON(-this._value); - }, - __sub__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return py.float.fromJSON(this._value - other._value); - }, - __mul__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return py.float.fromJSON(this._value * other._value); - }, - __pow__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return py.float.fromJSON(this._value ** other._value); - }, - __div__: function (other) { - if (!py.PY_isInstance(other, py.float)) { - return py.NotImplemented; - } - return py.float.fromJSON(this._value / other._value); - }, - __nonzero__: function () { - return this._value ? py.True : py.False; - }, - fromJSON: function (v) { - if (!(typeof v === 'number')) { - throw new Error('py.float.fromJSON can only take numbers'); - } - var instance = py.PY_call(py.float); - instance._value = v; - return instance; - }, - toJSON: function () { - return this._value; - } - }); - py.str = py.type('str', null, { - __init__: function () { - var placeholder = {}; - var args = py.PY_parseArgs(arguments, [['value', placeholder]]); - var s = args.value; - if (s === placeholder) { this._value = ''; return; } - this._value = py.PY_str(s)._value; - }, - __hash__: function () { - return '\1\0\1' + this._value; - }, - __str__: function () { - return this; - }, - __eq__: function (other) { - if (py.PY_isInstance(other, py.str) - && this._value === other._value) { - return py.True; - } - return py.False; - }, - __lt__: function (other) { - if (py.PY_not(py.PY_call(py.isinstance, [other, py.str]))) { - return py.NotImplemented; - } - return this._value < other._value ? py.True : py.False; - }, - __le__: function (other) { - if (!py.PY_isInstance(other, py.str)) { - return py.NotImplemented; - } - return this._value <= other._value ? py.True : py.False; - }, - __gt__: function (other) { - if (!py.PY_isInstance(other, py.str)) { - return py.NotImplemented; - } - return this._value > other._value ? py.True : py.False; - }, - __ge__: function (other) { - if (!py.PY_isInstance(other, py.str)) { - return py.NotImplemented; - } - return this._value >= other._value ? py.True : py.False; - }, - __add__: function (other) { - if (!py.PY_isInstance(other, py.str)) { - return py.NotImplemented; - } - return py.str.fromJSON(this._value + other._value); - }, - __nonzero__: function () { - return this._value.length ? py.True : py.False; - }, - __contains__: function (s) { - return (this._value.indexOf(s._value) !== -1) ? py.True : py.False; - }, - fromJSON: function (s) { - if (typeof s === 'string') { - var instance = py.PY_call(py.str); - instance._value = s; - return instance; - } - throw new Error("str.fromJSON can only take strings"); - }, - toJSON: function () { - return this._value; - } - }); - py.tuple = py.type('tuple', null, { - __init__: function () { - this._values = []; - }, - __len__: function () { - return this._values.length; - }, - __nonzero__: function () { - return py.PY_size(this) > 0 ? py.True : py.False; - }, - __contains__: function (value) { - for(var i=0, len=this._values.length; i 0 ? py.True : py.False; - }, - }); - py.list = Object.assign({}, py.tuple, py.list); // Copy attributes not redefined in type list - py.dict = py.type('dict', null, { - __init__: function () { - this._store = {}; - }, - __getitem__: function (key) { - var h = key.__hash__(); - if (!(h in this._store)) { - throw new Error("KeyError: '" + key.toJSON() + "'"); - } - return this._store[h][1]; - }, - __setitem__: function (key, value) { - this._store[key.__hash__()] = [key, value]; - }, - __len__: function () { - return Object.keys(this._store).length - }, - __nonzero__: function () { - return py.PY_size(this) > 0 ? py.True : py.False; - }, - get: function () { - var args = py.PY_parseArgs(arguments, ['k', ['d', py.None]]); - var h = args.k.__hash__(); - if (!(h in this._store)) { - return args.d; - } - return this._store[h][1]; - }, - fromJSON: function (d) { - var instance = py.PY_call(py.dict); - for (var k in (d || {})) { - if (!d.hasOwnProperty(k)) { continue; } - instance.__setitem__( - py.str.fromJSON(k), - PY_ensurepy(d[k])); - } - return instance; - }, - toJSON: function () { - var out = {}; - for(var k in this._store) { - var item = this._store[k]; - out[item[0].toJSON()] = item[1].toJSON(); - } - return out; - } - }); - py.PY_def = py.type('function', null, { - __call__: function () { - // don't want to rewrite __call__ for instancemethod - return this._func.apply(this._inst, arguments); - }, - fromJSON: function (nativefunc) { - var instance = py.PY_call(py.PY_def); - instance._inst = null; - instance._func = nativefunc; - return instance; - }, - toJSON: function () { - return this._func; - } - }); - py.classmethod = py.type('classmethod', null, { - __init__: function () { - var args = py.PY_parseArgs(arguments, 'function'); - this._func = args['function']; - }, - __get__: function (obj, type) { - return PY_instancemethod.fromJSON(this._func, type); - }, - fromJSON: function (func) { - return py.PY_call(py.classmethod, [func]); - } - }); - var PY_instancemethod = py.type('instancemethod', [py.PY_def], { - fromJSON: function (nativefunc, instance) { - var inst = py.PY_call(PY_instancemethod); - // could also use bind? - inst._inst = instance; - inst._func = nativefunc; - return inst; - } - }); - - py.abs = new py.PY_def.fromJSON(function abs() { - var args = py.PY_parseArgs(arguments, ['number']); - if (!args.number.__abs__) { - throw new Error( - "TypeError: bad operand type for abs(): '" - + typename(args.number) - + "'"); - } - return args.number.__abs__(); - }); - py.len = new py.PY_def.fromJSON(function len() { - var args = py.PY_parseArgs(arguments, ['object']); - return py.float.fromJSON(py.PY_size(args.object)); - }); - py.isinstance = new py.PY_def.fromJSON(function isinstance() { - var args = py.PY_parseArgs(arguments, ['object', 'class']); - return py.PY_isInstance(args.object, args['class']) - ? py.True : py.False; - }); - py.issubclass = new py.PY_def.fromJSON(function issubclass() { - var args = py.PY_parseArgs(arguments, ['C', 'B']); - return py.PY_isSubclass(args.C, args.B) - ? py.True : py.False; - }); - - - /** - * Implements the decoding of Python string literals (embedded in - * JS strings) into actual JS strings. This includes the decoding - * of escapes into their corresponding JS - * characters/codepoints/whatever. - * - * The ``unicode`` flags notes whether the literal should be - * decoded as a bytestring literal or a unicode literal, which - * pretty much only impacts decoding (or not) of unicode escapes - * at this point since bytestrings are not technically handled - * (everything is decoded to JS "unicode" strings) - * - * Eventurally, ``str`` could eventually use typed arrays, that'd - * be interesting... - */ - var PY_decode_string_literal = function (str, unicode) { - var out = [], code; - // Directly maps a single escape code to an output - // character - var direct_map = { - '\\': '\\', - '"': '"', - "'": "'", - 'a': '\x07', - 'b': '\x08', - 'f': '\x0c', - 'n': '\n', - 'r': '\r', - 't': '\t', - 'v': '\v' - }; - - for (var i=0; i': ['ne', 'ne', function (a, b) { return a !== b; }], - '<': ['lt', 'gt', function (a, b) {return a.__class__.__name__ < b.__class__.__name__;}], - '<=': ['le', 'ge', function (a, b) {return a.__class__.__name__ <= b.__class__.__name__;}], - '>': ['gt', 'lt', function (a, b) {return a.__class__.__name__ > b.__class__.__name__;}], - '>=': ['ge', 'le', function (a, b) {return a.__class__.__name__ >= b.__class__.__name__;}], - - '+': ['add', 'radd'], - '-': ['sub', 'rsub'], - '*': ['mul', 'rmul'], - '/': ['div', 'rdiv'], - '//': ['floordiv', 'rfloordiv'], - '%': ['mod', 'rmod'], - '**': ['pow', 'rpow'], - '<<': ['lshift', 'rlshift'], - '>>': ['rshift', 'rrshift'], - '&': ['and', 'rand'], - '^': ['xor', 'rxor'], - '|': ['or', 'ror'] - }; - /** - * Implements operator fallback/reflection. - * - * First two arguments are the objects to apply the operator on, - * in their actual order (ltr). - * - * Third argument is the actual operator. - * - * If the operator methods raise exceptions, those exceptions are - * not intercepted. - */ - var PY_op = function (o1, o2, op) { - var r; - var methods = PY_operators[op]; - var forward = '__' + methods[0] + '__', reverse = '__' + methods[1] + '__'; - var otherwise = methods[2]; - - if (forward in o1 && (r = o1[forward](o2)) !== py.NotImplemented) { - return r; - } - if (reverse in o2 && (r = o2[reverse](o1)) !== py.NotImplemented) { - return r; - } - if (otherwise) { - return PY_ensurepy(otherwise(o1, o2)); - } - throw new Error( - "TypeError: unsupported operand type(s) for " + op + ": '" - + typename(o1) + "' and '" + typename(o2) + "'"); - }; - - var PY_builtins = { - type: py.type, - - None: py.None, - True: py.True, - False: py.False, - NotImplemented: py.NotImplemented, - - object: py.object, - bool: py.bool, - float: py.float, - str: py.str, - unicode: py.unicode, - tuple: py.tuple, - list: py.list, - dict: py.dict, - - abs: py.abs, - len: py.len, - isinstance: py.isinstance, - issubclass: py.issubclass, - classmethod: py.classmethod, - }; - - py.parse = function (toks) { - var index = 0; - token = toks[0]; - next = function () { return toks[++index]; }; - return expression(); - }; - var evaluate_operator = function (operator, a, b) { - switch (operator) { - case 'is': return a === b ? py.True : py.False; - case 'is not': return a !== b ? py.True : py.False; - case 'in': - return b.__contains__(a); - case 'not in': - return py.PY_isTrue(b.__contains__(a)) ? py.False : py.True; - case '==': case '!=': case '<>': - case '<': case '<=': - case '>': case '>=': - return PY_op(a, b, operator); - } - throw new Error('SyntaxError: unknown comparator [[' + operator + ']]'); - }; - py.evaluate = function (expr, context) { - context = context || {}; - switch (expr.id) { - case '(name)': - var val = context[expr.value]; - if (val === undefined && expr.value in PY_builtins) { - return PY_builtins[expr.value]; - } - return PY_ensurepy(val, expr.value); - case '(string)': - return py.str.fromJSON(expr.value); - case '(number)': - return py.float.fromJSON(expr.value); - case '(constant)': - switch (expr.value) { - case 'None': return py.None; - case 'False': return py.False; - case 'True': return py.True; - } - throw new Error("SyntaxError: unknown constant '" + expr.value + "'"); - case '(comparator)': - var result, left = py.evaluate(expr.expressions[0], context); - for(var i=0; i>': - case '&': case '^': case '|': - return PY_op( - py.evaluate(expr.first, context), - py.evaluate(expr.second, context), - expr.id); - - default: - throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]'); - } - }; - py.eval = function (str, context) { - return py.evaluate( - py.parse( - py.tokenize( - str)), - context).toJSON(); - } -})(typeof exports === 'undefined' ? py : exports); diff --git a/addons/web/static/lib/py.js/lib/py_extras.js b/addons/web/static/lib/py.js/lib/py_extras.js deleted file mode 100644 index 0f4ae0134e3..00000000000 --- a/addons/web/static/lib/py.js/lib/py_extras.js +++ /dev/null @@ -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 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 . - 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 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); diff --git a/addons/web/static/src/core/py_js/py.js b/addons/web/static/src/core/py_js/py.js index 0c482d90068..50c41eaa7e4 100644 --- a/addons/web/static/src/core/py_js/py.js +++ b/addons/web/static/src/core/py_js/py.js @@ -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) { diff --git a/addons/web/static/src/legacy/js/core/collections.js b/addons/web/static/src/legacy/js/core/collections.js deleted file mode 100644 index 8374f94bbd6..00000000000 --- a/addons/web/static/src/legacy/js/core/collections.js +++ /dev/null @@ -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, - }; diff --git a/addons/web/static/src/legacy/js/core/context.js b/addons/web/static/src/legacy/js/core/context.js deleted file mode 100644 index 673ab7bcbf0..00000000000 --- a/addons/web/static/src/legacy/js/core/context.js +++ /dev/null @@ -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; diff --git a/addons/web/static/src/legacy/js/core/domain.js b/addons/web/static/src/legacy/js/core/domain.js deleted file mode 100644 index a1b3d3defdc..00000000000 --- a/addons/web/static/src/legacy/js/core/domain.js +++ /dev/null @@ -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; diff --git a/addons/web/static/src/legacy/js/core/py_utils.js b/addons/web/static/src/legacy/js/core/py_utils.js deleted file mode 100644 index 00abdf7e8dd..00000000000 --- a/addons/web/static/src/legacy/js/core/py_utils.js +++ /dev/null @@ -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 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, -}; diff --git a/addons/web/static/src/legacy/legacy_service_provider.js b/addons/web/static/src/legacy/legacy_service_provider.js index 10494f36038..73f42c8eac7 100644 --- a/addons/web/static/src/legacy/legacy_service_provider.js +++ b/addons/web/static/src/legacy/legacy_service_provider.js @@ -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); diff --git a/addons/web/static/tests/legacy/core/domain_tests.js b/addons/web/static/tests/legacy/core/domain_tests.js deleted file mode 100644 index 362f3c08b92..00000000000 --- a/addons/web/static/tests/legacy/core/domain_tests.js +++ /dev/null @@ -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 })); - }); -}); diff --git a/addons/web/static/tests/legacy/core/py_utils_tests.js b/addons/web/static/tests/legacy/core/py_utils_tests.js deleted file mode 100644 index 64463f18fe6..00000000000 --- a/addons/web/static/tests/legacy/core/py_utils_tests.js +++ /dev/null @@ -1,1429 +0,0 @@ -/** @odoo-module **/ - -import Context from "@web/legacy/js/core/context"; -import pyUtils from "@web/legacy/js/core/py_utils"; -import time from "@web/legacy/js/core/time"; - -const r = String.raw; - -QUnit.assert.checkAST = function (expr, message) { - var ast = pyUtils._getPyJSAST(expr); - var formattedAST = pyUtils._formatAST(ast); - this.pushResult({ - result: expr === formattedAST, - actual: formattedAST, - expected: expr, - message: message - }); -}; - -QUnit.module('core', function () { - - QUnit.module('py_utils'); - - QUnit.test('simple python expression', function(assert) { - assert.expect(2); - - var result = pyUtils.py_eval("True and False"); - assert.strictEqual(result, false, "should properly evaluate basic expression"); - result = pyUtils.py_eval("a + b", {a: 1, b: 41}); - assert.strictEqual(result, 42, "should properly evaluate basic sum"); - }); - - QUnit.test('simple arithmetic', function(assert) { - assert.expect(3); - - var result = pyUtils.py_eval("1 + 2"); - assert.strictEqual(result, 3, "should properly evaluate sum"); - result = pyUtils.py_eval("42 % 5"); - assert.strictEqual(result, 2, "should properly evaluate modulo operator"); - result = pyUtils.py_eval("2 ** 3"); - assert.strictEqual(result, 8, "should properly evaluate power operator"); - }); - - - QUnit.test('not prefix', function (assert) { - assert.expect(3); - - assert.ok(py.eval('not False')); - assert.ok(py.eval('not foo', {foo: false})); - assert.ok(py.eval('not a in b', {a: 3, b: [1, 2, 4, 8]})); - }); - - - function makeTimeCheck (assert) { - var context = pyUtils.context(); - return function (expr, func, message) { - // evaluate expr between two calls to new Date(), and check that - // the result is between the transformed dates - var d0 = new Date(); - var result = py.eval(expr, context); - var d1 = new Date(); - assert.ok(func(d0) <= result && result <= func(d1), message); - }; - } - - // Port from pypy/lib_pypy/test_datetime.py - function makeEq(assert, c2) { - var ctx = pyUtils.context(); - var c = Object.assign({ td: ctx.datetime.timedelta }, c2 || {}); - return function (a, b, message) { - assert.ok(py.eval(a + ' == ' + b, c), message); - }; - } - - QUnit.test('strftime', function (assert) { - assert.expect(3); - - var check = makeTimeCheck(assert); - - check("time.strftime('%Y')", function(d) { - return String(d.getFullYear()); - }); - - check("time.strftime('%Y')+'-01-30'", function(d) { - return String(d.getFullYear()) + '-01-30'; - }); - - check("time.strftime('%Y-%m-%d %H:%M:%S')", function (d) { - return ( - [ - String(d.getUTCFullYear()).padStart(4, "0"), - String(d.getUTCMonth() + 1).padStart(2, "0"), - String(d.getUTCDate()).padStart(2, "0"), - ].join("-") + - " " + - [ - String(d.getUTCHours()).padStart(2, "0"), - String(d.getUTCMinutes()).padStart(2, "0"), - String(d.getUTCSeconds()).padStart(2, "0"), - ].join(":") - ); - }); - }); - - QUnit.test('context_today', function (assert) { - assert.expect(1); - - var check = makeTimeCheck(assert, pyUtils); - - check("context_today().strftime('%Y-%m-%d')", function(d) { - return [ - String(d.getFullYear()).padStart(4, "0"), - String(d.getMonth() + 1).padStart(2, "0"), - String(d.getDate()).padStart(2, "0"), - ].join("-"); - }); - }); - - QUnit.test('timedelta.test_constructor', function (assert) { - assert.expect(16); - - var eq = makeEq(assert); - - // keyword args to constructor - eq('td()', 'td(weeks=0, days=0, hours=0, minutes=0, seconds=0, ' + - 'milliseconds=0, microseconds=0)'); - eq('td(1)', 'td(days=1)'); - eq('td(0, 1)', 'td(seconds=1)'); - eq('td(0, 0, 1)', 'td(microseconds=1)'); - eq('td(weeks=1)', 'td(days=7)'); - eq('td(days=1)', 'td(hours=24)'); - eq('td(hours=1)', 'td(minutes=60)'); - eq('td(minutes=1)', 'td(seconds=60)'); - eq('td(seconds=1)', 'td(milliseconds=1000)'); - eq('td(milliseconds=1)', 'td(microseconds=1000)'); - - // Check float args to constructor - eq('td(weeks=1.0/7)', 'td(days=1)'); - eq('td(days=1.0/24)', 'td(hours=1)'); - eq('td(hours=1.0/60)', 'td(minutes=1)'); - eq('td(minutes=1.0/60)', 'td(seconds=1)'); - eq('td(seconds=0.001)', 'td(milliseconds=1)'); - eq('td(milliseconds=0.001)', 'td(microseconds=1)'); - }); - - QUnit.test('timedelta.test_computations', function (assert) { - assert.expect(28); - - var c = pyUtils.context(); - var zero = py.float.fromJSON(0); - var eq = makeEq(assert, { - // one week - a: py.PY_call(c.datetime.timedelta, [ - py.float.fromJSON(7)]), - // one minute - b: py.PY_call(c.datetime.timedelta, [ - zero, py.float.fromJSON(60)]), - // one millisecond - c: py.PY_call(c.datetime.timedelta, [ - zero, zero, py.float.fromJSON(1000)]), - }); - - eq('a+b+c', 'td(7, 60, 1000)'); - eq('a-b', 'td(6, 24*3600 - 60)'); - eq('-a', 'td(-7)'); - eq('+a', 'td(7)'); - eq('-b', 'td(-1, 24*3600 - 60)'); - eq('-c', 'td(-1, 24*3600 - 1, 999000)'); - // eq('abs(a)', 'a'); - // eq('abs(-a)', 'a'); - eq('td(6, 24*3600)', 'a'); - eq('td(0, 0, 60*1000000)', 'b'); - eq('a*10', 'td(70)'); - eq('a*10', '10*a'); - // eq('a*10L', '10*a'); - eq('b*10', 'td(0, 600)'); - eq('10*b', 'td(0, 600)'); - // eq('b*10L', 'td(0, 600)'); - eq('c*10', 'td(0, 0, 10000)'); - eq('10*c', 'td(0, 0, 10000)'); - // eq('c*10L', 'td(0, 0, 10000)'); - eq('a*-1', '-a'); - eq('b*-2', '-b-b'); - eq('c*-2', '-c+-c'); - eq('b*(60*24)', '(b*60)*24'); - eq('b*(60*24)', '(60*b)*24'); - eq('c*1000', 'td(0, 1)'); - eq('1000*c', 'td(0, 1)'); - eq('a//7', 'td(1)'); - eq('b//10', 'td(0, 6)'); - eq('c//1000', 'td(0, 0, 1)'); - eq('a//10', 'td(0, 7*24*360)'); - eq('a//3600000', 'td(0, 0, 7*24*1000)'); - - // Issue #11576 - eq('td(999999999, 86399, 999999) - td(999999999, 86399, 999998)', 'td(0, 0, 1)'); - eq('td(999999999, 1, 1) - td(999999999, 1, 0)', - 'td(0, 0, 1)'); - }); - - QUnit.test('timedelta.test_basic_attributes', function (assert) { - assert.expect(3); - - var ctx = pyUtils.context(); - assert.strictEqual(py.eval('datetime.timedelta(1, 7, 31).days', ctx), 1); - assert.strictEqual(py.eval('datetime.timedelta(1, 7, 31).seconds', ctx), 7); - assert.strictEqual(py.eval('datetime.timedelta(1, 7, 31).microseconds', ctx), 31); - }); - - QUnit.test('timedelta.test_total_seconds', function (assert) { - assert.expect(6); - - var c = { timedelta: pyUtils.context().datetime.timedelta }; - assert.strictEqual(py.eval('timedelta(365).total_seconds()', c), 31536000); - assert.strictEqual( - py.eval('timedelta(seconds=123456.789012).total_seconds()', c), - 123456.789012); - assert.strictEqual( - py.eval('timedelta(seconds=-123456.789012).total_seconds()', c), - -123456.789012); - assert.strictEqual( - py.eval('timedelta(seconds=0.123456).total_seconds()', c), 0.123456); - assert.strictEqual(py.eval('timedelta().total_seconds()', c), 0); - assert.strictEqual( - py.eval('timedelta(seconds=1000000).total_seconds()', c), 1e6); - }); - - QUnit.test('timedelta.test_str', function (assert) { - assert.expect(10); - - var c = { td: pyUtils.context().datetime.timedelta }; - - assert.strictEqual(py.eval('str(td(1))', c), "1 day, 0:00:00"); - assert.strictEqual(py.eval('str(td(-1))', c), "-1 day, 0:00:00"); - assert.strictEqual(py.eval('str(td(2))', c), "2 days, 0:00:00"); - assert.strictEqual(py.eval('str(td(-2))', c), "-2 days, 0:00:00"); - - assert.strictEqual(py.eval('str(td(hours=12, minutes=58, seconds=59))', c), - "12:58:59"); - assert.strictEqual(py.eval('str(td(hours=2, minutes=3, seconds=4))', c), - "2:03:04"); - assert.strictEqual( - py.eval('str(td(weeks=-30, hours=23, minutes=12, seconds=34))', c), - "-210 days, 23:12:34"); - - assert.strictEqual(py.eval('str(td(milliseconds=1))', c), "0:00:00.001000"); - assert.strictEqual(py.eval('str(td(microseconds=3))', c), "0:00:00.000003"); - - assert.strictEqual( - py.eval('str(td(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999))', c), - "999999999 days, 23:59:59.999999"); - }); - - QUnit.test('timedelta.test_massive_normalization', function (assert) { - assert.expect(3); - - var td = py.PY_call( - pyUtils.context().datetime.timedelta, - {microseconds: py.float.fromJSON(-1)}); - assert.strictEqual(td.days, -1); - assert.strictEqual(td.seconds, 24 * 3600 - 1); - assert.strictEqual(td.microseconds, 999999); - }); - - QUnit.test('timedelta.test_bool', function (assert) { - assert.expect(5); - - var c = { td: pyUtils.context().datetime.timedelta }; - assert.ok(py.eval('bool(td(1))', c)); - assert.ok(py.eval('bool(td(0, 1))', c)); - assert.ok(py.eval('bool(td(0, 0, 1))', c)); - assert.ok(py.eval('bool(td(microseconds=1))', c)); - assert.ok(py.eval('bool(not td(0))', c)); - }); - - QUnit.test('date.test_computations', function (assert) { - assert.expect(31); - - var d = pyUtils.context().datetime; - - var a = d.date.fromJSON(2002, 1, 31); - var b = d.date.fromJSON(1956, 1, 31); - assert.strictEqual( - py.eval('(a - b).days', {a: a, b: b}), - 46 * 365 + 12); - assert.strictEqual(py.eval('(a - b).seconds', {a: a, b: b}), 0); - assert.strictEqual(py.eval('(a - b).microseconds', {a: a, b: b}), 0); - - var day = py.PY_call(d.timedelta, [py.float.fromJSON(1)]); - var week = py.PY_call(d.timedelta, [py.float.fromJSON(7)]); - a = d.date.fromJSON(2002, 3, 2); - var ctx = { - a: a, - day: day, - week: week, - date: d.date - }; - assert.ok(py.eval('a + day == date(2002, 3, 3)', ctx)); - assert.ok(py.eval('day + a == date(2002, 3, 3)', ctx)); // 5 - assert.ok(py.eval('a - day == date(2002, 3, 1)', ctx)); - assert.ok(py.eval('-day + a == date(2002, 3, 1)', ctx)); - assert.ok(py.eval('a + week == date(2002, 3, 9)', ctx)); - assert.ok(py.eval('a - week == date(2002, 2, 23)', ctx)); - assert.ok(py.eval('a + 52*week == date(2003, 3, 1)', ctx)); // 10 - assert.ok(py.eval('a - 52*week == date(2001, 3, 3)', ctx)); - assert.ok(py.eval('(a + week) - a == week', ctx)); - assert.ok(py.eval('(a + day) - a == day', ctx)); - assert.ok(py.eval('(a - week) - a == -week', ctx)); - assert.ok(py.eval('(a - day) - a == -day', ctx)); // 15 - assert.ok(py.eval('a - (a + week) == -week', ctx)); - assert.ok(py.eval('a - (a + day) == -day', ctx)); - assert.ok(py.eval('a - (a - week) == week', ctx)); - assert.ok(py.eval('a - (a - day) == day', ctx)); - - assert.throws(function () { - py.eval('a + 1', ctx); - }, /^Error: TypeError:/); //20 - assert.throws(function () { - py.eval('a - 1', ctx); - }, /^Error: TypeError:/); - assert.throws(function () { - py.eval('1 + a', ctx); - }, /^Error: TypeError:/); - assert.throws(function () { - py.eval('1 - a', ctx); - }, /^Error: TypeError:/); - - // delta - date is senseless. - assert.throws(function () { - py.eval('day - a', ctx); - }, /^Error: TypeError:/); - // mixing date and (delta or date) via * or // is senseless - assert.throws(function () { - py.eval('day * a', ctx); - }, /^Error: TypeError:/); // 25 - assert.throws(function () { - py.eval('a * day', ctx); - }, /^Error: TypeError:/); - assert.throws(function () { - py.eval('day // a', ctx); - }, /^Error: TypeError:/); - assert.throws(function () { - py.eval('a // day', ctx); - }, /^Error: TypeError:/); - assert.throws(function () { - py.eval('a * a', ctx); - }, /^Error: TypeError:/); - assert.throws(function () { - py.eval('a // a', ctx); - }, /^Error: TypeError:/); // 30 - // date + date is senseless - assert.throws(function () { - py.eval('a + a', ctx); - }, /^Error: TypeError:/); - - }); - - QUnit.test('add', function (assert) { - assert.expect(2); - assert.strictEqual( - py.eval("(datetime.datetime(2017, 4, 18, 9, 32, 15).add(hours=2, minutes=30, " + - "seconds=10)).strftime('%Y-%m-%d %H:%M:%S')", pyUtils.context()), - '2017-04-18 12:02:25' - ); - assert.strictEqual( - py.eval("(datetime.date(2017, 4, 18).add(months=1, years=3, days=5))" + - ".strftime('%Y-%m-%d')", pyUtils.context()), - '2020-05-23' - ); - }); - - QUnit.test('subtract', function(assert) { - assert.expect(2); - assert.strictEqual( - py.eval("(datetime.datetime(2017, 4, 18, 9, 32, 15).subtract(hours=1, minutes=5, " + - "seconds=33)).strftime('%Y-%m-%d %H:%M:%S')", pyUtils.context()), - '2017-04-18 08:26:42' - ); - assert.strictEqual( - py.eval("(datetime.date(2017, 4, 18).subtract(years=5, months=1, days=1))" + - ".strftime('%Y-%m-%d')", pyUtils.context()), - '2012-03-17' - ); - }) - - QUnit.test('start_of/end_of', function (assert) { - assert.expect(26); - - var datetime = pyUtils.context().datetime; - // Ain't that a kick in the head? - var _date = datetime.date.fromJSON(2281, 10, 11); - var _datetime = datetime.datetime.fromJSON(2281, 10, 11, 22, 33, 44); - var ctx = { - _date: _date, - _datetime: _datetime, - date: datetime.date, - datetime: datetime.datetime - }; - - // Start of period - // Dates first - assert.ok(py.eval('_date.start_of("year") == date(2281, 1, 1)', ctx)); - assert.ok(py.eval('_date.start_of("quarter") == date(2281, 10, 1)', ctx)); - assert.ok(py.eval('_date.start_of("month") == date(2281, 10, 1)', ctx)); - assert.ok(py.eval('_date.start_of("week") == date(2281, 10, 10)', ctx)); - assert.ok(py.eval('_date.start_of("day") == date(2281, 10, 11)', ctx)); - assert.throws(function () { - py.eval('_date.start_of("hour")', ctx); - }, /^Error: ValueError:/); - - // Datetimes - assert.ok(py.eval('_datetime.start_of("year") == datetime(2281, 1, 1)', ctx)); - assert.ok(py.eval('_datetime.start_of("quarter") == datetime(2281, 10, 1)', ctx)); - assert.ok(py.eval('_datetime.start_of("month") == datetime(2281, 10, 1)', ctx)); - assert.ok(py.eval('_datetime.start_of("week") == datetime(2281, 10, 10)', ctx)); - assert.ok(py.eval('_datetime.start_of("day") == datetime(2281, 10, 11)', ctx)); - assert.ok(py.eval('_datetime.start_of("hour") == datetime(2281, 10, 11, 22, 0, 0)', ctx)); - assert.throws(function () { - py.eval('_datetime.start_of("cheese")', ctx); - }, /^Error: ValueError:/); - - // End of period - // Dates - assert.ok(py.eval('_date.end_of("year") == date(2281, 12, 31)', ctx)); - assert.ok(py.eval('_date.end_of("quarter") == date(2281, 12, 31)', ctx)); - assert.ok(py.eval('_date.end_of("month") == date(2281, 10, 31)', ctx)); - assert.ok(py.eval('_date.end_of("week") == date(2281, 10, 16)', ctx)); - assert.ok(py.eval('_date.end_of("day") == date(2281, 10, 11)', ctx)); - assert.throws(function () { - py.eval('_date.start_of("hour")', ctx); - }, /^Error: ValueError:/); - - // Datetimes - assert.ok(py.eval('_datetime.end_of("year") == datetime(2281, 12, 31, 23, 59, 59)', ctx)); - assert.ok(py.eval('_datetime.end_of("quarter") == datetime(2281, 12, 31, 23, 59, 59)', ctx)); - assert.ok(py.eval('_datetime.end_of("month") == datetime(2281, 10, 31, 23, 59, 59)', ctx)); - assert.ok(py.eval('_datetime.end_of("week") == datetime(2281, 10, 16, 23, 59, 59)', ctx)); - assert.ok(py.eval('_datetime.end_of("day") == datetime(2281, 10, 11, 23, 59, 59)', ctx)); - assert.ok(py.eval('_datetime.end_of("hour") == datetime(2281, 10, 11, 22, 59, 59)', ctx)); - assert.throws(function () { - py.eval('_datetime.end_of("cheese")', ctx); - }, /^Error: ValueError:/); - }); - - QUnit.test('relativedelta', function (assert) { - assert.expect(7); - - assert.strictEqual( - py.eval("(datetime.date(2012, 2, 15) + relativedelta(days=-1)).strftime('%Y-%m-%d 23:59:59')", - pyUtils.context()), - "2012-02-14 23:59:59"); - assert.strictEqual( - py.eval("(datetime.date(2012, 2, 15) + relativedelta(days=1)).strftime('%Y-%m-%d')", - pyUtils.context()), - "2012-02-16"); - assert.strictEqual( - py.eval("(datetime.date(2012, 2, 15) + relativedelta(days=-1)).strftime('%Y-%m-%d')", - pyUtils.context()), - "2012-02-14"); - assert.strictEqual( - py.eval("(datetime.date(2012, 2, 1) + relativedelta(days=-1)).strftime('%Y-%m-%d')", - pyUtils.context()), - '2012-01-31'); - assert.strictEqual( - py.eval("(datetime.date(2015,2,5)+relativedelta(days=-6,weekday=0)).strftime('%Y-%m-%d')", - pyUtils.context()), - '2015-02-02'); - assert.strictEqual( - py.eval("(datetime.date(2018, 2, 1) + relativedelta(years=7, months=42, days=42)).strftime('%Y-%m-%d')", - pyUtils.context()), - '2028-09-12'); - assert.strictEqual( - py.eval("(datetime.date(2018, 2, 1) + relativedelta(years=-7, months=-42, days=-42)).strftime('%Y-%m-%d')", - pyUtils.context()), - '2007-06-20'); - }); - - - QUnit.test('timedelta', function (assert) { - assert.expect(4); - assert.strictEqual( - py.eval("(datetime.datetime(2017, 2, 15, 1, 7, 31) + datetime.timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')", - pyUtils.context()), - "2017-02-16 01:07:31"); - assert.strictEqual( - py.eval("(datetime.datetime(2012, 2, 15, 1, 7, 31) - datetime.timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S')", - pyUtils.context()), - "2012-02-15 00:07:31"); - assert.strictEqual( - py.eval("(datetime.datetime(2012, 2, 15, 1, 7, 31) + datetime.timedelta(hours=-1)).strftime('%Y-%m-%d %H:%M:%S')", - pyUtils.context()), - "2012-02-15 00:07:31"); - assert.strictEqual( - py.eval("(datetime.datetime(2012, 2, 15, 1, 7, 31) + datetime.timedelta(minutes=100)).strftime('%Y-%m-%d %H:%M:%S')", - pyUtils.context()), - "2012-02-15 02:47:31"); - }); - - QUnit.test('datetime.tojson', function (assert) { - assert.expect(7); - - var result = py.eval( - 'datetime.datetime(2012, 2, 15, 1, 7, 31)', - pyUtils.context()); - - assert.ok(result instanceof Date); - assert.strictEqual(result.getFullYear(), 2012); - assert.strictEqual(result.getMonth(), 1); - assert.strictEqual(result.getDate(), 15); - assert.strictEqual(result.getHours(), 1); - assert.strictEqual(result.getMinutes(), 7); - assert.strictEqual(result.getSeconds(), 31); - }); - - - QUnit.test('to_utc in october with winter/summer change', function (assert) { - assert.expect(7); - - const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset; - Date.prototype.getTimezoneOffset = function () { - const month = this.getMonth() // starts at 0; - if (10 <= month || month <= 2) { - //rough approximation - return -60; - } else { - return -120; - } - } - - var result = py.eval( - "datetime.datetime(2022, 10, 17).to_utc()", - pyUtils.context()); - - assert.ok(result instanceof Date); - assert.strictEqual(result.getFullYear(), 2022); - assert.strictEqual(result.getMonth(), 9); - assert.strictEqual(result.getDate(), 16); - assert.strictEqual(result.getHours(), 22); - assert.strictEqual(result.getMinutes(), 0); - assert.strictEqual(result.getSeconds(), 0); - - Date.prototype.getTimezoneOffset = originalGetTimezoneOffset; - }); - - QUnit.test('datetime.combine', function (assert) { - assert.expect(2); - - var result = py.eval( - 'datetime.datetime.combine(datetime.date(2012, 2, 15),' + - ' datetime.time(1, 7, 13))' + - ' .strftime("%Y-%m-%d %H:%M:%S")', - pyUtils.context()); - assert.strictEqual(result, "2012-02-15 01:07:13"); - - result = py.eval( - 'datetime.datetime.combine(datetime.date(2012, 2, 15),' + - ' datetime.time())' + - ' .strftime("%Y-%m-%d %H:%M:%S")', - pyUtils.context()); - assert.strictEqual(result, '2012-02-15 00:00:00'); - }); - - QUnit.test('datetime.replace', function (assert) { - assert.expect(1); - - var result = py.eval( - 'datetime.datetime(2012, 2, 15, 1, 7, 13)' + - ' .replace(hour=0, minute=0, second=0)' + - ' .strftime("%Y-%m-%d %H:%M:%S")', - pyUtils.context()); - assert.strictEqual(result, "2012-02-15 00:00:00"); - }); - - QUnit.test('conditional expressions', function (assert) { - assert.expect(2); - assert.strictEqual( - py.eval('1 if a else 2', {a: true}), - 1 - ); - assert.strictEqual( - py.eval('1 if a else 2', {a: false}), - 2 - ); - }); - - QUnit.module('py_utils (eval domain contexts)', { - beforeEach: function() { - this.user_context = { - uid: 1, - lang: 'en_US', - tz: false, - }; - }, - }); - - - QUnit.test('empty, basic', function (assert) { - assert.expect(3); - - var result = pyUtils.eval_domains_and_contexts({ - contexts: [this.user_context], - domains: [], - }); - - // default values for new db - assert.deepEqual(result.context, { - lang: 'en_US', - tz: false, - uid: 1 - }); - assert.deepEqual(result.domain, []); - assert.deepEqual(result.group_by, []); - }); - - - QUnit.test('context_merge_00', function (assert) { - assert.expect(1); - - var ctx = [ - { - "__contexts": [ - { "lang": "en_US", "tz": false, "uid": 1 }, - { - "active_id": 8, - "active_ids": [ 8 ], - "active_model": "sale.order", - "bin_raw": true, - "default_composition_mode": "comment", - "default_model": "sale.order", - "default_res_id": 8, - "default_template_id": 18, - "default_use_template": true, - "edi_web_url_view": "faaaake", - "lang": "en_US", - "mark_so_as_sent": null, - "show_address": null, - "tz": false, - "uid": null - }, - {} - ], - "__eval_context": null, - "__ref": "compound_context" - }, - { "active_id": 9, "active_ids": [ 9 ], "active_model": "mail.compose.message" } - ]; - var result = pyUtils.eval_domains_and_contexts({ - contexts: ctx, - domins: [], - }); - - assert.deepEqual(result.context, { - active_id: 9, - active_ids: [9], - active_model: 'mail.compose.message', - bin_raw: true, - default_composition_mode: 'comment', - default_model: 'sale.order', - default_res_id: 8, - default_template_id: 18, - default_use_template: true, - edi_web_url_view: "faaaake", - lang: 'en_US', - mark_so_as_sent: null, - show_address: null, - tz: false, - uid: null - }); - - }); - - QUnit.test('context_merge_01', function (assert) { - assert.expect(1); - - var ctx = [{ - "__contexts": [ - { - "lang": "en_US", - "tz": false, - "uid": 1 - }, - { - "default_attachment_ids": [], - "default_body": "", - "default_model": "res.users", - "default_parent_id": false, - "default_res_id": 1 - }, - {} - ], - "__eval_context": null, - "__ref": "compound_context" - }]; - var result = pyUtils.eval_domains_and_contexts({ - contexts: ctx, - domains: [], - }); - - assert.deepEqual(result.context, { - "default_attachment_ids": [], - "default_body": "", - "default_model": "res.users", - "default_parent_id": false, - "default_res_id": 1, - "lang": "en_US", - "tz": false, - "uid": 1 - }); - }); - - QUnit.test('domain with time', function (assert) { - assert.expect(1); - - var result = pyUtils.eval_domains_and_contexts({ - domains: [ - [['type', '=', 'contract']], - ["|", ["state", "in", ["open", "draft"]], [["type", "=", "contract"], ["state", "=", "pending"]]], - "['|', '&', ('date', '!=', False), ('date', '<=', time.strftime('%Y-%m-%d')), ('is_overdue_quantity', '=', True)]", - [['user_id', '=', 1]] - ], - contexts: [], - }); - - var d = new Date(); - var today = [ - String(d.getUTCFullYear()).padStart(4, "0"), - String(d.getUTCMonth() + 1).padStart(2, "0"), - String(d.getUTCDate()).padStart(2, "0"), - ].join("-"); - assert.deepEqual(result.domain, [ - ["type", "=", "contract"], - "|", ["state", "in", ["open", "draft"]], - [["type", "=", "contract"], - ["state", "=", "pending"]], - "|", - "&", ["date", "!=", false], - ["date", "<=", today], - ["is_overdue_quantity", "=", true], - ["user_id", "=", 1] - ]); - }); - - QUnit.test('conditional context', function (assert) { - assert.expect(2); - - var d = { - __ref: 'domain', - __debug: "[('company_id', '=', context.get('company_id',False))]" - }; - - var result1 = pyUtils.eval_domains_and_contexts({ - domains: [d], - contexts: [], - }); - assert.deepEqual(result1.domain, [['company_id', '=', false]]); - - var result2 = pyUtils.eval_domains_and_contexts({ - domains: [d], - contexts: [], - eval_context: {company_id: 42}, - }); - assert.deepEqual(result2.domain, [['company_id', '=', 42]]); - }); - - QUnit.test('substitution in context', function (assert) { - assert.expect(1); - - // setup(session); - var c = "{'default_opportunity_id': active_id, 'default_duration': 1.0, 'lng': lang}"; - var cc = new Context(c); - cc.set_eval_context({active_id: 42}); - var result = pyUtils.eval_domains_and_contexts({ - domains:[], contexts: [this.user_context, cc] - }); - - assert.deepEqual(result.context, { - lang: "en_US", - tz: false, - uid: 1, - default_opportunity_id: 42, - default_duration: 1.0, - lng: "en_US" - }); - }); - - QUnit.test('date', function (assert) { - assert.expect(1); - - var d = "[('state','!=','cancel'),('opening_date','>',context_today().strftime('%Y-%m-%d'))]"; - var result = pyUtils.eval_domains_and_contexts({ - domains: [d], - contexts: [], - }); - - var date = new Date(); - var today = [ - String(date.getFullYear()).padStart(4, "0"), - String(date.getMonth() + 1).padStart(2, "0"), - String(date.getDate()).padStart(2, "0"), - ].join("-"); - assert.deepEqual(result.domain, [ - ['state', '!=', 'cancel'], - ['opening_date', '>', today] - ]); - }); - - QUnit.test('delta', function (assert) { - assert.expect(1); - - var d = "[('type','=','in'),('day','<=', time.strftime('%Y-%m-%d')),('day','>',(context_today()-datetime.timedelta(days=15)).strftime('%Y-%m-%d'))]"; - var result = pyUtils.eval_domains_and_contexts({ - domains: [d], - contexts: [], - }); - var date = new Date(); - var today = [ - String(date.getFullYear()).padStart(4, "0"), - String(date.getMonth() + 1).padStart(2, "0"), - String(date.getDate()).padStart(2, "0"), - ].join("-"); - date.setDate(date.getDate() - 15); - var ago_15_d = [ - String(date.getFullYear()).padStart(4, "0"), - String(date.getMonth() + 1).padStart(2, "0"), - String(date.getDate()).padStart(2, "0"), - ].join("-"); - assert.deepEqual(result.domain, [ - ['type', '=', 'in'], - ['day', '<=', today], - ['day', '>', ago_15_d] - ]); - }); - - QUnit.test('horror from the deep', function (assert) { - assert.expect(1); - - var cs = [ - {"__ref": "compound_context", - "__contexts": [ - {"__ref": "context", "__debug": "{'k': 'foo,' + str(context.get('test_key', False))}"}, - {"__ref": "compound_context", - "__contexts": [ - {"lang": "en_US", "tz": false, "uid": 1}, - {"lang": "en_US", "tz": false, "uid": 1, - "active_model": "sale.order", "default_type": "out", - "show_address": 1, "contact_display": "partner_address", - "active_ids": [9], "active_id": 9}, - {} - ], "__eval_context": null }, - {"active_id": 8, "active_ids": [8], - "active_model": "stock.picking.out"}, - {"__ref": "context", "__debug": "{'default_ref': 'stock.picking.out,'+str(context.get('active_id', False))}", "__id": "54d6ad1d6c45"} - ], "__eval_context": null} - ]; - var result = pyUtils.eval_domains_and_contexts({ - domains: [], - contexts: cs, - }); - - assert.deepEqual(result.context, { - k: 'foo,False', - lang: 'en_US', - tz: false, - uid: 1, - active_model: 'stock.picking.out', - active_id: 8, - active_ids: [8], - default_type: 'out', - show_address: 1, - contact_display: 'partner_address', - default_ref: 'stock.picking.out,8' - }); - }); - - QUnit.module('py_utils (contexts)'); - - QUnit.test('context_recursive', function (assert) { - assert.expect(3); - - var context_to_eval = [{ - __ref: 'context', - __debug: '{"foo": context.get("bar", "qux")}' - }]; - assert.deepEqual( - pyUtils.eval('contexts', context_to_eval, {bar: "ok"}), - {foo: 'ok'}); - assert.deepEqual( - pyUtils.eval('contexts', context_to_eval, {bar: false}), - {foo: false}); - assert.deepEqual( - pyUtils.eval('contexts', context_to_eval), - {foo: 'qux'}); - }); - - QUnit.test('context_sequences', function (assert) { - assert.expect(1); - - // Context n should have base evaluation context + all of contexts - // 0..n-1 in its own evaluation context - var active_id = 4; - var result = pyUtils.eval('contexts', [ - { - "__contexts": [ - { - "department_id": false, - "lang": "en_US", - "project_id": false, - "section_id": false, - "tz": false, - "uid": 1 - }, - { "search_default_create_uid": 1 }, - {} - ], - "__eval_context": null, - "__ref": "compound_context" - }, - { - "active_id": active_id, - "active_ids": [ active_id ], - "active_model": "purchase.requisition" - }, - { - "__debug": "{'record_id' : active_id}", - "__id": "63e8e9bff8a6", - "__ref": "context" - } - ]); - - assert.deepEqual(result, { - department_id: false, - lang: 'en_US', - project_id: false, - section_id: false, - tz: false, - uid: 1, - search_default_create_uid: 1, - active_id: active_id, - active_ids: [active_id], - active_model: 'purchase.requisition', - record_id: active_id - }); - }); - - QUnit.test('non-literal_eval_contexts', function (assert) { - assert.expect(1); - - var result = pyUtils.eval('contexts', [{ - "__ref": "compound_context", - "__contexts": [ - {"__ref": "context", "__debug": "{'move_type':parent.move_type}", - "__id": "462b9dbed42f"} - ], - "__eval_context": { - "__ref": "compound_context", - "__contexts": [{ - "__ref": "compound_context", - "__contexts": [ - {"__ref": "context", "__debug": "{'move_type': move_type}", - "__id": "16a04ed5a194"} - ], - "__eval_context": { - "__ref": "compound_context", - "__contexts": [ - {"lang": "en_US", "tz": false, "uid": 1, - "journal_type": "sale", "section_id": false, - "default_move_type": "out_invoice", - "move_type": "out_invoice", "department_id": false}, - {"id": false, "journal_id": 10, - "number": false, "move_type": "out_invoice", - "currency_id": 1, "partner_id": 4, - "fiscal_position_id": false, - "invoice_date": false, "date": false, - "payment_term_id": false, - "reference": false, "account_id": 440, - "name": false, "invoice_line_ids": [], - "tax_line_ids": [], "amount_untaxed": 0, - "amount_tax": 0, "reconciled": false, - "amount_total": 0, "state": "draft", - "amount_residual": 0, "company_id": 1, - "date_due": false, "user_id": 1, - "partner_bank_id": false, "origin": false, - "move_id": false, "comment": false, - "payment_ids": [[6, false, []]], - "active_id": false, "active_ids": [], - "active_model": "account.move", - "parent": {}} - ], "__eval_context": null} - }, { - "id": false, - "product_id": 4, - "name": "[PC1] Basic PC", - "quantity": 1, - "uom_id": 1, - "price_unit": 100, - "account_id": 853, - "discount": 0, - "analytic_distribution": false, - "company_id": false, - "note": false, - "invoice_line_tax_ids": [[6, false, [1]]], - "active_id": false, - "active_ids": [], - "active_model": "account.move.line", - "parent": { - "id": false, "journal_id": 10, "number": false, - "move_type": "out_invoice", "currency_id": 1, - "partner_id": 4, "fiscal_position_id": false, - "invoice_date": false, "date": false, - "payment_term_id": false, - "reference": false, "account_id": 440, "name": false, - "tax_line_ids": [], "amount_untaxed": 0, "amount_tax": 0, - "reconciled": false, "amount_total": 0, - "state": "draft", "amount_residual": 0, "company_id": 1, - "date_due": false, "user_id": 1, - "partner_bank_id": false, "origin": false, - "move_id": false, "comment": false, - "payment_ids": [[6, false, []]]} - }], - "__eval_context": null - } - }]); - - assert.deepEqual(result, {move_type: 'out_invoice'}); - }); - - QUnit.test('return-input-value', function (assert) { - assert.expect(1); - - var result = pyUtils.eval('contexts', [{ - __ref: 'compound_context', - __contexts: ["{'line_id': line_id , 'journal_id': journal_id }"], - __eval_context: { - __ref: 'compound_context', - __contexts: [{ - __ref: 'compound_context', - __contexts: [ - {lang: 'en_US', tz: 'Europe/Paris', uid: 1}, - {lang: 'en_US', tz: 'Europe/Paris', uid: 1}, - {} - ], - __eval_context: null, - }, { - active_id: false, - active_ids: [], - active_model: 'account.move', - amount: 0, - company_id: 1, - id: false, - journal_id: 14, - line_id: [ - [0, false, { - account_id: 55, - amount_currency: 0, - analytic_distribution: false, - credit: 0, - currency_id: false, - date_maturity: false, - debit: 0, - name: "dscsd", - partner_id: false, - tax_line_id: false, - }] - ], - name: '/', - narration: false, - parent: {}, - partner_id: false, - date: '2011-01-1', - ref: false, - state: 'draft', - to_check: false, - }], - __eval_context: null, - }, - }]); - assert.deepEqual(result, { - journal_id: 14, - line_id: [[0, false, { - account_id: 55, - amount_currency: 0, - analytic_distribution: false, - credit: 0, - currency_id: false, - date_maturity: false, - debit: 0, - name: "dscsd", - partner_id: false, - tax_line_id: false, - }]], - }); - }); - - QUnit.module('py_utils (domains)'); - - QUnit.test('current_date', function (assert) { - assert.expect(1); - - var current_date = time.date_to_str(new Date()); - var result = pyUtils.eval('domains', - [[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}], - pyUtils.context()); - assert.deepEqual(result, [ - ['name', '>=', current_date], - ['name', '<=', current_date] - ]); - }); - - QUnit.test('context_freevar', function (assert) { - assert.expect(3); - - var domains_to_eval = [{ - __ref: 'domain', - __debug: '[("foo", "=", context.get("bar", "qux"))]' - }, [['bar', '>=', 42]]]; - assert.deepEqual( - pyUtils.eval('domains', domains_to_eval, {bar: "ok"}), - [['foo', '=', 'ok'], ['bar', '>=', 42]]); - assert.deepEqual( - pyUtils.eval('domains', domains_to_eval, {bar: false}), - [['foo', '=', false], ['bar', '>=', 42]]); - assert.deepEqual( - pyUtils.eval('domains', domains_to_eval), - [['foo', '=', 'qux'], ['bar', '>=', 42]]); - }); - - QUnit.module('py_utils (groupbys)'); - - QUnit.test('groupbys_00', function (assert) { - assert.expect(1); - - var result = pyUtils.eval('groupbys', [ - {group_by: 'foo'}, - {group_by: ['bar', 'qux']}, - {group_by: null}, - {group_by: 'grault'} - ]); - assert.deepEqual(result, ['foo', 'bar', 'qux', 'grault']); - }); - - QUnit.test('groupbys_01', function (assert) { - assert.expect(1); - - var result = pyUtils.eval('groupbys', [ - {group_by: 'foo'}, - { __ref: 'context', __debug: '{"group_by": "bar"}' }, - {group_by: 'grault'} - ]); - assert.deepEqual(result, ['foo', 'bar', 'grault']); - }); - - QUnit.test('groupbys_02', function (assert) { - assert.expect(1); - - var result = pyUtils.eval('groupbys', [ - {group_by: 'foo'}, - { - __ref: 'compound_context', - __contexts: [ {group_by: 'bar'} ], - __eval_context: null - }, - {group_by: 'grault'} - ]); - assert.deepEqual(result, ['foo', 'bar', 'grault']); - }); - - QUnit.test('groupbys_03', function (assert) { - assert.expect(1); - - var result = pyUtils.eval('groupbys', [ - {group_by: 'foo'}, - { - __ref: 'compound_context', - __contexts: [ - { __ref: 'context', __debug: '{"group_by": value}' } - ], - __eval_context: { value: 'bar' } - }, - {group_by: 'grault'} - ]); - assert.deepEqual(result, ['foo', 'bar', 'grault']); - }); - - QUnit.test('groupbys_04', function (assert) { - assert.expect(1); - - var result = pyUtils.eval('groupbys', [ - {group_by: 'foo'}, - { - __ref: 'compound_context', - __contexts: [ - { __ref: 'context', __debug: '{"group_by": value}' } - ], - __eval_context: { value: 'bar' } - }, - {group_by: 'grault'} - ], { value: 'bar' }); - assert.deepEqual(result, ['foo', 'bar', 'grault']); - }); - - QUnit.test('groupbys_05', function (assert) { - assert.expect(1); - - var result = pyUtils.eval('groupbys', [ - {group_by: 'foo'}, - { __ref: 'context', __debug: '{"group_by": value}' }, - {group_by: 'grault'} - ], { value: 'bar' }); - assert.deepEqual(result, ['foo', 'bar', 'grault']); - }); - - QUnit.module('pyutils (_formatAST)'); - - QUnit.test("basic values", function (assert) { - assert.expect(6); - - assert.checkAST("1", "integer value"); - assert.checkAST("1.4", "float value"); - assert.checkAST("-12", "negative integer value"); - assert.checkAST("True", "boolean"); - assert.checkAST(`"some string"`, "a string"); - assert.checkAST("None", "None"); - }); - - QUnit.test("dictionary", function (assert) { - assert.expect(3); - - assert.checkAST("{}", "empty dictionary"); - assert.checkAST(`{"a": 1}`, "dictionary with a single key"); - assert.checkAST(`d["a"]`, "get a value in a dictionary"); - }); - - QUnit.test("list", function (assert) { - assert.expect(2); - - assert.checkAST("[]", "empty list"); - assert.checkAST("[1]", "list with one value"); - }); - - QUnit.test("tuple", function (assert) { - assert.expect(2); - - assert.checkAST("()", "empty tuple"); - assert.checkAST("(1, 2)", "basic tuple"); - }); - - QUnit.test("simple arithmetic", function (assert) { - assert.expect(15); - - assert.checkAST("1 + 2", "addition"); - assert.checkAST("+(1 + 2)", "other addition, prefix"); - assert.checkAST("1 - 2", "substraction"); - assert.checkAST("-1 - 2", "other substraction"); - assert.checkAST("-(1 + 2)", "other substraction"); - assert.checkAST("1 + 2 + 3", "addition of 3 integers"); - assert.checkAST("a + b", "addition of two variables"); - assert.checkAST("42 % 5", "modulo operator"); - assert.checkAST("a * 10", "multiplication"); - assert.checkAST("a ** 10", "**"); - assert.checkAST("~10", "bitwise not"); - assert.checkAST("~(10 + 3)", "bitwise not"); - assert.checkAST("a * (1 + 2)", "multiplication and addition"); - assert.checkAST("(a + b) * 43", "addition and multiplication"); - assert.checkAST("a // 10", "integer division"); - }); - - QUnit.test("boolean operators", function (assert) { - assert.expect(6); - - assert.checkAST("True and False", "boolean operator"); - assert.checkAST("True or False", "boolean operator or"); - assert.checkAST("(True or False) and False", "boolean operators and and or"); - assert.checkAST("not False", "not prefix"); - assert.checkAST("not foo", "not prefix with variable"); - assert.checkAST("not a in b", "not prefix with expression"); - }); - - QUnit.test("conditional expression", function (assert) { - assert.expect(2); - - assert.checkAST("1 if a else 2"); - assert.checkAST("[] if a else 2"); - }); - - QUnit.test("other operators", function (assert) { - assert.expect(7); - - assert.checkAST("x == y", "== operator"); - assert.checkAST("x != y", "!= operator"); - assert.checkAST("x < y", "< operator"); - assert.checkAST("x is y", "is operator"); - assert.checkAST("x is not y", "is and not operator"); - assert.checkAST("x in y", "in operator"); - assert.checkAST("x not in y", "not in operator"); - }); - - QUnit.test("equality", function (assert) { - assert.expect(1); - assert.checkAST("a == b", "simple equality"); - }); - - QUnit.test("strftime", function (assert) { - assert.expect(3); - assert.checkAST(`time.strftime("%Y")`, "strftime with year"); - assert.checkAST(`time.strftime("%Y") + "-01-30"`, "strftime with year"); - assert.checkAST(`time.strftime("%Y-%m-%d %H:%M:%S")`, "strftime with year"); - }); - - QUnit.test("context_today", function (assert) { - assert.expect(1); - assert.checkAST(`context_today().strftime("%Y-%m-%d")`, "context today call"); - }); - - - QUnit.test("function call", function (assert) { - assert.expect(5); - assert.checkAST("td()", "simple call"); - assert.checkAST("td(a, b, c)", "simple call with args"); - assert.checkAST('td(days = 1)', "simple call with kwargs"); - assert.checkAST('f(1, 2, days = 1)', "mixing args and kwargs"); - assert.checkAST('str(td(2))', "function call in function call"); - }); - - QUnit.test("various expressions", function (assert) { - assert.expect(3); - assert.checkAST('(a - b).days', "substraction and .days"); - assert.checkAST('a + day == date(2002, 3, 3)'); - - var expr = `[("type", "=", "in"), ("day", "<=", time.strftime("%Y-%m-%d")), ("day", ">", (context_today() - datetime.timedelta(days = 15)).strftime("%Y-%m-%d"))]`; - assert.checkAST(expr); - }); - - QUnit.test('escaping support', function (assert) { - assert.expect(4); - assert.strictEqual(py.eval(r`"\x61"`), "a", "hex escapes"); - assert.strictEqual(py.eval(r`"\\abc"`), r`\abc`, "escaped backslash"); - assert.checkAST(r`"\\abc"`, "escaped backslash AST check"); - - const {_getPyJSAST, _formatAST} = pyUtils; - const a = r`'foo\\abc"\''`; - const b = _formatAST(_getPyJSAST(_formatAST(_getPyJSAST(a)))); - // Our repr uses JSON.stringify which always uses double quotes, - // whereas Python's repr is single-quote-biased: strings are repr'd - // using single quote delimiters *unless* they contain single quotes and - // no double quotes, then they're delimited with double quotes. - assert.strictEqual(b, r`"foo\\abc\"'"`); - }); - - QUnit.module('pyutils (_normalizeDomain)'); - - QUnit.assert.checkNormalization = function (domain, normalizedDomain) { - normalizedDomain = normalizedDomain || domain; - var result = pyUtils.normalizeDomain(domain); - this.pushResult({ - result: result === normalizedDomain, - actual: result, - expected: normalizedDomain - }); - }; - - - QUnit.test("return simple (normalized) domains", function (assert) { - assert.expect(3); - - assert.checkNormalization("[]"); - assert.checkNormalization(`[("a", "=", 1)]`); - assert.checkNormalization(`["!", ("a", "=", 1)]`); - }); - - QUnit.test("properly add the & in a non normalized domain", function (assert) { - assert.expect(1); - assert.checkNormalization( - `[("a", "=", 1), ("b", "=", 2)]`, - `["&", ("a", "=", 1), ("b", "=", 2)]` - ); - }); - - QUnit.test("normalize domain with ! operator", function (assert) { - assert.expect(1); - assert.checkNormalization( - `["!", ("a", "=", 1), ("b", "=", 2)]`, - `["&", "!", ("a", "=", 1), ("b", "=", 2)]` - ); - }); - - QUnit.module('pyutils (assembleDomains)'); - - QUnit.assert.checkAssemble = function (domains, operator, domain) { - domain = pyUtils.normalizeDomain(domain); - var result = pyUtils.assembleDomains(domains, operator); - this.pushResult({ - result: result === domain, - actual: result, - expected: domain - }); - }; - - QUnit.test("assemble domains", function (assert) { - assert.expect(7); - - assert.checkAssemble([], '&', "[]"); - assert.checkAssemble(["[('a', '=', 1)]"], null, "[('a', '=', 1)]"); - assert.checkAssemble( - ["[('a', '=', '1'), ('b', '!=', 2)]"], - 'AND', - "['&',('a', '=', '1'), ('b', '!=', 2)]" - ); - assert.checkAssemble( - ["[('a', '=', '1')]", "[]"], - 'AND', - "[('a', '=', '1')]" - ); - assert.checkAssemble( - ["[('a', '=', '1')]", "[('b', '<=', 3)]"], - 'AND', - "['&',('a', '=', '1'),('b','<=', 3)]" - ); - assert.checkAssemble( - ["[('a', '=', '1'), ('c', 'in', [4, 5])]", "[('b', '<=', 3)]"], - 'OR', - "['|', '&',('a', '=', '1'),('c', 'in', [4, 5]),('b','<=', 3)]" - ); - assert.checkAssemble( - ["[('user_id', '=', uid)]"], - null, - "[('user_id', '=', uid)]" - ); - }); -}); diff --git a/addons/web/static/tests/legacy/helpers/mock_server.js b/addons/web/static/tests/legacy/helpers/mock_server.js index 203a9faa75e..697c05782ce 100644 --- a/addons/web/static/tests/legacy/helpers/mock_server.js +++ b/addons/web/static/tests/legacy/helpers/mock_server.js @@ -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])) { diff --git a/addons/website_slides/__manifest__.py b/addons/website_slides/__manifest__.py index c4f2e2bf2aa..f453c83402c 100644 --- a/addons/website_slides/__manifest__.py +++ b/addons/website_slides/__manifest__.py @@ -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',