From cd3708d2f921e20a2e2bcfda6d06a4519147a5d7 Mon Sep 17 00:00:00 2001 From: sheldy Date: Sat, 19 Jul 2025 02:50:18 +0200 Subject: [PATCH 1/4] Extend message object placeholders with referenced message placeholders. --- fluentogram/stub_generator/parser.py | 1 + tests/assets/reference_with_args.ftl | 8 ++++++++ tests/test_generator.py | 11 +++++++++++ 3 files changed, 20 insertions(+) create mode 100644 tests/assets/reference_with_args.ftl diff --git a/fluentogram/stub_generator/parser.py b/fluentogram/stub_generator/parser.py index 8bbfa7a..5f9def5 100644 --- a/fluentogram/stub_generator/parser.py +++ b/fluentogram/stub_generator/parser.py @@ -46,6 +46,7 @@ def _parse_message_reference(self, message_obj: Message, element: ast.MessageRef # If the referenced message hasn't been processed yet, process it now self._process_message_elements(referenced_message) message_obj.result_text += referenced_message.result_text + message_obj.placeholders.extend(referenced_message.placeholders) else: logger.warning("Message reference %s not found", element.id.name) diff --git a/tests/assets/reference_with_args.ftl b/tests/assets/reference_with_args.ftl new file mode 100644 index 0000000..1018e66 --- /dev/null +++ b/tests/assets/reference_with_args.ftl @@ -0,0 +1,8 @@ +message-wth-placeholders = { $first_arg } { $second_arg } { $third_arg } + +just-another-text = just another text + +test-message = + { message-wth-placeholders } + + { just-another-text } diff --git a/tests/test_generator.py b/tests/test_generator.py index 69d8b60..5fbf4b8 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -91,3 +91,14 @@ def test_generator_if_conflict_in_prefix() -> None: assert "class AnotherUnknown" in content assert 'def error() -> Literal["""first-unknown-error"""]: ...' in content assert 'def error() -> Literal["""another-unknown-error"""]: ...' in content + + +def test_generator_if_reference_with_args() -> None: + with tempfile.NamedTemporaryFile(suffix=".pyi", delete=False) as tmp_file: + output_path = tmp_file.name + + generate(output_path, file_path="tests/assets/reference_with_args.ftl") + + assert Path(output_path).exists() + content = Path(output_path).read_text() + assert "def message(*, first_arg: PossibleValue, second_arg: PossibleValue, third_arg: PossibleValue)" in content From 4061f092c9b915615e62bbcc4a1b7e8c9273292c Mon Sep 17 00:00:00 2001 From: sheldy Date: Sat, 19 Jul 2025 04:13:38 +0200 Subject: [PATCH 2/4] Use set for placeholders. --- fluentogram/stub_generator/parser.py | 12 +++--------- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/fluentogram/stub_generator/parser.py b/fluentogram/stub_generator/parser.py index 5f9def5..aa46d18 100644 --- a/fluentogram/stub_generator/parser.py +++ b/fluentogram/stub_generator/parser.py @@ -13,7 +13,7 @@ class Message: name: str result_text: str raw_elements: list[ast.TextElement | ast.Placeable] # Store raw elements for later processing - placeholders: list[str] = field(default_factory=list) + placeholders: set[str] = field(default_factory=set) class Parser: @@ -31,8 +31,7 @@ def process_term(self, term: ast.Term) -> None: logger.warning("Unknown element type in term %s: %s", term.id.name, type(element)) def _parse_variable_reference(self, message_obj: Message, element: ast.VariableReference) -> None: - if element.id.name not in message_obj.placeholders: - message_obj.placeholders.append(element.id.name) + message_obj.placeholders.add(element.id.name) message_obj.result_text += f"{{ ${element.id.name} }}" def _parse_term_reference(self, message_obj: Message, element: ast.TermReference) -> None: @@ -46,7 +45,7 @@ def _parse_message_reference(self, message_obj: Message, element: ast.MessageRef # If the referenced message hasn't been processed yet, process it now self._process_message_elements(referenced_message) message_obj.result_text += referenced_message.result_text - message_obj.placeholders.extend(referenced_message.placeholders) + message_obj.placeholders.update(referenced_message.placeholders) else: logger.warning("Message reference %s not found", element.id.name) @@ -131,9 +130,6 @@ def process_message(self, message: ast.Message) -> None: message_obj.raw_elements = message.value.elements self.messages[message.id.name] = message_obj - def _sort_placeholders(self, message_obj: Message) -> None: - message_obj.placeholders = sorted(message_obj.placeholders) - def _process_message_elements(self, message_obj: Message) -> None: """Process the raw elements of a message to generate result_text and placeholders.""" # Skip if already processed @@ -148,8 +144,6 @@ def _process_message_elements(self, message_obj: Message) -> None: else: logger.warning("Unknown element type in message %s: %s", message_obj.name, type(element)) - self._sort_placeholders(message_obj) - def parse(self, resource: ast.Resource) -> dict[str, Message]: # First pass: collect all terms and message structures for entry in resource.body: diff --git a/pyproject.toml b/pyproject.toml index 7817ccf..38de396 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "fluentogram" -version = "1.2.0" +version = "1.2.1" description = "Fluentogram is easy way to use i18n (Fluent) mechanism in any python app." authors = [{ name = "Aleks" }] requires-python = ">=3.9" From b62f98bc1a009a95ddec4f935fb3d2f95510381a Mon Sep 17 00:00:00 2001 From: sheldy Date: Sat, 19 Jul 2025 04:22:53 +0200 Subject: [PATCH 3/4] Add `fluent_compiler.types.FluentType` to `PossibleValue` in stubs. --- fluentogram/stub_generator/templates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fluentogram/stub_generator/templates.py b/fluentogram/stub_generator/templates.py index df4f0db..2b5031f 100644 --- a/fluentogram/stub_generator/templates.py +++ b/fluentogram/stub_generator/templates.py @@ -104,9 +104,10 @@ class Runner(Class): """from decimal import Decimal from typing import Literal +from fluent_compiler.types import FluentType from typing_extensions import TypeAlias -PossibleValue: TypeAlias = str | int | float | Decimal | bool +PossibleValue: TypeAlias = str | int | float | Decimal | bool | FluentType class {{ class_name }}: def get(self, path: str, **kwargs: PossibleValue) -> str: ...""", From 9f5f89ed19964460783f83cbd01594d2e56855c0 Mon Sep 17 00:00:00 2001 From: sheldy Date: Sat, 19 Jul 2025 04:29:57 +0200 Subject: [PATCH 4/4] Remove set with lists, yes this is funny moment --- fluentogram/stub_generator/parser.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/fluentogram/stub_generator/parser.py b/fluentogram/stub_generator/parser.py index aa46d18..17b0780 100644 --- a/fluentogram/stub_generator/parser.py +++ b/fluentogram/stub_generator/parser.py @@ -13,7 +13,7 @@ class Message: name: str result_text: str raw_elements: list[ast.TextElement | ast.Placeable] # Store raw elements for later processing - placeholders: set[str] = field(default_factory=set) + placeholders: list[str] = field(default_factory=list) class Parser: @@ -31,7 +31,8 @@ def process_term(self, term: ast.Term) -> None: logger.warning("Unknown element type in term %s: %s", term.id.name, type(element)) def _parse_variable_reference(self, message_obj: Message, element: ast.VariableReference) -> None: - message_obj.placeholders.add(element.id.name) + if element.id.name not in message_obj.placeholders: + message_obj.placeholders.append(element.id.name) message_obj.result_text += f"{{ ${element.id.name} }}" def _parse_term_reference(self, message_obj: Message, element: ast.TermReference) -> None: @@ -45,7 +46,10 @@ def _parse_message_reference(self, message_obj: Message, element: ast.MessageRef # If the referenced message hasn't been processed yet, process it now self._process_message_elements(referenced_message) message_obj.result_text += referenced_message.result_text - message_obj.placeholders.update(referenced_message.placeholders) + # Add placeholders from referenced message, preserving order and avoiding duplicates + for placeholder in referenced_message.placeholders: + if placeholder not in message_obj.placeholders: + message_obj.placeholders.append(placeholder) else: logger.warning("Message reference %s not found", element.id.name) @@ -130,6 +134,9 @@ def process_message(self, message: ast.Message) -> None: message_obj.raw_elements = message.value.elements self.messages[message.id.name] = message_obj + def _sort_placeholders(self, message_obj: Message) -> None: + message_obj.placeholders = sorted(message_obj.placeholders) + def _process_message_elements(self, message_obj: Message) -> None: """Process the raw elements of a message to generate result_text and placeholders.""" # Skip if already processed @@ -144,6 +151,8 @@ def _process_message_elements(self, message_obj: Message) -> None: else: logger.warning("Unknown element type in message %s: %s", message_obj.name, type(element)) + self._sort_placeholders(message_obj) + def parse(self, resource: ast.Resource) -> dict[str, Message]: # First pass: collect all terms and message structures for entry in resource.body: