diff --git a/.travis.yml b/.travis.yml index c2a0230..fac4666 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,23 +10,23 @@ python: - "nightly" env: global: - - FLUENT_RUNTIME_DEFAULT_DEPS="fluent.syntax==0.13.0 attrs==19.1.0 babel==2.6.0 pytz==2018.9 six==1.12.0" + - FLUENT_RUNTIME_DEFAULT_DEPS="fluent.syntax==0.15 attrs==19.1.0 babel==2.6.0 pytz==2018.9 six==1.12.0" - FLUENT_SYNTAX_DEFAULT_DEPS="six" matrix: - PACKAGE=fluent.syntax - PACKAGE=fluent.runtime matrix: include: - - name: "fluent.runtime w/ fluent.syntax==0.10" + - name: "fluent.runtime w/ fluent.syntax==0.14" python: "3.6" - env: PACKAGE=fluent.runtime TEST_DEPS="fluent.syntax==0.10.0 attrs==19.1.0 babel==2.6.0 pytz==2018.9 six==1.12.0" + env: PACKAGE=fluent.runtime TEST_DEPS="fluent.syntax==0.14 attrs==19.1.0 babel==2.6.0 pytz==2018.9 six==1.12.0" - name: "fluent.runtime with latest everything" python: "3.6" # These are copy-pasted from setup.py - env: PACKAGE=fluent.runtime TEST_DEPS="fluent.syntax>=0.10,<=0.13 attrs babel pytz six" + env: PACKAGE=fluent.runtime TEST_DEPS="fluent.syntax>=0.14,<=0.16 attrs babel pytz six" allow_failures: - python: "3.6" - env: PACKAGE=fluent.runtime TEST_DEPS="fluent.syntax>=0.10,<=0.13 attrs babel pytz six" + env: PACKAGE=fluent.runtime TEST_DEPS="fluent.syntax>=0.14,<=0.16 attrs babel pytz six" install: - if [ "$PACKAGE" = "fluent.runtime" ]; then TEST_DEPS=${TEST_DEPS:-$FLUENT_RUNTIME_DEFAULT_DEPS}; else TEST_DEPS=${TEST_DEPS:-$FLUENT_SYNTAX_DEFAULT_DEPS}; fi diff --git a/fluent.runtime/fluent/runtime/resolver.py b/fluent.runtime/fluent/runtime/resolver.py index 450ca15..3d49747 100644 --- a/fluent.runtime/fluent/runtime/resolver.py +++ b/fluent.runtime/fluent/runtime/resolver.py @@ -158,7 +158,7 @@ def __call__(self, env): class StringLiteral(FTL.StringLiteral, Literal): def __call__(self, env): - return self.value + return self.parse()['value'] class NumberLiteral(FTL.NumberLiteral, BaseResolver): @@ -180,7 +180,14 @@ def __call__(self, env): class TermReference(FTL.TermReference, BaseResolver): def __call__(self, env): - with env.modified_for_term_reference(): + if self.arguments: + if self.arguments.positional: + env.errors.append(FluentFormatError("Ignored positional arguments passed to term '{0}'" + .format(reference_to_id(self)))) + kwargs = {kwarg.name.name: kwarg.value(env) for kwarg in self.arguments.named} + else: + kwargs = None + with env.modified_for_term_reference(args=kwargs): return lookup_reference(self, env)(env) @@ -200,9 +207,9 @@ def lookup_reference(ref, env): except LookupError: env.errors.append(unknown_reference_error_obj(ref_id)) - if isinstance(ref, AttributeExpression): + if ref.attribute: # Fallback - parent_id = reference_to_id(ref.ref) + parent_id = reference_to_id(ref, ignore_attributes=True) try: return env.context.lookup(parent_id) except LookupError: @@ -231,40 +238,10 @@ def __call__(self, env): return FluentNone(name) -class AttributeExpression(FTL.AttributeExpression, BaseResolver): - def __call__(self, env): - return lookup_reference(self, env)(env) - - class Attribute(FTL.Attribute, BaseResolver): pass -class VariantList(FTL.VariantList, BaseResolver): - def __call__(self, env, key=None): - found = None - for variant in self.variants: - if variant.default: - default = variant - if key is None: - # We only want the default - break - - compare_value = variant.key(env) - if match(key, compare_value, env): - found = variant - break - - if found is None: - if (key is not None and not isinstance(key, FluentNone)): - env.errors.append(FluentReferenceError("Unknown variant: {0}" - .format(key))) - found = default - assert found, "Not having a default variant is a parse error" - - return found.value(env) - - class SelectExpression(FTL.SelectExpression, BaseResolver): def __call__(self, env): key = self.selector(env) @@ -314,33 +291,15 @@ def __call__(self, env): return self.name -class VariantExpression(FTL.VariantExpression, BaseResolver): - def __call__(self, env): - message = lookup_reference(self.ref, env) - - # TODO What to do if message is not a VariantList? - # Need test at least. - assert isinstance(message, VariantList) - - variant_name = self.key.name - return message(env, variant_name) +class CallArguments(FTL.CallArguments, BaseResolver): + pass -class CallExpression(FTL.CallExpression, BaseResolver): +class FunctionReference(FTL.FunctionReference, BaseResolver): def __call__(self, env): - args = [arg(env) for arg in self.positional] - kwargs = {kwarg.name.name: kwarg.value(env) for kwarg in self.named} - - if isinstance(self.callee, (TermReference, AttributeExpression)): - term = lookup_reference(self.callee, env) - if args: - env.errors.append(FluentFormatError("Ignored positional arguments passed to term '{0}'" - .format(reference_to_id(self.callee)))) - with env.modified_for_term_reference(args=kwargs): - return term(env) - - # builtin or custom function call - function_name = self.callee.id.name + args = [arg(env) for arg in self.arguments.positional] + kwargs = {kwarg.name.name: kwarg.value(env) for kwarg in self.arguments.named} + function_name = self.id.name try: function = env.context._functions[function_name] except LookupError: diff --git a/fluent.runtime/fluent/runtime/utils.py b/fluent.runtime/fluent/runtime/utils.py index 47a67fd..435c31a 100644 --- a/fluent.runtime/fluent/runtime/utils.py +++ b/fluent.runtime/fluent/runtime/utils.py @@ -3,7 +3,7 @@ from datetime import date, datetime from decimal import Decimal -from fluent.syntax.ast import AttributeExpression, Term, TermReference +from fluent.syntax.ast import Term, TermReference from .types import FluentInt, FluentFloat, FluentDecimal, FluentDate, FluentDateTime from .errors import FluentReferenceError @@ -39,9 +39,9 @@ def native_to_fluent(val): return val -def reference_to_id(ref): +def reference_to_id(ref, ignore_attributes=False): """ - Returns a string reference for a MessageReference, TermReference or AttributeExpression + Returns a string reference for a MessageReference or TermReference AST node. e.g. @@ -50,12 +50,14 @@ def reference_to_id(ref): -term -term.attr """ - if isinstance(ref, AttributeExpression): - return _make_attr_id(reference_to_id(ref.ref), - ref.name.name) if isinstance(ref, TermReference): - return TERM_SIGIL + ref.id.name - return ref.id.name + start = TERM_SIGIL + ref.id.name + else: + start = ref.id.name + + if not ignore_attributes and ref.attribute: + return ''.join([start, ATTRIBUTE_SEPARATOR, ref.attribute.name]) + return start def unknown_reference_error_obj(ref_id): @@ -64,10 +66,3 @@ def unknown_reference_error_obj(ref_id): if ref_id.startswith(TERM_SIGIL): return FluentReferenceError("Unknown term: {0}".format(ref_id)) return FluentReferenceError("Unknown message: {0}".format(ref_id)) - - -def _make_attr_id(parent_ref_id, attr_name): - """ - Given a parent id and the attribute name, return the attribute id - """ - return ''.join([parent_ref_id, ATTRIBUTE_SEPARATOR, attr_name]) diff --git a/fluent.runtime/setup.py b/fluent.runtime/setup.py index 14b1f21..21324f1 100755 --- a/fluent.runtime/setup.py +++ b/fluent.runtime/setup.py @@ -21,7 +21,7 @@ packages=['fluent', 'fluent.runtime'], # These should also be duplicated in tox.ini and ../.travis.yml install_requires=[ - 'fluent.syntax>=0.10,<=0.13', + 'fluent.syntax>=0.14,<=0.16', 'attrs', 'babel', 'pytz', diff --git a/fluent.runtime/tests/format/test_placeables.py b/fluent.runtime/tests/format/test_placeables.py index aa26197..963b386 100644 --- a/fluent.runtime/tests/format/test_placeables.py +++ b/fluent.runtime/tests/format/test_placeables.py @@ -24,7 +24,6 @@ def setUp(self): uses-message = { message } uses-message-attr = { message.attr } uses-term = { -term } - uses-term-variant = { -term2[variant2] } bad-message-ref = Text { not-a-message } bad-message-attr-ref = Text { message.not-an-attr } @@ -57,11 +56,6 @@ def test_placeable_term(self): self.assertEqual(val, 'Term') self.assertEqual(len(errs), 0) - def test_placeable_term_variant(self): - val, errs = self.ctx.format('uses-term-variant', {}) - self.assertEqual(val, 'Term Variant 2') - self.assertEqual(len(errs), 0) - def test_placeable_bad_message(self): val, errs = self.ctx.format('bad-message-ref', {}) self.assertEqual(val, 'Text not-a-message') diff --git a/fluent.runtime/tests/format/test_primitives.py b/fluent.runtime/tests/format/test_primitives.py index 01e86cb..bdc1f66 100644 --- a/fluent.runtime/tests/format/test_primitives.py +++ b/fluent.runtime/tests/format/test_primitives.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import unittest @@ -10,7 +11,7 @@ class TestSimpleStringValue(unittest.TestCase): def setUp(self): self.ctx = FluentBundle(['en-US'], use_isolating=False) - self.ctx.add_messages(dedent_ftl(""" + self.ctx.add_messages(dedent_ftl(r""" foo = Foo placeable-literal = { "Foo" } Bar placeable-message = { foo } Bar @@ -27,6 +28,7 @@ def setUp(self): [BazAttribute] Member 3 *[other] Member 4 } + escapes = {" "}stuff{"\u0258}\"\\end"} """)) def test_can_be_used_as_a_value(self): @@ -64,6 +66,11 @@ def test_can_be_a_value_of_an_attribute_used_as_a_selector(self): self.assertEqual(val, 'Member 3') self.assertEqual(len(errs), 0) + def test_escapes(self): + val, errs = self.ctx.format('escapes', {}) + self.assertEqual(val, r' stuffɘ}"\end') + self.assertEqual(len(errs), 0) + class TestComplexStringValue(unittest.TestCase): def setUp(self): diff --git a/fluent.runtime/tests/format/test_variants.py b/fluent.runtime/tests/format/test_variants.py deleted file mode 100644 index 01fdb6c..0000000 --- a/fluent.runtime/tests/format/test_variants.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import unittest - -from fluent.runtime import FluentBundle -from fluent.runtime.errors import FluentReferenceError - -from ..utils import dedent_ftl - - -class TestVariants(unittest.TestCase): - - def setUp(self): - self.ctx = FluentBundle(['en-US'], use_isolating=False) - self.ctx.add_messages(dedent_ftl(""" - -variant = { - [a] A - *[b] B - } - foo = { -variant } - bar = { -variant[a] } - baz = { -variant[b] } - qux = { -variant[c] } - """)) - - def test_returns_the_default_variant(self): - val, errs = self.ctx.format('foo', {}) - self.assertEqual(val, 'B') - self.assertEqual(len(errs), 0) - - def test_choose_other_variant(self): - val, errs = self.ctx.format('bar', {}) - self.assertEqual(val, 'A') - self.assertEqual(len(errs), 0) - - def test_choose_default_variant(self): - val, errs = self.ctx.format('baz', {}) - self.assertEqual(val, 'B') - self.assertEqual(len(errs), 0) - - def test_choose_missing_variant(self): - val, errs = self.ctx.format('qux', {}) - self.assertEqual(val, 'B') - self.assertEqual(len(errs), 1) - self.assertEqual( - errs, - [FluentReferenceError("Unknown variant: c")]) diff --git a/fluent.runtime/tox.ini b/fluent.runtime/tox.ini index d1d5c04..5d2553b 100644 --- a/fluent.runtime/tox.ini +++ b/fluent.runtime/tox.ini @@ -1,14 +1,14 @@ # This config is for local testing. It should be duplicated into .travis.yml [tox] -envlist = {py27,py35,py36,py37,pypy,pypy3}-syntax0.13, py36-syntax0.10, latest +envlist = {py27,py35,py36,py37,pypy,pypy3}-syntax0.15, py36-syntax0.14, latest skipsdist=True [testenv] setenv = PYTHONPATH = {toxinidir} deps = - syntax0.10: fluent.syntax==0.10.0 - syntax0.13: fluent.syntax==0.13.0 + syntax0.15: fluent.syntax==0.15 + syntax0.14: fluent.syntax==0.14 attrs==19.1.0 babel==2.6.0 pytz==2018.9 @@ -22,7 +22,7 @@ deps = # It's tempting to use '.' here to get 'pip install .' # Unfortunately it is super slow: https://github.com/pypa/pip/issues/2195 # Instead we copy-paste from setup.py - fluent.syntax>=0.10,<=0.13 + fluent.syntax>=0.14,<=0.16 attrs babel pytz