├── .flake8
├── .gitattributes
├── .github
└── workflows
│ ├── ci.yml
│ └── documentation.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.rst
├── dev-requirements.txt
├── docs
├── _static
│ ├── project-fluent.css
│ └── versions.js
├── _templates
│ ├── layout.html
│ └── versions.html
├── conf.py
├── index.rst
├── requirements.in
└── requirements.txt
├── fluent.docs
├── README.rst
├── fluent
│ └── docs
│ │ ├── __init__.py
│ │ ├── build.py
│ │ └── tags.py
└── setup.py
├── fluent.pygments
├── CHANGELOG.rst
├── README.rst
├── docs
│ └── index.rst
├── fluent
│ └── pygments
│ │ ├── __init__.py
│ │ ├── cli.py
│ │ └── lexer.py
├── setup.cfg
├── setup.py
└── tests
│ ├── __init__.py
│ └── pygments
│ ├── __init__.py
│ └── test_lexer.py
├── fluent.runtime
├── CHANGELOG.rst
├── README.rst
├── docs
│ ├── history.rst
│ ├── index.rst
│ ├── installation.rst
│ ├── internals.rst
│ ├── make.bat
│ ├── reference.rst
│ ├── requirements.txt
│ └── usage.rst
├── fluent
│ └── runtime
│ │ ├── __init__.py
│ │ ├── builtins.py
│ │ ├── bundle.py
│ │ ├── errors.py
│ │ ├── fallback.py
│ │ ├── prepare.py
│ │ ├── py.typed
│ │ ├── resolver.py
│ │ ├── types.py
│ │ └── utils.py
├── setup.cfg
├── setup.py
├── tests
│ ├── __init__.py
│ ├── format
│ │ ├── __init__.py
│ │ ├── test_arguments.py
│ │ ├── test_attributes.py
│ │ ├── test_builtins.py
│ │ ├── test_functions.py
│ │ ├── test_isolating.py
│ │ ├── test_parameterized_terms.py
│ │ ├── test_placeables.py
│ │ ├── test_primitives.py
│ │ └── test_select_expression.py
│ ├── test_bomb.py
│ ├── test_bundle.py
│ ├── test_fallback.py
│ ├── test_types.py
│ ├── test_utils.py
│ └── utils.py
├── tools
│ └── benchmarks
│ │ ├── README.md
│ │ ├── fluent_benchmark.py
│ │ └── requirements.txt
└── tox.ini
├── fluent.syntax
├── .gitattributes
├── CHANGELOG.md
├── README.rst
├── docs
│ ├── ast.rst
│ ├── index.rst
│ ├── parsing.rst
│ ├── reference.rst
│ ├── serializing.rst
│ ├── usage.rst
│ └── visitor.rst
├── fluent
│ └── syntax
│ │ ├── __init__.py
│ │ ├── ast.py
│ │ ├── errors.py
│ │ ├── parser.py
│ │ ├── py.typed
│ │ ├── serializer.py
│ │ ├── stream.py
│ │ └── visitor.py
├── setup.cfg
├── setup.py
├── tests
│ ├── __init__.py
│ └── syntax
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── fixtures_perf
│ │ └── workload-low.ftl
│ │ ├── fixtures_reference
│ │ ├── any_char.ftl
│ │ ├── any_char.json
│ │ ├── astral.ftl
│ │ ├── astral.json
│ │ ├── call_expressions.ftl
│ │ ├── call_expressions.json
│ │ ├── callee_expressions.ftl
│ │ ├── callee_expressions.json
│ │ ├── comments.ftl
│ │ ├── comments.json
│ │ ├── cr.ftl
│ │ ├── cr.json
│ │ ├── crlf.ftl
│ │ ├── crlf.json
│ │ ├── eof_comment.ftl
│ │ ├── eof_comment.json
│ │ ├── eof_empty.ftl
│ │ ├── eof_empty.json
│ │ ├── eof_id.ftl
│ │ ├── eof_id.json
│ │ ├── eof_id_equals.ftl
│ │ ├── eof_id_equals.json
│ │ ├── eof_junk.ftl
│ │ ├── eof_junk.json
│ │ ├── eof_value.ftl
│ │ ├── eof_value.json
│ │ ├── escaped_characters.ftl
│ │ ├── escaped_characters.json
│ │ ├── junk.ftl
│ │ ├── junk.json
│ │ ├── leading_dots.ftl
│ │ ├── leading_dots.json
│ │ ├── literal_expressions.ftl
│ │ ├── literal_expressions.json
│ │ ├── member_expressions.ftl
│ │ ├── member_expressions.json
│ │ ├── messages.ftl
│ │ ├── messages.json
│ │ ├── mixed_entries.ftl
│ │ ├── mixed_entries.json
│ │ ├── multiline_values.ftl
│ │ ├── multiline_values.json
│ │ ├── numbers.ftl
│ │ ├── numbers.json
│ │ ├── obsolete.ftl
│ │ ├── obsolete.json
│ │ ├── placeables.ftl
│ │ ├── placeables.json
│ │ ├── reference_expressions.ftl
│ │ ├── reference_expressions.json
│ │ ├── select_expressions.ftl
│ │ ├── select_expressions.json
│ │ ├── select_indent.ftl
│ │ ├── select_indent.json
│ │ ├── sparse_entries.ftl
│ │ ├── sparse_entries.json
│ │ ├── special_chars.ftl
│ │ ├── special_chars.json
│ │ ├── tab.ftl
│ │ ├── tab.json
│ │ ├── term_parameters.ftl
│ │ ├── term_parameters.json
│ │ ├── terms.ftl
│ │ ├── terms.json
│ │ ├── variables.ftl
│ │ ├── variables.json
│ │ ├── variant_keys.ftl
│ │ ├── variant_keys.json
│ │ ├── whitespace_in_value.ftl
│ │ ├── whitespace_in_value.json
│ │ ├── zero_length.ftl
│ │ └── zero_length.json
│ │ ├── fixtures_structure
│ │ ├── attribute_expression_with_wrong_attr.ftl
│ │ ├── attribute_expression_with_wrong_attr.json
│ │ ├── attribute_of_private_as_placeable.ftl
│ │ ├── attribute_of_private_as_placeable.json
│ │ ├── attribute_of_public_as_selector.ftl
│ │ ├── attribute_of_public_as_selector.json
│ │ ├── attribute_starts_from_nl.ftl
│ │ ├── attribute_starts_from_nl.json
│ │ ├── attribute_with_empty_pattern.ftl
│ │ ├── attribute_with_empty_pattern.json
│ │ ├── attribute_without_equal_sign.ftl
│ │ ├── attribute_without_equal_sign.json
│ │ ├── blank_lines.ftl
│ │ ├── blank_lines.json
│ │ ├── broken_number.ftl
│ │ ├── broken_number.json
│ │ ├── call_expression_errors.ftl
│ │ ├── call_expression_errors.json
│ │ ├── comment_with_eof.ftl
│ │ ├── comment_with_eof.json
│ │ ├── crlf.ftl
│ │ ├── crlf.json
│ │ ├── dash_at_eof.ftl
│ │ ├── dash_at_eof.json
│ │ ├── elements_indent.ftl
│ │ ├── elements_indent.json
│ │ ├── empty_resource.ftl
│ │ ├── empty_resource.json
│ │ ├── empty_resource_with_ws.ftl
│ │ ├── empty_resource_with_ws.json
│ │ ├── escape_sequences.ftl
│ │ ├── escape_sequences.json
│ │ ├── expressions_call_args.ftl
│ │ ├── expressions_call_args.json
│ │ ├── indent.ftl
│ │ ├── indent.json
│ │ ├── junk.ftl
│ │ ├── junk.json
│ │ ├── leading_dots.ftl
│ │ ├── leading_dots.json
│ │ ├── leading_empty_lines.ftl
│ │ ├── leading_empty_lines.json
│ │ ├── leading_empty_lines_with_ws.ftl
│ │ ├── leading_empty_lines_with_ws.json
│ │ ├── message_reference_as_selector.ftl
│ │ ├── message_reference_as_selector.json
│ │ ├── message_with_empty_multiline_pattern.ftl
│ │ ├── message_with_empty_multiline_pattern.json
│ │ ├── message_with_empty_pattern.ftl
│ │ ├── message_with_empty_pattern.json
│ │ ├── multiline-comment.ftl
│ │ ├── multiline-comment.json
│ │ ├── multiline_pattern.ftl
│ │ ├── multiline_pattern.json
│ │ ├── multiline_string.ftl
│ │ ├── multiline_string.json
│ │ ├── multiline_with_non_empty_first_line.ftl
│ │ ├── multiline_with_non_empty_first_line.json
│ │ ├── multiline_with_placeables.ftl
│ │ ├── multiline_with_placeables.json
│ │ ├── non_id_attribute_name.ftl
│ │ ├── non_id_attribute_name.json
│ │ ├── placeable_at_eol.ftl
│ │ ├── placeable_at_eol.json
│ │ ├── placeable_at_line_extremes.ftl
│ │ ├── placeable_at_line_extremes.json
│ │ ├── placeable_in_placeable.ftl
│ │ ├── placeable_in_placeable.json
│ │ ├── placeable_without_close_bracket.ftl
│ │ ├── placeable_without_close_bracket.json
│ │ ├── resource_comment.ftl
│ │ ├── resource_comment.json
│ │ ├── resource_comment_trailing_line.ftl
│ │ ├── resource_comment_trailing_line.json
│ │ ├── second_attribute_starts_from_nl.ftl
│ │ ├── second_attribute_starts_from_nl.json
│ │ ├── select_expression_with_two_selectors.ftl
│ │ ├── select_expression_with_two_selectors.json
│ │ ├── select_expression_without_arrow.ftl
│ │ ├── select_expression_without_arrow.json
│ │ ├── select_expression_without_variants.ftl
│ │ ├── select_expression_without_variants.json
│ │ ├── select_expressions.ftl
│ │ ├── select_expressions.json
│ │ ├── simple_message.ftl
│ │ ├── simple_message.json
│ │ ├── single_char_id.ftl
│ │ ├── single_char_id.json
│ │ ├── sparse-messages.ftl
│ │ ├── sparse-messages.json
│ │ ├── standalone_comment.ftl
│ │ ├── standalone_comment.json
│ │ ├── standalone_identifier.ftl
│ │ ├── standalone_identifier.json
│ │ ├── term.ftl
│ │ ├── term.json
│ │ ├── term_with_empty_pattern.ftl
│ │ ├── term_with_empty_pattern.json
│ │ ├── unclosed.ftl
│ │ ├── unclosed.json
│ │ ├── unclosed_empty_placeable_error.ftl
│ │ ├── unclosed_empty_placeable_error.json
│ │ ├── unknown_entry_start.ftl
│ │ ├── unknown_entry_start.json
│ │ ├── variant_ends_abruptly.ftl
│ │ ├── variant_ends_abruptly.json
│ │ ├── variant_keys.ftl
│ │ ├── variant_keys.json
│ │ ├── variant_starts_from_nl.ftl
│ │ ├── variant_starts_from_nl.json
│ │ ├── variant_with_digit_key.ftl
│ │ ├── variant_with_digit_key.json
│ │ ├── variant_with_empty_pattern.ftl
│ │ ├── variant_with_empty_pattern.json
│ │ ├── variant_with_leading_space_in_name.ftl
│ │ ├── variant_with_leading_space_in_name.json
│ │ ├── variant_with_symbol_with_space.ftl
│ │ ├── variant_with_symbol_with_space.json
│ │ ├── variants_with_two_defaults.ftl
│ │ ├── variants_with_two_defaults.json
│ │ ├── whitespace_leading.ftl
│ │ ├── whitespace_leading.json
│ │ ├── whitespace_trailing.ftl
│ │ └── whitespace_trailing.json
│ │ ├── test_ast_json.py
│ │ ├── test_entry.py
│ │ ├── test_equals.py
│ │ ├── test_literal.py
│ │ ├── test_reference.py
│ │ ├── test_serializer.py
│ │ ├── test_stream.py
│ │ ├── test_structure.py
│ │ └── test_visitor.py
└── tox.ini
├── pyproject.toml
├── scripts
└── build-docs
└── tools
├── fluentfmt.py
├── parse.py
└── serialize.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | exclude=.tox
3 | extend-ignore = E203
4 | max-line-length=120
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ftl eol=lf
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | lint:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-python@v5
15 | with:
16 | cache: pip
17 | cache-dependency-path: |
18 | dev-requirements.txt
19 | fluent.syntax/setup.py
20 | fluent.runtime/setup.py
21 | - run: python -m pip install -r dev-requirements.txt
22 | - run: python -m pip install ./fluent.syntax ./fluent.runtime
23 | - run: python -m flake8
24 | - run: python -m mypy fluent.syntax/fluent fluent.runtime/fluent
25 | test:
26 | runs-on: ${{ matrix.os }}
27 | strategy:
28 | matrix:
29 | os: [ubuntu-22.04, windows-2022]
30 | python-version: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.9, pypy3.10]
31 | steps:
32 | - uses: actions/checkout@v4
33 | - uses: actions/setup-python@v5
34 | with:
35 | python-version: ${{ matrix.python-version }}
36 | cache: pip
37 | cache-dependency-path: |
38 | fluent.syntax/setup.py
39 | fluent.runtime/setup.py
40 | - run: python -m pip install ./fluent.syntax ./fluent.runtime
41 | - run: python -m unittest discover -s fluent.syntax
42 | - run: python -m unittest discover -s fluent.runtime
43 |
44 | # Test compatibility with the oldest Python version we claim to support,
45 | # and for fluent.runtime's compatibility with a range of fluent.syntax versions.
46 | compatibility:
47 | runs-on: ubuntu-20.04 # https://github.com/actions/setup-python/issues/544
48 | strategy:
49 | matrix:
50 | fluent-syntax:
51 | - ./fluent.syntax
52 | - fluent.syntax==0.19.0
53 | - fluent.syntax==0.18.1 six
54 | - fluent.syntax==0.17.0 six
55 | steps:
56 | - uses: actions/checkout@v3
57 | - uses: actions/setup-python@v4
58 | with:
59 | python-version: 3.6
60 | - run: python -m pip install ${{ matrix.fluent-syntax }}
61 | - run: python -m pip install ./fluent.runtime
62 | - run: python -m unittest discover -s fluent.runtime
63 |
--------------------------------------------------------------------------------
/.github/workflows/documentation.yml:
--------------------------------------------------------------------------------
1 | name: documentation
2 |
3 | on:
4 | workflow_dispatch:
5 | # Trigger the workflow on push or pull request,
6 | # but only for the main branch
7 | push:
8 | branches:
9 | - main
10 | paths:
11 | - docs/**
12 | - fluent.*/docs/**
13 | - fluent.*/fluent/**
14 | - fluent.*/setup.cfg
15 | pull_request:
16 | branches:
17 | - main
18 | paths:
19 | - docs/**
20 | - fluent.*/docs/**
21 | - fluent.*/fluent/**
22 | - fluent.*/setup.cfg
23 |
24 | jobs:
25 | build:
26 | name: build
27 | runs-on: ubuntu-latest
28 | steps:
29 | - uses: actions/checkout@v4
30 | with:
31 | fetch-depth: 0
32 | - uses: actions/setup-python@v5
33 | with:
34 | python-version: 3.9
35 | - name: Install dependencies
36 | run: |
37 | pip install -r docs/requirements.txt
38 | pip install ./fluent.docs
39 | - run: ./scripts/build-docs python-fluent
40 | - uses: actions/upload-artifact@v4
41 | with:
42 | name: html
43 | path: |
44 | _build/python-fluent
45 | !_build/**/.buildinfo
46 |
47 | publish:
48 | name: publish
49 | needs: [build]
50 | runs-on: ubuntu-latest
51 | steps:
52 | - uses: actions/checkout@v4
53 | - uses: actions/download-artifact@v4
54 | with:
55 | name: html
56 | path: _build
57 | - name: Deploy 🚀
58 | uses: JamesIves/github-pages-deploy-action@v4.4.1
59 | with:
60 | branch: gh-pages # The branch the action should deploy to.
61 | folder: _build # The folder the action should deploy.
62 | clean: true # Automatically remove deleted files from the deploy branch
63 | dry-run: ${{ github.event_name == 'pull_request' }}
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .*
2 | *.pyc
3 | *.egg-info/
4 | _build
5 | build
6 | dist
7 | /fluent.*/docs/_templates/versions.html
8 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Community Participation Guidelines
2 |
3 | This repository is governed by Mozilla's code of conduct and etiquette
4 | guidelines. For more details, please read the [Mozilla Community Participation
5 | Guidelines][].
6 |
7 |
8 | ## How to Report
9 |
10 | For more information on how to report violations of the Community Participation
11 | Guidelines, please read our [How to Report][] page.
12 |
13 |
14 | [Mozilla Community Participation Guidelines]: https://www.mozilla.org/about/governance/policies/participation/
15 | [How to Report]: https://www.mozilla.org/about/governance/policies/participation/reporting/
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 Mozilla Foundation
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Project Fluent
2 | ==============
3 |
4 | This is a collection of Python packages to use the `Fluent localization
5 | system `__.
6 |
7 | python-fluent consists of these packages:
8 |
9 | ``fluent.syntax``
10 | -----------------
11 |
12 | The `syntax package `_ includes the parser, serializer, and traversal
13 | utilities like Visitor and Transformer. You’re looking for this package
14 | if you work on tooling for Fluent in Python.
15 |
16 | ``fluent.runtime``
17 | ------------------
18 |
19 | The `runtime package `__ includes the library required to use Fluent to localize
20 | your Python application. It comes with a ``Localization`` class to use,
21 | based on an implementation of ``FluentBundle``. It uses the tooling parser above
22 | to read Fluent files.
23 |
24 | ``fluent.pygments``
25 | -------------------
26 |
27 | A `plugin for pygments `_ to add syntax highlighting to Sphinx.
28 |
29 | Discuss
30 | -------
31 |
32 | We’d love to hear your thoughts on Project Fluent! Whether you’re a
33 | localizer looking for a better way to express yourself in your language,
34 | or a developer trying to make your app localizable and multilingual, or
35 | a hacker looking for a project to contribute to, please do get in touch
36 | on the mailing list and the IRC channel.
37 |
38 | - Mozilla Discourse: https://discourse.mozilla.org/c/fluent
39 | - Matrix channel:
40 | `#fluent:mozilla.org `__
41 |
42 | Get Involved
43 | ------------
44 |
45 | python-fluent is open-source, licensed under the Apache License, Version
46 | 2.0. We encourage everyone to take a look at our code and we’ll listen
47 | to your feedback.
48 |
--------------------------------------------------------------------------------
/dev-requirements.txt:
--------------------------------------------------------------------------------
1 | black ~= 24.0
2 | flake8 ~= 7.0
3 | isort ~= 5.0
4 | mypy ~= 1.0
5 | tox ~= 4.0
6 | types-babel
7 | types-pytz
8 |
--------------------------------------------------------------------------------
/docs/_static/project-fluent.css:
--------------------------------------------------------------------------------
1 | div.related {
2 | background-color: #356eb7;
3 | }
4 |
5 | #versions {
6 | position: relative;
7 | color: #444;
8 | }
9 |
10 | #versions > span.version {
11 | cursor: pointer;
12 | }
13 |
14 | #versions > span.version::after {
15 | content: " ⮞";
16 | }
17 |
18 | #versions.opened > span.version {
19 | display: none;
20 | }
21 |
22 | #versions:not(.opened) > span.links {
23 | display: none;
24 | }
25 |
--------------------------------------------------------------------------------
/docs/_static/versions.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Show/hide versions when clicking on the versions paragraph
3 | */
4 |
5 | $(function() {
6 | let versions = document.getElementById('versions');
7 | if (versions) {
8 | versions.onclick = function(ev) {
9 | this.classList.toggle('opened');
10 | }
11 | }
12 | })
13 |
--------------------------------------------------------------------------------
/docs/_templates/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "!layout.html" %}
2 |
3 | {%- block css %}
4 |
5 |
6 | {%- for css in css_files %}
7 |
8 | {%- endfor %}
9 | {%- endblock %}
10 |
11 | {%- block scripts %}
12 |
13 | {%- for js in script_files %}
14 |
15 | {%- endfor %}
16 | {%- endblock %}
17 |
18 | {%- block footer %}
19 |
22 | {%- endblock %}
23 |
--------------------------------------------------------------------------------
/docs/_templates/versions.html:
--------------------------------------------------------------------------------
1 | {#
2 | Intentionally left blank.
3 | Project documtations fill in a sidebar control as part of the build process.
4 | #}
5 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | # import os
14 | # import sys
15 | # sys.path.insert(0, os.path.abspath('.'))
16 |
17 |
18 | # -- Project information -----------------------------------------------------
19 |
20 | project = "python-fluent"
21 |
22 |
23 | # -- General configuration ---------------------------------------------------
24 |
25 | # Add any Sphinx extension module names here, as strings. They can be
26 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
27 | # ones.
28 | extensions = [
29 | "sphinx.ext.intersphinx",
30 | "sphinx.ext.viewcode",
31 | "sphinx.ext.autodoc",
32 | ]
33 |
34 | # Add any paths that contain templates here, relative to this directory.
35 | templates_path = ["_templates"]
36 |
37 |
38 | # Add src_dir/docs/_templates in a hook as we only have the src_dir then.
39 | def setup(app):
40 | app.connect("config-inited", add_templates)
41 |
42 |
43 | def add_templates(app, config):
44 | config.templates_path.insert(0, f"{app.srcdir}/_templates")
45 |
46 |
47 | # List of patterns, relative to source directory, that match files and
48 | # directories to ignore when looking for source files.
49 | # This pattern also affects html_static_path and html_extra_path.
50 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
51 |
52 |
53 | # -- Options for HTML output -------------------------------------------------
54 |
55 | # The theme to use for HTML and HTML Help pages. See the documentation for
56 | # a list of builtin themes.
57 | #
58 | html_theme = "nature"
59 |
60 | # Add any paths that contain custom static files (such as style sheets) here,
61 | # relative to this directory. They are copied after the builtin static files,
62 | # so a file named "default.css" will overwrite the builtin "default.css".
63 | html_static_path = ["_static"]
64 | html_css_files = ["project-fluent.css"]
65 | html_js_files = ["versions.js"]
66 |
67 | html_sidebars = {
68 | "**": ["globaltoc.html", "versions.html", "searchbox.html"],
69 | }
70 | html_theme_options = {}
71 |
72 | # -- Extension configuration -------------------------------------------------
73 |
74 | # -- Options for intersphinx extension ---------------------------------------
75 |
76 | # Example configuration for intersphinx: refer to the Python standard library.
77 | intersphinx_mapping = {}
78 |
79 | # -- Options for autodoc extension --------------------------------------------
80 | autodoc_mock_imports = [
81 | "attr",
82 | ]
83 | autodoc_member_order = "bysource"
84 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 |
2 | python-fluent
3 | =============
4 |
5 | The :py:mod:`python-fluent` project contains several packages to
6 | bring `Project Fluent `__ to Python.
7 | Visit the `Github project `__
8 | for the full list. Three packages in particular are of general interest
9 | and documented here.
10 |
11 | fluent.syntax
12 | -------------
13 |
14 | ``fluent.syntax`` is the package to use for tooling, analysis, and
15 | processing of Fluent files.
16 |
17 | fluent.runtime
18 | --------------
19 |
20 | ``fluent.runtime`` is the reference runtime implementation for
21 | Fluent in Python.
22 |
23 | fluent.pygments
24 | ---------------
25 |
26 | ``fluent.pygments`` can be used to add syntax highlighting for
27 | Fluent files to Sphinx documentation.
28 |
29 | .. toctree::
30 | :caption: Packages
31 | :maxdepth: 1
32 |
33 | fluent.syntax
34 | fluent.runtime
35 | fluent.pygments
36 |
--------------------------------------------------------------------------------
/docs/requirements.in:
--------------------------------------------------------------------------------
1 | Sphinx==3.5.1
2 | fluent.pygments==1.0
3 | wheel
4 |
--------------------------------------------------------------------------------
/fluent.docs/README.rst:
--------------------------------------------------------------------------------
1 | ``fluent.docs``
2 | ===============
3 |
4 | Python utilities used by the ``python-fluent`` documentation build
5 | process. The entry point is ``scripts/build-docs``.
6 |
7 | The generated documentation is in ``_build/python-fluent``, and you
8 | can surf it locally via ``python3 -m http.server `` in ``_build``.
9 |
10 | The documentation is created for each tagged version after May 2020,
11 | at which point we had good docs. The current branch (PR tips or
12 | main) is versioned as *dev*, and *stable* is a symlink to the latest
13 | release. The releases are in a dir with their corresponding version number.
14 |
15 | When cutting a new release, manually run the documentation workflow
16 | to pick that up.
17 |
18 | The rationale for regenerating all historic documentation releases is
19 | to provide an easy process to change style and renderering across
20 | the full documentation. For that, the build setup is taken from
21 | the current checkout, and release docs are generated from sources in
22 | a git worktree.
23 |
--------------------------------------------------------------------------------
/fluent.docs/fluent/docs/__init__.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from .build import DocBuilder
4 |
5 |
6 | def finalize_builddir(repo_name):
7 | "Bookkeeping on the docs build directory"
8 | root = Path("_build") / repo_name
9 | with open(root / ".nojekyll", "w") as fh:
10 | fh.write("")
11 |
12 |
13 | def build_root(repo_name):
14 | """Build the top-level documentation.
15 |
16 | See :py:mod:`.build` on building sub-projects.
17 | """
18 | with DocBuilder(repo_name, ".") as builder:
19 | builder.build()
20 |
--------------------------------------------------------------------------------
/fluent.docs/fluent/docs/tags.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | from datetime import date
3 |
4 |
5 | def get_tag_infos(cut_off_date):
6 | """Get fluent.* tags newer than cut_off_date.
7 | TagInfo objects are ordered by committer date, newest first.
8 | """
9 | taglines = subprocess.run(
10 | [
11 | "git",
12 | "tag",
13 | "--list",
14 | "fluent.*",
15 | "--sort=-committerdate",
16 | "--format=%(refname:lstrip=2) %(committerdate:short)",
17 | ],
18 | encoding="utf-8",
19 | stdout=subprocess.PIPE,
20 | check=True,
21 | ).stdout.splitlines()
22 | return [ti for ti in (TagInfo(line) for line in taglines) if ti.date > cut_off_date]
23 |
24 |
25 | class TagInfo:
26 | def __init__(self, tagline):
27 | tag, date_string = tagline.split(" ")
28 | self.project, self.version = tag.split("@")
29 | self.date = date.fromisoformat(date_string)
30 |
31 | @property
32 | def tag(self):
33 | return f"{self.project}@{self.version}"
34 |
35 | def __repr__(self):
36 | return f"{self.tag} ({self.date})"
37 |
--------------------------------------------------------------------------------
/fluent.docs/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name="fluent.docs",
5 | packages=["fluent.docs"],
6 | install_requires=["typing-extensions>=3.7,<5"],
7 | )
8 |
--------------------------------------------------------------------------------
/fluent.pygments/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | fluent.runtime 2.0 (TBD)
5 | -----------------------------------
6 |
7 | * Drop support for Python 2.7 and 3.5
8 | * Add support for Python 3.6 through 3.9
9 |
10 | fluent.pygments 1.0 (May 20, 2020)
11 | ----------------------------------
12 |
13 | * Updated documentation to be hosted on https://projectfluent.org/python-fluent/fluent.runtime/.
14 | * Updated package metadata.
15 |
--------------------------------------------------------------------------------
/fluent.pygments/README.rst:
--------------------------------------------------------------------------------
1 | fluent.pygments
2 | ===============
3 |
4 | A plugin for pygments to add `Fluent`_ syntax highlighting to Sphinx.
5 |
6 | Installation
7 | ------------
8 |
9 | .. code-block:: bash
10 |
11 | pip install fluent.pygments
12 |
13 |
14 | Usage
15 | -----
16 |
17 | .. code-block:: rst
18 |
19 |
20 | .. code-block:: fluent
21 |
22 | my-message = a localized string
23 |
24 |
25 | The `documentation`_ has an example of Fluent content
26 | highlighted with ``fluent.pygments``.
27 |
28 | .. _fluent: https://projectfluent.org/
29 | .. _documentation: https://projectfluent.org/python-fluent/fluent.pygments
30 |
--------------------------------------------------------------------------------
/fluent.pygments/docs/index.rst:
--------------------------------------------------------------------------------
1 | Fluent Syntax Highlighting
2 | ==========================
3 |
4 | The :py:mod:`fluent.pygments` library is built to do syntax highlighting
5 | for `Fluent`_ files in Sphinx.
6 |
7 |
8 | Installation
9 | ------------
10 |
11 | .. code-block:: bash
12 |
13 | pip install fluent.pygments
14 |
15 |
16 | Usage
17 | -----
18 |
19 | .. code-block:: rst
20 |
21 |
22 | .. code-block:: fluent
23 |
24 | my-key = Localize { -brand-name }
25 |
26 | Example
27 | -------
28 |
29 | .. code-block:: fluent
30 |
31 | ### A resource comment for the whole file
32 |
33 | my-key = Localize { -brand-name }
34 | -brand-name = Fluent
35 |
36 | # $num is the number of strings to localize
37 | plurals = { $num ->
38 | [one] One string
39 | *[other] {$num} strings
40 | }
41 | an error ;-)
42 | Most-strings = are just simple strings.
43 |
44 | .. _fluent: https://projectfluent.org/
45 |
--------------------------------------------------------------------------------
/fluent.pygments/fluent/pygments/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.pygments/fluent/pygments/__init__.py
--------------------------------------------------------------------------------
/fluent.pygments/fluent/pygments/cli.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import sys
3 |
4 | from fluent.pygments.lexer import FluentLexer
5 | from pygments import highlight
6 | from pygments.formatters import Terminal256Formatter
7 |
8 |
9 | def main():
10 | parser = argparse.ArgumentParser()
11 | parser.add_argument("path")
12 | args = parser.parse_args()
13 | with open(args.path) as fh:
14 | code = fh.read()
15 | highlight(code, FluentLexer(), Terminal256Formatter(), sys.stdout)
16 |
17 |
18 | if __name__ == "__main__":
19 | main()
20 |
--------------------------------------------------------------------------------
/fluent.pygments/fluent/pygments/lexer.py:
--------------------------------------------------------------------------------
1 | from fluent.syntax import ast as FTL
2 | from fluent.syntax import parse
3 | from pygments.lexer import Lexer
4 | from pygments.token import Token
5 |
6 |
7 | class FluentLexer(Lexer):
8 | name = "Fluent Lexer"
9 | aliases = ["fluent", "ftl"]
10 | filenames = ["*.ftl"]
11 |
12 | def get_tokens_unprocessed(self, text):
13 | last_end = 0
14 | tokenizer = Tokenizer(text)
15 | for token in tokenizer.tokenize():
16 | node, start, token, span = token
17 | if start > last_end:
18 | yield last_end, Token.Punctuation, text[last_end:start]
19 | last_end = node.span.end
20 | yield start, token, span
21 | if last_end < len(text):
22 | yield last_end, Token.Punctuation, text[last_end:]
23 |
24 |
25 | ATOMIC = {
26 | "Comment": Token.Comment.Multiline,
27 | "GroupComment": Token.Comment.Multiline,
28 | "ResourceComment": Token.Comment.Multiline,
29 | "Identifier": Token.Name.Constant,
30 | "TextElement": Token.Literal,
31 | "NumberLiteral": Token.Literal.Number,
32 | "StringLiteral": Token.Literal.String,
33 | "VariableReference": Token.Name.Variable,
34 | "Junk": Token.Generic.Error,
35 | }
36 |
37 |
38 | class Tokenizer:
39 | def __init__(self, text):
40 | self.text = text
41 | self.ast = parse(text)
42 |
43 | def tokenize(self, node=None):
44 | if node is None:
45 | node = self.ast
46 | if isinstance(node, (FTL.Annotation, FTL.Span)):
47 | return
48 | if isinstance(node, FTL.SyntaxNode):
49 | yield from self.tokenize_node(node)
50 | elif isinstance(node, list):
51 | for child in node:
52 | yield from self.tokenize(child)
53 |
54 | def tokenize_node(self, node):
55 | nodename = type(node).__name__
56 | if nodename in ATOMIC:
57 | yield self._token(node, ATOMIC[nodename])
58 | else:
59 | tokenize = getattr(self, f"tokenize_{nodename}", self.generic_tokenize)
60 | yield from tokenize(node)
61 |
62 | def generic_tokenize(self, node):
63 | children = [
64 | child
65 | for child in vars(node).values()
66 | if isinstance(child, (FTL.SyntaxNode, list)) and child != []
67 | ]
68 | children.sort(
69 | key=lambda child: (
70 | child.span.start
71 | if isinstance(child, FTL.SyntaxNode)
72 | else child[0].span.start
73 | )
74 | )
75 | for child in children:
76 | yield from self.tokenize(child)
77 |
78 | def tokenize_Variant(self, node):
79 | yield self._token(node.key, Token.Name.Attribute)
80 | yield from self.tokenize(node.value)
81 |
82 | def _token(self, node, token):
83 | return (
84 | node,
85 | node.span.start,
86 | token,
87 | self.text[node.span.start : node.span.end],
88 | )
89 |
--------------------------------------------------------------------------------
/fluent.pygments/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | version=2.0
3 |
4 | [bdist_wheel]
5 | universal=1
6 |
7 | [options]
8 | install_requires =
9 | pygments
10 | fluent.syntax
11 |
12 | [options.entry_points]
13 | pygments.lexers =
14 | fluent=fluent.pygments.lexer:FluentLexer
15 |
--------------------------------------------------------------------------------
/fluent.pygments/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from setuptools import find_namespace_packages, setup
4 |
5 | this_directory = os.path.abspath(os.path.dirname(__file__))
6 | with open(os.path.join(this_directory, "README.rst"), "rb") as f:
7 | long_description = f.read().decode("utf-8")
8 |
9 | setup(
10 | name="fluent.pygments",
11 | description="Pygments lexer for Fluent.",
12 | long_description=long_description,
13 | long_description_content_type="text/x-rst",
14 | author="Mozilla",
15 | author_email="l10n-drivers@mozilla.org",
16 | license="APL 2",
17 | url="https://github.com/projectfluent/python-fluent",
18 | keywords=["fluent", "pygments"],
19 | classifiers=[
20 | "Development Status :: 3 - Alpha",
21 | "Intended Audience :: Developers",
22 | "License :: OSI Approved :: Apache Software License",
23 | "Programming Language :: Python :: 3.6",
24 | "Programming Language :: Python :: 3.7",
25 | "Programming Language :: Python :: 3.8",
26 | "Programming Language :: Python :: 3.9",
27 | "Programming Language :: Python :: 3 :: Only",
28 | ],
29 | packages=find_namespace_packages(include=["fluent.*"]),
30 | test_suite="tests.pygments",
31 | )
32 |
--------------------------------------------------------------------------------
/fluent.pygments/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.pygments/tests/__init__.py
--------------------------------------------------------------------------------
/fluent.pygments/tests/pygments/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.pygments/tests/pygments/__init__.py
--------------------------------------------------------------------------------
/fluent.pygments/tests/pygments/test_lexer.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from fluent.pygments.lexer import FluentLexer
4 | from pygments.token import Token
5 |
6 |
7 | class LexerTest(unittest.TestCase):
8 | def setUp(self):
9 | self.lexer = FluentLexer()
10 |
11 | def test_comment(self):
12 | fragment = "# comment\n"
13 | tokens = [
14 | (Token.Comment.Multiline, "# comment"),
15 | (Token.Punctuation, "\n"),
16 | ]
17 | self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
18 |
19 | def test_message(self):
20 | fragment = "msg = some value\n"
21 | tokens = [
22 | (Token.Name.Constant, "msg"),
23 | (Token.Punctuation, " = "),
24 | (Token.Literal, "some value"),
25 | (Token.Punctuation, "\n"),
26 | ]
27 | self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
28 |
29 | def test_message_with_comment(self):
30 | fragment = "# good comment\nmsg = some value\n"
31 | tokens = [
32 | (Token.Comment.Multiline, "# good comment"),
33 | (Token.Punctuation, "\n"),
34 | (Token.Name.Constant, "msg"),
35 | (Token.Punctuation, " = "),
36 | (Token.Literal, "some value"),
37 | (Token.Punctuation, "\n"),
38 | ]
39 | self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
40 |
--------------------------------------------------------------------------------
/fluent.runtime/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | fluent.runtime 0.4.0 (March 13, 2023)
5 | -------------------------------------
6 |
7 | * Drop support for Python 2.7 and 3.5 & support for Python 3.6 through 3.9 ([#163](https://github.com/projectfluent/python-fluent/pull/163))
8 | * Add type hints ([#180](https://github.com/projectfluent/python-fluent/pull/180))
9 |
10 | fluent.runtime 0.3.1 (May 20, 2020)
11 | -----------------------------------
12 |
13 | * Updated documentation to be hosted on https://projectfluent.org/python-fluent/fluent.runtime/.
14 | * Updated package metadata.
15 |
16 | fluent.runtime 0.3 (October 23, 2019)
17 | -------------------------------------
18 |
19 | * Added ``fluent.runtime.FluentResource`` and
20 | ``fluent.runtime.FluentBundle.add_resource``.
21 | * Removed ``fluent.runtime.FluentBundle.add_messages``.
22 | * Replaced ``bundle.format()`` with ``bundle.format_pattern(bundle.get_message().value)``.
23 | * Added ``fluent.runtime.FluentLocalization`` as main entrypoint for applications.
24 |
25 | fluent.runtime 0.2 (September 10, 2019)
26 | ---------------------------------------
27 |
28 | * Support for Fluent spec 1.0 (``fluent.syntax`` 0.17), including parameterized
29 | terms.
30 |
31 | fluent.runtime 0.1 (January 21, 2019)
32 | -------------------------------------
33 |
34 | First release to PyPI of ``fluent.runtime``. This release contains a
35 | ``FluentBundle`` implementation that can generate translations from FTL
36 | messages. It targets the `Fluent 0.7 spec
37 | `_.
38 |
--------------------------------------------------------------------------------
/fluent.runtime/README.rst:
--------------------------------------------------------------------------------
1 | fluent.runtime |fluent.runtime|
2 | ===============================
3 |
4 | Use `Fluent`_ to localize your Python application. It comes with a ``Localization``
5 | class to use, based on an implementation of ``FluentBundle``. It uses the parser from
6 | ``fluent.syntax`` to read Fluent files.
7 |
8 | .. code-block:: python
9 |
10 | >>> from datetime import date
11 | >>> l10n = DemoLocalization("today-is = Today is { $today }")
12 | >>> val = l10n.format_value("today-is", {"today": date.today() })
13 | >>> val
14 | 'Today is Jun 16, 2018'
15 |
16 | Find the full documentation on https://projectfluent.org/python-fluent/fluent.runtime/.
17 |
18 | .. _fluent: https://projectfluent.org/
19 | .. |fluent.runtime| image:: https://github.com/projectfluent/python-fluent/workflows/fluent.runtime/badge.svg
20 |
--------------------------------------------------------------------------------
/fluent.runtime/docs/history.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../CHANGELOG.rst
2 |
--------------------------------------------------------------------------------
/fluent.runtime/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. fluent.runtime documentation master file, created by
2 | sphinx-quickstart on Thu Jan 24 15:45:23 2019.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to fluent.runtime's documentation!
7 | ==========================================
8 |
9 | These are the docs for ``fluent.runtime`` |release|. Check the :doc:`/history` for
10 | significant changes.
11 |
12 | .. toctree::
13 | :maxdepth: 1
14 | :caption: Contents:
15 |
16 | installation
17 | usage
18 | internals
19 | reference
20 | history
21 |
--------------------------------------------------------------------------------
/fluent.runtime/docs/installation.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Installation
3 | ============
4 |
5 | ``fluent.runtime`` can be installed with pip::
6 |
7 | $ pip install fluent.runtime
8 |
9 | Python 2.7 or 3.5+ required. Earlier versions of Python 3 may work but they are
10 | not officially supported.
11 |
--------------------------------------------------------------------------------
/fluent.runtime/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/fluent.runtime/docs/reference.rst:
--------------------------------------------------------------------------------
1 | API Reference
2 | =============
3 |
4 | .. automodule:: fluent.runtime
5 | :members:
6 |
--------------------------------------------------------------------------------
/fluent.runtime/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | fluent.pygments
2 |
--------------------------------------------------------------------------------
/fluent.runtime/fluent/runtime/__init__.py:
--------------------------------------------------------------------------------
1 | from fluent.syntax import FluentParser
2 | from fluent.syntax.ast import Resource
3 |
4 | from .bundle import FluentBundle
5 | from .fallback import AbstractResourceLoader, FluentLocalization, FluentResourceLoader
6 |
7 | __all__ = [
8 | "FluentLocalization",
9 | "AbstractResourceLoader",
10 | "FluentResourceLoader",
11 | "FluentResource",
12 | "FluentBundle",
13 | ]
14 |
15 |
16 | def FluentResource(source: str) -> Resource:
17 | parser = FluentParser()
18 | return parser.parse(source)
19 |
--------------------------------------------------------------------------------
/fluent.runtime/fluent/runtime/builtins.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Callable, Dict
2 |
3 | from .types import FluentType, fluent_date, fluent_number
4 |
5 | NUMBER = fluent_number
6 | DATETIME = fluent_date
7 |
8 |
9 | BUILTINS: Dict[str, Callable[[Any], FluentType]] = {
10 | "NUMBER": NUMBER,
11 | "DATETIME": DATETIME,
12 | }
13 |
--------------------------------------------------------------------------------
/fluent.runtime/fluent/runtime/errors.py:
--------------------------------------------------------------------------------
1 | from typing import cast
2 |
3 |
4 | class FluentFormatError(ValueError):
5 | def __eq__(self, other: object) -> bool:
6 | return (other.__class__ == self.__class__) and cast(
7 | ValueError, other
8 | ).args == self.args
9 |
10 |
11 | class FluentReferenceError(FluentFormatError):
12 | pass
13 |
14 |
15 | class FluentCyclicReferenceError(FluentFormatError):
16 | pass
17 |
--------------------------------------------------------------------------------
/fluent.runtime/fluent/runtime/prepare.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict, List
2 |
3 | from fluent.syntax import ast as FTL
4 |
5 | from . import resolver
6 |
7 |
8 | class Compiler:
9 | def __call__(self, item: Any) -> Any:
10 | if isinstance(item, FTL.BaseNode):
11 | return self.compile(item)
12 | if isinstance(item, (tuple, list)):
13 | return [self(elem) for elem in item]
14 | return item
15 |
16 | def compile(self, node: Any) -> Any:
17 | nodename: str = type(node).__name__
18 | if not hasattr(resolver, nodename):
19 | return node
20 | kwargs: Dict[str, Any] = vars(node).copy()
21 | for propname, propvalue in kwargs.items():
22 | kwargs[propname] = self(propvalue)
23 | handler = getattr(self, "compile_" + nodename, self.compile_generic)
24 | return handler(nodename, **kwargs)
25 |
26 | def compile_generic(self, nodename: str, **kwargs: Any) -> Any:
27 | return getattr(resolver, nodename)(**kwargs)
28 |
29 | def compile_Placeable(self, _: Any, expression: Any, **kwargs: Any) -> Any:
30 | if isinstance(expression, resolver.Literal):
31 | return expression
32 | return resolver.Placeable(expression=expression, **kwargs)
33 |
34 | def compile_Pattern(self, _: Any, elements: List[Any], **kwargs: Any) -> Any:
35 | if len(elements) == 1 and isinstance(elements[0], resolver.Placeable):
36 | # Don't isolate isolated placeables
37 | return resolver.NeverIsolatingPlaceable(elements[0].expression)
38 | if any(not isinstance(child, resolver.Literal) for child in elements):
39 | return resolver.Pattern(elements=elements, **kwargs)
40 | if len(elements) == 1:
41 | return elements[0]
42 | return resolver.TextElement("".join(child(None) for child in elements))
43 |
--------------------------------------------------------------------------------
/fluent.runtime/fluent/runtime/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.runtime/fluent/runtime/py.typed
--------------------------------------------------------------------------------
/fluent.runtime/fluent/runtime/utils.py:
--------------------------------------------------------------------------------
1 | from datetime import date, datetime
2 | from decimal import Decimal
3 | from typing import Any, Union
4 |
5 | from fluent.syntax.ast import MessageReference, TermReference
6 |
7 | from .errors import FluentReferenceError
8 | from .types import FluentDate, FluentDateTime, FluentDecimal, FluentFloat, FluentInt
9 |
10 | TERM_SIGIL = "-"
11 | ATTRIBUTE_SEPARATOR = "."
12 |
13 |
14 | def native_to_fluent(val: Any) -> Any:
15 | """
16 | Convert a python type to a Fluent Type.
17 | """
18 | if isinstance(val, int):
19 | return FluentInt(val)
20 | if isinstance(val, float):
21 | return FluentFloat(val)
22 | if isinstance(val, Decimal):
23 | return FluentDecimal(val)
24 |
25 | if isinstance(val, datetime):
26 | return FluentDateTime.from_date_time(val)
27 | if isinstance(val, date):
28 | return FluentDate.from_date(val)
29 | return val
30 |
31 |
32 | def reference_to_id(ref: Union[MessageReference, TermReference]) -> str:
33 | """
34 | Returns a string reference for a MessageReference or TermReference
35 | AST node.
36 |
37 | e.g.
38 | message
39 | message.attr
40 | -term
41 | -term.attr
42 | """
43 | start: str
44 | if isinstance(ref, TermReference):
45 | start = TERM_SIGIL + ref.id.name
46 | else:
47 | start = ref.id.name
48 |
49 | if ref.attribute:
50 | return "".join([start, ATTRIBUTE_SEPARATOR, ref.attribute.name])
51 | return start
52 |
53 |
54 | def unknown_reference_error_obj(ref_id: str) -> FluentReferenceError:
55 | if ATTRIBUTE_SEPARATOR in ref_id:
56 | return FluentReferenceError(f"Unknown attribute: {ref_id}")
57 | if ref_id.startswith(TERM_SIGIL):
58 | return FluentReferenceError(f"Unknown term: {ref_id}")
59 | return FluentReferenceError(f"Unknown message: {ref_id}")
60 |
--------------------------------------------------------------------------------
/fluent.runtime/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | version=0.4.0
3 |
4 | [bdist_wheel]
5 | universal=1
6 |
--------------------------------------------------------------------------------
/fluent.runtime/setup.py:
--------------------------------------------------------------------------------
1 | from os import path
2 |
3 | from setuptools import setup
4 |
5 | this_directory = path.abspath(path.dirname(__file__))
6 | with open(path.join(this_directory, "README.rst"), "rb") as f:
7 | long_description = f.read().decode("utf-8")
8 |
9 |
10 | setup(
11 | name="fluent.runtime",
12 | description="Localization library for expressive translations.",
13 | long_description=long_description,
14 | long_description_content_type="text/x-rst",
15 | author="Luke Plant",
16 | author_email="L.Plant.98@cantab.net",
17 | license="APL 2",
18 | url="https://github.com/projectfluent/python-fluent",
19 | keywords=["fluent", "localization", "l10n"],
20 | classifiers=[
21 | "Development Status :: 3 - Alpha",
22 | "Intended Audience :: Developers",
23 | "License :: OSI Approved :: Apache Software License",
24 | "Programming Language :: Python :: 3.6",
25 | "Programming Language :: Python :: 3.7",
26 | "Programming Language :: Python :: 3.8",
27 | "Programming Language :: Python :: 3.9",
28 | "Programming Language :: Python :: 3 :: Only",
29 | ],
30 | packages=["fluent.runtime"],
31 | package_data={"fluent.runtime": ["py.typed"]},
32 | # These should also be duplicated in tox.ini and /.github/workflows/fluent.runtime.yml
33 | python_requires=">=3.6",
34 | install_requires=[
35 | "fluent.syntax>=0.17,<0.20",
36 | "attrs",
37 | "babel",
38 | "pytz",
39 | "typing-extensions>=3.7,<5",
40 | ],
41 | test_suite="tests",
42 | )
43 |
--------------------------------------------------------------------------------
/fluent.runtime/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.runtime/tests/__init__.py
--------------------------------------------------------------------------------
/fluent.runtime/tests/format/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.runtime/tests/format/__init__.py
--------------------------------------------------------------------------------
/fluent.runtime/tests/format/test_arguments.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from fluent.runtime import FluentBundle, FluentResource
4 |
5 | from ..utils import dedent_ftl
6 |
7 |
8 | class TestNumbersInValues(unittest.TestCase):
9 | def setUp(self):
10 | self.bundle = FluentBundle(["en-US"], use_isolating=False)
11 | self.bundle.add_resource(
12 | FluentResource(
13 | dedent_ftl(
14 | """
15 | foo = Foo { $num }
16 | bar = { foo }
17 | baz =
18 | .attr = Baz Attribute { $num }
19 | qux = { "a" ->
20 | *[a] Baz Variant A { $num }
21 | }
22 | """
23 | )
24 | )
25 | )
26 |
27 | def test_can_be_used_in_the_message_value(self):
28 | val, errs = self.bundle.format_pattern(
29 | self.bundle.get_message("foo").value, {"num": 3}
30 | )
31 | self.assertEqual(val, "Foo 3")
32 | self.assertEqual(len(errs), 0)
33 |
34 | def test_can_be_used_in_the_message_value_which_is_referenced(self):
35 | val, errs = self.bundle.format_pattern(
36 | self.bundle.get_message("bar").value, {"num": 3}
37 | )
38 | self.assertEqual(val, "Foo 3")
39 | self.assertEqual(len(errs), 0)
40 |
41 | def test_can_be_used_in_an_attribute(self):
42 | val, errs = self.bundle.format_pattern(
43 | self.bundle.get_message("baz").attributes["attr"], {"num": 3}
44 | )
45 | self.assertEqual(val, "Baz Attribute 3")
46 | self.assertEqual(len(errs), 0)
47 |
48 | def test_can_be_used_in_a_variant(self):
49 | val, errs = self.bundle.format_pattern(
50 | self.bundle.get_message("qux").value, {"num": 3}
51 | )
52 | self.assertEqual(val, "Baz Variant A 3")
53 | self.assertEqual(len(errs), 0)
54 |
55 |
56 | class TestStrings(unittest.TestCase):
57 | def setUp(self):
58 | self.bundle = FluentBundle(["en-US"], use_isolating=False)
59 | self.bundle.add_resource(
60 | FluentResource(
61 | dedent_ftl(
62 | """
63 | foo = { $arg }
64 | """
65 | )
66 | )
67 | )
68 |
69 | def test_can_be_a_string(self):
70 | val, errs = self.bundle.format_pattern(
71 | self.bundle.get_message("foo").value, {"arg": "Argument"}
72 | )
73 | self.assertEqual(val, "Argument")
74 | self.assertEqual(len(errs), 0)
75 |
--------------------------------------------------------------------------------
/fluent.runtime/tests/format/test_isolating.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from fluent.runtime import FluentBundle, FluentResource
4 |
5 | from ..utils import dedent_ftl
6 |
7 | # Unicode bidi isolation characters.
8 | FSI = "\u2068"
9 | PDI = "\u2069"
10 |
11 |
12 | class TestUseIsolating(unittest.TestCase):
13 |
14 | def setUp(self):
15 | self.bundle = FluentBundle(["en-US"])
16 | self.bundle.add_resource(
17 | FluentResource(
18 | dedent_ftl(
19 | """
20 | foo = Foo
21 | bar = { foo } Bar
22 | baz = { $arg } Baz
23 | qux = { bar } { baz }
24 | """
25 | )
26 | )
27 | )
28 |
29 | def test_isolates_interpolated_message_references(self):
30 | val, errs = self.bundle.format_pattern(self.bundle.get_message("bar").value, {})
31 | self.assertEqual(val, FSI + "Foo" + PDI + " Bar")
32 | self.assertEqual(len(errs), 0)
33 |
34 | def test_isolates_interpolated_string_typed_variable_references(self):
35 | val, errs = self.bundle.format_pattern(
36 | self.bundle.get_message("baz").value, {"arg": "Arg"}
37 | )
38 | self.assertEqual(val, FSI + "Arg" + PDI + " Baz")
39 | self.assertEqual(len(errs), 0)
40 |
41 | def test_isolates_interpolated_number_typed_variable_references(self):
42 | val, errs = self.bundle.format_pattern(
43 | self.bundle.get_message("baz").value, {"arg": 1}
44 | )
45 | self.assertEqual(val, FSI + "1" + PDI + " Baz")
46 | self.assertEqual(len(errs), 0)
47 |
48 | def test_isolates_complex_interpolations(self):
49 | val, errs = self.bundle.format_pattern(
50 | self.bundle.get_message("qux").value, {"arg": "Arg"}
51 | )
52 | expected_bar = FSI + FSI + "Foo" + PDI + " Bar" + PDI
53 | expected_baz = FSI + FSI + "Arg" + PDI + " Baz" + PDI
54 | self.assertEqual(val, expected_bar + " " + expected_baz)
55 | self.assertEqual(len(errs), 0)
56 |
57 |
58 | class TestSkipIsolating(unittest.TestCase):
59 |
60 | def setUp(self):
61 | self.bundle = FluentBundle(["en-US"])
62 | self.bundle.add_resource(
63 | FluentResource(
64 | dedent_ftl(
65 | """
66 | -brand-short-name = Amaya
67 | foo = { -brand-short-name }
68 | with-arg = { $arg }
69 | """
70 | )
71 | )
72 | )
73 |
74 | def test_skip_isolating_chars_if_just_one_message_ref(self):
75 | val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {})
76 | self.assertEqual(val, "Amaya")
77 | self.assertEqual(len(errs), 0)
78 |
79 | def test_skip_isolating_chars_if_just_one_placeable_arg(self):
80 | val, errs = self.bundle.format_pattern(
81 | self.bundle.get_message("with-arg").value, {"arg": "Arg"}
82 | )
83 | self.assertEqual(val, "Arg")
84 | self.assertEqual(len(errs), 0)
85 |
--------------------------------------------------------------------------------
/fluent.runtime/tests/test_bomb.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from fluent.runtime import FluentBundle, FluentResource
4 |
5 | from .utils import dedent_ftl
6 |
7 |
8 | class TestBillionLaughs(unittest.TestCase):
9 |
10 | def setUp(self):
11 | self.ctx = FluentBundle(["en-US"], use_isolating=False)
12 | self.ctx.add_resource(
13 | FluentResource(
14 | dedent_ftl(
15 | """
16 | lol0 = 01234567890123456789012345678901234567890123456789
17 | lol1 = {lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}
18 | lol2 = {lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}
19 | lol3 = {lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}
20 | lol4 = {lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}
21 | lolz = {lol4}
22 |
23 | elol0 = { "" }
24 | elol1 = {elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}
25 | elol2 = {elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}
26 | elol3 = {elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}
27 | elol4 = {elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}
28 | elol5 = {elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}
29 | elol6 = {elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}
30 | emptylolz = {elol6}
31 |
32 | """
33 | )
34 | )
35 | )
36 |
37 | def test_max_length_protection(self):
38 | val, errs = self.ctx.format_pattern(self.ctx.get_message("lolz").value)
39 | self.assertEqual(val, "{???}")
40 | self.assertNotEqual(len(errs), 0)
41 | self.assertIn("Too many characters", str(errs[-1]))
42 |
43 | def test_max_expansions_protection(self):
44 | # Without protection, emptylolz will take a really long time to
45 | # evaluate, although it generates an empty message.
46 | val, errs = self.ctx.format_pattern(self.ctx.get_message("emptylolz").value)
47 | self.assertEqual(val, "{???}")
48 | self.assertEqual(len(errs), 1)
49 | self.assertIn("Too many parts", str(errs[-1]))
50 |
--------------------------------------------------------------------------------
/fluent.runtime/tests/test_fallback.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from .utils import patch_files
3 |
4 | from fluent.runtime import FluentLocalization, FluentResourceLoader
5 |
6 |
7 | class TestLocalization(unittest.TestCase):
8 | def test_init(self):
9 | l10n = FluentLocalization(
10 | ["en"], ["file.ftl"], FluentResourceLoader("{locale}")
11 | )
12 | self.assertTrue(callable(l10n.format_value))
13 |
14 | @patch_files({
15 | "de/one.ftl": "one = in German",
16 | "de/two.ftl": "two = in German",
17 | "fr/two.ftl": "three = in French",
18 | "en/one.ftl": "four = exists",
19 | "en/two.ftl": "five = exists",
20 | })
21 | def test_bundles(self):
22 | l10n = FluentLocalization(
23 | ["de", "fr", "en"], ["one.ftl", "two.ftl"], FluentResourceLoader("{locale}")
24 | )
25 | bundles_gen = l10n._bundles()
26 | bundle_de = next(bundles_gen)
27 | self.assertEqual(bundle_de.locales[0], "de")
28 | self.assertTrue(bundle_de.has_message("one"))
29 | self.assertTrue(bundle_de.has_message("two"))
30 | bundle_fr = next(bundles_gen)
31 | self.assertEqual(bundle_fr.locales[0], "fr")
32 | self.assertFalse(bundle_fr.has_message("one"))
33 | self.assertTrue(bundle_fr.has_message("three"))
34 | self.assertListEqual(list(l10n._bundles())[:2], [bundle_de, bundle_fr])
35 | bundle_en = next(bundles_gen)
36 | self.assertEqual(bundle_en.locales[0], "en")
37 | self.assertEqual(l10n.format_value("one"), "in German")
38 | self.assertEqual(l10n.format_value("two"), "in German")
39 | self.assertEqual(l10n.format_value("three"), "in French")
40 | self.assertEqual(l10n.format_value("four"), "exists")
41 | self.assertEqual(l10n.format_value("five"), "exists")
42 |
43 |
44 | class TestResourceLoader(unittest.TestCase):
45 | @patch_files({
46 | "en/one.ftl": "one = exists",
47 | "en/two.ftl": "two = exists",
48 | })
49 | def test_all_exist(self):
50 | loader = FluentResourceLoader("{locale}")
51 | resources_list = list(loader.resources("en", ["one.ftl", "two.ftl"]))
52 | self.assertEqual(len(resources_list), 1)
53 | resources = resources_list[0]
54 | self.assertEqual(len(resources), 2)
55 |
56 | @patch_files({
57 | "en/two.ftl": "two = exists",
58 | })
59 | def test_one_exists(self):
60 | loader = FluentResourceLoader("{locale}")
61 | resources_list = list(loader.resources("en", ["one.ftl", "two.ftl"]))
62 | self.assertEqual(len(resources_list), 1)
63 | resources = resources_list[0]
64 | self.assertEqual(len(resources), 1)
65 |
66 | @patch_files({})
67 | def test_none_exist(self):
68 | loader = FluentResourceLoader("{locale}")
69 | resources_list = list(loader.resources("en", ["one.ftl", "two.ftl"]))
70 | self.assertEqual(len(resources_list), 0)
71 |
--------------------------------------------------------------------------------
/fluent.runtime/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from .utils import patch_files
3 | import os
4 | import codecs
5 |
6 |
7 | class TestFileSimulate(unittest.TestCase):
8 | def test_basic(self):
9 | @patch_files({
10 | "the.txt": "The",
11 | "en/one.txt": "One",
12 | "en/two.txt": "Two"
13 | })
14 | def patch_me(a, b):
15 | self.assertEqual(a, 10)
16 | self.assertEqual(b, "b")
17 | self.assertFileIs(os.path.basename(__file__), None)
18 | self.assertFileIs("the.txt", "The")
19 | self.assertFileIs("en/one.txt", "One")
20 | self.assertFileIs("en\\one.txt", "One")
21 | self.assertFileIs("en/two.txt", "Two")
22 | self.assertFileIs("en\\two.txt", "Two")
23 | self.assertFileIs("en/three.txt", None)
24 | self.assertFileIs("en\\three.txt", None)
25 |
26 | with self.assertRaises(ValueError):
27 | os.path.isfile("en/")
28 | patch_me(10, "b")
29 |
30 | def assertFileIs(self, filename, expect_contents):
31 | """
32 | expect_contents is None: Expect file does not exist
33 | expect_contents is a str: Expect file to exist and contents to match
34 | """
35 | if expect_contents is None:
36 | self.assertFalse(os.path.isfile(filename),
37 | f"Expected {filename} to not exist.")
38 | else:
39 | self.assertTrue(os.path.isfile(filename),
40 | f"Expected {filename} to exist.")
41 | with codecs.open(filename, "r", "utf-8") as f:
42 | self.assertEqual(f.read(), expect_contents)
43 |
--------------------------------------------------------------------------------
/fluent.runtime/tests/utils.py:
--------------------------------------------------------------------------------
1 | """Utilities for testing."""
2 |
3 | import textwrap
4 | from pathlib import PureWindowsPath, PurePosixPath
5 | from unittest import mock
6 | from io import StringIO
7 | import functools
8 |
9 |
10 | def dedent_ftl(text):
11 | return textwrap.dedent(f"{text.rstrip()}\n")
12 |
13 |
14 | # Needed in test_falllback.py because it uses dict + string compare to make a virtual file structure
15 | def _normalize_file_path(path):
16 | """Note: Does not support absolute paths or paths that
17 | contain '.' or '..' parts."""
18 | # Cannot use os.path or PurePath, because they only recognize
19 | # one kind of path separator
20 | if PureWindowsPath(path).is_absolute() or PurePosixPath(path).is_absolute():
21 | raise ValueError(f"Unsupported path: {path}")
22 | parts = path.replace("\\", "/").split("/")
23 | if "." in parts or ".." in parts:
24 | raise ValueError(f"Unsupported path: {path}")
25 | if parts and parts[-1] == "":
26 | # path ends with a trailing pathsep
27 | raise ValueError(f"Path appears to be a directory, not a file: {path}")
28 | return "/".join(parts)
29 |
30 |
31 | def patch_files(files: dict):
32 | """Decorate a function to simulate files ``files`` during the function.
33 |
34 | The keys of ``files`` are file names and must use '/' for path separator.
35 | The values are file contents. Directories or relative paths are not supported.
36 | Example: ``{"en/one.txt": "One", "en/two.txt": "Two"}``
37 |
38 | The implementation may be changed to match the mechanism used.
39 | """
40 |
41 | # Here it is possible to validate file names, but skipped
42 |
43 | def then(func):
44 | @mock.patch("os.path.isfile", side_effect=lambda p: _normalize_file_path(p) in files)
45 | @mock.patch("codecs.open", side_effect=lambda p, _, __: StringIO(files[_normalize_file_path(p)]))
46 | @functools.wraps(func) # Make ret look like func to later decorators
47 | def ret(*args, **kwargs):
48 | func(*args[:-2], **kwargs)
49 | return ret
50 | return then
51 |
--------------------------------------------------------------------------------
/fluent.runtime/tools/benchmarks/README.md:
--------------------------------------------------------------------------------
1 | To run the benchmarks, do:
2 |
3 | $ pip install -r tools/benchmarks/requirements.txt
4 | $ py.test ./tools/benchmarks/fluent_benchmark.py::TestBenchmark --benchmark-warmup=on
5 |
6 | To profile the benchmark suite, we recommend py-spy as a
7 | good tool. Install py-spy: https://github.com/benfred/py-spy
8 |
9 | Then do something like this to profile the benchmark. Depending on your
10 | platform, you might need to use `sudo`.
11 |
12 | $ py-spy -f prof.svg -- py.test ./tools/benchmarks/fluent_benchmark.py::TestBenchmark --benchmark-warmup=off
13 |
14 | And look at prof.svg in a browser. Note that this diagram includes the fixture
15 | setup, warmup and calibration phases which you should ignore.
16 |
--------------------------------------------------------------------------------
/fluent.runtime/tools/benchmarks/fluent_benchmark.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # This should be run using pytest
3 |
4 | from __future__ import unicode_literals
5 |
6 | import sys
7 |
8 | import pytest
9 | from fluent.runtime import FluentBundle, FluentResource
10 |
11 | FTL_CONTENT = """
12 | one = One
13 | two = Two
14 | three = Three
15 | four = Four
16 | five = Five
17 | six = Six
18 | seven = Seven ways to { $destination }
19 | eight = Eight
20 | nine = Nine
21 | ten = Ten
22 | """
23 |
24 |
25 | @pytest.fixture
26 | def fluent_bundle():
27 | bundle = FluentBundle(["pl"], use_isolating=False)
28 | bundle.add_resource(FluentResource(FTL_CONTENT))
29 | return bundle
30 |
31 |
32 | def fluent_template(bundle):
33 | return (
34 | "preface"
35 | + bundle.format_pattern(bundle.get_message("one").value)[0]
36 | + bundle.format_pattern(bundle.get_message("two").value)[0]
37 | + bundle.format_pattern(bundle.get_message("three").value)[0]
38 | + bundle.format_pattern(bundle.get_message("four").value)[0]
39 | + bundle.format_pattern(bundle.get_message("five").value)[0]
40 | + bundle.format_pattern(bundle.get_message("six").value)[0]
41 | + bundle.format_pattern(
42 | bundle.get_message("seven").value, {"destination": "Mars"}
43 | )[0]
44 | + bundle.format_pattern(bundle.get_message("eight").value)[0]
45 | + bundle.format_pattern(bundle.get_message("nine").value)[0]
46 | + bundle.format_pattern(bundle.get_message("ten").value)[0]
47 | + "tail"
48 | )
49 |
50 |
51 | class TestBenchmark(object):
52 | def test_template(self, fluent_bundle, benchmark):
53 | benchmark(lambda: fluent_template(fluent_bundle))
54 |
55 | def test_bundle(self, benchmark):
56 | def test_bundles():
57 | FluentBundle(["pl"], use_isolating=False)
58 | FluentBundle(["fr"], use_isolating=False)
59 |
60 | benchmark(test_bundles)
61 |
62 | def test_import(self, benchmark):
63 | def test_imports():
64 | # prune cached imports
65 | fluent_deps = [
66 | k
67 | for k in sys.modules.keys()
68 | if k.split(".", 1)[0] in ("babel", "fluent", "pytz")
69 | ]
70 | for k in fluent_deps:
71 | del sys.modules[k]
72 | from fluent.runtime import FluentBundle # noqa
73 |
74 | benchmark(test_imports)
75 |
--------------------------------------------------------------------------------
/fluent.runtime/tools/benchmarks/requirements.txt:
--------------------------------------------------------------------------------
1 | pytest
2 | pytest-benchmark
3 |
--------------------------------------------------------------------------------
/fluent.runtime/tox.ini:
--------------------------------------------------------------------------------
1 | # This config is for local testing.
2 | # It should be correspond to .github/workflows/fluent.runtime.yml
3 | [tox]
4 | envlist = {py36,py37,py38,py39,pypy3}-syntax, py3-syntax0.17, latest
5 | skipsdist=True
6 |
7 | [testenv]
8 | setenv =
9 | PYTHONPATH = {toxinidir}
10 | deps =
11 | syntax0.17: fluent.syntax==0.17
12 | attrs==19.1.0
13 | babel==2.7.0
14 | pytz==2019.2
15 | typing-extensions~=3.7
16 | syntax: .
17 | commands = python -m unittest
18 |
19 | [testenv:latest]
20 | basepython = python3
21 | deps =
22 | .
23 |
--------------------------------------------------------------------------------
/fluent.syntax/.gitattributes:
--------------------------------------------------------------------------------
1 | tests/syntax/fixtures_reference/crlf.ftl eol=crlf
2 | tests/syntax/fixtures_reference/cr.ftl eol=cr
3 | tests/syntax/fixtures_structure/crlf.ftl eol=crlf
4 |
--------------------------------------------------------------------------------
/fluent.syntax/README.rst:
--------------------------------------------------------------------------------
1 | ``fluent.syntax`` |fluent.syntax|
2 | ---------------------------------
3 |
4 | Read, write, and transform `Fluent`_ files.
5 |
6 | This package includes the parser, serializer, and traversal
7 | utilities like Visitor and Transformer. You’re looking for this package
8 | if you work on tooling for Fluent in Python.
9 |
10 | .. code-block:: python
11 |
12 | >>> from fluent.syntax import parse, ast, serialize
13 | >>> resource = parse("a-key = String to localize")
14 | >>> resource.body[0].value.elements[0].value = "Localized string"
15 | >>> serialize(resource)
16 | 'a-key = Localized string\n'
17 |
18 |
19 | Find the full documentation on https://projectfluent.org/python-fluent/fluent.syntax/.
20 |
21 | .. _fluent: https://projectfluent.org/
22 | .. |fluent.syntax| image:: https://github.com/projectfluent/python-fluent/workflows/fluent.syntax/badge.svg
23 |
--------------------------------------------------------------------------------
/fluent.syntax/docs/ast.rst:
--------------------------------------------------------------------------------
1 | AST
2 | ===
3 |
4 |
5 | .. automodule:: fluent.syntax.ast
6 | :members:
7 | :exclude-members: scalars_equal
8 | :show-inheritance:
9 |
--------------------------------------------------------------------------------
/fluent.syntax/docs/index.rst:
--------------------------------------------------------------------------------
1 | Fluent Syntax
2 | =============
3 |
4 | The :py:mod:`fluent.syntax` library is built for tooling around Fluent. It's
5 | designed to parse, analyze, process, and serialize Fluent files.
6 |
7 | The :py:mod:`fluent.syntax.ast` module implements ``Visitor`` and ``Transformer``
8 | patterns, which are the recommended interfaces to be used in analysis and
9 | post-processing of Fluent source files.
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 | :caption: Contents:
14 |
15 | usage
16 | reference
17 |
18 |
19 | Indices and tables
20 | ==================
21 |
22 | * :ref:`genindex`
23 | * :ref:`modindex`
24 | * :ref:`search`
25 |
--------------------------------------------------------------------------------
/fluent.syntax/docs/parsing.rst:
--------------------------------------------------------------------------------
1 | Parsing
2 | =======
3 |
4 | .. py:module:: fluent.syntax.parser
5 |
6 | .. autoclass:: fluent.syntax.parser.FluentParser
7 | :members: parse, parse_entry
8 |
--------------------------------------------------------------------------------
/fluent.syntax/docs/reference.rst:
--------------------------------------------------------------------------------
1 | API Reference
2 | =============
3 |
4 | The :py:mod:`fluent.syntax` module contains two helper methods.
5 |
6 | The :py:mod:`fluent.syntax.parser` and :py:mod:`fluent.syntax.serializer` modules
7 | provide more fine-grained control and detail.
8 |
9 |
10 | .. automodule:: fluent.syntax
11 | :members:
12 |
13 |
14 | .. toctree::
15 | :maxdepth: 2
16 | :caption: Contents:
17 |
18 | parsing
19 | ast
20 | visitor
21 | serializing
22 |
--------------------------------------------------------------------------------
/fluent.syntax/docs/serializing.rst:
--------------------------------------------------------------------------------
1 | Serializing
2 | ===========
3 |
4 | .. automodule:: fluent.syntax.serializer
5 | :members:
6 |
--------------------------------------------------------------------------------
/fluent.syntax/docs/visitor.rst:
--------------------------------------------------------------------------------
1 | Visitor & Transformer
2 | =====================
3 |
4 |
5 | .. automodule:: fluent.syntax.visitor
6 | :members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/fluent.syntax/fluent/syntax/__init__.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 | from . import ast
4 | from .errors import ParseError
5 | from .parser import FluentParser
6 | from .serializer import FluentSerializer
7 | from .stream import FluentParserStream
8 | from .visitor import Transformer, Visitor
9 |
10 | __all__ = [
11 | "FluentParser",
12 | "FluentParserStream",
13 | "FluentSerializer",
14 | "ParseError",
15 | "Transformer",
16 | "Visitor",
17 | "ast",
18 | "parse",
19 | "serialize",
20 | ]
21 |
22 |
23 | def parse(source: str, **kwargs: Any) -> ast.Resource:
24 | """Create an ast.Resource from a Fluent Syntax source."""
25 | parser = FluentParser(**kwargs)
26 | return parser.parse(source)
27 |
28 |
29 | def serialize(resource: ast.Resource, **kwargs: Any) -> str:
30 | """Serialize an ast.Resource to a unicode string."""
31 | serializer = FluentSerializer(**kwargs)
32 | return serializer.serialize(resource)
33 |
--------------------------------------------------------------------------------
/fluent.syntax/fluent/syntax/errors.py:
--------------------------------------------------------------------------------
1 | from typing import Tuple, Union
2 |
3 |
4 | class ParseError(Exception):
5 | def __init__(self, code: str, *args: Union[str, None]):
6 | self.code = code
7 | self.args = args
8 | self.message = get_error_message(code, args)
9 |
10 |
11 | def get_error_message(code: str, args: Tuple[Union[str, None], ...]) -> str:
12 | if code == "E00001":
13 | return "Generic error"
14 | if code == "E0002":
15 | return "Expected an entry start"
16 | if code == "E0003":
17 | return 'Expected token: "{}"'.format(args[0])
18 | if code == "E0004":
19 | return 'Expected a character from range: "{}"'.format(args[0])
20 | if code == "E0005":
21 | msg = 'Expected message "{}" to have a value or attributes'
22 | return msg.format(args[0])
23 | if code == "E0006":
24 | msg = 'Expected term "-{}" to have a value'
25 | return msg.format(args[0])
26 | if code == "E0007":
27 | return "Keyword cannot end with a whitespace"
28 | if code == "E0008":
29 | return "The callee has to be an upper-case identifier or a term"
30 | if code == "E0009":
31 | return "The argument name has to be a simple identifier"
32 | if code == "E0010":
33 | return "Expected one of the variants to be marked as default (*)"
34 | if code == "E0011":
35 | return 'Expected at least one variant after "->"'
36 | if code == "E0012":
37 | return "Expected value"
38 | if code == "E0013":
39 | return "Expected variant key"
40 | if code == "E0014":
41 | return "Expected literal"
42 | if code == "E0015":
43 | return "Only one variant can be marked as default (*)"
44 | if code == "E0016":
45 | return "Message references cannot be used as selectors"
46 | if code == "E0017":
47 | return "Terms cannot be used as selectors"
48 | if code == "E0018":
49 | return "Attributes of messages cannot be used as selectors"
50 | if code == "E0019":
51 | return "Attributes of terms cannot be used as placeables"
52 | if code == "E0020":
53 | return "Unterminated string expression"
54 | if code == "E0021":
55 | return "Positional arguments must not follow named arguments"
56 | if code == "E0022":
57 | return "Named arguments must be unique"
58 | if code == "E0024":
59 | return "Cannot access variants of a message."
60 | if code == "E0025":
61 | return "Unknown escape sequence: \\{}.".format(args[0])
62 | if code == "E0026":
63 | return "Invalid Unicode escape sequence: {}.".format(args[0])
64 | if code == "E0027":
65 | return "Unbalanced closing brace in TextElement."
66 | if code == "E0028":
67 | return "Expected an inline expression"
68 | if code == "E0029":
69 | return "Expected simple expression as selector"
70 | return code
71 |
--------------------------------------------------------------------------------
/fluent.syntax/fluent/syntax/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.syntax/fluent/syntax/py.typed
--------------------------------------------------------------------------------
/fluent.syntax/fluent/syntax/visitor.py:
--------------------------------------------------------------------------------
1 | from typing import Any, List
2 |
3 | from .ast import BaseNode, Node
4 |
5 |
6 | class Visitor:
7 | """Read-only visitor pattern.
8 |
9 | Subclass this to gather information from an AST.
10 | To generally define which nodes not to descend in to, overload
11 | `generic_visit`.
12 | To handle specific node types, add methods like `visit_Pattern`.
13 | If you want to still descend into the children of the node, call
14 | `generic_visit` of the superclass.
15 | """
16 |
17 | def visit(self, node: Any) -> None:
18 | if isinstance(node, list):
19 | for child in node:
20 | self.visit(child)
21 | return
22 | if not isinstance(node, BaseNode):
23 | return
24 | nodename = type(node).__name__
25 | visit = getattr(self, f"visit_{nodename}", self.generic_visit)
26 | visit(node)
27 |
28 | def generic_visit(self, node: BaseNode) -> None:
29 | for propvalue in vars(node).values():
30 | self.visit(propvalue)
31 |
32 |
33 | class Transformer(Visitor):
34 | """In-place AST Transformer pattern.
35 |
36 | Subclass this to create an in-place modified variant
37 | of the given AST.
38 | If you need to keep the original AST around, pass
39 | a `node.clone()` to the transformer.
40 | """
41 |
42 | def visit(self, node: Any) -> Any:
43 | if not isinstance(node, BaseNode):
44 | return node
45 |
46 | nodename = type(node).__name__
47 | visit = getattr(self, f"visit_{nodename}", self.generic_visit)
48 | return visit(node)
49 |
50 | def generic_visit(self, node: Node) -> Node: # type: ignore
51 | for propname, propvalue in vars(node).items():
52 | if isinstance(propvalue, list):
53 | new_vals: List[Any] = []
54 | for child in propvalue:
55 | new_val = self.visit(child)
56 | if new_val is not None:
57 | new_vals.append(new_val)
58 | # in-place manipulation
59 | propvalue[:] = new_vals
60 | elif isinstance(propvalue, BaseNode):
61 | new_val = self.visit(propvalue)
62 | if new_val is None:
63 | delattr(node, propname)
64 | else:
65 | setattr(node, propname, new_val)
66 | return node
67 |
--------------------------------------------------------------------------------
/fluent.syntax/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | version=0.19.0
3 |
4 | [bdist_wheel]
5 | universal=1
6 |
--------------------------------------------------------------------------------
/fluent.syntax/setup.py:
--------------------------------------------------------------------------------
1 | from os import path
2 |
3 | from setuptools import setup
4 |
5 | this_directory = path.abspath(path.dirname(__file__))
6 | with open(path.join(this_directory, "README.rst"), "rb") as f:
7 | long_description = f.read().decode("utf-8")
8 |
9 | setup(
10 | name="fluent.syntax",
11 | description="Localization library for expressive translations.",
12 | long_description=long_description,
13 | long_description_content_type="text/x-rst",
14 | author="Mozilla",
15 | author_email="l10n-drivers@mozilla.org",
16 | license="APL 2",
17 | url="https://github.com/projectfluent/python-fluent",
18 | keywords=["fluent", "localization", "l10n"],
19 | classifiers=[
20 | "Development Status :: 3 - Alpha",
21 | "Intended Audience :: Developers",
22 | "License :: OSI Approved :: Apache Software License",
23 | "Programming Language :: Python :: 3.6",
24 | "Programming Language :: Python :: 3.7",
25 | "Programming Language :: Python :: 3.8",
26 | "Programming Language :: Python :: 3.9",
27 | "Programming Language :: Python :: 3 :: Only",
28 | ],
29 | packages=["fluent.syntax"],
30 | package_data={"fluent.syntax": ["py.typed"]},
31 | install_requires=["typing-extensions>=3.7,<5"],
32 | test_suite="tests.syntax",
33 | )
34 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.syntax/tests/__init__.py
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/README.md:
--------------------------------------------------------------------------------
1 | The files in `fixtures_*` are copied from `fluent.js/fluent-syntax`. Due to
2 | the backwards compatibility with Syntax 0.4, the Python parser sometimes
3 | produces different output, mainly in terms of reported errors. Currently, the
4 | files which are known to differ are:
5 |
6 | fixtures_behavior/standalone_identifier.ftl
7 | fixtures_structure/multiline_pattern.ftl
8 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/__init__.py:
--------------------------------------------------------------------------------
1 | import textwrap
2 |
3 |
4 | def dedent_ftl(text):
5 | return textwrap.dedent(f"{text.rstrip()}\n")
6 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/any_char.ftl:
--------------------------------------------------------------------------------
1 | # ↓ BEL, U+0007
2 | control0 = abcdef
3 |
4 | # ↓ DEL, U+007F
5 | delete = abcdef
6 |
7 | # ↓ BPM, U+0082
8 | control1 = abcdef
9 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/any_char.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "control0"
9 | },
10 | "value": {
11 | "type": "Pattern",
12 | "elements": [
13 | {
14 | "type": "TextElement",
15 | "value": "abc\u0007def"
16 | }
17 | ]
18 | },
19 | "attributes": [],
20 | "comment": {
21 | "type": "Comment",
22 | "content": " ↓ BEL, U+0007"
23 | }
24 | },
25 | {
26 | "type": "Message",
27 | "id": {
28 | "type": "Identifier",
29 | "name": "delete"
30 | },
31 | "value": {
32 | "type": "Pattern",
33 | "elements": [
34 | {
35 | "type": "TextElement",
36 | "value": "abcdef"
37 | }
38 | ]
39 | },
40 | "attributes": [],
41 | "comment": {
42 | "type": "Comment",
43 | "content": " ↓ DEL, U+007F"
44 | }
45 | },
46 | {
47 | "type": "Message",
48 | "id": {
49 | "type": "Identifier",
50 | "name": "control1"
51 | },
52 | "value": {
53 | "type": "Pattern",
54 | "elements": [
55 | {
56 | "type": "TextElement",
57 | "value": "abcdef"
58 | }
59 | ]
60 | },
61 | "attributes": [],
62 | "comment": {
63 | "type": "Comment",
64 | "content": " ↓ BPM, U+0082"
65 | }
66 | }
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/astral.ftl:
--------------------------------------------------------------------------------
1 | face-with-tears-of-joy = 😂
2 | tetragram-for-centre = 𝌆
3 |
4 | surrogates-in-text = \uD83D\uDE02
5 | surrogates-in-string = {"\uD83D\uDE02"}
6 | surrogates-in-adjacent-strings = {"\uD83D"}{"\uDE02"}
7 |
8 | emoji-in-text = A face 😂 with tears of joy.
9 | emoji-in-string = {"A face 😂 with tears of joy."}
10 |
11 | # ERROR Invalid identifier
12 | err-😂 = Value
13 |
14 | # ERROR Invalid expression
15 | err-invalid-expression = { 😂 }
16 |
17 | # ERROR Invalid variant key
18 | err-invalid-variant-key = { $sel ->
19 | *[😂] Value
20 | }
21 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/call_expressions.ftl:
--------------------------------------------------------------------------------
1 | ## Function names
2 |
3 | valid-func-name-01 = {FUN1()}
4 | valid-func-name-02 = {FUN_FUN()}
5 | valid-func-name-03 = {FUN-FUN()}
6 |
7 | # JUNK 0 is not a valid Identifier start
8 | invalid-func-name-01 = {0FUN()}
9 | # JUNK Function names may not be lowercase
10 | invalid-func-name-02 = {fun()}
11 | # JUNK Function names may not contain lowercase character
12 | invalid-func-name-03 = {Fun()}
13 | # JUNK ? is not a valid Identifier character
14 | invalid-func-name-04 = {FUN?()}
15 |
16 | ## Arguments
17 |
18 | positional-args = {FUN(1, "a", msg)}
19 | named-args = {FUN(x: 1, y: "Y")}
20 | dense-named-args = {FUN(x:1, y:"Y")}
21 | mixed-args = {FUN(1, "a", msg, x: 1, y: "Y")}
22 |
23 | # ERROR Positional arg must not follow keyword args
24 | shuffled-args = {FUN(1, x: 1, "a", y: "Y", msg)}
25 |
26 | # ERROR Named arguments must be unique
27 | duplicate-named-args = {FUN(x: 1, x: "X")}
28 |
29 |
30 | ## Whitespace around arguments
31 |
32 | sparse-inline-call = {FUN ( "a" , msg, x: 1 )}
33 | empty-inline-call = {FUN( )}
34 | multiline-call = {FUN(
35 | "a",
36 | msg,
37 | x: 1
38 | )}
39 | sparse-multiline-call = {FUN
40 | (
41 |
42 | "a" ,
43 | msg
44 | , x: 1
45 | )}
46 | empty-multiline-call = {FUN(
47 |
48 | )}
49 |
50 |
51 | unindented-arg-number = {FUN(
52 | 1)}
53 |
54 | unindented-arg-string = {FUN(
55 | "a")}
56 |
57 | unindented-arg-msg-ref = {FUN(
58 | msg)}
59 |
60 | unindented-arg-term-ref = {FUN(
61 | -msg)}
62 |
63 | unindented-arg-var-ref = {FUN(
64 | $var)}
65 |
66 | unindented-arg-call = {FUN(
67 | OTHER())}
68 |
69 | unindented-named-arg = {FUN(
70 | x:1)}
71 |
72 | unindented-closing-paren = {FUN(
73 | x
74 | )}
75 |
76 |
77 |
78 | ## Optional trailing comma
79 |
80 | one-argument = {FUN(1,)}
81 | many-arguments = {FUN(1, 2, 3,)}
82 | inline-sparse-args = {FUN( 1, 2, 3, )}
83 | mulitline-args = {FUN(
84 | 1,
85 | 2,
86 | )}
87 | mulitline-sparse-args = {FUN(
88 |
89 | 1
90 | ,
91 | 2
92 | ,
93 | )}
94 |
95 |
96 | ## Syntax errors for trailing comma
97 |
98 | one-argument = {FUN(1,,)}
99 | missing-arg = {FUN(,)}
100 | missing-sparse-arg = {FUN( , )}
101 |
102 |
103 | ## Whitespace in named arguments
104 |
105 | sparse-named-arg = {FUN(
106 | x : 1,
107 | y : 2,
108 | z
109 | :
110 | 3
111 | )}
112 |
113 |
114 | unindented-colon = {FUN(
115 | x
116 | :1)}
117 |
118 | unindented-value = {FUN(
119 | x:
120 | 1)}
121 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/callee_expressions.ftl:
--------------------------------------------------------------------------------
1 | ## Callees in placeables.
2 |
3 | function-callee-placeable = {FUNCTION()}
4 | term-callee-placeable = {-term()}
5 |
6 | # ERROR Messages cannot be parameterized.
7 | message-callee-placeable = {message()}
8 | # ERROR Equivalent to a MessageReference callee.
9 | mixed-case-callee-placeable = {Function()}
10 | # ERROR Message attributes cannot be parameterized.
11 | message-attr-callee-placeable = {message.attr()}
12 | # ERROR Term attributes may not be used in Placeables.
13 | term-attr-callee-placeable = {-term.attr()}
14 | # ERROR Variables cannot be parameterized.
15 | variable-callee-placeable = {$variable()}
16 |
17 |
18 | ## Callees in selectors.
19 |
20 | function-callee-selector = {FUNCTION() ->
21 | *[key] Value
22 | }
23 | term-attr-callee-selector = {-term.attr() ->
24 | *[key] Value
25 | }
26 |
27 | # ERROR Messages cannot be parameterized.
28 | message-callee-selector = {message() ->
29 | *[key] Value
30 | }
31 | # ERROR Equivalent to a MessageReference callee.
32 | mixed-case-callee-selector = {Function() ->
33 | *[key] Value
34 | }
35 | # ERROR Message attributes cannot be parameterized.
36 | message-attr-callee-selector = {message.attr() ->
37 | *[key] Value
38 | }
39 | # ERROR Term values may not be used as selectors.
40 | term-callee-selector = {-term() ->
41 | *[key] Value
42 | }
43 | # ERROR Variables cannot be parameterized.
44 | variable-callee-selector = {$variable() ->
45 | *[key] Value
46 | }
47 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/comments.ftl:
--------------------------------------------------------------------------------
1 | # Standalone Comment
2 |
3 | # Message Comment
4 | foo = Foo
5 |
6 | # Term Comment
7 | # with a blank last line.
8 | #
9 | -term = Term
10 |
11 | # Another standalone
12 | #
13 | # with indent
14 | ## Group Comment
15 | ### Resource Comment
16 |
17 | # Errors
18 | #error
19 | ##error
20 | ###error
21 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/comments.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Comment",
6 | "content": "Standalone Comment"
7 | },
8 | {
9 | "type": "Message",
10 | "id": {
11 | "type": "Identifier",
12 | "name": "foo"
13 | },
14 | "value": {
15 | "type": "Pattern",
16 | "elements": [
17 | {
18 | "type": "TextElement",
19 | "value": "Foo"
20 | }
21 | ]
22 | },
23 | "attributes": [],
24 | "comment": {
25 | "type": "Comment",
26 | "content": "Message Comment"
27 | }
28 | },
29 | {
30 | "type": "Term",
31 | "id": {
32 | "type": "Identifier",
33 | "name": "term"
34 | },
35 | "value": {
36 | "type": "Pattern",
37 | "elements": [
38 | {
39 | "type": "TextElement",
40 | "value": "Term"
41 | }
42 | ]
43 | },
44 | "attributes": [],
45 | "comment": {
46 | "type": "Comment",
47 | "content": "Term Comment\nwith a blank last line.\n"
48 | }
49 | },
50 | {
51 | "type": "Comment",
52 | "content": "Another standalone\n\n with indent"
53 | },
54 | {
55 | "type": "GroupComment",
56 | "content": "Group Comment"
57 | },
58 | {
59 | "type": "ResourceComment",
60 | "content": "Resource Comment"
61 | },
62 | {
63 | "type": "Comment",
64 | "content": "Errors"
65 | },
66 | {
67 | "type": "Junk",
68 | "annotations": [],
69 | "content": "#error\n"
70 | },
71 | {
72 | "type": "Junk",
73 | "annotations": [],
74 | "content": "##error\n"
75 | },
76 | {
77 | "type": "Junk",
78 | "annotations": [],
79 | "content": "###error\n"
80 | }
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/cr.ftl:
--------------------------------------------------------------------------------
1 | ### This entire file uses CR as EOL.
err01 = Value 01
err02 = Value 02
err03 =
Value 03
Continued
.title = Title
err04 = { "str
err05 = { $sel -> }
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/cr.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "This entire file uses CR as EOL.\r\rerr01 = Value 01\rerr02 = Value 02\r\rerr03 =\r\r Value 03\r Continued\r\r .title = Title\r\rerr04 = { \"str\r\rerr05 = { $sel -> }\r"
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/crlf.ftl:
--------------------------------------------------------------------------------
1 |
2 | key01 = Value 01
3 | key02 =
4 |
5 | Value 02
6 | Continued
7 |
8 | .title = Title
9 |
10 | # ERROR Unclosed StringLiteral
11 | err03 = { "str
12 |
13 | # ERROR Missing newline after ->.
14 | err04 = { $sel -> }
15 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/crlf.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "key01"
9 | },
10 | "value": {
11 | "type": "Pattern",
12 | "elements": [
13 | {
14 | "type": "TextElement",
15 | "value": "Value 01"
16 | }
17 | ]
18 | },
19 | "attributes": [],
20 | "comment": null
21 | },
22 | {
23 | "type": "Message",
24 | "id": {
25 | "type": "Identifier",
26 | "name": "key02"
27 | },
28 | "value": {
29 | "type": "Pattern",
30 | "elements": [
31 | {
32 | "type": "TextElement",
33 | "value": "Value 02\nContinued"
34 | }
35 | ]
36 | },
37 | "attributes": [
38 | {
39 | "type": "Attribute",
40 | "id": {
41 | "type": "Identifier",
42 | "name": "title"
43 | },
44 | "value": {
45 | "type": "Pattern",
46 | "elements": [
47 | {
48 | "type": "TextElement",
49 | "value": "Title"
50 | }
51 | ]
52 | }
53 | }
54 | ],
55 | "comment": null
56 | },
57 | {
58 | "type": "Comment",
59 | "content": "ERROR Unclosed StringLiteral"
60 | },
61 | {
62 | "type": "Junk",
63 | "annotations": [],
64 | "content": "err03 = { \"str\r\n\r\n"
65 | },
66 | {
67 | "type": "Comment",
68 | "content": "ERROR Missing newline after ->."
69 | },
70 | {
71 | "type": "Junk",
72 | "annotations": [],
73 | "content": "err04 = { $sel -> }\r\n"
74 | }
75 | ]
76 | }
77 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_comment.ftl:
--------------------------------------------------------------------------------
1 | ### NOTE: Disable final newline insertion when editing this file.
2 |
3 | # No EOL
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_comment.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "NOTE: Disable final newline insertion when editing this file."
7 | },
8 | {
9 | "type": "Comment",
10 | "content": "No EOL"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_empty.ftl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.syntax/tests/syntax/fixtures_reference/eof_empty.ftl
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_empty.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": []
4 | }
5 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_id.ftl:
--------------------------------------------------------------------------------
1 | ### NOTE: Disable final newline insertion when editing this file.
2 |
3 | message-id
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_id.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "NOTE: Disable final newline insertion when editing this file."
7 | },
8 | {
9 | "type": "Junk",
10 | "annotations": [],
11 | "content": "message-id"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_id_equals.ftl:
--------------------------------------------------------------------------------
1 | ### NOTE: Disable final newline insertion when editing this file.
2 |
3 | message-id =
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_id_equals.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "NOTE: Disable final newline insertion when editing this file."
7 | },
8 | {
9 | "type": "Junk",
10 | "annotations": [],
11 | "content": "message-id ="
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_junk.ftl:
--------------------------------------------------------------------------------
1 | ### NOTE: Disable final newline insertion when editing this file.
2 |
3 | 000
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_junk.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "NOTE: Disable final newline insertion when editing this file."
7 | },
8 | {
9 | "type": "Junk",
10 | "annotations": [],
11 | "content": "000"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_value.ftl:
--------------------------------------------------------------------------------
1 | ### NOTE: Disable final newline insertion when editing this file.
2 |
3 | no-eol = No EOL
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/eof_value.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "NOTE: Disable final newline insertion when editing this file."
7 | },
8 | {
9 | "type": "Message",
10 | "id": {
11 | "type": "Identifier",
12 | "name": "no-eol"
13 | },
14 | "value": {
15 | "type": "Pattern",
16 | "elements": [
17 | {
18 | "type": "TextElement",
19 | "value": "No EOL"
20 | }
21 | ]
22 | },
23 | "attributes": [],
24 | "comment": null
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/escaped_characters.ftl:
--------------------------------------------------------------------------------
1 | ## Literal text
2 | text-backslash-one = Value with \ a backslash
3 | text-backslash-two = Value with \\ two backslashes
4 | text-backslash-brace = Value with \{placeable}
5 | text-backslash-u = \u0041
6 | text-backslash-backslash-u = \\u0041
7 |
8 | ## String literals
9 | quote-in-string = {"\""}
10 | backslash-in-string = {"\\"}
11 | # ERROR Mismatched quote
12 | mismatched-quote = {"\\""}
13 | # ERROR Unknown escape
14 | unknown-escape = {"\x"}
15 | # ERROR Multiline literal
16 | invalid-multiline-literal = {"
17 | "}
18 |
19 | ## Unicode escapes
20 | string-unicode-4digits = {"\u0041"}
21 | escape-unicode-4digits = {"\\u0041"}
22 | string-unicode-6digits = {"\U01F602"}
23 | escape-unicode-6digits = {"\\U01F602"}
24 |
25 | # OK The trailing "00" is part of the literal value.
26 | string-too-many-4digits = {"\u004100"}
27 | # OK The trailing "00" is part of the literal value.
28 | string-too-many-6digits = {"\U01F60200"}
29 |
30 | # ERROR Too few hex digits after \u.
31 | string-too-few-4digits = {"\u41"}
32 | # ERROR Too few hex digits after \U.
33 | string-too-few-6digits = {"\U1F602"}
34 |
35 | ## Literal braces
36 | brace-open = An opening {"{"} brace.
37 | brace-close = A closing {"}"} brace.
38 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/junk.ftl:
--------------------------------------------------------------------------------
1 | ## Two adjacent Junks.
2 | err01 = {1x}
3 | err02 = {2x}
4 |
5 | # A single Junk.
6 | err03 = {1x
7 | 2
8 |
9 | # A single Junk.
10 | ą=Invalid identifier
11 | ć=Another one
12 |
13 | # The COMMENT ends this junk.
14 | err04 = {
15 | # COMMENT
16 |
17 | # The COMMENT ends this junk.
18 | # The closing brace is a separate Junk.
19 | err04 = {
20 | # COMMENT
21 | }
22 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/junk.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "GroupComment",
6 | "content": "Two adjacent Junks."
7 | },
8 | {
9 | "type": "Junk",
10 | "annotations": [],
11 | "content": "err01 = {1x}\n"
12 | },
13 | {
14 | "type": "Junk",
15 | "annotations": [],
16 | "content": "err02 = {2x}\n\n"
17 | },
18 | {
19 | "type": "Comment",
20 | "content": "A single Junk."
21 | },
22 | {
23 | "type": "Junk",
24 | "annotations": [],
25 | "content": "err03 = {1x\n2\n\n"
26 | },
27 | {
28 | "type": "Comment",
29 | "content": "A single Junk."
30 | },
31 | {
32 | "type": "Junk",
33 | "annotations": [],
34 | "content": "ą=Invalid identifier\nć=Another one\n\n"
35 | },
36 | {
37 | "type": "Comment",
38 | "content": "The COMMENT ends this junk."
39 | },
40 | {
41 | "type": "Junk",
42 | "annotations": [],
43 | "content": "err04 = {\n"
44 | },
45 | {
46 | "type": "Comment",
47 | "content": "COMMENT"
48 | },
49 | {
50 | "type": "Comment",
51 | "content": "The COMMENT ends this junk.\nThe closing brace is a separate Junk."
52 | },
53 | {
54 | "type": "Junk",
55 | "annotations": [],
56 | "content": "err04 = {\n"
57 | },
58 | {
59 | "type": "Comment",
60 | "content": "COMMENT"
61 | },
62 | {
63 | "type": "Junk",
64 | "annotations": [],
65 | "content": "}\n"
66 | }
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/leading_dots.ftl:
--------------------------------------------------------------------------------
1 | key01 = .Value
2 | key02 = …Value
3 | key03 = {"."}Value
4 | key04 =
5 | {"."}Value
6 |
7 | key05 = Value
8 | {"."}Continued
9 |
10 | key06 = .Value
11 | {"."}Continued
12 |
13 | # MESSAGE (value = "Value", attributes = [])
14 | # JUNK (attr .Continued" must have a value)
15 | key07 = Value
16 | .Continued
17 |
18 | # JUNK (attr .Value must have a value)
19 | key08 =
20 | .Value
21 |
22 | # JUNK (attr .Value must have a value)
23 | key09 =
24 | .Value
25 | Continued
26 |
27 | key10 =
28 | .Value = which is an attribute
29 | Continued
30 |
31 | key11 =
32 | {"."}Value = which looks like an attribute
33 | Continued
34 |
35 | key12 =
36 | .accesskey =
37 | A
38 |
39 | key13 =
40 | .attribute = .Value
41 |
42 | key14 =
43 | .attribute =
44 | {"."}Value
45 |
46 | key15 =
47 | { 1 ->
48 | [one] .Value
49 | *[other]
50 | {"."}Value
51 | }
52 |
53 | # JUNK (variant must have a value)
54 | key16 =
55 | { 1 ->
56 | *[one]
57 | .Value
58 | }
59 |
60 | # JUNK (unclosed placeable)
61 | key17 =
62 | { 1 ->
63 | *[one] Value
64 | .Continued
65 | }
66 |
67 | # JUNK (attr .Value must have a value)
68 | key18 =
69 | .Value
70 |
71 | key19 =
72 | .attribute = Value
73 | Continued
74 |
75 | key20 =
76 | {"."}Value
77 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/literal_expressions.ftl:
--------------------------------------------------------------------------------
1 | string-expression = {"abc"}
2 | number-expression = {123}
3 | number-expression = {-3.14}
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/literal_expressions.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "string-expression"
9 | },
10 | "value": {
11 | "type": "Pattern",
12 | "elements": [
13 | {
14 | "type": "Placeable",
15 | "expression": {
16 | "value": "abc",
17 | "type": "StringLiteral"
18 | }
19 | }
20 | ]
21 | },
22 | "attributes": [],
23 | "comment": null
24 | },
25 | {
26 | "type": "Message",
27 | "id": {
28 | "type": "Identifier",
29 | "name": "number-expression"
30 | },
31 | "value": {
32 | "type": "Pattern",
33 | "elements": [
34 | {
35 | "type": "Placeable",
36 | "expression": {
37 | "value": "123",
38 | "type": "NumberLiteral"
39 | }
40 | }
41 | ]
42 | },
43 | "attributes": [],
44 | "comment": null
45 | },
46 | {
47 | "type": "Message",
48 | "id": {
49 | "type": "Identifier",
50 | "name": "number-expression"
51 | },
52 | "value": {
53 | "type": "Pattern",
54 | "elements": [
55 | {
56 | "type": "Placeable",
57 | "expression": {
58 | "value": "-3.14",
59 | "type": "NumberLiteral"
60 | }
61 | }
62 | ]
63 | },
64 | "attributes": [],
65 | "comment": null
66 | }
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/member_expressions.ftl:
--------------------------------------------------------------------------------
1 | ## Member expressions in placeables.
2 |
3 | # OK Message attributes may be interpolated in values.
4 | message-attribute-expression-placeable = {msg.attr}
5 |
6 | # ERROR Term attributes may not be used for interpolation.
7 | term-attribute-expression-placeable = {-term.attr}
8 |
9 |
10 | ## Member expressions in selectors.
11 |
12 | # OK Term attributes may be used as selectors.
13 | term-attribute-expression-selector = {-term.attr ->
14 | *[key] Value
15 | }
16 | # ERROR Message attributes may not be used as selectors.
17 | message-attribute-expression-selector = {msg.attr ->
18 | *[key] Value
19 | }
20 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/messages.ftl:
--------------------------------------------------------------------------------
1 | key01 = Value
2 |
3 | key02 = Value
4 | .attr = Attribute
5 |
6 | key02 = Value
7 | .attr1 = Attribute 1
8 | .attr2 = Attribute 2
9 |
10 | key03 =
11 | .attr = Attribute
12 |
13 | key04 =
14 | .attr1 = Attribute 1
15 | .attr2 = Attribute 2
16 |
17 | # < whitespace >
18 | key05 =
19 | .attr1 = Attribute 1
20 |
21 | no-whitespace=Value
22 | .attr1=Attribute 1
23 |
24 | extra-whitespace = Value
25 | .attr1 = Attribute 1
26 |
27 | key06 = {""}
28 |
29 | # JUNK Missing value
30 | key07 =
31 |
32 | # JUNK Missing =
33 | key08
34 |
35 | KEY09 = Value 09
36 |
37 | key-10 = Value 10
38 | key_11 = Value 11
39 | key-12- = Value 12
40 | key_13_ = Value 13
41 |
42 | # JUNK Invalid id
43 | 0err-14 = Value 14
44 |
45 | # JUNK Invalid id
46 | err-15? = Value 15
47 |
48 | # JUNK Invalid id
49 | err-ąę-16 = Value 16
50 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/mixed_entries.ftl:
--------------------------------------------------------------------------------
1 | # License Comment
2 |
3 | ### Resource Comment
4 |
5 | -brand-name = Aurora
6 |
7 | ## Group Comment
8 |
9 | key01 =
10 | .attr = Attribute
11 |
12 | ą=Invalid identifier
13 | ć=Another one
14 |
15 | # Message Comment
16 | key02 = Value
17 |
18 | # Standalone Comment
19 | .attr = Dangling attribute
20 |
21 | # There are 5 spaces on the line between key03 and key04.
22 | key03 = Value 03
23 |
24 | key04 = Value 04
25 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/multiline_values.ftl:
--------------------------------------------------------------------------------
1 | key01 = A multiline value
2 | continued on the next line
3 |
4 | and also down here.
5 |
6 | key02 =
7 | A multiline value starting
8 | on a new line.
9 |
10 | key03 =
11 | .attr = A multiline attribute value
12 | continued on the next line
13 |
14 | and also down here.
15 |
16 | key04 =
17 | .attr =
18 | A multiline attribute value
19 | staring on a new line
20 |
21 | key05 =
22 |
23 | A multiline value with non-standard
24 |
25 | indentation.
26 |
27 | key06 =
28 | A multiline value with {"placeables"}
29 | {"at"} the beginning and the end
30 | {"of lines"}{"."}
31 |
32 | key07 =
33 | {"A multiline value"} starting and ending {"with a placeable"}
34 |
35 | key08 = Leading and trailing whitespace.
36 |
37 | key09 = zero
38 | three
39 | two
40 | one
41 | zero
42 |
43 | key10 =
44 | two
45 | zero
46 | four
47 |
48 | key11 =
49 |
50 |
51 | two
52 | zero
53 |
54 | key12 =
55 | {"."}
56 | four
57 |
58 | key13 =
59 | four
60 | {"."}
61 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/numbers.ftl:
--------------------------------------------------------------------------------
1 | int-zero = {0}
2 | int-positive = {1}
3 | int-negative = {-1}
4 | int-negative-zero = {-0}
5 |
6 | int-positive-padded = {01}
7 | int-negative-padded = {-01}
8 | int-zero-padded = {00}
9 | int-negative-zero-padded = {-00}
10 |
11 | float-zero = {0.0}
12 | float-positive = {0.01}
13 | float-positive-one = {1.03}
14 | float-positive-without-fraction = {1.000}
15 |
16 | float-negative = {-0.01}
17 | float-negative-one = {-1.03}
18 | float-negative-zero = {-0.0}
19 | float-negative-without-fraction = {-1.000}
20 |
21 | float-positive-padded-left = {01.03}
22 | float-positive-padded-right = {1.0300}
23 | float-positive-padded-both = {01.0300}
24 |
25 | float-negative-padded-left = {-01.03}
26 | float-negative-padded-right = {-1.0300}
27 | float-negative-padded-both = {-01.0300}
28 |
29 |
30 | ## ERRORS
31 |
32 | err01 = {1.}
33 | err02 = {.02}
34 | err03 = {1.02.03}
35 | err04 = {1. 02}
36 | err05 = {1 .02}
37 | err06 = {- 1}
38 | err07 = {1,02}
39 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/obsolete.ftl:
--------------------------------------------------------------------------------
1 | ### The syntax in this file has been discontinued. It is no longer part of the
2 | ### Fluent specification and should not be implemented nor used. We're keeping
3 | ### these fixtures around to protect against accidental syntax reuse.
4 |
5 |
6 | ## Variant lists.
7 |
8 | message-variant-list =
9 | {
10 | *[key] Value
11 | }
12 |
13 | -term-variant-list =
14 | {
15 | *[key] Value
16 | }
17 |
18 |
19 | ## Variant expressions.
20 |
21 | message-variant-expression-placeable = {msg[case]}
22 | message-variant-expression-selector = {msg[case] ->
23 | *[key] Value
24 | }
25 |
26 | term-variant-expression-placeable = {-term[case]}
27 | term-variant-expression-selector = {-term[case] ->
28 | *[key] Value
29 | }
30 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/obsolete.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "The syntax in this file has been discontinued. It is no longer part of the\nFluent specification and should not be implemented nor used. We're keeping\nthese fixtures around to protect against accidental syntax reuse."
7 | },
8 | {
9 | "type": "GroupComment",
10 | "content": "Variant lists."
11 | },
12 | {
13 | "type": "Junk",
14 | "annotations": [],
15 | "content": "message-variant-list =\n {\n *[key] Value\n }\n\n"
16 | },
17 | {
18 | "type": "Junk",
19 | "annotations": [],
20 | "content": "-term-variant-list =\n {\n *[key] Value\n }\n\n\n"
21 | },
22 | {
23 | "type": "GroupComment",
24 | "content": "Variant expressions."
25 | },
26 | {
27 | "type": "Junk",
28 | "annotations": [],
29 | "content": "message-variant-expression-placeable = {msg[case]}\n"
30 | },
31 | {
32 | "type": "Junk",
33 | "annotations": [],
34 | "content": "message-variant-expression-selector = {msg[case] ->\n *[key] Value\n}\n\n"
35 | },
36 | {
37 | "type": "Junk",
38 | "annotations": [],
39 | "content": "term-variant-expression-placeable = {-term[case]}\n"
40 | },
41 | {
42 | "type": "Junk",
43 | "annotations": [],
44 | "content": "term-variant-expression-selector = {-term[case] ->\n *[key] Value\n}\n"
45 | }
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/placeables.ftl:
--------------------------------------------------------------------------------
1 | nested-placeable = {{{1}}}
2 | padded-placeable = { 1 }
3 | sparse-placeable = { { 1 } }
4 |
5 | # ERROR Unmatched opening brace
6 | unmatched-open1 = { 1
7 |
8 | # ERROR Unmatched opening brace
9 | unmatched-open2 = {{ 1 }
10 |
11 | # ERROR Unmatched closing brace
12 | unmatched-close1 = 1 }
13 |
14 | # ERROR Unmatched closing brace
15 | unmatched-close2 = { 1 }}
16 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/reference_expressions.ftl:
--------------------------------------------------------------------------------
1 | ## Reference expressions in placeables.
2 |
3 | message-reference-placeable = {msg}
4 | term-reference-placeable = {-term}
5 | variable-reference-placeable = {$var}
6 |
7 | # Function references are invalid outside of call expressions.
8 | # This parses as a valid MessageReference.
9 | function-reference-placeable = {FUN}
10 |
11 |
12 | ## Reference expressions in selectors.
13 |
14 | variable-reference-selector = {$var ->
15 | *[key] Value
16 | }
17 |
18 | # ERROR Message values may not be used as selectors.
19 | message-reference-selector = {msg ->
20 | *[key] Value
21 | }
22 | # ERROR Term values may not be used as selectors.
23 | term-reference-selector = {-term ->
24 | *[key] Value
25 | }
26 | # ERROR Function references are invalid outside of call expressions, and this
27 | # parses as a MessageReference which isn't a valid selector.
28 | function-expression-selector = {FUN ->
29 | *[key] Value
30 | }
31 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/select_expressions.ftl:
--------------------------------------------------------------------------------
1 | new-messages =
2 | { BUILTIN() ->
3 | [0] Zero
4 | *[other] {""}Other
5 | }
6 |
7 | valid-selector-term-attribute =
8 | { -term.case ->
9 | *[key] value
10 | }
11 |
12 | # ERROR Term values are not valid selectors
13 | invalid-selector-term-value =
14 | { -term ->
15 | *[key] value
16 | }
17 |
18 | # ERROR CallExpressions on Terms are similar to TermReferences
19 | invalid-selector-term-variant =
20 | { -term(case: "nominative") ->
21 | *[key] value
22 | }
23 |
24 | # ERROR Nested expressions are not valid selectors
25 | invalid-selector-nested-expression =
26 | { { 3 } ->
27 | *[key] default
28 | }
29 |
30 | # ERROR Select expressions are not valid selectors
31 | invalid-selector-select-expression =
32 | { { $sel ->
33 | *[key] value
34 | } ->
35 | *[key] default
36 | }
37 |
38 | empty-variant =
39 | { $sel ->
40 | *[key] {""}
41 | }
42 |
43 | reduced-whitespace =
44 | {FOO()->
45 | *[key] {""}
46 | }
47 |
48 | nested-select =
49 | { $sel ->
50 | *[one] { $sel ->
51 | *[two] Value
52 | }
53 | }
54 |
55 | # ERROR Missing selector
56 | missing-selector =
57 | {
58 | *[key] Value
59 | }
60 |
61 | # ERROR Missing line end after variant list
62 | missing-line-end =
63 | { $sel ->
64 | *[key] Value}
65 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/select_indent.ftl:
--------------------------------------------------------------------------------
1 | select-1tbs-inline = { $selector ->
2 | *[key] Value
3 | }
4 |
5 | select-1tbs-newline = {
6 | $selector ->
7 | *[key] Value
8 | }
9 |
10 | select-1tbs-indent = {
11 | $selector ->
12 | *[key] Value
13 | }
14 |
15 | select-allman-inline =
16 | { $selector ->
17 | *[key] Value
18 | [other] Other
19 | }
20 |
21 | select-allman-newline =
22 | {
23 | $selector ->
24 | *[key] Value
25 | }
26 |
27 | select-allman-indent =
28 | {
29 | $selector ->
30 | *[key] Value
31 | }
32 |
33 | select-gnu-inline =
34 | { $selector ->
35 | *[key] Value
36 | }
37 |
38 | select-gnu-newline =
39 | {
40 | $selector ->
41 | *[key] Value
42 | }
43 |
44 | select-gnu-indent =
45 | {
46 | $selector ->
47 | *[key] Value
48 | }
49 |
50 | select-no-indent =
51 | {
52 | $selector ->
53 | *[key] Value
54 | [other] Other
55 | }
56 |
57 | select-no-indent-multiline =
58 | {
59 | $selector ->
60 | *[key] Value
61 | Continued
62 | [other]
63 | Other
64 | Multiline
65 | }
66 |
67 | # ERROR (Multiline text must be indented)
68 | select-no-indent-multiline = { $selector ->
69 | *[key] Value
70 | Continued without indent.
71 | }
72 |
73 | select-flat =
74 | {
75 | $selector
76 | ->
77 | *[
78 | key
79 | ] Value
80 | [
81 | other
82 | ] Other
83 | }
84 |
85 | # Each line ends with 5 spaces.
86 | select-flat-with-trailing-spaces =
87 | {
88 | $selector
89 | ->
90 | *[
91 | key
92 | ] Value
93 | [
94 | other
95 | ] Other
96 | }
97 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/sparse_entries.ftl:
--------------------------------------------------------------------------------
1 | key01 =
2 |
3 |
4 | Value
5 |
6 | key02 =
7 |
8 |
9 | .attr = Attribute
10 |
11 |
12 | key03 =
13 | Value
14 | Continued
15 |
16 |
17 | Over multiple
18 | Lines
19 |
20 |
21 |
22 | .attr = Attribute
23 |
24 |
25 | key05 = Value
26 |
27 | key06 = { 1 ->
28 |
29 |
30 | [one] One
31 |
32 |
33 |
34 |
35 | *[two] Two
36 |
37 |
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/special_chars.ftl:
--------------------------------------------------------------------------------
1 | ## OK
2 |
3 | bracket-inline = [Value]
4 | dot-inline = .Value
5 | star-inline = *Value
6 |
7 | ## ERRORS
8 |
9 | bracket-newline =
10 | [Value]
11 | dot-newline =
12 | .Value
13 | star-newline =
14 | *Value
15 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/special_chars.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "GroupComment",
6 | "content": "OK"
7 | },
8 | {
9 | "type": "Message",
10 | "id": {
11 | "type": "Identifier",
12 | "name": "bracket-inline"
13 | },
14 | "value": {
15 | "type": "Pattern",
16 | "elements": [
17 | {
18 | "type": "TextElement",
19 | "value": "[Value]"
20 | }
21 | ]
22 | },
23 | "attributes": [],
24 | "comment": null
25 | },
26 | {
27 | "type": "Message",
28 | "id": {
29 | "type": "Identifier",
30 | "name": "dot-inline"
31 | },
32 | "value": {
33 | "type": "Pattern",
34 | "elements": [
35 | {
36 | "type": "TextElement",
37 | "value": ".Value"
38 | }
39 | ]
40 | },
41 | "attributes": [],
42 | "comment": null
43 | },
44 | {
45 | "type": "Message",
46 | "id": {
47 | "type": "Identifier",
48 | "name": "star-inline"
49 | },
50 | "value": {
51 | "type": "Pattern",
52 | "elements": [
53 | {
54 | "type": "TextElement",
55 | "value": "*Value"
56 | }
57 | ]
58 | },
59 | "attributes": [],
60 | "comment": null
61 | },
62 | {
63 | "type": "GroupComment",
64 | "content": "ERRORS"
65 | },
66 | {
67 | "type": "Junk",
68 | "annotations": [],
69 | "content": "bracket-newline =\n [Value]\n"
70 | },
71 | {
72 | "type": "Junk",
73 | "annotations": [],
74 | "content": "dot-newline =\n .Value\n"
75 | },
76 | {
77 | "type": "Junk",
78 | "annotations": [],
79 | "content": "star-newline =\n *Value\n"
80 | }
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/tab.ftl:
--------------------------------------------------------------------------------
1 | # OK (tab after = is part of the value)
2 | key01 = Value 01
3 |
4 | # Error (tab before =)
5 | key02 = Value 02
6 |
7 | # Error (tab is not a valid indent)
8 | key03 =
9 | This line isn't properly indented.
10 |
11 | # Partial Error (tab is not a valid indent)
12 | key04 =
13 | This line is indented by 4 spaces,
14 | whereas this line by 1 tab.
15 |
16 | # OK (value is a single tab)
17 | key05 =
18 |
19 | # OK (attribute value is two tabs)
20 | key06 =
21 | .attr =
22 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/term_parameters.ftl:
--------------------------------------------------------------------------------
1 | -term = { $arg ->
2 | *[key] Value
3 | }
4 |
5 | key01 = { -term }
6 | key02 = { -term () }
7 | key03 = { -term(arg: 1) }
8 | key04 = { -term("positional", narg1: 1, narg2: 2) }
9 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/terms.ftl:
--------------------------------------------------------------------------------
1 | -term01 = Value
2 | .attr = Attribute
3 |
4 | -term02 = {""}
5 |
6 | # JUNK Missing value
7 | -term03 =
8 | .attr = Attribute
9 |
10 | # JUNK Missing value
11 | # < whitespace >
12 | -term04 =
13 | .attr1 = Attribute 1
14 |
15 | # JUNK Missing value
16 | -term05 =
17 |
18 | # JUNK Missing value
19 | # < whitespace >
20 | -term06 =
21 |
22 | # JUNK Missing =
23 | -term07
24 |
25 | -term08=Value
26 | .attr=Attribute
27 |
28 | -term09 = Value
29 | .attr = Attribute
30 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/variables.ftl:
--------------------------------------------------------------------------------
1 | key01 = {$var}
2 | key02 = { $var }
3 | key03 = {
4 | $var
5 | }
6 | key04 = {
7 | $var}
8 |
9 |
10 | ## Errors
11 |
12 | # ERROR Missing variable identifier
13 | err01 = {$}
14 | # ERROR Double $$
15 | err02 = {$$var}
16 | # ERROR Invalid first char of the identifier
17 | err03 = {$-var}
18 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/variant_keys.ftl:
--------------------------------------------------------------------------------
1 | simple-identifier =
2 | { $sel ->
3 | *[key] value
4 | }
5 |
6 | identifier-surrounded-by-whitespace =
7 | { $sel ->
8 | *[ key ] value
9 | }
10 |
11 | int-number =
12 | { $sel ->
13 | *[1] value
14 | }
15 |
16 | float-number =
17 | { $sel ->
18 | *[3.14] value
19 | }
20 |
21 | # ERROR
22 | invalid-identifier =
23 | { $sel ->
24 | *[two words] value
25 | }
26 |
27 | # ERROR
28 | invalid-int =
29 | { $sel ->
30 | *[1 apple] value
31 | }
32 |
33 | # ERROR
34 | invalid-int =
35 | { $sel ->
36 | *[3.14 apples] value
37 | }
38 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/whitespace_in_value.ftl:
--------------------------------------------------------------------------------
1 | # Caution, lines 6 and 7 contain white-space-only lines
2 | key =
3 | first line
4 |
5 |
6 |
7 |
8 |
9 |
10 | last line
11 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/whitespace_in_value.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "key"
9 | },
10 | "value": {
11 | "type": "Pattern",
12 | "elements": [
13 | {
14 | "type": "TextElement",
15 | "value": "first line\n\n\n\n\n\n\nlast line"
16 | }
17 | ]
18 | },
19 | "attributes": [],
20 | "comment": {
21 | "type": "Comment",
22 | "content": "Caution, lines 6 and 7 contain white-space-only lines"
23 | }
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/zero_length.ftl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.syntax/tests/syntax/fixtures_reference/zero_length.ftl
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_reference/zero_length.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": []
4 | }
5 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_expression_with_wrong_attr.ftl:
--------------------------------------------------------------------------------
1 | err1 = { foo.23 }
2 | err2 = { foo. }
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_expression_with_wrong_attr.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0004",
10 | "arguments": [
11 | "a-zA-Z"
12 | ],
13 | "message": "Expected a character from range: \"a-zA-Z\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 13,
17 | "end": 13
18 | }
19 | }
20 | ],
21 | "content": "err1 = { foo.23 }\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 18
26 | }
27 | },
28 | {
29 | "type": "Junk",
30 | "annotations": [
31 | {
32 | "type": "Annotation",
33 | "code": "E0004",
34 | "arguments": [
35 | "a-zA-Z"
36 | ],
37 | "message": "Expected a character from range: \"a-zA-Z\"",
38 | "span": {
39 | "type": "Span",
40 | "start": 31,
41 | "end": 31
42 | }
43 | }
44 | ],
45 | "content": "err2 = { foo. }\n",
46 | "span": {
47 | "type": "Span",
48 | "start": 18,
49 | "end": 34
50 | }
51 | }
52 | ],
53 | "span": {
54 | "type": "Span",
55 | "start": 0,
56 | "end": 34
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_of_private_as_placeable.ftl:
--------------------------------------------------------------------------------
1 | err1 = { -brand.gender }
2 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_of_private_as_placeable.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0019",
10 | "arguments": [],
11 | "message": "Attributes of terms cannot be used as placeables",
12 | "span": {
13 | "type": "Span",
14 | "start": 23,
15 | "end": 23
16 | }
17 | }
18 | ],
19 | "content": "err1 = { -brand.gender }\n",
20 | "span": {
21 | "type": "Span",
22 | "start": 0,
23 | "end": 25
24 | }
25 | }
26 | ],
27 | "span": {
28 | "type": "Span",
29 | "start": 0,
30 | "end": 25
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_of_public_as_selector.ftl:
--------------------------------------------------------------------------------
1 | err1 =
2 | { foo.bar ->
3 | [1] One
4 | *[2] Two
5 | }
6 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_of_public_as_selector.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0018",
10 | "arguments": [],
11 | "message": "Attributes of messages cannot be used as selectors",
12 | "span": {
13 | "type": "Span",
14 | "start": 21,
15 | "end": 21
16 | }
17 | }
18 | ],
19 | "content": "err1 =\n { foo.bar ->\n [1] One\n *[2] Two\n }\n",
20 | "span": {
21 | "type": "Span",
22 | "start": 0,
23 | "end": 62
24 | }
25 | }
26 | ],
27 | "span": {
28 | "type": "Span",
29 | "start": 0,
30 | "end": 62
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_starts_from_nl.ftl:
--------------------------------------------------------------------------------
1 | foo = Value
2 | .attr = Value 2
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_starts_from_nl.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "foo",
9 | "span": {
10 | "type": "Span",
11 | "start": 0,
12 | "end": 3
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "TextElement",
20 | "value": "Value",
21 | "span": {
22 | "type": "Span",
23 | "start": 6,
24 | "end": 11
25 | }
26 | }
27 | ],
28 | "span": {
29 | "type": "Span",
30 | "start": 6,
31 | "end": 11
32 | }
33 | },
34 | "attributes": [
35 | {
36 | "type": "Attribute",
37 | "id": {
38 | "type": "Identifier",
39 | "name": "attr",
40 | "span": {
41 | "type": "Span",
42 | "start": 13,
43 | "end": 17
44 | }
45 | },
46 | "value": {
47 | "type": "Pattern",
48 | "elements": [
49 | {
50 | "type": "TextElement",
51 | "value": "Value 2",
52 | "span": {
53 | "type": "Span",
54 | "start": 20,
55 | "end": 27
56 | }
57 | }
58 | ],
59 | "span": {
60 | "type": "Span",
61 | "start": 20,
62 | "end": 27
63 | }
64 | },
65 | "span": {
66 | "type": "Span",
67 | "start": 12,
68 | "end": 27
69 | }
70 | }
71 | ],
72 | "comment": null,
73 | "span": {
74 | "type": "Span",
75 | "start": 0,
76 | "end": 27
77 | }
78 | }
79 | ],
80 | "span": {
81 | "type": "Span",
82 | "start": 0,
83 | "end": 28
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_with_empty_pattern.ftl:
--------------------------------------------------------------------------------
1 | key1 = Value 1
2 | .attr =
3 |
4 | key2 =
5 | .attr =
6 |
7 | key3 =
8 | .attr1 = Attr 1
9 | .attr2 =
10 |
11 | key4 =
12 | .attr1 =
13 | .attr2 = Attr 2
14 |
15 | key5 =
16 | .attr1 =
17 | .attr2 =
18 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_with_empty_pattern.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0012",
10 | "arguments": [],
11 | "message": "Expected value",
12 | "span": {
13 | "type": "Span",
14 | "start": 26,
15 | "end": 26
16 | }
17 | }
18 | ],
19 | "content": "key1 = Value 1\n .attr =\n\n",
20 | "span": {
21 | "type": "Span",
22 | "start": 0,
23 | "end": 28
24 | }
25 | },
26 | {
27 | "type": "Junk",
28 | "annotations": [
29 | {
30 | "type": "Annotation",
31 | "code": "E0012",
32 | "arguments": [],
33 | "message": "Expected value",
34 | "span": {
35 | "type": "Span",
36 | "start": 46,
37 | "end": 46
38 | }
39 | }
40 | ],
41 | "content": "key2 =\n .attr =\n\n",
42 | "span": {
43 | "type": "Span",
44 | "start": 28,
45 | "end": 48
46 | }
47 | },
48 | {
49 | "type": "Junk",
50 | "annotations": [
51 | {
52 | "type": "Annotation",
53 | "code": "E0012",
54 | "arguments": [],
55 | "message": "Expected value",
56 | "span": {
57 | "type": "Span",
58 | "start": 87,
59 | "end": 87
60 | }
61 | }
62 | ],
63 | "content": "key3 =\n .attr1 = Attr 1\n .attr2 =\n\n",
64 | "span": {
65 | "type": "Span",
66 | "start": 48,
67 | "end": 89
68 | }
69 | },
70 | {
71 | "type": "Junk",
72 | "annotations": [
73 | {
74 | "type": "Annotation",
75 | "code": "E0012",
76 | "arguments": [],
77 | "message": "Expected value",
78 | "span": {
79 | "type": "Span",
80 | "start": 108,
81 | "end": 108
82 | }
83 | }
84 | ],
85 | "content": "key4 =\n .attr1 =\n .attr2 = Attr 2\n\n",
86 | "span": {
87 | "type": "Span",
88 | "start": 89,
89 | "end": 130
90 | }
91 | },
92 | {
93 | "type": "Junk",
94 | "annotations": [
95 | {
96 | "type": "Annotation",
97 | "code": "E0012",
98 | "arguments": [],
99 | "message": "Expected value",
100 | "span": {
101 | "type": "Span",
102 | "start": 149,
103 | "end": 149
104 | }
105 | }
106 | ],
107 | "content": "key5 =\n .attr1 =\n .attr2 =\n",
108 | "span": {
109 | "type": "Span",
110 | "start": 130,
111 | "end": 163
112 | }
113 | }
114 | ],
115 | "span": {
116 | "type": "Span",
117 | "start": 0,
118 | "end": 163
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_without_equal_sign.ftl:
--------------------------------------------------------------------------------
1 | key = Value
2 | .label
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/attribute_without_equal_sign.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0003",
10 | "arguments": [
11 | "="
12 | ],
13 | "message": "Expected token: \"=\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 22,
17 | "end": 22
18 | }
19 | }
20 | ],
21 | "content": "key = Value\n .label\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 23
26 | }
27 | }
28 | ],
29 | "span": {
30 | "type": "Span",
31 | "start": 0,
32 | "end": 23
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/blank_lines.ftl:
--------------------------------------------------------------------------------
1 | ### NOTE: Disable final newline insertion and trimming when editing this file.
2 |
3 | key01 = Value 01
4 |
5 | key02 = Value 02
6 |
7 |
8 | key03 =
9 |
10 | Value 03
11 |
12 | Continued
13 |
14 | # There are four spaces on the line between "Value 04" and "Continued".
15 | key04 =
16 |
17 | Value 04
18 |
19 | Continued
20 |
21 | # There are four spaces on the line following "Value 05".
22 | key05 =
23 | Value 05
24 |
25 | # There are four spaces on the line following "Value 06".
26 | key06 = Value 06
27 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/broken_number.ftl:
--------------------------------------------------------------------------------
1 | err01 = { -2.4.5 }
2 | err02 = { -2.4. }
3 | err03 = { -.4 }
4 | err04 = { -2..4 }
5 | err05 = { 24d }
6 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/broken_number.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0003",
10 | "arguments": [
11 | "}"
12 | ],
13 | "message": "Expected token: \"}\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 14,
17 | "end": 14
18 | }
19 | }
20 | ],
21 | "content": "err01 = { -2.4.5 }\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 19
26 | }
27 | },
28 | {
29 | "type": "Junk",
30 | "annotations": [
31 | {
32 | "type": "Annotation",
33 | "code": "E0003",
34 | "arguments": [
35 | "}"
36 | ],
37 | "message": "Expected token: \"}\"",
38 | "span": {
39 | "type": "Span",
40 | "start": 33,
41 | "end": 33
42 | }
43 | }
44 | ],
45 | "content": "err02 = { -2.4. }\n",
46 | "span": {
47 | "type": "Span",
48 | "start": 19,
49 | "end": 37
50 | }
51 | },
52 | {
53 | "type": "Junk",
54 | "annotations": [
55 | {
56 | "type": "Annotation",
57 | "code": "E0004",
58 | "arguments": [
59 | "a-zA-Z"
60 | ],
61 | "message": "Expected a character from range: \"a-zA-Z\"",
62 | "span": {
63 | "type": "Span",
64 | "start": 48,
65 | "end": 48
66 | }
67 | }
68 | ],
69 | "content": "err03 = { -.4 }\n",
70 | "span": {
71 | "type": "Span",
72 | "start": 37,
73 | "end": 53
74 | }
75 | },
76 | {
77 | "type": "Junk",
78 | "annotations": [
79 | {
80 | "type": "Annotation",
81 | "code": "E0004",
82 | "arguments": [
83 | "0-9"
84 | ],
85 | "message": "Expected a character from range: \"0-9\"",
86 | "span": {
87 | "type": "Span",
88 | "start": 66,
89 | "end": 66
90 | }
91 | }
92 | ],
93 | "content": "err04 = { -2..4 }\n",
94 | "span": {
95 | "type": "Span",
96 | "start": 53,
97 | "end": 71
98 | }
99 | },
100 | {
101 | "type": "Junk",
102 | "annotations": [
103 | {
104 | "type": "Annotation",
105 | "code": "E0003",
106 | "arguments": [
107 | "}"
108 | ],
109 | "message": "Expected token: \"}\"",
110 | "span": {
111 | "type": "Span",
112 | "start": 83,
113 | "end": 83
114 | }
115 | }
116 | ],
117 | "content": "err05 = { 24d }\n",
118 | "span": {
119 | "type": "Span",
120 | "start": 71,
121 | "end": 87
122 | }
123 | }
124 | ],
125 | "span": {
126 | "type": "Span",
127 | "start": 0,
128 | "end": 87
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/call_expression_errors.ftl:
--------------------------------------------------------------------------------
1 | err01 = { no-caps-name() }
2 | err02 = { BUILTIN(2: "foo") }
3 | err03 = { BUILTIN(key: foo) }
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/call_expression_errors.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0008",
10 | "arguments": [],
11 | "message": "The callee has to be an upper-case identifier or a term",
12 | "span": {
13 | "type": "Span",
14 | "start": 22,
15 | "end": 22
16 | }
17 | }
18 | ],
19 | "content": "err01 = { no-caps-name() }\n",
20 | "span": {
21 | "type": "Span",
22 | "start": 0,
23 | "end": 27
24 | }
25 | },
26 | {
27 | "type": "Junk",
28 | "annotations": [
29 | {
30 | "type": "Annotation",
31 | "code": "E0009",
32 | "arguments": [],
33 | "message": "The argument name has to be a simple identifier",
34 | "span": {
35 | "type": "Span",
36 | "start": 46,
37 | "end": 46
38 | }
39 | }
40 | ],
41 | "content": "err02 = { BUILTIN(2: \"foo\") }\n",
42 | "span": {
43 | "type": "Span",
44 | "start": 27,
45 | "end": 57
46 | }
47 | },
48 | {
49 | "type": "Junk",
50 | "annotations": [
51 | {
52 | "type": "Annotation",
53 | "code": "E0014",
54 | "arguments": [],
55 | "message": "Expected literal",
56 | "span": {
57 | "type": "Span",
58 | "start": 80,
59 | "end": 80
60 | }
61 | }
62 | ],
63 | "content": "err03 = { BUILTIN(key: foo) }\n",
64 | "span": {
65 | "type": "Span",
66 | "start": 57,
67 | "end": 87
68 | }
69 | }
70 | ],
71 | "span": {
72 | "type": "Span",
73 | "start": 0,
74 | "end": 87
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/comment_with_eof.ftl:
--------------------------------------------------------------------------------
1 | # This is a comment with no new line
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/comment_with_eof.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Comment",
6 | "content": "This is a comment with no new line",
7 | "span": {
8 | "type": "Span",
9 | "start": 0,
10 | "end": 36
11 | }
12 | }
13 | ],
14 | "span": {
15 | "type": "Span",
16 | "start": 0,
17 | "end": 36
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/crlf.ftl:
--------------------------------------------------------------------------------
1 |
2 | key01 = Value 01
3 | key02 =
4 |
5 | Value 02
6 | Continued
7 |
8 | .title = Title
9 |
10 | # ERROR Unclosed StringLiteral
11 | err03 = { "str
12 |
13 | # ERROR Missing newline after ->.
14 | err04 = { $sel -> }
15 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/dash_at_eof.ftl:
--------------------------------------------------------------------------------
1 | ### BE CAREFUL WHEN EDITING THIS FILE
2 | ###
3 | ### The last character in this file is the dash ("-") on line 8.
4 | ### We want to test a literal which starts like a negative number.
5 | ### Most editors automatically add a trailing newline at EOF.
6 | ### If you edit this file make sure to turn this behavior off.
7 |
8 | key1 = {-
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/dash_at_eof.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "BE CAREFUL WHEN EDITING THIS FILE\n\nThe last character in this file is the dash (\"-\") on line 8.\nWe want to test a literal which starts like a negative number.\nMost editors automatically add a trailing newline at EOF.\nIf you edit this file make sure to turn this behavior off.",
7 | "span": {
8 | "type": "Span",
9 | "start": 0,
10 | "end": 298
11 | }
12 | },
13 | {
14 | "type": "Junk",
15 | "annotations": [
16 | {
17 | "type": "Annotation",
18 | "code": "E0004",
19 | "arguments": [
20 | "a-zA-Z"
21 | ],
22 | "message": "Expected a character from range: \"a-zA-Z\"",
23 | "span": {
24 | "type": "Span",
25 | "start": 309,
26 | "end": 309
27 | }
28 | }
29 | ],
30 | "content": "key1 = {-",
31 | "span": {
32 | "type": "Span",
33 | "start": 300,
34 | "end": 309
35 | }
36 | }
37 | ],
38 | "span": {
39 | "type": "Span",
40 | "start": 0,
41 | "end": 309
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/elements_indent.ftl:
--------------------------------------------------------------------------------
1 | foo = Foo
2 | .attr = Foo Attr
3 |
4 | bar = Bar
5 | .attr1 = Bar Attr 1
6 | .attr2 = Bar Attr 2
7 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/empty_resource.ftl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectfluent/python-fluent/e4fc3c20e632babfa70d9cc6b8eaf4141c2aa83f/fluent.syntax/tests/syntax/fixtures_structure/empty_resource.ftl
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/empty_resource.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [],
4 | "span": {
5 | "type": "Span",
6 | "start": 0,
7 | "end": 0
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/empty_resource_with_ws.ftl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/empty_resource_with_ws.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [],
4 | "span": {
5 | "type": "Span",
6 | "start": 0,
7 | "end": 10
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/escape_sequences.ftl:
--------------------------------------------------------------------------------
1 | ## Literal text
2 | text-backslash-one = Value with \ a backslash
3 | text-backslash-two = Value with \\ two backslashes
4 | text-backslash-brace = Value with \{placeable}
5 | text-backslash-u = \u0041
6 | text-backslash-backslash-u = \\u0041
7 |
8 | ## String literals
9 | quote-in-string = {"\""}
10 | backslash-in-string = {"\\"}
11 | # ERROR Mismatched quote
12 | mismatched-quote = {"\\""}
13 | # ERROR Unknown escape
14 | unknown-escape = {"\x"}
15 |
16 | ## Unicode escapes
17 | string-unicode-sequence = {"\u0041"}
18 | string-escaped-unicode = {"\\u0041"}
19 | # ERROR Unknown escape
20 | unknown-unicode = {"\u000z"}
21 |
22 | ## Literal braces
23 | brace-open = An opening {"{"} brace.
24 | brace-close = A closing {"}"} brace.
25 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/expressions_call_args.ftl:
--------------------------------------------------------------------------------
1 | key = { FOO(arg1: 1,
2 | arg2: 2) }
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/indent.ftl:
--------------------------------------------------------------------------------
1 | err01 = A
2 |
3 | key2 = {
4 | a }
5 |
6 | key3 = { a
7 | }
8 |
9 | key4 = {
10 | { a }}
11 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/junk.ftl:
--------------------------------------------------------------------------------
1 | err01 = {1xx}
2 | err02 = {1xx}
3 |
4 | err03 = {1xx}
5 | 1xx
6 |
7 | err04 = {1xx}
8 |
9 | 1xx
10 |
11 | err05 = {
12 |
13 | 1xx
14 |
15 | err06 = {1xx
16 |
17 | .attr = Value
18 |
19 | err07 = {
20 |
21 | key08 = Value
22 |
23 | err09 = {
24 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/leading_dots.ftl:
--------------------------------------------------------------------------------
1 | key01 = .Value
2 | key02 = …Value
3 | key03 = {"."}Value
4 | key04 =
5 | {"."}Value
6 |
7 | key05 = Value
8 | {"."}Continued
9 |
10 | key06 = .Value
11 | {"."}Continued
12 |
13 | # ERROR (attr .Continued must have a value)
14 | key07 = Value
15 | .Continued
16 |
17 | # ERROR (attr .Value must have a value)
18 | key08 =
19 | .Value
20 |
21 | # ERROR (attr .Value must have a value)
22 | key09 =
23 | .Value
24 | Continued
25 |
26 | key10 =
27 | .Value = which looks like an attribute
28 | Continued
29 |
30 | key11 =
31 | {"."}Value = which looks like an attribute
32 | Continued
33 |
34 | key12 =
35 | .accesskey =
36 | A
37 |
38 | key13 =
39 | .attribute = .Value
40 |
41 | key14 =
42 | .attribute =
43 | {"."}Value
44 |
45 | key15 =
46 | { 1 ->
47 | [one] .Value
48 | *[other]
49 | {"."}Value
50 | }
51 |
52 | # ERROR (variant must have a value)
53 | key16 =
54 | { 1 ->
55 | *[one]
56 | .Value
57 | }
58 |
59 | # ERROR (unclosed placeable)
60 | key17 =
61 | { 1 ->
62 | *[one] Value
63 | .Continued
64 | }
65 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/leading_empty_lines.ftl:
--------------------------------------------------------------------------------
1 |
2 |
3 | key01 = Value
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/leading_empty_lines.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "key01",
9 | "span": {
10 | "type": "Span",
11 | "start": 2,
12 | "end": 7
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "TextElement",
20 | "value": "Value",
21 | "span": {
22 | "type": "Span",
23 | "start": 10,
24 | "end": 15
25 | }
26 | }
27 | ],
28 | "span": {
29 | "type": "Span",
30 | "start": 10,
31 | "end": 15
32 | }
33 | },
34 | "attributes": [],
35 | "comment": null,
36 | "span": {
37 | "type": "Span",
38 | "start": 2,
39 | "end": 15
40 | }
41 | }
42 | ],
43 | "span": {
44 | "type": "Span",
45 | "start": 0,
46 | "end": 16
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/leading_empty_lines_with_ws.ftl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | key01 = Value
6 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/leading_empty_lines_with_ws.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "key01",
9 | "span": {
10 | "type": "Span",
11 | "start": 10,
12 | "end": 15
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "TextElement",
20 | "value": "Value",
21 | "span": {
22 | "type": "Span",
23 | "start": 18,
24 | "end": 23
25 | }
26 | }
27 | ],
28 | "span": {
29 | "type": "Span",
30 | "start": 18,
31 | "end": 23
32 | }
33 | },
34 | "attributes": [],
35 | "comment": null,
36 | "span": {
37 | "type": "Span",
38 | "start": 10,
39 | "end": 23
40 | }
41 | }
42 | ],
43 | "span": {
44 | "type": "Span",
45 | "start": 0,
46 | "end": 24
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/message_reference_as_selector.ftl:
--------------------------------------------------------------------------------
1 | err1 =
2 | { foo ->
3 | *[1] One
4 | }
5 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/message_reference_as_selector.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0016",
10 | "arguments": [],
11 | "message": "Message references cannot be used as selectors",
12 | "span": {
13 | "type": "Span",
14 | "start": 17,
15 | "end": 17
16 | }
17 | }
18 | ],
19 | "content": "err1 =\n { foo ->\n *[1] One\n }\n",
20 | "span": {
21 | "type": "Span",
22 | "start": 0,
23 | "end": 42
24 | }
25 | }
26 | ],
27 | "span": {
28 | "type": "Span",
29 | "start": 0,
30 | "end": 42
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/message_with_empty_multiline_pattern.ftl:
--------------------------------------------------------------------------------
1 | ### BE CAREFUL WHEN EDITING THIS FILE
2 | ###
3 | ### The last character in this file is the space (" ") on line 14.
4 | ### We want to test a message with no value and with no EOL at the
5 | ### end of the fie. Most editors automatically add a trailing newline
6 | ### at EOF. If you edit this file make sure to turn this behavior off.
7 |
8 | key1 =
9 |
10 |
11 |
12 | key2 =
13 |
14 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/message_with_empty_multiline_pattern.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "BE CAREFUL WHEN EDITING THIS FILE\n\nThe last character in this file is the space (\" \") on line 14.\nWe want to test a message with no value and with no EOL at the\nend of the fie. Most editors automatically add a trailing newline\nat EOF. If you edit this file make sure to turn this behavior off.",
7 | "span": {
8 | "type": "Span",
9 | "start": 0,
10 | "end": 316
11 | }
12 | },
13 | {
14 | "type": "Junk",
15 | "annotations": [
16 | {
17 | "type": "Annotation",
18 | "code": "E0005",
19 | "arguments": [
20 | "key1"
21 | ],
22 | "message": "Expected message \"key1\" to have a value or attributes",
23 | "span": {
24 | "type": "Span",
25 | "start": 324,
26 | "end": 324
27 | }
28 | }
29 | ],
30 | "content": "key1 =\n\n \n\n",
31 | "span": {
32 | "type": "Span",
33 | "start": 318,
34 | "end": 329
35 | }
36 | },
37 | {
38 | "type": "Junk",
39 | "annotations": [
40 | {
41 | "type": "Annotation",
42 | "code": "E0005",
43 | "arguments": [
44 | "key2"
45 | ],
46 | "message": "Expected message \"key2\" to have a value or attributes",
47 | "span": {
48 | "type": "Span",
49 | "start": 335,
50 | "end": 335
51 | }
52 | }
53 | ],
54 | "content": "key2 =\n\n ",
55 | "span": {
56 | "type": "Span",
57 | "start": 329,
58 | "end": 338
59 | }
60 | }
61 | ],
62 | "span": {
63 | "type": "Span",
64 | "start": 0,
65 | "end": 338
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/message_with_empty_pattern.ftl:
--------------------------------------------------------------------------------
1 | ### BE CAREFUL WHEN EDITING THIS FILE
2 | ###
3 | ### The last character in this file is the equals sign in `key5 =` in line 18.
4 | ### We want to test a message with no value and with no EOL after its
5 | ### identifier. Most editors automatically add a trailing newline at EOF.
6 | ### If you edit this file make sure to turn this behavior off.
7 |
8 | key1 =
9 |
10 | key2 =
11 |
12 | key3 =
13 | .attr = Attr
14 |
15 | key4 =
16 | .attr = Attr
17 |
18 | key5 =
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/multiline-comment.ftl:
--------------------------------------------------------------------------------
1 |
2 | # This is
3 | #
4 | # An example of a multiline comment
5 |
6 | key = Value
7 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/multiline-comment.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Comment",
6 | "content": "This is\n\nAn example of a multiline comment",
7 | "span": {
8 | "type": "Span",
9 | "start": 1,
10 | "end": 48
11 | }
12 | },
13 | {
14 | "type": "Message",
15 | "id": {
16 | "type": "Identifier",
17 | "name": "key",
18 | "span": {
19 | "type": "Span",
20 | "start": 50,
21 | "end": 53
22 | }
23 | },
24 | "value": {
25 | "type": "Pattern",
26 | "elements": [
27 | {
28 | "type": "TextElement",
29 | "value": "Value",
30 | "span": {
31 | "type": "Span",
32 | "start": 56,
33 | "end": 61
34 | }
35 | }
36 | ],
37 | "span": {
38 | "type": "Span",
39 | "start": 56,
40 | "end": 61
41 | }
42 | },
43 | "attributes": [],
44 | "comment": null,
45 | "span": {
46 | "type": "Span",
47 | "start": 50,
48 | "end": 61
49 | }
50 | }
51 | ],
52 | "span": {
53 | "type": "Span",
54 | "start": 0,
55 | "end": 62
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/multiline_pattern.ftl:
--------------------------------------------------------------------------------
1 | key01 = Value
2 | Continued here.
3 |
4 | key02 =
5 | Value
6 | Continued here.
7 |
8 | # ERROR "Continued" looks like a new message.
9 | # key03 parses fine with just "Value".
10 | key03 =
11 | Value
12 | Continued here
13 | and here.
14 |
15 | # ERROR "Continued" and "and" look like new messages
16 | # key04 parses fine with just "Value".
17 | key04 =
18 | Value
19 | Continued here
20 | and even here.
21 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/multiline_string.ftl:
--------------------------------------------------------------------------------
1 | err01 = { BUILTIN(key: "
2 | text
3 | ") }
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/multiline_string.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0020",
10 | "arguments": [],
11 | "message": "Unterminated string expression",
12 | "span": {
13 | "type": "Span",
14 | "start": 24,
15 | "end": 24
16 | }
17 | }
18 | ],
19 | "content": "err01 = { BUILTIN(key: \"\n text\n \") }\n",
20 | "span": {
21 | "type": "Span",
22 | "start": 0,
23 | "end": 40
24 | }
25 | }
26 | ],
27 | "span": {
28 | "type": "Span",
29 | "start": 0,
30 | "end": 40
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/multiline_with_non_empty_first_line.ftl:
--------------------------------------------------------------------------------
1 | key = Value
2 | Value 2
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/multiline_with_non_empty_first_line.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "key",
9 | "span": {
10 | "type": "Span",
11 | "start": 0,
12 | "end": 3
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "TextElement",
20 | "value": "Value\nValue 2",
21 | "span": {
22 | "type": "Span",
23 | "start": 6,
24 | "end": 23
25 | }
26 | }
27 | ],
28 | "span": {
29 | "type": "Span",
30 | "start": 6,
31 | "end": 23
32 | }
33 | },
34 | "attributes": [],
35 | "comment": null,
36 | "span": {
37 | "type": "Span",
38 | "start": 0,
39 | "end": 23
40 | }
41 | }
42 | ],
43 | "span": {
44 | "type": "Span",
45 | "start": 0,
46 | "end": 24
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/multiline_with_placeables.ftl:
--------------------------------------------------------------------------------
1 | key =
2 | Foo { bar }
3 | Baz
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/multiline_with_placeables.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "key",
9 | "span": {
10 | "type": "Span",
11 | "start": 0,
12 | "end": 3
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "TextElement",
20 | "value": "Foo ",
21 | "span": {
22 | "type": "Span",
23 | "start": 10,
24 | "end": 14
25 | }
26 | },
27 | {
28 | "type": "Placeable",
29 | "expression": {
30 | "type": "MessageReference",
31 | "id": {
32 | "type": "Identifier",
33 | "name": "bar",
34 | "span": {
35 | "type": "Span",
36 | "start": 16,
37 | "end": 19
38 | }
39 | },
40 | "attribute": null,
41 | "span": {
42 | "type": "Span",
43 | "start": 16,
44 | "end": 19
45 | }
46 | },
47 | "span": {
48 | "type": "Span",
49 | "start": 14,
50 | "end": 21
51 | }
52 | },
53 | {
54 | "type": "TextElement",
55 | "value": "\nBaz",
56 | "span": {
57 | "type": "Span",
58 | "start": 21,
59 | "end": 29
60 | }
61 | }
62 | ],
63 | "span": {
64 | "type": "Span",
65 | "start": 6,
66 | "end": 29
67 | }
68 | },
69 | "attributes": [],
70 | "comment": null,
71 | "span": {
72 | "type": "Span",
73 | "start": 0,
74 | "end": 29
75 | }
76 | }
77 | ],
78 | "span": {
79 | "type": "Span",
80 | "start": 0,
81 | "end": 30
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/non_id_attribute_name.ftl:
--------------------------------------------------------------------------------
1 | err01 = Value
2 | .2 = Foo
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/non_id_attribute_name.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0004",
10 | "arguments": [
11 | "a-zA-Z"
12 | ],
13 | "message": "Expected a character from range: \"a-zA-Z\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 19,
17 | "end": 19
18 | }
19 | }
20 | ],
21 | "content": "err01 = Value\n .2 = Foo\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 27
26 | }
27 | }
28 | ],
29 | "span": {
30 | "type": "Span",
31 | "start": 0,
32 | "end": 27
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/placeable_at_eol.ftl:
--------------------------------------------------------------------------------
1 | key1 =
2 | A multiline message with a { placeable }
3 | at the end of line. The message should
4 | consist of three lines of text.
5 |
6 | key2 =
7 | A multiline message with a { placeable }
8 |
9 | key3 = A singleline message with a { placeable }
10 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/placeable_at_line_extremes.ftl:
--------------------------------------------------------------------------------
1 | key1 =
2 | { foo }
3 |
4 | key2 =
5 | Foo { foo }
6 |
7 | key3 =
8 | { foo } Foo
9 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/placeable_in_placeable.ftl:
--------------------------------------------------------------------------------
1 | key1 = {{ foo }}
2 |
3 | key2 = { { foo } }
4 |
5 | key3 =
6 | {
7 | { foo }
8 | }
9 |
10 | err1 = { { foo }
11 |
12 | err2 = { foo } }
13 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/placeable_without_close_bracket.ftl:
--------------------------------------------------------------------------------
1 | err01 = { $num
2 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/placeable_without_close_bracket.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0003",
10 | "arguments": [
11 | "}"
12 | ],
13 | "message": "Expected token: \"}\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 15,
17 | "end": 15
18 | }
19 | }
20 | ],
21 | "content": "err01 = { $num\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 15
26 | }
27 | }
28 | ],
29 | "span": {
30 | "type": "Span",
31 | "start": 0,
32 | "end": 15
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/resource_comment.ftl:
--------------------------------------------------------------------------------
1 | ### This is a resource wide comment
2 | ### It's multiline
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/resource_comment.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "This is a resource wide comment\nIt's multiline",
7 | "span": {
8 | "type": "Span",
9 | "start": 0,
10 | "end": 54
11 | }
12 | }
13 | ],
14 | "span": {
15 | "type": "Span",
16 | "start": 0,
17 | "end": 55
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/resource_comment_trailing_line.ftl:
--------------------------------------------------------------------------------
1 | ### This is a comment
2 | ### This comment is multiline
3 | ###
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/resource_comment_trailing_line.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "ResourceComment",
6 | "content": "This is a comment\nThis comment is multiline\n",
7 | "span": {
8 | "type": "Span",
9 | "start": 0,
10 | "end": 55
11 | }
12 | }
13 | ],
14 | "span": {
15 | "type": "Span",
16 | "start": 0,
17 | "end": 56
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/second_attribute_starts_from_nl.ftl:
--------------------------------------------------------------------------------
1 | key = Value
2 | .label = Value
3 | .accesskey = K
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/second_attribute_starts_from_nl.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "key",
9 | "span": {
10 | "type": "Span",
11 | "start": 0,
12 | "end": 3
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "TextElement",
20 | "value": "Value",
21 | "span": {
22 | "type": "Span",
23 | "start": 6,
24 | "end": 11
25 | }
26 | }
27 | ],
28 | "span": {
29 | "type": "Span",
30 | "start": 6,
31 | "end": 11
32 | }
33 | },
34 | "attributes": [
35 | {
36 | "type": "Attribute",
37 | "id": {
38 | "type": "Identifier",
39 | "name": "label",
40 | "span": {
41 | "type": "Span",
42 | "start": 17,
43 | "end": 22
44 | }
45 | },
46 | "value": {
47 | "type": "Pattern",
48 | "elements": [
49 | {
50 | "type": "TextElement",
51 | "value": "Value",
52 | "span": {
53 | "type": "Span",
54 | "start": 25,
55 | "end": 30
56 | }
57 | }
58 | ],
59 | "span": {
60 | "type": "Span",
61 | "start": 25,
62 | "end": 30
63 | }
64 | },
65 | "span": {
66 | "type": "Span",
67 | "start": 16,
68 | "end": 30
69 | }
70 | },
71 | {
72 | "type": "Attribute",
73 | "id": {
74 | "type": "Identifier",
75 | "name": "accesskey",
76 | "span": {
77 | "type": "Span",
78 | "start": 32,
79 | "end": 41
80 | }
81 | },
82 | "value": {
83 | "type": "Pattern",
84 | "elements": [
85 | {
86 | "type": "TextElement",
87 | "value": "K",
88 | "span": {
89 | "type": "Span",
90 | "start": 44,
91 | "end": 45
92 | }
93 | }
94 | ],
95 | "span": {
96 | "type": "Span",
97 | "start": 44,
98 | "end": 45
99 | }
100 | },
101 | "span": {
102 | "type": "Span",
103 | "start": 31,
104 | "end": 45
105 | }
106 | }
107 | ],
108 | "comment": null,
109 | "span": {
110 | "type": "Span",
111 | "start": 0,
112 | "end": 45
113 | }
114 | }
115 | ],
116 | "span": {
117 | "type": "Span",
118 | "start": 0,
119 | "end": 46
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/select_expression_with_two_selectors.ftl:
--------------------------------------------------------------------------------
1 | err01 = { $foo $faa }
2 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/select_expression_with_two_selectors.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0003",
10 | "arguments": [
11 | "}"
12 | ],
13 | "message": "Expected token: \"}\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 15,
17 | "end": 15
18 | }
19 | }
20 | ],
21 | "content": "err01 = { $foo $faa }\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 22
26 | }
27 | }
28 | ],
29 | "span": {
30 | "type": "Span",
31 | "start": 0,
32 | "end": 22
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/select_expression_without_arrow.ftl:
--------------------------------------------------------------------------------
1 | err01 = { $foo - }
2 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/select_expression_without_arrow.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0003",
10 | "arguments": [
11 | "}"
12 | ],
13 | "message": "Expected token: \"}\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 15,
17 | "end": 15
18 | }
19 | }
20 | ],
21 | "content": "err01 = { $foo - }\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 19
26 | }
27 | }
28 | ],
29 | "span": {
30 | "type": "Span",
31 | "start": 0,
32 | "end": 19
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/select_expression_without_variants.ftl:
--------------------------------------------------------------------------------
1 | err01 = { $foo -> }
2 |
3 | err02 = { $foo ->
4 | }
5 |
6 | err03 = { $foo ->
7 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/select_expression_without_variants.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0003",
10 | "arguments": [
11 | ""
12 | ],
13 | "message": "Expected token: \"\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 18,
17 | "end": 18
18 | }
19 | }
20 | ],
21 | "content": "err01 = { $foo -> }\n\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 21
26 | }
27 | },
28 | {
29 | "type": "Junk",
30 | "annotations": [
31 | {
32 | "type": "Annotation",
33 | "code": "E0011",
34 | "arguments": [],
35 | "message": "Expected at least one variant after \"->\"",
36 | "span": {
37 | "type": "Span",
38 | "start": 43,
39 | "end": 43
40 | }
41 | }
42 | ],
43 | "content": "err02 = { $foo ->\n }\n\n",
44 | "span": {
45 | "type": "Span",
46 | "start": 21,
47 | "end": 46
48 | }
49 | },
50 | {
51 | "type": "Junk",
52 | "annotations": [
53 | {
54 | "type": "Annotation",
55 | "code": "E0011",
56 | "arguments": [],
57 | "message": "Expected at least one variant after \"->\"",
58 | "span": {
59 | "type": "Span",
60 | "start": 64,
61 | "end": 64
62 | }
63 | }
64 | ],
65 | "content": "err03 = { $foo ->\n",
66 | "span": {
67 | "type": "Span",
68 | "start": 46,
69 | "end": 64
70 | }
71 | }
72 | ],
73 | "span": {
74 | "type": "Span",
75 | "start": 0,
76 | "end": 64
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/select_expressions.ftl:
--------------------------------------------------------------------------------
1 | # ERROR No blanks are allowed between * and [.
2 | err01 = { $sel ->
3 | * [key] Value
4 | }
5 |
6 | # ERROR Missing default variant.
7 | err02 = { $sel ->
8 | [key] Value
9 | }
10 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/select_expressions.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Comment",
6 | "content": "ERROR No blanks are allowed between * and [.",
7 | "span": {
8 | "type": "Span",
9 | "start": 0,
10 | "end": 46
11 | }
12 | },
13 | {
14 | "type": "Junk",
15 | "annotations": [
16 | {
17 | "type": "Annotation",
18 | "code": "E0011",
19 | "arguments": [],
20 | "message": "Expected at least one variant after \"->\"",
21 | "span": {
22 | "type": "Span",
23 | "start": 69,
24 | "end": 69
25 | }
26 | }
27 | ],
28 | "content": "err01 = { $sel ->\n * [key] Value\n}\n\n",
29 | "span": {
30 | "type": "Span",
31 | "start": 47,
32 | "end": 87
33 | }
34 | },
35 | {
36 | "type": "Comment",
37 | "content": "ERROR Missing default variant.",
38 | "span": {
39 | "type": "Span",
40 | "start": 87,
41 | "end": 119
42 | }
43 | },
44 | {
45 | "type": "Junk",
46 | "annotations": [
47 | {
48 | "type": "Annotation",
49 | "code": "E0010",
50 | "arguments": [],
51 | "message": "Expected one of the variants to be marked as default (*)",
52 | "span": {
53 | "type": "Span",
54 | "start": 154,
55 | "end": 154
56 | }
57 | }
58 | ],
59 | "content": "err02 = { $sel ->\n [key] Value\n}\n",
60 | "span": {
61 | "type": "Span",
62 | "start": 120,
63 | "end": 156
64 | }
65 | }
66 | ],
67 | "span": {
68 | "type": "Span",
69 | "start": 0,
70 | "end": 156
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/simple_message.ftl:
--------------------------------------------------------------------------------
1 | foo = Foo
2 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/simple_message.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "foo",
9 | "span": {
10 | "type": "Span",
11 | "start": 0,
12 | "end": 3
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "TextElement",
20 | "value": "Foo",
21 | "span": {
22 | "type": "Span",
23 | "start": 6,
24 | "end": 9
25 | }
26 | }
27 | ],
28 | "span": {
29 | "type": "Span",
30 | "start": 6,
31 | "end": 9
32 | }
33 | },
34 | "attributes": [],
35 | "comment": null,
36 | "span": {
37 | "type": "Span",
38 | "start": 0,
39 | "end": 9
40 | }
41 | }
42 | ],
43 | "span": {
44 | "type": "Span",
45 | "start": 0,
46 | "end": 10
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/single_char_id.ftl:
--------------------------------------------------------------------------------
1 | k = Value
2 | .l = Foo
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/single_char_id.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "k",
9 | "span": {
10 | "type": "Span",
11 | "start": 0,
12 | "end": 1
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "TextElement",
20 | "value": "Value",
21 | "span": {
22 | "type": "Span",
23 | "start": 4,
24 | "end": 9
25 | }
26 | }
27 | ],
28 | "span": {
29 | "type": "Span",
30 | "start": 4,
31 | "end": 9
32 | }
33 | },
34 | "attributes": [
35 | {
36 | "type": "Attribute",
37 | "id": {
38 | "type": "Identifier",
39 | "name": "l",
40 | "span": {
41 | "type": "Span",
42 | "start": 15,
43 | "end": 16
44 | }
45 | },
46 | "value": {
47 | "type": "Pattern",
48 | "elements": [
49 | {
50 | "type": "TextElement",
51 | "value": "Foo",
52 | "span": {
53 | "type": "Span",
54 | "start": 19,
55 | "end": 22
56 | }
57 | }
58 | ],
59 | "span": {
60 | "type": "Span",
61 | "start": 19,
62 | "end": 22
63 | }
64 | },
65 | "span": {
66 | "type": "Span",
67 | "start": 14,
68 | "end": 22
69 | }
70 | }
71 | ],
72 | "comment": null,
73 | "span": {
74 | "type": "Span",
75 | "start": 0,
76 | "end": 22
77 | }
78 | }
79 | ],
80 | "span": {
81 | "type": "Span",
82 | "start": 0,
83 | "end": 23
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/sparse-messages.ftl:
--------------------------------------------------------------------------------
1 | key =
2 |
3 |
4 | Value
5 |
6 | key2 =
7 |
8 |
9 | .attr = Attribute
10 |
11 |
12 | key3 =
13 | Value
14 | Value2
15 |
16 |
17 | Value 4
18 | Value3
19 |
20 |
21 |
22 | .attr2 = Attr 2
23 |
24 |
25 | key5 = Value 5
26 |
27 | key6 = { $sel ->
28 |
29 | [one] One
30 |
31 | *[two] Two
32 |
33 | }
34 |
35 | key8 =
36 |
37 | {
38 |
39 | $sel
40 |
41 | ->
42 |
43 | *[one] One
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/standalone_comment.ftl:
--------------------------------------------------------------------------------
1 | foo = Value
2 |
3 | # This is a standalone comment
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/standalone_comment.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Message",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "foo",
9 | "span": {
10 | "type": "Span",
11 | "start": 0,
12 | "end": 3
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "TextElement",
20 | "value": "Value",
21 | "span": {
22 | "type": "Span",
23 | "start": 6,
24 | "end": 11
25 | }
26 | }
27 | ],
28 | "span": {
29 | "type": "Span",
30 | "start": 6,
31 | "end": 11
32 | }
33 | },
34 | "attributes": [],
35 | "comment": null,
36 | "span": {
37 | "type": "Span",
38 | "start": 0,
39 | "end": 11
40 | }
41 | },
42 | {
43 | "type": "Comment",
44 | "content": "This is a standalone comment",
45 | "span": {
46 | "type": "Span",
47 | "start": 13,
48 | "end": 43
49 | }
50 | }
51 | ],
52 | "span": {
53 | "type": "Span",
54 | "start": 0,
55 | "end": 44
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/standalone_identifier.ftl:
--------------------------------------------------------------------------------
1 | foo
2 | # ~ERROR E0003, pos 3, args "="
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/standalone_identifier.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0003",
10 | "arguments": [
11 | "="
12 | ],
13 | "message": "Expected token: \"=\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 3,
17 | "end": 3
18 | }
19 | }
20 | ],
21 | "content": "foo\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 4
26 | }
27 | },
28 | {
29 | "type": "Comment",
30 | "content": "~ERROR E0003, pos 3, args \"=\"",
31 | "span": {
32 | "type": "Span",
33 | "start": 4,
34 | "end": 35
35 | }
36 | }
37 | ],
38 | "span": {
39 | "type": "Span",
40 | "start": 0,
41 | "end": 36
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/term.ftl:
--------------------------------------------------------------------------------
1 | -term =
2 | { $case ->
3 | *[uppercase] Term
4 | [lowercase] term
5 | }
6 | .attr = a
7 |
8 | key01 = {-term}
9 | key02 = {-term()}
10 | key03 = {-term(case: "uppercase")}
11 |
12 |
13 | key04 =
14 | { -term.attr ->
15 | [a] { -term } A
16 | [b] { -term() } B
17 | *[x] X
18 | }
19 |
20 | -err1 =
21 | -err2 =
22 | .attr = Attribute
23 | --err3 = Error
24 | err4 = { --err4 }
25 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/term_with_empty_pattern.ftl:
--------------------------------------------------------------------------------
1 | -foo =
2 | .attr = Attribute
3 |
4 | -bar =
5 |
6 | -baz
7 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/term_with_empty_pattern.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0006",
10 | "arguments": [
11 | "foo"
12 | ],
13 | "message": "Expected term \"-foo\" to have a value",
14 | "span": {
15 | "type": "Span",
16 | "start": 6,
17 | "end": 6
18 | }
19 | }
20 | ],
21 | "content": "-foo =\n .attr = Attribute\n\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 30
26 | }
27 | },
28 | {
29 | "type": "Junk",
30 | "annotations": [
31 | {
32 | "type": "Annotation",
33 | "code": "E0006",
34 | "arguments": [
35 | "bar"
36 | ],
37 | "message": "Expected term \"-bar\" to have a value",
38 | "span": {
39 | "type": "Span",
40 | "start": 36,
41 | "end": 36
42 | }
43 | }
44 | ],
45 | "content": "-bar =\n\n",
46 | "span": {
47 | "type": "Span",
48 | "start": 30,
49 | "end": 38
50 | }
51 | },
52 | {
53 | "type": "Junk",
54 | "annotations": [
55 | {
56 | "type": "Annotation",
57 | "code": "E0003",
58 | "arguments": [
59 | "="
60 | ],
61 | "message": "Expected token: \"=\"",
62 | "span": {
63 | "type": "Span",
64 | "start": 42,
65 | "end": 42
66 | }
67 | }
68 | ],
69 | "content": "-baz\n",
70 | "span": {
71 | "type": "Span",
72 | "start": 38,
73 | "end": 43
74 | }
75 | }
76 | ],
77 | "span": {
78 | "type": "Span",
79 | "start": 0,
80 | "end": 43
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/unclosed.ftl:
--------------------------------------------------------------------------------
1 | err01 = {
2 | key02 = Value 02
3 |
4 | err03 = {
5 | FUNC(
6 | arg
7 | ,
8 | namedArg: "Value"
9 | ,
10 | key04 = Value 04
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/unclosed_empty_placeable_error.ftl:
--------------------------------------------------------------------------------
1 | err01 = {
2 | key01 = Bar
3 | err02 = {
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/unclosed_empty_placeable_error.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0003",
10 | "arguments": [
11 | "}"
12 | ],
13 | "message": "Expected token: \"}\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 10,
17 | "end": 10
18 | }
19 | }
20 | ],
21 | "content": "err01 = {\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 10
26 | }
27 | },
28 | {
29 | "type": "Message",
30 | "id": {
31 | "type": "Identifier",
32 | "name": "key01",
33 | "span": {
34 | "type": "Span",
35 | "start": 10,
36 | "end": 15
37 | }
38 | },
39 | "value": {
40 | "type": "Pattern",
41 | "elements": [
42 | {
43 | "type": "TextElement",
44 | "value": "Bar",
45 | "span": {
46 | "type": "Span",
47 | "start": 18,
48 | "end": 21
49 | }
50 | }
51 | ],
52 | "span": {
53 | "type": "Span",
54 | "start": 18,
55 | "end": 21
56 | }
57 | },
58 | "attributes": [],
59 | "comment": null,
60 | "span": {
61 | "type": "Span",
62 | "start": 10,
63 | "end": 21
64 | }
65 | },
66 | {
67 | "type": "Junk",
68 | "annotations": [
69 | {
70 | "type": "Annotation",
71 | "code": "E0028",
72 | "arguments": [],
73 | "message": "Expected an inline expression",
74 | "span": {
75 | "type": "Span",
76 | "start": 32,
77 | "end": 32
78 | }
79 | }
80 | ],
81 | "content": "err02 = {\n",
82 | "span": {
83 | "type": "Span",
84 | "start": 22,
85 | "end": 32
86 | }
87 | }
88 | ],
89 | "span": {
90 | "type": "Span",
91 | "start": 0,
92 | "end": 32
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/unknown_entry_start.ftl:
--------------------------------------------------------------------------------
1 |
2 | 8err = Foo
3 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/unknown_entry_start.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0002",
10 | "arguments": [],
11 | "message": "Expected an entry start",
12 | "span": {
13 | "type": "Span",
14 | "start": 1,
15 | "end": 1
16 | }
17 | }
18 | ],
19 | "content": "8err = Foo\n",
20 | "span": {
21 | "type": "Span",
22 | "start": 1,
23 | "end": 12
24 | }
25 | }
26 | ],
27 | "span": {
28 | "type": "Span",
29 | "start": 0,
30 | "end": 12
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_ends_abruptly.ftl:
--------------------------------------------------------------------------------
1 | key = { $foo ->
2 | *[
3 | # ~ERROR E0013, pos 23
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_ends_abruptly.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0004",
10 | "arguments": [
11 | "a-zA-Z"
12 | ],
13 | "message": "Expected a character from range: \"a-zA-Z\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 23,
17 | "end": 23
18 | }
19 | }
20 | ],
21 | "content": "key = { $foo ->\n *[\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 23
26 | }
27 | },
28 | {
29 | "type": "Comment",
30 | "content": "~ERROR E0013, pos 23",
31 | "span": {
32 | "type": "Span",
33 | "start": 23,
34 | "end": 45
35 | }
36 | }
37 | ],
38 | "span": {
39 | "type": "Span",
40 | "start": 0,
41 | "end": 46
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_keys.ftl:
--------------------------------------------------------------------------------
1 | key01 = { $sel ->
2 | *[
3 | key
4 | ] Value
5 | }
6 |
7 | key02 = { $sel ->
8 | *[
9 | key
10 | ]
11 |
12 | Value
13 | }
14 |
15 | err01 = { $sel ->
16 | *["key"] Value
17 | }
18 |
19 | err02 = { $sel ->
20 | *[-key] Value
21 | }
22 |
23 | err03 = { $sel ->
24 | *[-key.attr] Value
25 | }
26 |
27 | err04 = { $sel ->
28 | *[-key()] Value
29 | }
30 |
31 | err05 = { $sel ->
32 | *[-key.attr()] Value
33 | }
34 |
35 | err06 = { $sel ->
36 | *[key.attr] Value
37 | }
38 |
39 | err07 = { $sel ->
40 | *[$key] Value
41 | }
42 |
43 | err08 = { $sel ->
44 | *[FUNC()] Value
45 | }
46 |
47 | err09 = { $sel ->
48 | *[{key}] Value
49 | }
50 |
51 | err10 = { $sel ->
52 | *[{"key"}] Value
53 | }
54 |
55 | err11 = { $sel ->
56 | *[{3.14}] Value
57 | }
58 |
59 | err12 = { $sel ->
60 | *[{$key}] Value
61 | }
62 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_starts_from_nl.ftl:
--------------------------------------------------------------------------------
1 | -term = { $sel ->
2 | *[one] Value
3 | }
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_starts_from_nl.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Term",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "term",
9 | "span": {
10 | "type": "Span",
11 | "start": 1,
12 | "end": 5
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "Placeable",
20 | "expression": {
21 | "type": "SelectExpression",
22 | "selector": {
23 | "type": "VariableReference",
24 | "id": {
25 | "type": "Identifier",
26 | "name": "sel",
27 | "span": {
28 | "type": "Span",
29 | "start": 11,
30 | "end": 14
31 | }
32 | },
33 | "span": {
34 | "type": "Span",
35 | "start": 10,
36 | "end": 14
37 | }
38 | },
39 | "variants": [
40 | {
41 | "type": "Variant",
42 | "key": {
43 | "type": "Identifier",
44 | "name": "one",
45 | "span": {
46 | "type": "Span",
47 | "start": 20,
48 | "end": 23
49 | }
50 | },
51 | "value": {
52 | "type": "Pattern",
53 | "elements": [
54 | {
55 | "type": "TextElement",
56 | "value": "Value",
57 | "span": {
58 | "type": "Span",
59 | "start": 25,
60 | "end": 30
61 | }
62 | }
63 | ],
64 | "span": {
65 | "type": "Span",
66 | "start": 25,
67 | "end": 30
68 | }
69 | },
70 | "default": true,
71 | "span": {
72 | "type": "Span",
73 | "start": 18,
74 | "end": 30
75 | }
76 | }
77 | ],
78 | "span": {
79 | "type": "Span",
80 | "start": 10,
81 | "end": 35
82 | }
83 | },
84 | "span": {
85 | "type": "Span",
86 | "start": 8,
87 | "end": 36
88 | }
89 | }
90 | ],
91 | "span": {
92 | "type": "Span",
93 | "start": 8,
94 | "end": 36
95 | }
96 | },
97 | "attributes": [],
98 | "comment": null,
99 | "span": {
100 | "type": "Span",
101 | "start": 0,
102 | "end": 36
103 | }
104 | }
105 | ],
106 | "span": {
107 | "type": "Span",
108 | "start": 0,
109 | "end": 37
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_with_digit_key.ftl:
--------------------------------------------------------------------------------
1 | -term = { $sel ->
2 | *[-2] Foo
3 | }
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_with_digit_key.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Term",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "term",
9 | "span": {
10 | "type": "Span",
11 | "start": 1,
12 | "end": 5
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "Placeable",
20 | "expression": {
21 | "type": "SelectExpression",
22 | "selector": {
23 | "type": "VariableReference",
24 | "id": {
25 | "type": "Identifier",
26 | "name": "sel",
27 | "span": {
28 | "type": "Span",
29 | "start": 11,
30 | "end": 14
31 | }
32 | },
33 | "span": {
34 | "type": "Span",
35 | "start": 10,
36 | "end": 14
37 | }
38 | },
39 | "variants": [
40 | {
41 | "type": "Variant",
42 | "key": {
43 | "value": "-2",
44 | "type": "NumberLiteral",
45 | "span": {
46 | "type": "Span",
47 | "start": 28,
48 | "end": 30
49 | }
50 | },
51 | "value": {
52 | "type": "Pattern",
53 | "elements": [
54 | {
55 | "type": "TextElement",
56 | "value": "Foo",
57 | "span": {
58 | "type": "Span",
59 | "start": 32,
60 | "end": 35
61 | }
62 | }
63 | ],
64 | "span": {
65 | "type": "Span",
66 | "start": 32,
67 | "end": 35
68 | }
69 | },
70 | "default": true,
71 | "span": {
72 | "type": "Span",
73 | "start": 26,
74 | "end": 35
75 | }
76 | }
77 | ],
78 | "span": {
79 | "type": "Span",
80 | "start": 10,
81 | "end": 40
82 | }
83 | },
84 | "span": {
85 | "type": "Span",
86 | "start": 8,
87 | "end": 41
88 | }
89 | }
90 | ],
91 | "span": {
92 | "type": "Span",
93 | "start": 8,
94 | "end": 41
95 | }
96 | },
97 | "attributes": [],
98 | "comment": null,
99 | "span": {
100 | "type": "Span",
101 | "start": 0,
102 | "end": 41
103 | }
104 | }
105 | ],
106 | "span": {
107 | "type": "Span",
108 | "start": 0,
109 | "end": 42
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_with_empty_pattern.ftl:
--------------------------------------------------------------------------------
1 | key1 =
2 | { 1 ->
3 | *[one] {""}
4 | }
5 |
6 | err2 =
7 | { $sel ->
8 | *[one]
9 | }
10 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_with_leading_space_in_name.ftl:
--------------------------------------------------------------------------------
1 | -term = { $sel ->
2 | *[ one] Foo
3 | }
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_with_leading_space_in_name.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Term",
6 | "id": {
7 | "type": "Identifier",
8 | "name": "term",
9 | "span": {
10 | "type": "Span",
11 | "start": 1,
12 | "end": 5
13 | }
14 | },
15 | "value": {
16 | "type": "Pattern",
17 | "elements": [
18 | {
19 | "type": "Placeable",
20 | "expression": {
21 | "type": "SelectExpression",
22 | "selector": {
23 | "type": "VariableReference",
24 | "id": {
25 | "type": "Identifier",
26 | "name": "sel",
27 | "span": {
28 | "type": "Span",
29 | "start": 11,
30 | "end": 14
31 | }
32 | },
33 | "span": {
34 | "type": "Span",
35 | "start": 10,
36 | "end": 14
37 | }
38 | },
39 | "variants": [
40 | {
41 | "type": "Variant",
42 | "key": {
43 | "type": "Identifier",
44 | "name": "one",
45 | "span": {
46 | "type": "Span",
47 | "start": 30,
48 | "end": 33
49 | }
50 | },
51 | "value": {
52 | "type": "Pattern",
53 | "elements": [
54 | {
55 | "type": "TextElement",
56 | "value": "Foo",
57 | "span": {
58 | "type": "Span",
59 | "start": 35,
60 | "end": 38
61 | }
62 | }
63 | ],
64 | "span": {
65 | "type": "Span",
66 | "start": 35,
67 | "end": 38
68 | }
69 | },
70 | "default": true,
71 | "span": {
72 | "type": "Span",
73 | "start": 26,
74 | "end": 38
75 | }
76 | }
77 | ],
78 | "span": {
79 | "type": "Span",
80 | "start": 10,
81 | "end": 43
82 | }
83 | },
84 | "span": {
85 | "type": "Span",
86 | "start": 8,
87 | "end": 44
88 | }
89 | }
90 | ],
91 | "span": {
92 | "type": "Span",
93 | "start": 8,
94 | "end": 44
95 | }
96 | },
97 | "attributes": [],
98 | "comment": null,
99 | "span": {
100 | "type": "Span",
101 | "start": 0,
102 | "end": 44
103 | }
104 | }
105 | ],
106 | "span": {
107 | "type": "Span",
108 | "start": 0,
109 | "end": 45
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_with_symbol_with_space.ftl:
--------------------------------------------------------------------------------
1 | -err01 = { $sel ->
2 | *[New York] Nowy Jork
3 | }
4 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variant_with_symbol_with_space.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0003",
10 | "arguments": [
11 | "]"
12 | ],
13 | "message": "Expected token: \"]\"",
14 | "span": {
15 | "type": "Span",
16 | "start": 33,
17 | "end": 33
18 | }
19 | }
20 | ],
21 | "content": "-err01 = { $sel ->\n *[New York] Nowy Jork\n }\n",
22 | "span": {
23 | "type": "Span",
24 | "start": 0,
25 | "end": 55
26 | }
27 | }
28 | ],
29 | "span": {
30 | "type": "Span",
31 | "start": 0,
32 | "end": 55
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variants_with_two_defaults.ftl:
--------------------------------------------------------------------------------
1 | -err01 = { $sel ->
2 | *[one] Foo
3 | *[two] Two
4 | }
5 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/variants_with_two_defaults.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "Resource",
3 | "body": [
4 | {
5 | "type": "Junk",
6 | "annotations": [
7 | {
8 | "type": "Annotation",
9 | "code": "E0015",
10 | "arguments": [],
11 | "message": "Only one variant can be marked as default (*)",
12 | "span": {
13 | "type": "Span",
14 | "start": 38,
15 | "end": 38
16 | }
17 | }
18 | ],
19 | "content": "-err01 = { $sel ->\n *[one] Foo\n *[two] Two\n }\n",
20 | "span": {
21 | "type": "Span",
22 | "start": 0,
23 | "end": 55
24 | }
25 | }
26 | ],
27 | "span": {
28 | "type": "Span",
29 | "start": 0,
30 | "end": 55
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/whitespace_leading.ftl:
--------------------------------------------------------------------------------
1 | # < whitespace >
2 | key1 = Value
3 |
4 | # ↓ nbsp
5 | key2 = Value
6 |
7 | key3 = {""} Value
8 | key4 = {" "}Value
9 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/fixtures_structure/whitespace_trailing.ftl:
--------------------------------------------------------------------------------
1 | # < whitespace >
2 | key1 = Value
3 |
4 | # ↓ nbsp
5 | key2 = Value
6 |
7 | key3 = Value {placeable}.
8 | key4 = Value{" "}
9 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/test_ast_json.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from fluent.syntax.ast import from_json
4 | from fluent.syntax.parser import FluentParser
5 | from tests.syntax import dedent_ftl
6 |
7 |
8 | class TestASTJSON(unittest.TestCase):
9 | maxDiff = None
10 |
11 | def setUp(self):
12 | self.parser = FluentParser()
13 |
14 | def test_simple_resource(self):
15 | input = """\
16 | foo = Foo
17 | """
18 |
19 | ast1 = self.parser.parse(dedent_ftl(input))
20 | json1 = ast1.to_json()
21 | ast2 = from_json(json1)
22 | json2 = ast2.to_json()
23 |
24 | self.assertEqual(json1, json2)
25 |
26 | def test_complex_resource(self):
27 | input = """\
28 | ### A Resource comment
29 |
30 | # A comment about shared-photos
31 | shared-photos =
32 | { $user_name } { $photo_count ->
33 | [0] hasn't added any photos yet
34 | [one] added a new photo
35 | *[other] added { $photo_count } new photos
36 | }.
37 |
38 |
39 | ## A Section comment
40 |
41 | // A Syntax 0.4 comment about liked-comment
42 | liked-comment =
43 | { $user_name } liked your comment on { $user_gender ->
44 | [male] his
45 | [female] her
46 | *[other] their
47 | } post.
48 | """
49 |
50 | ast1 = self.parser.parse(dedent_ftl(input))
51 | json1 = ast1.to_json()
52 | ast2 = from_json(json1)
53 | json2 = ast2.to_json()
54 |
55 | self.assertEqual(json1, json2)
56 |
57 | def test_syntax_error(self):
58 | input = """\
59 | foo = Foo {
60 | """
61 |
62 | ast1 = self.parser.parse(dedent_ftl(input))
63 | json1 = ast1.to_json()
64 | ast2 = from_json(json1)
65 | json2 = ast2.to_json()
66 |
67 | self.assertEqual(json1, json2)
68 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/test_literal.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import unittest
3 |
4 | from fluent.syntax.parser import FluentParser
5 |
6 |
7 | def parse_literal(input):
8 | parser = FluentParser(with_spans=False)
9 | ast = parser.parse_entry(input)
10 | expr = ast.value.elements[0].expression
11 | return expr.parse()
12 |
13 |
14 | class TestStringLiteralParse(unittest.TestCase):
15 | def test_no_escape_sequences(self):
16 | self.assertDictEqual(parse_literal('x = {"abc"}'), {"value": "abc"})
17 |
18 | def test_double_quote_backslash(self):
19 | self.assertDictEqual(parse_literal(r'x = {"\""}'), {"value": '"'})
20 | self.assertDictEqual(parse_literal(r'x = {"\\"}'), {"value": "\\"})
21 |
22 | def test_unicode_escape(self):
23 | self.assertDictEqual(parse_literal('x = {"\\u0041"}'), {"value": "A"})
24 | self.assertDictEqual(parse_literal('x = {"\\\\u0041"}'), {"value": "\\u0041"})
25 | if sys.maxunicode > 0xFFFF:
26 | self.assertDictEqual(parse_literal('x = {"\\U01F602"}'), {"value": "😂"})
27 | self.assertDictEqual(
28 | parse_literal('x = {"\\\\U01F602"}'), {"value": "\\U01F602"}
29 | )
30 |
31 | def test_trailing_number(self):
32 | self.assertDictEqual(parse_literal('x = {"\\u004100"}'), {"value": "A00"})
33 | if sys.maxunicode > 0xFFFF:
34 | self.assertDictEqual(
35 | parse_literal('x = {"\\U01F60200"}'), {"value": "😂00"}
36 | )
37 |
38 |
39 | class TestNumberLiteralParse(unittest.TestCase):
40 | def test_integers(self):
41 | self.assertDictEqual(parse_literal("x = {0}"), {"value": 0, "precision": 0})
42 | self.assertDictEqual(parse_literal("x = {1}"), {"value": 1, "precision": 0})
43 | self.assertDictEqual(parse_literal("x = {-0}"), {"value": 0, "precision": 0})
44 | self.assertDictEqual(parse_literal("x = {-1}"), {"value": -1, "precision": 0})
45 |
46 | def test_padded_integers(self):
47 | self.assertDictEqual(parse_literal("x = {00}"), {"value": 0, "precision": 0})
48 | self.assertDictEqual(parse_literal("x = {01}"), {"value": 1, "precision": 0})
49 | self.assertDictEqual(parse_literal("x = {-00}"), {"value": 0, "precision": 0})
50 | self.assertDictEqual(parse_literal("x = {-01}"), {"value": -1, "precision": 0})
51 |
52 | def test_positive_floats(self):
53 | self.assertDictEqual(parse_literal("x = {0.0}"), {"value": 0, "precision": 1})
54 | self.assertDictEqual(
55 | parse_literal("x = {0.01}"), {"value": 0.01, "precision": 2}
56 | )
57 | self.assertDictEqual(
58 | parse_literal("x = {1.03}"), {"value": 1.03, "precision": 2}
59 | )
60 | self.assertDictEqual(parse_literal("x = {1.000}"), {"value": 1, "precision": 3})
61 |
62 | def test_negative_floats(self):
63 | self.assertDictEqual(parse_literal("x = {-0.0}"), {"value": 0, "precision": 1})
64 | self.assertDictEqual(
65 | parse_literal("x = {-0.01}"), {"value": -0.01, "precision": 2}
66 | )
67 | self.assertDictEqual(
68 | parse_literal("x = {-1.03}"), {"value": -1.03, "precision": 2}
69 | )
70 | self.assertDictEqual(
71 | parse_literal("x = {-1.000}"), {"value": -1, "precision": 3}
72 | )
73 |
74 | def test_padded_floats(self):
75 | self.assertDictEqual(
76 | parse_literal("x = {-00.00}"), {"value": 0, "precision": 2}
77 | )
78 | self.assertDictEqual(
79 | parse_literal("x = {-01.000}"), {"value": -1, "precision": 3}
80 | )
81 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/test_reference.py:
--------------------------------------------------------------------------------
1 | import codecs
2 | import json
3 | import os
4 | import unittest
5 |
6 | from fluent.syntax import parse
7 |
8 |
9 | def read_file(path):
10 | with codecs.open(path, "r", encoding="utf-8") as file:
11 | text = file.read()
12 | return text
13 |
14 |
15 | fixtures = os.path.join(os.path.dirname(__file__), "fixtures_reference")
16 |
17 |
18 | class TestReferenceMeta(type):
19 | def __new__(mcs, name, bases, attrs):
20 |
21 | def remove_untested(obj):
22 | if obj["type"] == "Junk":
23 | obj["annotations"] = []
24 | if "span" in obj:
25 | del obj["span"]
26 | return obj
27 |
28 | def gen_test(file_name):
29 | def test(self):
30 | ftl_path = os.path.join(fixtures, file_name + ".ftl")
31 | ast_path = os.path.join(fixtures, file_name + ".json")
32 |
33 | source = read_file(ftl_path)
34 | expected = read_file(ast_path)
35 |
36 | ast = parse(source)
37 | self.assertEqual(ast.to_json(remove_untested), json.loads(expected))
38 |
39 | return test
40 |
41 | for f in os.listdir(fixtures):
42 | file_name, ext = os.path.splitext(f)
43 |
44 | if ext != ".ftl":
45 | continue
46 |
47 | # Skip fixtures which are known to differ between the reference
48 | # parser and the tooling parser.
49 | if file_name in ("leading_dots", "variant_lists"):
50 | continue
51 |
52 | test_name = f"test_{file_name}"
53 | attrs[test_name] = gen_test(file_name)
54 |
55 | return type.__new__(mcs, name, bases, attrs)
56 |
57 |
58 | class TestReference(unittest.TestCase, metaclass=TestReferenceMeta):
59 | maxDiff = None
60 |
--------------------------------------------------------------------------------
/fluent.syntax/tests/syntax/test_structure.py:
--------------------------------------------------------------------------------
1 | import codecs
2 | import json
3 | import os
4 | import unittest
5 |
6 | from fluent.syntax import parse
7 |
8 |
9 | def read_file(path):
10 | with codecs.open(path, "r", encoding="utf-8") as file:
11 | text = file.read()
12 | return text
13 |
14 |
15 | def without_spans(expected):
16 | """
17 | Given an expected JSON fragment with span information, recursively replace all of the spans
18 | with None.
19 | """
20 | if isinstance(expected, dict):
21 | result = {}
22 | for key, value in expected.items():
23 | if key == "span":
24 | result[key] = None
25 | else:
26 | result[key] = without_spans(value)
27 |
28 | return result
29 | elif isinstance(expected, list):
30 | return [without_spans(item) for item in expected]
31 | else:
32 | # We have been passed something which would not have span information in it
33 | return expected
34 |
35 |
36 | fixtures = os.path.join(os.path.dirname(__file__), "fixtures_structure")
37 |
38 |
39 | class TestStructureMeta(type):
40 | def __new__(mcs, name, bases, attrs):
41 |
42 | def gen_test(file_name, with_spans):
43 | def test(self):
44 | ftl_path = os.path.join(fixtures, file_name + ".ftl")
45 | ast_path = os.path.join(fixtures, file_name + ".json")
46 |
47 | source = read_file(ftl_path)
48 | expected = json.loads(read_file(ast_path))
49 |
50 | if not with_spans:
51 | expected = without_spans(expected)
52 |
53 | ast = parse(source, with_spans=with_spans)
54 |
55 | self.assertEqual(ast.to_json(), expected)
56 |
57 | return test
58 |
59 | for f in os.listdir(fixtures):
60 | file_name, ext = os.path.splitext(f)
61 |
62 | if ext != ".ftl":
63 | continue
64 |
65 | attrs[f"test_{file_name}_with_spans"] = gen_test(file_name, with_spans=True)
66 | attrs[f"test_{file_name}_without_spans"] = gen_test(file_name, with_spans=False)
67 |
68 | return type.__new__(mcs, name, bases, attrs)
69 |
70 |
71 | class TestStructure(unittest.TestCase, metaclass=TestStructureMeta):
72 | maxDiff = None
73 |
--------------------------------------------------------------------------------
/fluent.syntax/tox.ini:
--------------------------------------------------------------------------------
1 | # This config is for local testing.
2 | # It should be correspond to .github/workflows/fluent.syntax.yml
3 | [tox]
4 | envlist = py36, py37, py38, py39, pypy3
5 | skipsdist=True
6 |
7 | [testenv]
8 | setenv =
9 | PYTHONPATH = {toxinidir}
10 | deps =
11 | typing-extensions~=3.7
12 | commands = python -m unittest
13 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.isort]
2 | line_length = 120
3 | skip_glob = ".tox"
4 |
5 | [tool.mypy]
6 | strict = true
7 |
--------------------------------------------------------------------------------
/scripts/build-docs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import argparse
4 | from datetime import date
5 |
6 | from fluent.docs import build_root, finalize_builddir
7 | from fluent.docs.build import build
8 |
9 | if __name__ == "__main__":
10 | parser = argparse.ArgumentParser()
11 | parser.add_argument(
12 | 'repo_name',
13 | help='URL prefix on the web. Repo name on gh-pages'
14 | )
15 | default_docs = ['.', 'fluent.syntax', 'fluent.pygments', 'fluent.runtime']
16 | parser.add_argument(
17 | '--doc', action='append', choices=default_docs,
18 | help='Only build select documentation roots.'
19 | )
20 | parser.add_argument(
21 | '--dev', action='store_false', dest='show_releases',
22 | help='Only build development versions of projects'
23 | )
24 | args = parser.parse_args()
25 | if args.doc is None:
26 | args.doc = default_docs[:]
27 | if '.' in args.doc:
28 | build_root(args.repo_name)
29 | args.doc.remove('.')
30 | releases_after = None
31 | if args.show_releases:
32 | # python-fluent had descent docs since May 2020
33 | releases_after = date(2020, 5, 1)
34 | build(args.repo_name, args.doc, releases_after)
35 | finalize_builddir(args.repo_name)
36 |
--------------------------------------------------------------------------------
/tools/fluentfmt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import codecs
4 | import sys
5 |
6 | from fluent.syntax import parse, serialize
7 |
8 | sys.path.append("./")
9 |
10 |
11 | def read_file(path):
12 | with codecs.open(path, "r", encoding="utf-8") as file:
13 | text = file.read()
14 | return text
15 |
16 |
17 | def pretty_print(fileType, data):
18 | ast = parse(data)
19 | print(serialize(ast))
20 |
21 |
22 | if __name__ == "__main__":
23 | file_type = "ftl"
24 | f = read_file(sys.argv[1])
25 | pretty_print(file_type, f)
26 |
--------------------------------------------------------------------------------
/tools/parse.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import codecs
4 | import json
5 | import sys
6 |
7 | from fluent.syntax import parse
8 |
9 | sys.path.append("./")
10 |
11 |
12 | def read_file(path):
13 | with codecs.open(path, "r", encoding="utf-8") as file:
14 | text = file.read()
15 | return text
16 |
17 |
18 | def print_ast(fileType, data):
19 | ast = parse(data)
20 | print(json.dumps(ast.to_json(), indent=2, ensure_ascii=False))
21 |
22 |
23 | if __name__ == "__main__":
24 | file_type = "ftl"
25 | f = read_file(sys.argv[1])
26 | print_ast(file_type, f)
27 |
--------------------------------------------------------------------------------
/tools/serialize.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import codecs
4 | import json
5 | import sys
6 |
7 | from fluent.syntax import ast, serialize
8 |
9 | sys.path.append("./")
10 |
11 |
12 | def read_json(path):
13 | with codecs.open(path, "r", encoding="utf-8") as file:
14 | return json.load(file)
15 |
16 |
17 | def pretty_print(fileType, data):
18 | resource = ast.from_json(data)
19 | print(serialize(resource))
20 |
21 |
22 | if __name__ == "__main__":
23 | file_type = "ftl"
24 | f = read_json(sys.argv[1])
25 | pretty_print(file_type, f)
26 |
--------------------------------------------------------------------------------