diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
commit | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (patch) | |
tree | 012bb94d777798f1f56ac1cec429509766d05181 /library/python/runtime_py3 | |
parent | 6751af0b0c1b952fede40b19b71da8025b5d8bcf (diff) | |
download | ydb-2598ef1d0aee359b4b6d5fdd1758916d5907d04f.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'library/python/runtime_py3')
-rw-r--r-- | library/python/runtime_py3/entry_points.py | 2 | ||||
-rw-r--r-- | library/python/runtime_py3/importer.pxi | 104 | ||||
-rw-r--r-- | library/python/runtime_py3/main/main.c | 52 | ||||
-rw-r--r-- | library/python/runtime_py3/main/ya.make | 8 | ||||
-rw-r--r-- | library/python/runtime_py3/sitecustomize.pyx | 138 | ||||
-rw-r--r-- | library/python/runtime_py3/test/.dist-info/METADATA | 28 | ||||
-rw-r--r-- | library/python/runtime_py3/test/.dist-info/RECORD | 2 | ||||
-rw-r--r-- | library/python/runtime_py3/test/.dist-info/entry_points.txt | 4 | ||||
-rw-r--r-- | library/python/runtime_py3/test/.dist-info/top_level.txt | 2 | ||||
-rw-r--r-- | library/python/runtime_py3/test/resources/foo.txt | 2 | ||||
-rw-r--r-- | library/python/runtime_py3/test/resources/submodule/bar.txt | 2 | ||||
-rw-r--r-- | library/python/runtime_py3/test/test_metadata.py | 88 | ||||
-rw-r--r-- | library/python/runtime_py3/test/test_resources.py | 120 | ||||
-rw-r--r-- | library/python/runtime_py3/test/ya.make | 44 | ||||
-rw-r--r-- | library/python/runtime_py3/ya.make | 10 |
15 files changed, 303 insertions, 303 deletions
diff --git a/library/python/runtime_py3/entry_points.py b/library/python/runtime_py3/entry_points.py index 05098723cb..9f099489c0 100644 --- a/library/python/runtime_py3/entry_points.py +++ b/library/python/runtime_py3/entry_points.py @@ -19,7 +19,7 @@ def repl(): if func_name and '__main__' not in user_ns: def run(args): - if isinstance(args, str): + if isinstance(args, str): import shlex args = shlex.split(args) diff --git a/library/python/runtime_py3/importer.pxi b/library/python/runtime_py3/importer.pxi index 904f94dea2..6abbb3cf58 100644 --- a/library/python/runtime_py3/importer.pxi +++ b/library/python/runtime_py3/importer.pxi @@ -333,58 +333,58 @@ class ResourceImporter(object): for m in self.arcadia_source_finder.iter_modules(self._package_prefix, prefix): yield m - def get_resource_reader(self, fullname): - try: - if not self.is_package(fullname): - return None - except ImportError: - return None - return _ResfsResourceReader(self, fullname) - - -class _ResfsResourceReader: - - def __init__(self, importer, fullname): - self.importer = importer - self.fullname = fullname - - import os - self.prefix = "{}/".format(os.path.dirname(self.importer.get_filename(self.fullname))) - - def open_resource(self, resource): - path = f'{self.prefix}{resource}' - from io import BytesIO - try: - return BytesIO(self.importer.get_data(path)) - except OSError: - raise FileNotFoundError(path) - - def resource_path(self, resource): - # All resources are in the binary file, so there is no path to the file. - # Raising FileNotFoundError tells the higher level API to extract the - # binary data and create a temporary file. - raise FileNotFoundError - - def is_resource(self, name): - path = f'{self.prefix}{name}' - try: - self.importer.get_data(path) - except OSError: - return False - return True - - def contents(self): - subdirs_seen = set() - for key in resfs_files(self.prefix): - relative = key[len(self.prefix):] - res_or_subdir, *other = relative.split(b'/') - if not other: - yield _s(res_or_subdir) - elif res_or_subdir not in subdirs_seen: - subdirs_seen.add(res_or_subdir) - yield _s(res_or_subdir) - - + def get_resource_reader(self, fullname): + try: + if not self.is_package(fullname): + return None + except ImportError: + return None + return _ResfsResourceReader(self, fullname) + + +class _ResfsResourceReader: + + def __init__(self, importer, fullname): + self.importer = importer + self.fullname = fullname + + import os + self.prefix = "{}/".format(os.path.dirname(self.importer.get_filename(self.fullname))) + + def open_resource(self, resource): + path = f'{self.prefix}{resource}' + from io import BytesIO + try: + return BytesIO(self.importer.get_data(path)) + except OSError: + raise FileNotFoundError(path) + + def resource_path(self, resource): + # All resources are in the binary file, so there is no path to the file. + # Raising FileNotFoundError tells the higher level API to extract the + # binary data and create a temporary file. + raise FileNotFoundError + + def is_resource(self, name): + path = f'{self.prefix}{name}' + try: + self.importer.get_data(path) + except OSError: + return False + return True + + def contents(self): + subdirs_seen = set() + for key in resfs_files(self.prefix): + relative = key[len(self.prefix):] + res_or_subdir, *other = relative.split(b'/') + if not other: + yield _s(res_or_subdir) + elif res_or_subdir not in subdirs_seen: + subdirs_seen.add(res_or_subdir) + yield _s(res_or_subdir) + + class BuiltinSubmoduleImporter(BuiltinImporter): @classmethod def find_spec(cls, fullname, path=None, target=None): diff --git a/library/python/runtime_py3/main/main.c b/library/python/runtime_py3/main/main.c index 3159800615..939c290827 100644 --- a/library/python/runtime_py3/main/main.c +++ b/library/python/runtime_py3/main/main.c @@ -1,5 +1,5 @@ #include <Python.h> -#include <contrib/tools/python3/src/Include/internal/pycore_runtime.h> // _PyRuntime_Initialize() +#include <contrib/tools/python3/src/Include/internal/pycore_runtime.h> // _PyRuntime_Initialize() #include <stdlib.h> #include <string.h> @@ -9,7 +9,7 @@ void Py_InitArgcArgv(int argc, wchar_t **argv); char* GetPyMain(); static const char* env_entry_point = "Y_PYTHON_ENTRY_POINT"; -static const char* env_bytes_warning = "Y_PYTHON_BYTES_WARNING"; +static const char* env_bytes_warning = "Y_PYTHON_BYTES_WARNING"; #ifdef _MSC_VER extern char** environ; @@ -74,11 +74,11 @@ static int RunModule(const char *modname) } static int pymain(int argc, char** argv) { - PyStatus status = _PyRuntime_Initialize(); - if (PyStatus_Exception(status)) { - Py_ExitStatusException(status); - } - + PyStatus status = _PyRuntime_Initialize(); + if (PyStatus_Exception(status)) { + Py_ExitStatusException(status); + } + int i, sts = 1; char* oldloc = NULL; wchar_t** argv_copy = NULL; @@ -95,15 +95,15 @@ static int pymain(int argc, char** argv) { } } - PyConfig config; - PyConfig_InitPythonConfig(&config); - config.pathconfig_warnings = 0; /* Suppress errors from getpath.c */ - - const char* bytes_warning = getenv(env_bytes_warning); - if (bytes_warning) { - config.bytes_warning = atoi(bytes_warning); - } - + PyConfig config; + PyConfig_InitPythonConfig(&config); + config.pathconfig_warnings = 0; /* Suppress errors from getpath.c */ + + const char* bytes_warning = getenv(env_bytes_warning); + if (bytes_warning) { + config.bytes_warning = atoi(bytes_warning); + } + oldloc = _PyMem_RawStrdup(setlocale(LC_ALL, NULL)); if (!oldloc) { fprintf(stderr, "out of memory\n"); @@ -116,7 +116,7 @@ static int pymain(int argc, char** argv) { argv_copy2[i] = argv_copy[i]; if (!argv_copy[i]) { fprintf(stderr, "Unable to decode the command line argument #%i\n", - i + 1); + i + 1); argc = i; goto error; } @@ -125,15 +125,15 @@ static int pymain(int argc, char** argv) { PyMem_RawFree(oldloc); oldloc = NULL; - if (argc >= 1) - Py_SetProgramName(argv_copy[0]); - - status = Py_InitializeFromConfig(&config); - PyConfig_Clear(&config); - if (PyStatus_Exception(status)) { - Py_ExitStatusException(status); - } - + if (argc >= 1) + Py_SetProgramName(argv_copy[0]); + + status = Py_InitializeFromConfig(&config); + PyConfig_Clear(&config); + if (PyStatus_Exception(status)) { + Py_ExitStatusException(status); + } + const char* entry_point = getenv(env_entry_point); if (entry_point) { entry_point_copy = strdup(entry_point); diff --git a/library/python/runtime_py3/main/ya.make b/library/python/runtime_py3/main/ya.make index f308a93b28..30a96c8728 100644 --- a/library/python/runtime_py3/main/ya.make +++ b/library/python/runtime_py3/main/ya.make @@ -14,10 +14,10 @@ ADDINCL( contrib/tools/python3/src/Include ) -CFLAGS( - -DPy_BUILD_CORE -) - +CFLAGS( + -DPy_BUILD_CORE +) + SRCS( main.c get_py_main.cpp diff --git a/library/python/runtime_py3/sitecustomize.pyx b/library/python/runtime_py3/sitecustomize.pyx index 966bbe8ba6..0273d059de 100644 --- a/library/python/runtime_py3/sitecustomize.pyx +++ b/library/python/runtime_py3/sitecustomize.pyx @@ -1,69 +1,69 @@ -import re -import sys - -import __res - -from importlib.abc import ResourceReader -from importlib.metadata import Distribution, DistributionFinder, PackageNotFoundError, Prepared - -ResourceReader.register(__res._ResfsResourceReader) - -METADATA_NAME = re.compile('^Name: (.*)$', re.MULTILINE) - - -class ArcadiaDistribution(Distribution): - - def __init__(self, prefix): - self.prefix = prefix - - def read_text(self, filename): - data = __res.resfs_read(f'{self.prefix}{filename}') - if data: - return data.decode('utf-8') - read_text.__doc__ = Distribution.read_text.__doc__ - - def locate_file(self, path): - return f'{self.prefix}{path}' - - -class ArcadiaMetadataFinder(DistributionFinder): - - prefixes = {} - - @classmethod - def find_distributions(cls, context=DistributionFinder.Context()): - found = cls._search_prefixes(context.name) - return map(ArcadiaDistribution, found) - - @classmethod - def _init_prefixes(cls): - cls.prefixes.clear() - - for resource in __res.resfs_files(): - resource = resource.decode('utf-8') - if not resource.endswith('METADATA'): - continue - data = __res.resfs_read(resource).decode('utf-8') - metadata_name = METADATA_NAME.search(data) - if metadata_name: - metadata_name = Prepared(metadata_name.group(1)) - cls.prefixes[metadata_name.normalized] = resource[:-len('METADATA')] - - @classmethod - def _search_prefixes(cls, name): - if not cls.prefixes: - cls._init_prefixes() - - if name: - try: - yield cls.prefixes[Prepared(name).normalized] - except KeyError: - raise PackageNotFoundError(name) - else: - for prefix in sorted(cls.prefixes.values()): - yield prefix - - -# monkeypatch standart library -import importlib.metadata -importlib.metadata.MetadataPathFinder = ArcadiaMetadataFinder +import re +import sys + +import __res + +from importlib.abc import ResourceReader +from importlib.metadata import Distribution, DistributionFinder, PackageNotFoundError, Prepared + +ResourceReader.register(__res._ResfsResourceReader) + +METADATA_NAME = re.compile('^Name: (.*)$', re.MULTILINE) + + +class ArcadiaDistribution(Distribution): + + def __init__(self, prefix): + self.prefix = prefix + + def read_text(self, filename): + data = __res.resfs_read(f'{self.prefix}{filename}') + if data: + return data.decode('utf-8') + read_text.__doc__ = Distribution.read_text.__doc__ + + def locate_file(self, path): + return f'{self.prefix}{path}' + + +class ArcadiaMetadataFinder(DistributionFinder): + + prefixes = {} + + @classmethod + def find_distributions(cls, context=DistributionFinder.Context()): + found = cls._search_prefixes(context.name) + return map(ArcadiaDistribution, found) + + @classmethod + def _init_prefixes(cls): + cls.prefixes.clear() + + for resource in __res.resfs_files(): + resource = resource.decode('utf-8') + if not resource.endswith('METADATA'): + continue + data = __res.resfs_read(resource).decode('utf-8') + metadata_name = METADATA_NAME.search(data) + if metadata_name: + metadata_name = Prepared(metadata_name.group(1)) + cls.prefixes[metadata_name.normalized] = resource[:-len('METADATA')] + + @classmethod + def _search_prefixes(cls, name): + if not cls.prefixes: + cls._init_prefixes() + + if name: + try: + yield cls.prefixes[Prepared(name).normalized] + except KeyError: + raise PackageNotFoundError(name) + else: + for prefix in sorted(cls.prefixes.values()): + yield prefix + + +# monkeypatch standart library +import importlib.metadata +importlib.metadata.MetadataPathFinder = ArcadiaMetadataFinder diff --git a/library/python/runtime_py3/test/.dist-info/METADATA b/library/python/runtime_py3/test/.dist-info/METADATA index bb36162199..a2af7dff1a 100644 --- a/library/python/runtime_py3/test/.dist-info/METADATA +++ b/library/python/runtime_py3/test/.dist-info/METADATA @@ -1,14 +1,14 @@ -Metadata-Version: 2.1 -Name: foo-bar -Version: 1.2.3 -Summary: -Home-page: https://foo.org/ -Author: Foo -Author-email: foo@ya.com -License: UNKNOWN -Platform: any -Classifier: Development Status :: 4 - Beta -Classifier: Programming Language :: Python :: 3 -Requires-Python: >=3.8 -Requires-Dist: Werkzeug (>=0.15) -Requires-Dist: Jinja2 (>=2.10.1) +Metadata-Version: 2.1 +Name: foo-bar +Version: 1.2.3 +Summary: +Home-page: https://foo.org/ +Author: Foo +Author-email: foo@ya.com +License: UNKNOWN +Platform: any +Classifier: Development Status :: 4 - Beta +Classifier: Programming Language :: Python :: 3 +Requires-Python: >=3.8 +Requires-Dist: Werkzeug (>=0.15) +Requires-Dist: Jinja2 (>=2.10.1) diff --git a/library/python/runtime_py3/test/.dist-info/RECORD b/library/python/runtime_py3/test/.dist-info/RECORD index dabbbff80d..4199b6bece 100644 --- a/library/python/runtime_py3/test/.dist-info/RECORD +++ b/library/python/runtime_py3/test/.dist-info/RECORD @@ -1 +1 @@ -foo_bar.py,sha256=0000000000000000000000000000000000000000000,20 +foo_bar.py,sha256=0000000000000000000000000000000000000000000,20 diff --git a/library/python/runtime_py3/test/.dist-info/entry_points.txt b/library/python/runtime_py3/test/.dist-info/entry_points.txt index f5e2fd2657..525429d658 100644 --- a/library/python/runtime_py3/test/.dist-info/entry_points.txt +++ b/library/python/runtime_py3/test/.dist-info/entry_points.txt @@ -1,2 +1,2 @@ -[console_scripts] -foo_cli = foo_bar:cli +[console_scripts] +foo_cli = foo_bar:cli diff --git a/library/python/runtime_py3/test/.dist-info/top_level.txt b/library/python/runtime_py3/test/.dist-info/top_level.txt index d2c068bc6b..8009c4b1f1 100644 --- a/library/python/runtime_py3/test/.dist-info/top_level.txt +++ b/library/python/runtime_py3/test/.dist-info/top_level.txt @@ -1 +1 @@ -foo_bar +foo_bar diff --git a/library/python/runtime_py3/test/resources/foo.txt b/library/python/runtime_py3/test/resources/foo.txt index ba0e162e1c..59ea98c2c6 100644 --- a/library/python/runtime_py3/test/resources/foo.txt +++ b/library/python/runtime_py3/test/resources/foo.txt @@ -1 +1 @@ -bar
\ No newline at end of file +bar
\ No newline at end of file diff --git a/library/python/runtime_py3/test/resources/submodule/bar.txt b/library/python/runtime_py3/test/resources/submodule/bar.txt index 1910281566..356e2c858c 100644 --- a/library/python/runtime_py3/test/resources/submodule/bar.txt +++ b/library/python/runtime_py3/test/resources/submodule/bar.txt @@ -1 +1 @@ -foo
\ No newline at end of file +foo
\ No newline at end of file diff --git a/library/python/runtime_py3/test/test_metadata.py b/library/python/runtime_py3/test/test_metadata.py index 686c176468..7e7d9f0c1c 100644 --- a/library/python/runtime_py3/test/test_metadata.py +++ b/library/python/runtime_py3/test/test_metadata.py @@ -1,44 +1,44 @@ -import importlib.metadata as im - -import pytest - - -@pytest.mark.parametrize("name", ("foo-bar", "foo_bar", "Foo-Bar")) -def test_distribution(name): - assert im.distribution(name) is not None - - -def test_unknown_package(): - with pytest.raises(im.PackageNotFoundError): - im.distribution("bar") - - -def test_version(): - assert im.version("foo-bar") == "1.2.3" - - -def test_metadata(): - assert im.metadata("foo-bar") is not None - - -def test_files(): - files = im.files("foo-bar") - assert len(files) == 1 - assert files[0].name == "foo_bar.py" - assert files[0].size == 20 - - -def test_requires(): - assert im.requires("foo-bar") == ["Werkzeug (>=0.15)", "Jinja2 (>=2.10.1)"] - - -def test_entry_points(): - entry_points = im.entry_points() - assert "console_scripts" in entry_points - - flg_found = False - for entry_point in entry_points["console_scripts"]: - if entry_point.name == "foo_cli" and entry_point.value == "foo_bar:cli": - flg_found = True - - assert flg_found +import importlib.metadata as im + +import pytest + + +@pytest.mark.parametrize("name", ("foo-bar", "foo_bar", "Foo-Bar")) +def test_distribution(name): + assert im.distribution(name) is not None + + +def test_unknown_package(): + with pytest.raises(im.PackageNotFoundError): + im.distribution("bar") + + +def test_version(): + assert im.version("foo-bar") == "1.2.3" + + +def test_metadata(): + assert im.metadata("foo-bar") is not None + + +def test_files(): + files = im.files("foo-bar") + assert len(files) == 1 + assert files[0].name == "foo_bar.py" + assert files[0].size == 20 + + +def test_requires(): + assert im.requires("foo-bar") == ["Werkzeug (>=0.15)", "Jinja2 (>=2.10.1)"] + + +def test_entry_points(): + entry_points = im.entry_points() + assert "console_scripts" in entry_points + + flg_found = False + for entry_point in entry_points["console_scripts"]: + if entry_point.name == "foo_cli" and entry_point.value == "foo_bar:cli": + flg_found = True + + assert flg_found diff --git a/library/python/runtime_py3/test/test_resources.py b/library/python/runtime_py3/test/test_resources.py index a269329f42..8eb05380a5 100644 --- a/library/python/runtime_py3/test/test_resources.py +++ b/library/python/runtime_py3/test/test_resources.py @@ -1,60 +1,60 @@ -# -*- coding: utf-8 -*- - -import importlib.resources as ir - -import pytest - - -@pytest.mark.parametrize("package, resource", ( - ("resources", "foo.txt"), - ("resources.submodule", "bar.txt") -)) -def test_is_resource_good_path(package, resource): - assert ir.is_resource(package, resource) - - -@pytest.mark.parametrize("package, resource", ( - ("resources", "111.txt"), - ("resources.submodule", "222.txt") -)) -def test_is_resource_missing(package, resource): - assert not ir.is_resource(package, resource) - - -def test_is_resource_subresource_directory(): - # Directories are not resources. - assert not ir.is_resource("resources", "submodule") - - -@pytest.mark.parametrize("package, resource, expected", ( - ("resources", "foo.txt", b"bar"), - ("resources.submodule", "bar.txt", b"foo") -)) -def test_read_binary_good_path(package, resource, expected): - assert ir.read_binary(package, resource) == expected - - -def test_read_binary_missing(): - with pytest.raises(FileNotFoundError): - ir.read_binary("resources", "111.txt") - - -@pytest.mark.parametrize("package, resource, expected", ( - ("resources", "foo.txt", "bar"), - ("resources.submodule", "bar.txt", "foo") -)) -def test_read_text_good_path(package, resource, expected): - assert ir.read_text(package, resource) == expected - - -def test_read_text_missing(): - with pytest.raises(FileNotFoundError): - ir.read_text("resources", "111.txt") - - -@pytest.mark.parametrize("package, expected", ( - ("resources", ["submodule", "foo.txt"]), - ("resources.submodule", ["bar.txt"]) -)) -def test_contents_good_path(package, expected): - assert sorted(ir.contents(package)) == sorted(expected) +# -*- coding: utf-8 -*- + +import importlib.resources as ir + +import pytest + + +@pytest.mark.parametrize("package, resource", ( + ("resources", "foo.txt"), + ("resources.submodule", "bar.txt") +)) +def test_is_resource_good_path(package, resource): + assert ir.is_resource(package, resource) + + +@pytest.mark.parametrize("package, resource", ( + ("resources", "111.txt"), + ("resources.submodule", "222.txt") +)) +def test_is_resource_missing(package, resource): + assert not ir.is_resource(package, resource) + + +def test_is_resource_subresource_directory(): + # Directories are not resources. + assert not ir.is_resource("resources", "submodule") + + +@pytest.mark.parametrize("package, resource, expected", ( + ("resources", "foo.txt", b"bar"), + ("resources.submodule", "bar.txt", b"foo") +)) +def test_read_binary_good_path(package, resource, expected): + assert ir.read_binary(package, resource) == expected + + +def test_read_binary_missing(): + with pytest.raises(FileNotFoundError): + ir.read_binary("resources", "111.txt") + + +@pytest.mark.parametrize("package, resource, expected", ( + ("resources", "foo.txt", "bar"), + ("resources.submodule", "bar.txt", "foo") +)) +def test_read_text_good_path(package, resource, expected): + assert ir.read_text(package, resource) == expected + + +def test_read_text_missing(): + with pytest.raises(FileNotFoundError): + ir.read_text("resources", "111.txt") + + +@pytest.mark.parametrize("package, expected", ( + ("resources", ["submodule", "foo.txt"]), + ("resources.submodule", ["bar.txt"]) +)) +def test_contents_good_path(package, expected): + assert sorted(ir.contents(package)) == sorted(expected) diff --git a/library/python/runtime_py3/test/ya.make b/library/python/runtime_py3/test/ya.make index 4ec3db74f5..9056e48a39 100644 --- a/library/python/runtime_py3/test/ya.make +++ b/library/python/runtime_py3/test/ya.make @@ -12,29 +12,29 @@ PEERDIR( contrib/python/PyYAML ) -PY_SRCS( - TOP_LEVEL - resources/__init__.py - resources/submodule/__init__.py -) - -TEST_SRCS( - test_metadata.py - test_resources.py - test_traceback.py +PY_SRCS( + TOP_LEVEL + resources/__init__.py + resources/submodule/__init__.py +) + +TEST_SRCS( + test_metadata.py + test_resources.py + test_traceback.py test_arcadia_source_finder.py -) - -RESOURCE_FILES( - PREFIX library/python/runtime_py3/test/ - .dist-info/METADATA - .dist-info/RECORD - .dist-info/entry_points.txt - .dist-info/top_level.txt - resources/foo.txt - resources/submodule/bar.txt -) - +) + +RESOURCE_FILES( + PREFIX library/python/runtime_py3/test/ + .dist-info/METADATA + .dist-info/RECORD + .dist-info/entry_points.txt + .dist-info/top_level.txt + resources/foo.txt + resources/submodule/bar.txt +) + END() RECURSE_FOR_TESTS(traceback) diff --git a/library/python/runtime_py3/ya.make b/library/python/runtime_py3/ya.make index fa5c11341a..dc4dbd2b10 100644 --- a/library/python/runtime_py3/ya.make +++ b/library/python/runtime_py3/ya.make @@ -23,12 +23,12 @@ ENABLE(PYBUILD_NO_PYC) PY_SRCS( entry_points.py TOP_LEVEL - - CYTHON_DIRECTIVE - language_level=3 - + + CYTHON_DIRECTIVE + language_level=3 + __res.pyx - sitecustomize.pyx + sitecustomize.pyx ) IF (CYTHON_COVERAGE) |