diff --git a/.github/workflows/fluent.syntax.yml b/.github/workflows/fluent.syntax.yml index 0ff16cc0..68c1dd4c 100644 --- a/.github/workflows/fluent.syntax.yml +++ b/.github/workflows/fluent.syntax.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, pypy2, pypy3] + python-version: [3.6, 3.7, 3.8, 3.9, pypy3] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 @@ -35,7 +35,6 @@ jobs: run: | python -m pip install wheel python -m pip install --upgrade pip - python -m pip install six - name: Test working-directory: ./fluent.syntax run: | @@ -47,7 +46,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/fluent.syntax/CHANGELOG.md b/fluent.syntax/CHANGELOG.md index 05e0dc1f..0114b33a 100644 --- a/fluent.syntax/CHANGELOG.md +++ b/fluent.syntax/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## fluent.syntax 0.19 (TBD) + + - Drop support for Python 2.7 and 3.5 + - Add support for Python 3.6 through 3.9 + ## fluent.syntax 0.18.1 (September 15, 2020) - Fix serialization of multiline patterns starting with special characters. (#156) diff --git a/fluent.syntax/fluent/syntax/ast.py b/fluent.syntax/fluent/syntax/ast.py index 7ad5d611..acd5a1f8 100644 --- a/fluent.syntax/fluent/syntax/ast.py +++ b/fluent.syntax/fluent/syntax/ast.py @@ -1,9 +1,6 @@ -# coding=utf-8 -from __future__ import unicode_literals import re import sys import json -import six def to_json(value, fn=None): @@ -44,7 +41,7 @@ def scalars_equal(node1, node2, ignored_fields): return node1 == node2 -class BaseNode(object): +class BaseNode: """Base class for all Fluent AST nodes. All productions described in the ASDL subclass BaseNode, including Span and @@ -125,7 +122,7 @@ class SyntaxNode(BaseNode): """Base class for AST nodes which can have Spans.""" def __init__(self, span=None, **kwargs): - super(SyntaxNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.span = span def add_span(self, start, end): @@ -134,7 +131,7 @@ def add_span(self, start, end): class Resource(SyntaxNode): def __init__(self, body=None, **kwargs): - super(Resource, self).__init__(**kwargs) + super().__init__(**kwargs) self.body = body or [] @@ -145,7 +142,7 @@ class Entry(SyntaxNode): class Message(Entry): def __init__(self, id, value=None, attributes=None, comment=None, **kwargs): - super(Message, self).__init__(**kwargs) + super().__init__(**kwargs) self.id = id self.value = value self.attributes = attributes or [] @@ -155,7 +152,7 @@ def __init__(self, id, value=None, attributes=None, class Term(Entry): def __init__(self, id, value, attributes=None, comment=None, **kwargs): - super(Term, self).__init__(**kwargs) + super().__init__(**kwargs) self.id = id self.value = value self.attributes = attributes or [] @@ -164,7 +161,7 @@ def __init__(self, id, value, attributes=None, class Pattern(SyntaxNode): def __init__(self, elements, **kwargs): - super(Pattern, self).__init__(**kwargs) + super().__init__(**kwargs) self.elements = elements @@ -174,13 +171,13 @@ class PatternElement(SyntaxNode): class TextElement(PatternElement): def __init__(self, value, **kwargs): - super(TextElement, self).__init__(**kwargs) + super().__init__(**kwargs) self.value = value class Placeable(PatternElement): def __init__(self, expression, **kwargs): - super(Placeable, self).__init__(**kwargs) + super().__init__(**kwargs) self.expression = expression @@ -191,7 +188,7 @@ class Expression(SyntaxNode): class Literal(Expression): """An abstract base class for literals.""" def __init__(self, value, **kwargs): - super(Literal, self).__init__(**kwargs) + super().__init__(**kwargs) self.value = value def parse(self): @@ -206,7 +203,7 @@ def from_escape_sequence(matchobj): return c codepoint = int(codepoint4 or codepoint6, 16) if codepoint <= 0xD7FF or 0xE000 <= codepoint: - return six.unichr(codepoint) + return chr(codepoint) # Escape sequences reresenting surrogate code points are # well-formed but invalid in Fluent. Replace them with U+FFFD # REPLACEMENT CHARACTER. @@ -235,14 +232,14 @@ def parse(self): class MessageReference(Expression): def __init__(self, id, attribute=None, **kwargs): - super(MessageReference, self).__init__(**kwargs) + super().__init__(**kwargs) self.id = id self.attribute = attribute class TermReference(Expression): def __init__(self, id, attribute=None, arguments=None, **kwargs): - super(TermReference, self).__init__(**kwargs) + super().__init__(**kwargs) self.id = id self.attribute = attribute self.arguments = arguments @@ -250,41 +247,41 @@ def __init__(self, id, attribute=None, arguments=None, **kwargs): class VariableReference(Expression): def __init__(self, id, **kwargs): - super(VariableReference, self).__init__(**kwargs) + super().__init__(**kwargs) self.id = id class FunctionReference(Expression): def __init__(self, id, arguments, **kwargs): - super(FunctionReference, self).__init__(**kwargs) + super().__init__(**kwargs) self.id = id self.arguments = arguments class SelectExpression(Expression): def __init__(self, selector, variants, **kwargs): - super(SelectExpression, self).__init__(**kwargs) + super().__init__(**kwargs) self.selector = selector self.variants = variants class CallArguments(SyntaxNode): def __init__(self, positional=None, named=None, **kwargs): - super(CallArguments, self).__init__(**kwargs) + super().__init__(**kwargs) self.positional = [] if positional is None else positional self.named = [] if named is None else named class Attribute(SyntaxNode): def __init__(self, id, value, **kwargs): - super(Attribute, self).__init__(**kwargs) + super().__init__(**kwargs) self.id = id self.value = value class Variant(SyntaxNode): def __init__(self, key, value, default=False, **kwargs): - super(Variant, self).__init__(**kwargs) + super().__init__(**kwargs) self.key = key self.value = value self.default = default @@ -292,41 +289,41 @@ def __init__(self, key, value, default=False, **kwargs): class NamedArgument(SyntaxNode): def __init__(self, name, value, **kwargs): - super(NamedArgument, self).__init__(**kwargs) + super().__init__(**kwargs) self.name = name self.value = value class Identifier(SyntaxNode): def __init__(self, name, **kwargs): - super(Identifier, self).__init__(**kwargs) + super().__init__(**kwargs) self.name = name class BaseComment(Entry): def __init__(self, content=None, **kwargs): - super(BaseComment, self).__init__(**kwargs) + super().__init__(**kwargs) self.content = content class Comment(BaseComment): def __init__(self, content=None, **kwargs): - super(Comment, self).__init__(content, **kwargs) + super().__init__(content, **kwargs) class GroupComment(BaseComment): def __init__(self, content=None, **kwargs): - super(GroupComment, self).__init__(content, **kwargs) + super().__init__(content, **kwargs) class ResourceComment(BaseComment): def __init__(self, content=None, **kwargs): - super(ResourceComment, self).__init__(content, **kwargs) + super().__init__(content, **kwargs) class Junk(SyntaxNode): def __init__(self, content=None, annotations=None, **kwargs): - super(Junk, self).__init__(**kwargs) + super().__init__(**kwargs) self.content = content self.annotations = annotations or [] @@ -336,14 +333,14 @@ def add_annotation(self, annot): class Span(BaseNode): def __init__(self, start, end, **kwargs): - super(Span, self).__init__(**kwargs) + super().__init__(**kwargs) self.start = start self.end = end class Annotation(SyntaxNode): def __init__(self, code, arguments=None, message=None, **kwargs): - super(Annotation, self).__init__(**kwargs) + super().__init__(**kwargs) self.code = code self.arguments = arguments or [] self.message = message diff --git a/fluent.syntax/fluent/syntax/errors.py b/fluent.syntax/fluent/syntax/errors.py index cd137871..36859d20 100644 --- a/fluent.syntax/fluent/syntax/errors.py +++ b/fluent.syntax/fluent/syntax/errors.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals - - class ParseError(Exception): def __init__(self, code, *args): self.code = code diff --git a/fluent.syntax/fluent/syntax/parser.py b/fluent.syntax/fluent/syntax/parser.py index 6731136c..fb296368 100644 --- a/fluent.syntax/fluent/syntax/parser.py +++ b/fluent.syntax/fluent/syntax/parser.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import re from . import ast from .stream import EOF, EOL, FluentParserStream @@ -25,7 +24,7 @@ def decorated(self, ps, *args, **kwargs): return decorated -class FluentParser(object): +class FluentParser: """This class is used to parse Fluent source content. ``with_spans`` enables source information in the form of @@ -469,7 +468,7 @@ def get_escape_sequence(self, ps): if next == '\\' or next == '"': ps.next() - return '\\{}'.format(next) + return f'\\{next}' if next == 'u': return self.get_unicode_escape_sequence(ps, next, 4) @@ -485,10 +484,10 @@ def get_unicode_escape_sequence(self, ps, u, digits): for _ in range(digits): ch = ps.take_hex_digit() if not ch: - raise ParseError('E0026', '\\{}{}{}'.format(u, sequence, ps.current_char)) + raise ParseError('E0026', f'\\{u}{sequence}{ps.current_char}') sequence += ch - return '\\{}{}'.format(u, sequence) + return f'\\{u}{sequence}' @with_span def get_placeable(self, ps): diff --git a/fluent.syntax/fluent/syntax/serializer.py b/fluent.syntax/fluent/syntax/serializer.py index 7c1bb087..b6e16b1c 100644 --- a/fluent.syntax/fluent/syntax/serializer.py +++ b/fluent.syntax/fluent/syntax/serializer.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from . import ast @@ -32,7 +31,7 @@ def should_start_on_new_line(pattern): return False -class FluentSerializer(object): +class FluentSerializer: """FluentSerializer converts :class:`.ast.SyntaxNode` objects to unicode strings. `with_junk` controls if parse errors are written back or not. @@ -83,11 +82,11 @@ def serialize_entry(self, entry, state=0): def serialize_comment(comment, prefix="#"): prefixed = "\n".join([ - prefix if len(line) == 0 else "{} {}".format(prefix, line) + prefix if len(line) == 0 else f"{prefix} {line}" for line in comment.content.split("\n") ]) # Add the trailing line break. - return '{}\n'.format(prefixed) + return f'{prefixed}\n' def serialize_junk(junk): @@ -100,7 +99,7 @@ def serialize_message(message): if message.comment: parts.append(serialize_comment(message.comment)) - parts.append("{} =".format(message.id.name)) + parts.append(f"{message.id.name} =") if message.value: parts.append(serialize_pattern(message.value)) @@ -119,7 +118,7 @@ def serialize_term(term): if term.comment: parts.append(serialize_comment(term.comment)) - parts.append("-{} =".format(term.id.name)) + parts.append(f"-{term.id.name} =") parts.append(serialize_pattern(term.value)) if term.attributes: @@ -142,9 +141,9 @@ def serialize_pattern(pattern): content = indent_except_first_line(content) if should_start_on_new_line(pattern): - return '\n {}'.format(content) + return f'\n {content}' - return ' {}'.format(content) + return f' {content}' def serialize_element(element): @@ -169,32 +168,32 @@ def serialize_placeable(placeable): def serialize_expression(expression): if isinstance(expression, ast.StringLiteral): - return '"{}"'.format(expression.value) + return f'"{expression.value}"' if isinstance(expression, ast.NumberLiteral): return expression.value if isinstance(expression, ast.VariableReference): - return "${}".format(expression.id.name) + return f"${expression.id.name}" if isinstance(expression, ast.TermReference): - out = "-{}".format(expression.id.name) + out = f"-{expression.id.name}" if expression.attribute is not None: - out += ".{}".format(expression.attribute.name) + out += f".{expression.attribute.name}" if expression.arguments is not None: out += serialize_call_arguments(expression.arguments) return out if isinstance(expression, ast.MessageReference): out = expression.id.name if expression.attribute is not None: - out += ".{}".format(expression.attribute.name) + out += f".{expression.attribute.name}" return out if isinstance(expression, ast.FunctionReference): args = serialize_call_arguments(expression.arguments) - return "{}{}".format(expression.id.name, args) + return f"{expression.id.name}{args}" if isinstance(expression, ast.SelectExpression): out = "{} ->".format( serialize_expression(expression.selector)) for variant in expression.variants: out += serialize_variant(variant) - return "{}\n".format(out) + return f"{out}\n" if isinstance(expression, ast.Placeable): return serialize_placeable(expression) raise Exception('Unknown expression type: {}'.format(type(expression))) @@ -214,7 +213,7 @@ def serialize_call_arguments(expr): named = ", ".join( serialize_named_argument(arg) for arg in expr.named) if len(expr.positional) > 0 and len(expr.named) > 0: - return '({}, {})'.format(positional, named) + return f'({positional}, {named})' return '({})'.format(positional or named) diff --git a/fluent.syntax/fluent/syntax/stream.py b/fluent.syntax/fluent/syntax/stream.py index 1f3852c8..6b38179e 100644 --- a/fluent.syntax/fluent/syntax/stream.py +++ b/fluent.syntax/fluent/syntax/stream.py @@ -1,8 +1,7 @@ -from __future__ import unicode_literals from .errors import ParseError -class ParserStream(object): +class ParserStream: def __init__(self, string): self.string = string self.index = 0 diff --git a/fluent.syntax/fluent/syntax/visitor.py b/fluent.syntax/fluent/syntax/visitor.py index 491a7959..cae5baac 100644 --- a/fluent.syntax/fluent/syntax/visitor.py +++ b/fluent.syntax/fluent/syntax/visitor.py @@ -1,10 +1,7 @@ -# coding=utf-8 -from __future__ import unicode_literals, absolute_import - from .ast import BaseNode -class Visitor(object): +class Visitor: '''Read-only visitor pattern. Subclass this to gather information from an AST. @@ -22,7 +19,7 @@ def visit(self, node): if not isinstance(node, BaseNode): return nodename = type(node).__name__ - visit = getattr(self, 'visit_{}'.format(nodename), self.generic_visit) + visit = getattr(self, f'visit_{nodename}', self.generic_visit) visit(node) def generic_visit(self, node): @@ -43,7 +40,7 @@ def visit(self, node): return node nodename = type(node).__name__ - visit = getattr(self, 'visit_{}'.format(nodename), self.generic_visit) + visit = getattr(self, f'visit_{nodename}', self.generic_visit) return visit(node) def generic_visit(self, node): diff --git a/fluent.syntax/setup.cfg b/fluent.syntax/setup.cfg index 6c7afb09..53b3d312 100644 --- a/fluent.syntax/setup.cfg +++ b/fluent.syntax/setup.cfg @@ -1,5 +1,5 @@ [metadata] -version=0.18.1 +version=0.19 [bdist_wheel] universal=1 diff --git a/fluent.syntax/setup.py b/fluent.syntax/setup.py index 411e912f..0b5f3aae 100755 --- a/fluent.syntax/setup.py +++ b/fluent.syntax/setup.py @@ -19,11 +19,12 @@ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3 :: Only', ], packages=['fluent', 'fluent.syntax'], - # These should also be duplicated in tox.ini and /.github/workflow/fluent.syntax.yml - tests_require=['six'], test_suite='tests.syntax' ) diff --git a/fluent.syntax/tests/syntax/__init__.py b/fluent.syntax/tests/syntax/__init__.py index 5bb38076..56301df6 100644 --- a/fluent.syntax/tests/syntax/__init__.py +++ b/fluent.syntax/tests/syntax/__init__.py @@ -1,7 +1,5 @@ -from __future__ import unicode_literals - import textwrap def dedent_ftl(text): - return textwrap.dedent("{}\n".format(text.rstrip())) + return textwrap.dedent(f"{text.rstrip()}\n") diff --git a/fluent.syntax/tests/syntax/test_ast_json.py b/fluent.syntax/tests/syntax/test_ast_json.py index 352b748a..bbfa524b 100644 --- a/fluent.syntax/tests/syntax/test_ast_json.py +++ b/fluent.syntax/tests/syntax/test_ast_json.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import unittest from tests.syntax import dedent_ftl diff --git a/fluent.syntax/tests/syntax/test_entry.py b/fluent.syntax/tests/syntax/test_entry.py index 494785af..7e4e25f9 100644 --- a/fluent.syntax/tests/syntax/test_entry.py +++ b/fluent.syntax/tests/syntax/test_entry.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import unittest from tests.syntax import dedent_ftl diff --git a/fluent.syntax/tests/syntax/test_equals.py b/fluent.syntax/tests/syntax/test_equals.py index c60a35a0..bd9ceb80 100644 --- a/fluent.syntax/tests/syntax/test_equals.py +++ b/fluent.syntax/tests/syntax/test_equals.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import unittest from tests.syntax import dedent_ftl diff --git a/fluent.syntax/tests/syntax/test_literal.py b/fluent.syntax/tests/syntax/test_literal.py index dc4d709b..7aba643f 100644 --- a/fluent.syntax/tests/syntax/test_literal.py +++ b/fluent.syntax/tests/syntax/test_literal.py @@ -1,5 +1,3 @@ -# coding: utf-8 -from __future__ import unicode_literals import sys import unittest diff --git a/fluent.syntax/tests/syntax/test_reference.py b/fluent.syntax/tests/syntax/test_reference.py index c69b1073..53c771f6 100644 --- a/fluent.syntax/tests/syntax/test_reference.py +++ b/fluent.syntax/tests/syntax/test_reference.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from six import with_metaclass - import os import json import codecs @@ -54,11 +51,11 @@ def test(self): if file_name in ('leading_dots', 'variant_lists'): continue - test_name = 'test_{}'.format(file_name) + test_name = f'test_{file_name}' attrs[test_name] = gen_test(file_name) return type.__new__(mcs, name, bases, attrs) -class TestReference(with_metaclass(TestReferenceMeta, unittest.TestCase)): +class TestReference(unittest.TestCase, metaclass=TestReferenceMeta): maxDiff = None diff --git a/fluent.syntax/tests/syntax/test_serializer.py b/fluent.syntax/tests/syntax/test_serializer.py index 1f716bc7..ffdd93df 100644 --- a/fluent.syntax/tests/syntax/test_serializer.py +++ b/fluent.syntax/tests/syntax/test_serializer.py @@ -1,7 +1,5 @@ -from __future__ import unicode_literals import unittest -import six from tests.syntax import dedent_ftl from fluent.syntax import FluentParser, FluentSerializer from fluent.syntax.serializer import serialize_expression, serialize_variant_key @@ -18,10 +16,10 @@ def pretty_ftl(text): def test_invalid_resource(self): serializer = FluentSerializer() - with six.assertRaisesRegex(self, Exception, 'Unknown resource type'): + with self.assertRaisesRegex(Exception, 'Unknown resource type'): serializer.serialize(None) - with six.assertRaisesRegex(self, Exception, 'Unknown resource type'): + with self.assertRaisesRegex(Exception, 'Unknown resource type'): serializer.serialize(object()) def test_simple_message(self): @@ -456,10 +454,10 @@ def pretty_expr(text): return serialize_expression(expr) def test_invalid_expression(self): - with six.assertRaisesRegex(self, Exception, 'Unknown expression type'): + with self.assertRaisesRegex(Exception, 'Unknown expression type'): serialize_expression(None) - with six.assertRaisesRegex(self, Exception, 'Unknown expression type'): + with self.assertRaisesRegex(Exception, 'Unknown expression type'): serialize_expression(object()) def test_string_expression(self): @@ -517,10 +515,10 @@ def pretty_variant_key(text, index): return serialize_variant_key(variants[index].key) def test_invalid_expression(self): - with six.assertRaisesRegex(self, Exception, 'Unknown variant key type'): + with self.assertRaisesRegex(Exception, 'Unknown variant key type'): serialize_variant_key(None) - with six.assertRaisesRegex(self, Exception, 'Unknown variant key type'): + with self.assertRaisesRegex(Exception, 'Unknown variant key type'): serialize_variant_key(object()) def test_identifiers(self): diff --git a/fluent.syntax/tests/syntax/test_structure.py b/fluent.syntax/tests/syntax/test_structure.py index 3f8789dc..e7650b88 100644 --- a/fluent.syntax/tests/syntax/test_structure.py +++ b/fluent.syntax/tests/syntax/test_structure.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -from six import with_metaclass - import os import json import codecs @@ -43,11 +40,11 @@ def test(self): if ext != '.ftl': continue - test_name = 'test_{}'.format(file_name) + test_name = f'test_{file_name}' attrs[test_name] = gen_test(file_name) return type.__new__(mcs, name, bases, attrs) -class TestStructure(with_metaclass(TestStructureMeta, unittest.TestCase)): +class TestStructure(unittest.TestCase, metaclass=TestStructureMeta): maxDiff = None diff --git a/fluent.syntax/tests/syntax/test_visitor.py b/fluent.syntax/tests/syntax/test_visitor.py index acd34284..dc1dd228 100644 --- a/fluent.syntax/tests/syntax/test_visitor.py +++ b/fluent.syntax/tests/syntax/test_visitor.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import defaultdict import unittest @@ -16,7 +14,7 @@ def __init__(self): def generic_visit(self, node): self.calls[type(node).__name__] += 1 - super(MockVisitor, self).generic_visit(node) + super().generic_visit(node) def visit_Pattern(self, node): self.pattern_calls += 1 @@ -71,7 +69,7 @@ def test(self): ) -class WordCounter(object): +class WordCounter: def __init__(self): self.word_count = 0 @@ -87,13 +85,13 @@ def __init__(self): def generic_visit(self, node): if not isinstance(node, (ast.Span, ast.Annotation)): - super(VisitorCounter, self).generic_visit(node) + super().generic_visit(node) def visit_TextElement(self, node): self.word_count += len(node.value.split()) -class ReplaceText(object): +class ReplaceText: def __init__(self, before, after): self.before = before self.after = after @@ -113,7 +111,7 @@ def __init__(self, before, after): def generic_visit(self, node): if isinstance(node, (ast.Span, ast.Annotation)): return node - return super(ReplaceTransformer, self).generic_visit(node) + return super().generic_visit(node) def visit_TextElement(self, node): """Perform find and replace on text values only""" diff --git a/fluent.syntax/tox.ini b/fluent.syntax/tox.ini index a81134c3..2ed33bdb 100644 --- a/fluent.syntax/tox.ini +++ b/fluent.syntax/tox.ini @@ -1,12 +1,10 @@ # This config is for local testing. # It should be correspond to .github/workflows/fluent.syntax.yml [tox] -envlist = py27, py35, py36, py37, pypy, pypy3 +envlist = py36, py37, py38, py39, pypy3 skipsdist=True [testenv] setenv = PYTHONPATH = {toxinidir} -deps = - six commands = ./runtests.py