Skip to content

Commit

Permalink
[BREAKING CHANGES]
Browse files Browse the repository at this point in the history
Drop `libcst`.
Drop argument `beauty`.
Drop `BeautyFluentSerializer`

Add use of `ast` (built-in python lib)
Add arguments `ignore_attributes` & `expand_ignore_attributes`
Add time measurements
  • Loading branch information
andrew000 committed Jul 26, 2024
1 parent db6018b commit 3486fac
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 290 deletions.
42 changes: 1 addition & 41 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ packages = [
[tool.poetry.dependencies]
python = ">=3.9"
fluent-syntax = "^0.19"
libcst = "^1.4"

[tool.poetry.group.test.dependencies]
pytest = "8.3.2"
Expand Down
24 changes: 18 additions & 6 deletions src/ftl_extract/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from pathlib import Path
from time import perf_counter_ns

import click

Expand All @@ -27,11 +28,17 @@
help="Names of function that is used to get translation.",
)
@click.option(
"--beauty",
is_flag=True,
default=False,
"--ignore-attributes",
default=("set_locale", "use_locale", "use_context", "set_context"),
multiple=True,
show_default=True,
help="Beautify output FTL files.",
help="Ignore attributes, like `i18n.set_locale`.",
)
@click.option(
"--expand-ignore-attributes",
"-a",
multiple=True,
help="Expand default|targeted ignore attributes.",
)
@click.option(
"--comment-junks",
Expand All @@ -46,16 +53,21 @@ def cli_extract(
output_path: Path,
language: tuple[str, ...],
i18n_keys: tuple[str, ...],
beauty: bool = False,
ignore_attributes: tuple[str, ...],
expand_ignore_attributes: tuple[str, ...] | None = None,
comment_junks: bool = False,
) -> None:
click.echo(f"Extracting from {code_path}...")
start_time = perf_counter_ns()

extract(
code_path=code_path,
output_path=output_path,
language=language,
i18n_keys=i18n_keys,
beauty=beauty,
ignore_attributes=ignore_attributes,
expand_ignore_attributes=expand_ignore_attributes,
comment_junks=comment_junks,
)

click.echo(f"Done in {(perf_counter_ns() - start_time) * 1e-9:.3f}s.")
34 changes: 23 additions & 11 deletions src/ftl_extract/code_extractor.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from __future__ import annotations

import ast
from pathlib import Path
from typing import TYPE_CHECKING, cast

import libcst as cst
from fluent.syntax import ast
from fluent.syntax import ast as fluent_ast

from ftl_extract.exceptions import (
FTLExtractorDifferentPathsError,
Expand All @@ -13,7 +13,7 @@
from ftl_extract.matcher import I18nMatcher

if TYPE_CHECKING:
from collections.abc import Iterator, Sequence
from collections.abc import Iterable, Iterator

from ftl_extract.matcher import FluentKey

Expand All @@ -30,20 +30,26 @@ def find_py_files(path: Path) -> Iterator[Path]:
yield from path.rglob("[!{.}]*.py") if path.is_dir() else [path]


def parse_file(path: Path, i18n_keys: str | Sequence[str]) -> dict[str, FluentKey]:
def parse_file(
path: Path,
i18n_keys: str | Iterable[str],
ignore_attributes: Iterable[str],
) -> dict[str, FluentKey]:
"""
Second step: parse given .py file and find all i18n calls.
:param path: Path to .py file.
:type path: Path
:param i18n_keys: Names of function that is used to get translation.
:type i18n_keys: str | Sequence[str]
:param ignore_attributes: Ignore attributes, like `i18n.set_locale`.
:type ignore_attributes: Sequence[str]
:return: Dict with `key` and `FluentKey`.
:rtype: dict[str, FluentKey]
"""
module = cst.parse_module(path.read_bytes())
matcher = I18nMatcher(code_path=path, func_names=i18n_keys)
matcher.extract_matches(module)
node = ast.parse(path.read_bytes())
matcher = I18nMatcher(code_path=path, func_names=i18n_keys, ignore_attributes=ignore_attributes)
matcher.visit(node)
return matcher.fluent_keys


Expand Down Expand Up @@ -90,27 +96,33 @@ def find_conflicts(
if not current_fluent_keys[key].translation.equals(new_fluent_keys[key].translation):
raise FTLExtractorDifferentTranslationError(
key,
cast(ast.Message, current_fluent_keys[key].translation),
cast(ast.Message, new_fluent_keys[key].translation),
cast(fluent_ast.Message, current_fluent_keys[key].translation),
cast(fluent_ast.Message, new_fluent_keys[key].translation),
)


def extract_fluent_keys(path: Path, i18n_keys: str | Sequence[str]) -> dict[str, FluentKey]:
def extract_fluent_keys(
path: Path,
i18n_keys: str | Iterable[str],
ignore_attributes: Iterable[str],
) -> dict[str, FluentKey]:
"""
Extract all `FluentKey`s from given path.
:param path: Path to [.py file] / [directory with .py files].
:type path: Path
:param i18n_keys: Names of function that is used to get translation.
:type i18n_keys: str | Sequence[str]
:param ignore_attributes: Ignore attributes, like `i18n.set_locale`.
:type ignore_attributes: Sequence[str]
:return: Dict with `key` and `FluentKey`.
:rtype: dict[str, FluentKey]
"""
fluent_keys: dict[str, FluentKey] = {}

for file in find_py_files(path):
keys = parse_file(file, i18n_keys)
keys = parse_file(path=file, i18n_keys=i18n_keys, ignore_attributes=ignore_attributes)
post_process_fluent_keys(keys)
find_conflicts(fluent_keys, keys)
fluent_keys.update(keys)
Expand Down
10 changes: 10 additions & 0 deletions src/ftl_extract/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

from typing import Literal

I18N_LITERAL: Literal["i18n"] = "i18n"
GET_LITERAL: Literal["get"] = "get"
PATH_LITERAL: Literal["_path"] = "_path"
IGNORE_ATTRIBUTES: frozenset[str] = frozenset(
{"set_locale", "use_locale", "use_context", "set_context"}
)
21 changes: 14 additions & 7 deletions src/ftl_extract/ftl_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

from ftl_extract import extract_fluent_keys
from ftl_extract.code_extractor import sort_fluent_keys_by_path
from ftl_extract.const import IGNORE_ATTRIBUTES
from ftl_extract.ftl_importer import import_ftl_from_dir
from ftl_extract.process.commentator import comment_ftl_key
from ftl_extract.process.kwargs_extractor import extract_kwargs
from ftl_extract.process.serializer import BeautyFluentSerializer, generate_ftl
from ftl_extract.process.serializer import generate_ftl

if TYPE_CHECKING:
from collections.abc import Iterable
from pathlib import Path

from ftl_extract.matcher import FluentKey
Expand All @@ -23,18 +25,23 @@ def extract(
output_path: Path,
language: tuple[str, ...],
i18n_keys: tuple[str, ...],
beauty: bool = False,
ignore_attributes: Iterable[str] = IGNORE_ATTRIBUTES,
expand_ignore_attributes: Iterable[str] | None = None,
comment_junks: bool = False,
serializer: FluentSerializer | None = None,
) -> None:
serializer: FluentSerializer | BeautyFluentSerializer
if expand_ignore_attributes is not None:
ignore_attributes = frozenset(set(ignore_attributes) | set(expand_ignore_attributes or []))

if beauty is True:
serializer = BeautyFluentSerializer(with_junk=True)
else:
if serializer is None:
serializer = FluentSerializer(with_junk=True)

# Extract fluent keys from code
in_code_fluent_keys = extract_fluent_keys(code_path, i18n_keys)
in_code_fluent_keys = extract_fluent_keys(
path=code_path,
i18n_keys=i18n_keys,
ignore_attributes=ignore_attributes,
)

for lang in language:
# Import fluent keys from existing FTL files
Expand Down
Loading

0 comments on commit 3486fac

Please sign in to comment.