mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 04:52:00 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
726
odoo-bringout-oca-ocb-base/odoo/tools/js_transpiler.py
Normal file
726
odoo-bringout-oca-ocb-base/odoo/tools/js_transpiler.py
Normal file
|
|
@ -0,0 +1,726 @@
|
|||
"""
|
||||
This code is what let us use ES6-style modules in odoo.
|
||||
Classic Odoo modules are composed of a top-level :samp:`odoo.define({name},{body_function})` call.
|
||||
This processor will take files starting with an `@odoo-module` annotation (in a comment) and convert them to classic modules.
|
||||
If any file has the ``/** odoo-module */`` on top of it, it will get processed by this class.
|
||||
It performs several operations to get from ES6 syntax to the usual odoo one with minimal changes.
|
||||
This is done on the fly, this not a pre-processing tool.
|
||||
|
||||
Caveat: This is done without a full parser, only using regex. One can only expect to cover as much edge cases
|
||||
as possible with reasonable limitations. Also, this only changes imports and exports, so all JS features used in
|
||||
the original source need to be supported by the browsers.
|
||||
"""
|
||||
|
||||
import re
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
def transpile_javascript(url, content):
|
||||
"""
|
||||
Transpile the code from native JS modules to custom odoo modules.
|
||||
|
||||
:param content: The original source code
|
||||
:param url: The url of the file in the project
|
||||
:return: The transpiled source code
|
||||
"""
|
||||
module_path = url_to_module_path(url)
|
||||
legacy_odoo_define = get_aliased_odoo_define_content(module_path, content)
|
||||
|
||||
# The order of the operations does sometimes matter.
|
||||
steps = [
|
||||
convert_legacy_default_import,
|
||||
convert_basic_import,
|
||||
convert_default_and_named_import,
|
||||
convert_default_and_star_import,
|
||||
convert_default_import,
|
||||
convert_star_import,
|
||||
convert_unnamed_relative_import,
|
||||
convert_from_export,
|
||||
convert_star_from_export,
|
||||
partial(convert_relative_require, url),
|
||||
remove_index,
|
||||
convert_export_function,
|
||||
convert_export_class,
|
||||
convert_variable_export,
|
||||
convert_object_export,
|
||||
convert_default_export,
|
||||
partial(wrap_with_odoo_define, module_path),
|
||||
]
|
||||
for s in steps:
|
||||
content = s(content)
|
||||
if legacy_odoo_define:
|
||||
content += legacy_odoo_define
|
||||
return content
|
||||
|
||||
|
||||
URL_RE = re.compile(r"""
|
||||
/?(?P<module>\S+) # /module name
|
||||
/([\S/]*/)?static/ # ... /static/
|
||||
(?P<type>src|tests|lib) # src, test, or lib file
|
||||
(?P<url>/[\S/]*) # URL (/...)
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
def url_to_module_path(url):
|
||||
"""
|
||||
Odoo modules each have a name. (odoo.define("<the name>", async function (require) {...});
|
||||
It is used in to be required later. (const { something } = require("<the name>").
|
||||
The transpiler transforms the url of the file in the project to this name.
|
||||
It takes the module name and add a @ on the start of it, and map it to be the source of the static/src (or
|
||||
static/tests, or static/lib) folder in that module.
|
||||
|
||||
in: web/static/src/one/two/three.js
|
||||
out: @web/one/two/three.js
|
||||
The module would therefore be defined and required by this path.
|
||||
|
||||
:param url: an url in the project
|
||||
:return: a special path starting with @<module-name>.
|
||||
"""
|
||||
match = URL_RE.match(url)
|
||||
if match:
|
||||
url = match["url"]
|
||||
if url.endswith(('/index.js', '/index')):
|
||||
url, _ = url.rsplit('/', 1)
|
||||
if url.endswith('.js'):
|
||||
url = url[:-3]
|
||||
if match["type"] == "src":
|
||||
return "@%s%s" % (match['module'], url)
|
||||
elif match["type"] == "lib":
|
||||
return "@%s/../lib%s" % (match['module'], url)
|
||||
else:
|
||||
return "@%s/../tests%s" % (match['module'], url)
|
||||
else:
|
||||
raise ValueError("The js file %r must be in the folder '/static/src' or '/static/lib' or '/static/test'" % url)
|
||||
|
||||
|
||||
def wrap_with_odoo_define(module_path, content):
|
||||
"""
|
||||
Wraps the current content (source code) with the odoo.define call.
|
||||
Should logically be called once all other operations have been performed.
|
||||
"""
|
||||
return f"""odoo.define({module_path!r}, async function (require) {{
|
||||
'use strict';
|
||||
let __exports = {{}};
|
||||
{content}
|
||||
return __exports;
|
||||
}});
|
||||
"""
|
||||
|
||||
|
||||
EXPORT_FCT_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s+ # export
|
||||
(?P<type>(async\s+)?function)\s+ # async function or function
|
||||
(?P<identifier>\w+) # name the function
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_export_function(content):
|
||||
"""
|
||||
Transpile functions that are being exported.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export function name
|
||||
// after
|
||||
__exports.name = name; function name
|
||||
|
||||
// before
|
||||
export async function name
|
||||
// after
|
||||
__exports.name = name; async function name
|
||||
|
||||
"""
|
||||
repl = r"\g<space>__exports.\g<identifier> = \g<identifier>; \g<type> \g<identifier>"
|
||||
return EXPORT_FCT_RE.sub(repl, content)
|
||||
|
||||
EXPORT_CLASS_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s+ # export
|
||||
(?P<type>class)\s+ # class
|
||||
(?P<identifier>\w+) # name of the class
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_export_class(content):
|
||||
"""
|
||||
Transpile classes that are being exported.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export class name
|
||||
// after
|
||||
const name = __exports.name = class name
|
||||
|
||||
"""
|
||||
repl = r"\g<space>const \g<identifier> = __exports.\g<identifier> = \g<type> \g<identifier>"
|
||||
return EXPORT_CLASS_RE.sub(repl, content)
|
||||
|
||||
|
||||
EXPORT_FCT_DEFAULT_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s+default\s+ # export default
|
||||
(?P<type>(async\s+)?function)\s+ # async function or function
|
||||
(?P<identifier>\w+) # name of the function
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_export_function_default(content):
|
||||
"""
|
||||
Transpile functions that are being exported as default value.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export default function name
|
||||
// after
|
||||
__exports[Symbol.for("default")] = name; function name
|
||||
|
||||
// before
|
||||
export default async function name
|
||||
// after
|
||||
__exports[Symbol.for("default")] = name; async function name
|
||||
|
||||
"""
|
||||
repl = r"""\g<space>__exports[Symbol.for("default")] = \g<identifier>; \g<type> \g<identifier>"""
|
||||
return EXPORT_FCT_DEFAULT_RE.sub(repl, content)
|
||||
|
||||
EXPORT_CLASS_DEFAULT_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s+default\s+ # export default
|
||||
(?P<type>class)\s+ # class
|
||||
(?P<identifier>\w+) # name of the class or the function
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_export_class_default(content):
|
||||
"""
|
||||
Transpile classes that are being exported as default value.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export default class name
|
||||
// after
|
||||
const name = __exports[Symbol.for("default")] = class name
|
||||
|
||||
"""
|
||||
repl = r"""\g<space>const \g<identifier> = __exports[Symbol.for("default")] = \g<type> \g<identifier>"""
|
||||
return EXPORT_CLASS_DEFAULT_RE.sub(repl, content)
|
||||
|
||||
EXPORT_VAR_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s+ # export
|
||||
(?P<type>let|const|var)\s+ # let or cont or var
|
||||
(?P<identifier>\w+) # variable name
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_variable_export(content):
|
||||
"""
|
||||
Transpile variables that are being exported.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export let name
|
||||
// after
|
||||
let name = __exports.name
|
||||
// (same with var and const)
|
||||
|
||||
"""
|
||||
repl = r"\g<space>\g<type> \g<identifier> = __exports.\g<identifier>"
|
||||
return EXPORT_VAR_RE.sub(repl, content)
|
||||
|
||||
|
||||
EXPORT_DEFAULT_VAR_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s+default\s+ # export default
|
||||
(?P<type>let|const|var)\s+ # let or const or var
|
||||
(?P<identifier>\w+)\s* # variable name
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_variable_export_default(content):
|
||||
"""
|
||||
Transpile the variables that are exported as default values.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export default let name
|
||||
// after
|
||||
let name = __exports[Symbol.for("default")]
|
||||
|
||||
"""
|
||||
repl = r"""\g<space>\g<type> \g<identifier> = __exports[Symbol.for("default")]"""
|
||||
return EXPORT_DEFAULT_VAR_RE.sub(repl, content)
|
||||
|
||||
|
||||
EXPORT_OBJECT_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s* # export
|
||||
(?P<object>{[\w\s,]+}) # { a, b, c as x, ... }
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_object_export(content):
|
||||
"""
|
||||
Transpile exports of multiple elements
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export { a, b, c as x }
|
||||
// after
|
||||
Object.assign(__exports, { a, b, x: c })
|
||||
"""
|
||||
def repl(matchobj):
|
||||
object_process = "{" + ", ".join([convert_as(val) for val in matchobj["object"][1:-1].split(",")]) + "}"
|
||||
space = matchobj["space"]
|
||||
return f"{space}Object.assign(__exports, {object_process})"
|
||||
return EXPORT_OBJECT_RE.sub(repl, content)
|
||||
|
||||
|
||||
EXPORT_FROM_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s* # export
|
||||
(?P<object>{[\w\s,]+})\s* # { a, b, c as x, ... }
|
||||
from\s* # from
|
||||
(?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path.js")
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_from_export(content):
|
||||
"""
|
||||
Transpile exports coming from another source
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export { a, b, c as x } from "some/path.js"
|
||||
// after
|
||||
{ a, b, c } = {require("some/path.js"); Object.assign(__exports, { a, b, x: c });}
|
||||
"""
|
||||
def repl(matchobj):
|
||||
object_clean = "{" + ",".join([remove_as(val) for val in matchobj["object"][1:-1].split(",")]) + "}"
|
||||
object_process = "{" + ", ".join([convert_as(val) for val in matchobj["object"][1:-1].split(",")]) + "}"
|
||||
return "%(space)s{const %(object_clean)s = require(%(path)s);Object.assign(__exports, %(object_process)s)}" % {
|
||||
'object_clean': object_clean,
|
||||
'object_process': object_process,
|
||||
'space': matchobj['space'],
|
||||
'path': matchobj['path'],
|
||||
}
|
||||
return EXPORT_FROM_RE.sub(repl, content)
|
||||
|
||||
|
||||
EXPORT_STAR_FROM_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s*\*\s*from\s* # export * from
|
||||
(?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path.js")
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_star_from_export(content):
|
||||
"""
|
||||
Transpile exports star coming from another source
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export * from "some/path.js"
|
||||
// after
|
||||
Object.assign(__exports, require("some/path.js"))
|
||||
"""
|
||||
repl = r"\g<space>Object.assign(__exports, require(\g<path>))"
|
||||
return EXPORT_STAR_FROM_RE.sub(repl, content)
|
||||
|
||||
|
||||
EXPORT_DEFAULT_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
export\s+default # export default
|
||||
(\s+\w+\s*=)? # something (optional)
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_default_export(content):
|
||||
"""
|
||||
This function handles the default exports.
|
||||
Either by calling another operation with a TRUE flag, and if any default is left, doing a simple replacement.
|
||||
|
||||
(see convert_export_function_or_class_default and convert_variable_export_default).
|
||||
+
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export default
|
||||
// after
|
||||
__exports[Symbol.for("default")] =
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
export default something =
|
||||
// after
|
||||
__exports[Symbol.for("default")] =
|
||||
"""
|
||||
new_content = convert_export_function_default(content)
|
||||
new_content = convert_export_class_default(new_content)
|
||||
new_content = convert_variable_export_default(new_content)
|
||||
repl = r"""\g<space>__exports[Symbol.for("default")] ="""
|
||||
return EXPORT_DEFAULT_RE.sub(repl, new_content)
|
||||
|
||||
|
||||
IMPORT_BASIC_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
import\s+ # import
|
||||
(?P<object>{[\s\w,]+})\s* # { a, b, c as x, ... }
|
||||
from\s* # from
|
||||
(?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path")
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_basic_import(content):
|
||||
"""
|
||||
Transpile the simpler import call.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
import { a, b, c as x } from "some/path"
|
||||
// after
|
||||
const {a, b, c: x} = require("some/path")
|
||||
"""
|
||||
def repl(matchobj):
|
||||
new_object = matchobj["object"].replace(" as ", ": ")
|
||||
return f"{matchobj['space']}const {new_object} = require({matchobj['path']})"
|
||||
return IMPORT_BASIC_RE.sub(repl, content)
|
||||
|
||||
|
||||
IMPORT_LEGACY_DEFAULT_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
import\s+ # import
|
||||
(?P<identifier>\w+)\s* # default variable name
|
||||
from\s* # from
|
||||
(?P<path>(?P<quote>["'`])([^@\."'`][^"'`]*)(?P=quote)) # legacy alias file ("addon_name.module_name" or "some/path")
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_legacy_default_import(content):
|
||||
"""
|
||||
Transpile legacy imports (that were used as they were default import).
|
||||
Legacy imports means that their name is not a path but a <addon_name>.<module_name>.
|
||||
It requires slightly different processing.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
import module_name from "addon.module_name"
|
||||
// after
|
||||
const module_name = require("addon.module_name")
|
||||
"""
|
||||
repl = r"""\g<space>const \g<identifier> = require(\g<path>)"""
|
||||
return IMPORT_LEGACY_DEFAULT_RE.sub(repl, content)
|
||||
|
||||
|
||||
IMPORT_DEFAULT = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
import\s+ # import
|
||||
(?P<identifier>\w+)\s* # default variable name
|
||||
from\s* # from
|
||||
(?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path")
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_default_import(content):
|
||||
"""
|
||||
Transpile the default import call.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
import something from "some/path"
|
||||
// after
|
||||
const something = require("some/path")[Symbol.for("default")]
|
||||
"""
|
||||
repl = r"""\g<space>const \g<identifier> = require(\g<path>)[Symbol.for("default")]"""
|
||||
return IMPORT_DEFAULT.sub(repl, content)
|
||||
|
||||
|
||||
IS_PATH_LEGACY_RE = re.compile(r"""(?P<quote>["'`])([^@\."'`][^"'`]*)(?P=quote)""")
|
||||
|
||||
IMPORT_DEFAULT_AND_NAMED_RE = re.compile(r"""
|
||||
^
|
||||
(?P<space>\s*) # space and empty line
|
||||
import\s+ # import
|
||||
(?P<default_export>\w+)\s*,\s* # default variable name,
|
||||
(?P<named_exports>{[\s\w,]+})\s* # { a, b, c as x, ... }
|
||||
from\s* # from
|
||||
(?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path")
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_default_and_named_import(content):
|
||||
"""
|
||||
Transpile default and named import on one line.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
import something, { a } from "some/path";
|
||||
import somethingElse, { b } from "legacy.module";
|
||||
// after
|
||||
const { [Symbol.for("default")]: something, a } = require("some/path");
|
||||
const somethingElse = require("legacy.module");
|
||||
const { b } = somethingElse;
|
||||
"""
|
||||
def repl(matchobj):
|
||||
is_legacy = IS_PATH_LEGACY_RE.match(matchobj['path'])
|
||||
new_object = matchobj["named_exports"].replace(" as ", ": ")
|
||||
if is_legacy:
|
||||
return f"""{matchobj['space']}const {matchobj['default_export']} = require({matchobj['path']});
|
||||
{matchobj['space']}const {new_object} = {matchobj['default_export']}"""
|
||||
new_object = f"""{{ [Symbol.for("default")]: {matchobj['default_export']},{new_object[1:]}"""
|
||||
return f"{matchobj['space']}const {new_object} = require({matchobj['path']})"
|
||||
return IMPORT_DEFAULT_AND_NAMED_RE.sub(repl, content)
|
||||
|
||||
|
||||
RELATIVE_REQUIRE_RE = re.compile(r"""
|
||||
require\((?P<quote>["'`])([^@"'`]+)(?P=quote)\) # require("some/path")
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
def convert_relative_require(url, content):
|
||||
"""
|
||||
Convert the relative path contained in a 'require()'
|
||||
to the new path system (@module/path)
|
||||
.. code-block:: javascript
|
||||
|
||||
// Relative path:
|
||||
// before
|
||||
require("./path")
|
||||
// after
|
||||
require("@module/path")
|
||||
|
||||
// Not a relative path:
|
||||
// before
|
||||
require("other_alias")
|
||||
// after
|
||||
require("other_alias")
|
||||
"""
|
||||
new_content = content
|
||||
for quote, path in RELATIVE_REQUIRE_RE.findall(new_content):
|
||||
if path.startswith(".") and "/" in path:
|
||||
pattern = rf"require\({quote}{path}{quote}\)"
|
||||
repl = f'require("{relative_path_to_module_path(url, path)}")'
|
||||
new_content = re.sub(pattern, repl, new_content)
|
||||
return new_content
|
||||
|
||||
|
||||
IMPORT_STAR = re.compile(r"""
|
||||
^(?P<space>\s*) # indentation
|
||||
import\s+\*\s+as\s+ # import * as
|
||||
(?P<identifier>\w+) # alias
|
||||
\s*from\s* # from
|
||||
(?P<path>[^;\n]+) # path
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_star_import(content):
|
||||
"""
|
||||
Transpile import star.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
import * as name from "some/path"
|
||||
// after
|
||||
const name = require("some/path")
|
||||
"""
|
||||
repl = r"\g<space>const \g<identifier> = require(\g<path>)"
|
||||
return IMPORT_STAR.sub(repl, content)
|
||||
|
||||
|
||||
IMPORT_DEFAULT_AND_STAR = re.compile(r"""
|
||||
^(?P<space>\s*) # indentation
|
||||
import\s+ # import
|
||||
(?P<default_export>\w+)\s*,\s* # default export name,
|
||||
\*\s+as\s+ # * as
|
||||
(?P<named_exports_alias>\w+) # alias
|
||||
\s*from\s* # from
|
||||
(?P<path>[^;\n]+) # path
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_default_and_star_import(content):
|
||||
"""
|
||||
Transpile import star.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
import something, * as name from "some/path";
|
||||
// after
|
||||
const name = require("some/path");
|
||||
const something = name[Symbol.for("default")];
|
||||
"""
|
||||
repl = r"""\g<space>const \g<named_exports_alias> = require(\g<path>);
|
||||
\g<space>const \g<default_export> = \g<named_exports_alias>[Symbol.for("default")]"""
|
||||
return IMPORT_DEFAULT_AND_STAR.sub(repl, content)
|
||||
|
||||
|
||||
IMPORT_UNNAMED_RELATIVE_RE = re.compile(r"""
|
||||
^(?P<space>\s*) # indentation
|
||||
import\s+ # import
|
||||
(?P<path>[^;\n]+) # relative path
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def convert_unnamed_relative_import(content):
|
||||
"""
|
||||
Transpile relative "direct" imports. Direct meaning they are not store in a variable.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
import "some/path"
|
||||
// after
|
||||
require("some/path")
|
||||
"""
|
||||
repl = r"require(\g<path>)"
|
||||
return IMPORT_UNNAMED_RELATIVE_RE.sub(repl, content)
|
||||
|
||||
|
||||
URL_INDEX_RE = re.compile(r"""
|
||||
require\s* # require
|
||||
\(\s* # (
|
||||
(?P<path>(?P<quote>["'`])([^"'`]*/index/?)(?P=quote)) # path ended by /index or /index/
|
||||
\s*\) # )
|
||||
""", re.MULTILINE | re.VERBOSE)
|
||||
|
||||
|
||||
def remove_index(content):
|
||||
"""
|
||||
Remove in the paths the /index.js.
|
||||
We want to be able to import a module just trough its directory name if it contains an index.js.
|
||||
So we no longer need to specify the index.js in the paths.
|
||||
"""
|
||||
def repl(matchobj):
|
||||
path = matchobj["path"]
|
||||
new_path = path[: path.rfind("/index")] + path[0]
|
||||
return f"require({new_path})"
|
||||
return URL_INDEX_RE.sub(repl, content)
|
||||
|
||||
|
||||
def relative_path_to_module_path(url, path_rel):
|
||||
"""Convert the relative path into a module path, which is more generic and
|
||||
fancy.
|
||||
|
||||
:param str url:
|
||||
:param path_rel: a relative path to the current url.
|
||||
:return: module path (@module/...)
|
||||
"""
|
||||
url_split = url.split("/")
|
||||
path_rel_split = path_rel.split("/")
|
||||
nb_back = len([v for v in path_rel_split if v == ".."]) + 1
|
||||
result = "/".join(url_split[:-nb_back] + [v for v in path_rel_split if not v in ["..", "."]])
|
||||
return url_to_module_path(result)
|
||||
|
||||
|
||||
ODOO_MODULE_RE = re.compile(r"""
|
||||
\s* # some starting space
|
||||
\/(\*|\/).*\s* # // or /*
|
||||
@odoo-module # @odoo-module
|
||||
(\s+alias=(?P<alias>[\w.]+))? # alias=web.AbstractAction (optional)
|
||||
(\s+default=(?P<default>False|false|0))? # default=False or false or 0 (optional)
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
def is_odoo_module(content):
|
||||
"""
|
||||
Detect if the file is a native odoo module.
|
||||
We look for a comment containing @odoo-module.
|
||||
|
||||
:param content: source code
|
||||
:return: is this a odoo module that need transpilation ?
|
||||
"""
|
||||
result = ODOO_MODULE_RE.match(content)
|
||||
return bool(result)
|
||||
|
||||
|
||||
def get_aliased_odoo_define_content(module_path, content):
|
||||
"""
|
||||
To allow smooth transition between the new system and the legacy one, we have the possibility to
|
||||
defined an alternative module name (an alias) that will act as proxy between legacy require calls and
|
||||
new modules.
|
||||
|
||||
Example:
|
||||
If we have a require call somewhere in the odoo source base being:
|
||||
> vat AbstractAction require("web.AbstractAction")
|
||||
we have a problem when we will have converted to module to ES6: its new name will be more like
|
||||
"web/chrome/abstract_action". So the require would fail !
|
||||
So we add a second small modules, an alias, as such:
|
||||
> odoo.define("web/chrome/abstract_action", async function(require) {
|
||||
> return require('web.AbstractAction')[Symbol.for("default")];
|
||||
> });
|
||||
|
||||
To generate this, change your comment on the top of the file.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
/** @odoo-module */
|
||||
// after
|
||||
/** @odoo-module alias=web.AbstractAction */
|
||||
|
||||
Notice that often, the legacy system acted like they it did defaukt imports. That's why we have the
|
||||
"[Symbol.for("default")];" bit. If your use case does not need this default import, just do:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// before
|
||||
/** @odoo-module */
|
||||
// after
|
||||
/** @odoo-module alias=web.AbstractAction default=false */
|
||||
|
||||
:return: the alias content to append to the source code.
|
||||
"""
|
||||
matchobj = ODOO_MODULE_RE.match(content)
|
||||
if matchobj:
|
||||
alias = matchobj['alias']
|
||||
if alias:
|
||||
if matchobj['default']:
|
||||
return """\nodoo.define(`%s`, async function(require) {
|
||||
return require('%s');
|
||||
});\n""" % (alias, module_path)
|
||||
else:
|
||||
return """\nodoo.define(`%s`, async function(require) {
|
||||
return require('%s')[Symbol.for("default")];
|
||||
});\n""" % (alias, module_path)
|
||||
|
||||
|
||||
def convert_as(val):
|
||||
parts = val.split(" as ")
|
||||
return val if len(parts) < 2 else "%s: %s" % tuple(reversed(parts))
|
||||
|
||||
|
||||
def remove_as(val):
|
||||
parts = val.split(" as ")
|
||||
return val if len(parts) < 2 else parts[0]
|
||||
Loading…
Add table
Add a link
Reference in a new issue