1 changed files with 690 additions and 0 deletions
@ -0,0 +1,690 @@ |
|||
import os |
|||
import platform |
|||
import re |
|||
import subprocess |
|||
from collections.abc import Iterable |
|||
from dataclasses import dataclass |
|||
from pathlib import Path |
|||
from typing import Annotated, Literal, cast |
|||
|
|||
import typer |
|||
|
|||
ROOT = Path("../") # assuming this script is in the scripts directory |
|||
DOCS_ROOT = os.getenv("DOCS_ROOT", "docs") |
|||
TMP_DOCS_PATH = os.getenv("TMP_DOCS_PATH", "non-git/translations") |
|||
VSCODE_COMMAND = os.getenv( |
|||
"VSCODE_COMMAND", "code.cmd" if platform.system() == "Windows" else "code" |
|||
) |
|||
|
|||
# TBD: `Literal` is not supported in typer 0.16.0, which is the |
|||
# version given in the requirements-docs.txt. |
|||
# Shall we upgrade that requirement to 0.20.0? |
|||
LANGS = Literal["es", "de", "ru", "pt", "uk", "fr"] |
|||
|
|||
|
|||
non_translated_sections = ( |
|||
f"reference{os.sep}", |
|||
"release-notes.md", |
|||
"fastapi-people.md", |
|||
"external-links.md", |
|||
"newsletter.md", |
|||
"management-tasks.md", |
|||
"management.md", |
|||
"contributing.md", |
|||
) |
|||
|
|||
|
|||
class Retry(Exception): |
|||
pass |
|||
|
|||
|
|||
class CompareError(Exception): |
|||
pass |
|||
|
|||
|
|||
@dataclass |
|||
class Config: |
|||
lang: LANGS |
|||
interactive: bool = True |
|||
check_code_includes: bool = True |
|||
check_multiline_blocks: bool = True |
|||
check_headers_and_permalinks: bool = True |
|||
check_markdown_links: bool = True |
|||
check_html_links: bool = True |
|||
full_paths: bool = False |
|||
|
|||
|
|||
# =================================================================================== |
|||
# Code includes |
|||
|
|||
CODE_INCLUDE_RE = re.compile(r"^\{\*\s*(\S+)\s*(.*)\*\}$") |
|||
|
|||
|
|||
def extract_code_includes(lines: list[str]) -> list[tuple[str, str, str, int]]: |
|||
includes = [] |
|||
for line_no, line in enumerate(lines, start=1): |
|||
if CODE_INCLUDE_RE.match(line): |
|||
includes.append((line_no, line)) |
|||
return includes |
|||
|
|||
|
|||
def replace_code_includes(source_text: str, target_text: str) -> str: |
|||
target_lines = target_text.splitlines() |
|||
source_code_includes = extract_code_includes(source_text.splitlines()) |
|||
target_code_includes = extract_code_includes(target_lines) |
|||
|
|||
if len(source_code_includes) != len(target_code_includes): |
|||
raise CompareError( |
|||
f"Number of code includes differs: " |
|||
f"{len(source_code_includes)} in source vs {len(target_code_includes)} in target." |
|||
) |
|||
|
|||
for src_include, tgt_include in zip(source_code_includes, target_code_includes): |
|||
_, src_line = src_include |
|||
tgt_line_no, _ = tgt_include |
|||
target_lines[tgt_line_no - 1] = src_line |
|||
|
|||
target_lines.append("") # To preserve the empty line in the end of the file |
|||
return "\n".join(target_lines) |
|||
|
|||
|
|||
# =================================================================================== |
|||
# Multiline code blocks |
|||
|
|||
LANG_RE = re.compile(r"^```([\w-]*)", re.MULTILINE) |
|||
|
|||
|
|||
def get_code_block_lang(line: str) -> str: |
|||
match = LANG_RE.match(line) |
|||
if match: |
|||
return match.group(1) |
|||
return "" |
|||
|
|||
|
|||
def extract_multiline_blocks(text: str) -> list[tuple[str, int, str]]: |
|||
lines = text.splitlines() |
|||
blocks = [] |
|||
|
|||
in_code_block3 = False |
|||
in_code_block4 = False |
|||
current_block_lang = "" |
|||
current_block_start_line = -1 |
|||
current_block_lines = [] |
|||
|
|||
for line_no, line in enumerate(lines, start=1): |
|||
stripped = line.lstrip() |
|||
|
|||
# --- Detect opening fence --- |
|||
if not (in_code_block3 or in_code_block4): |
|||
if stripped.startswith("```"): |
|||
current_block_start_line = line_no |
|||
count = len(stripped) - len(stripped.lstrip("`")) |
|||
if count == 3: |
|||
in_code_block3 = True |
|||
current_block_lang = get_code_block_lang(stripped) |
|||
current_block_lines = [line] |
|||
continue |
|||
elif count >= 4: |
|||
in_code_block4 = True |
|||
current_block_lang = get_code_block_lang(stripped) |
|||
current_block_lines = [line] |
|||
continue |
|||
|
|||
# --- Detect closing fence --- |
|||
elif in_code_block3: |
|||
if stripped.startswith("```"): |
|||
count = len(stripped) - len(stripped.lstrip("`")) |
|||
if count == 3: |
|||
current_block_lines.append(line) |
|||
blocks.append( |
|||
( |
|||
current_block_lang, |
|||
current_block_start_line, |
|||
"\n".join(current_block_lines), |
|||
) |
|||
) |
|||
in_code_block3 = False |
|||
current_block_lang = "" |
|||
current_block_start_line = -1 |
|||
continue |
|||
current_block_lines.append(line) |
|||
|
|||
elif in_code_block4: |
|||
if stripped.startswith("````"): |
|||
count = len(stripped) - len(stripped.lstrip("`")) |
|||
if count >= 4: |
|||
current_block_lines.append(line) |
|||
blocks.append( |
|||
( |
|||
current_block_lang, |
|||
current_block_start_line, |
|||
"\n".join(current_block_lines), |
|||
) |
|||
) |
|||
in_code_block4 = False |
|||
current_block_lang = "" |
|||
current_block_start_line = -1 |
|||
continue |
|||
current_block_lines.append(line) |
|||
|
|||
return blocks |
|||
|
|||
|
|||
def replace_blocks(source_text: str, target_text: str) -> str: |
|||
source_blocks = extract_multiline_blocks(source_text) |
|||
target_blocks = extract_multiline_blocks(target_text) |
|||
|
|||
if len(source_blocks) != len(target_blocks): |
|||
raise CompareError( |
|||
f"Number of code blocks differs: " |
|||
f"{len(source_blocks)} in source vs {len(target_blocks)} in target." |
|||
) |
|||
|
|||
for i, ((src_lang, *_), (tgt_lang, tgt_line_no, *_)) in enumerate( |
|||
zip(source_blocks, target_blocks), 1 |
|||
): |
|||
if src_lang != tgt_lang: |
|||
raise CompareError( |
|||
f"Type mismatch in block #{i} (line {tgt_line_no}): " |
|||
f"'{src_lang or '(no lang)'}' vs '{tgt_lang or '(no lang)'}'" |
|||
) |
|||
|
|||
# Sequentially replace each block in target with the one from source |
|||
result = target_text |
|||
for (*_, src_block), (*_, tgt_block) in zip(source_blocks, target_blocks): |
|||
result = result.replace(tgt_block, src_block, 1) |
|||
|
|||
return result |
|||
|
|||
|
|||
# =================================================================================== |
|||
# Headers and permalinks |
|||
|
|||
header_with_permalink_pattern = re.compile(r"^(#{1,6}) (.+?)(\s*\{\s*#.*\s*\})?\s*$") |
|||
|
|||
|
|||
def extract_headers_and_permalinks(lines: list[str]) -> list[tuple[str, int, str]]: |
|||
headers = [] |
|||
in_code_block3 = False |
|||
in_code_block4 = False |
|||
|
|||
for line_no, line in enumerate(lines, start=1): |
|||
if not (in_code_block3 or in_code_block4): |
|||
if line.startswith("```"): |
|||
count = len(line) - len(line.lstrip("`")) |
|||
if count == 3: |
|||
in_code_block3 = True |
|||
continue |
|||
elif count >= 4: |
|||
in_code_block4 = True |
|||
continue |
|||
|
|||
header_match = header_with_permalink_pattern.match(line) |
|||
if header_match: |
|||
hashes, _title, permalink = header_match.groups() |
|||
headers.append((hashes, line_no, permalink)) |
|||
|
|||
elif in_code_block3: |
|||
if line.startswith("```"): |
|||
count = len(line) - len(line.lstrip("`")) |
|||
if count == 3: |
|||
in_code_block3 = False |
|||
continue |
|||
|
|||
elif in_code_block4: |
|||
if line.startswith("````"): |
|||
count = len(line) - len(line.lstrip("`")) |
|||
if count >= 4: |
|||
in_code_block4 = False |
|||
continue |
|||
|
|||
return headers |
|||
|
|||
|
|||
def replace_headers_and_permalinks(source_text: str, target_text: str) -> str: |
|||
target_lines = target_text.splitlines() |
|||
|
|||
source_headers = extract_headers_and_permalinks(source_text.splitlines()) |
|||
target_headers = extract_headers_and_permalinks(target_lines) |
|||
|
|||
if len(source_headers) != len(target_headers): |
|||
raise CompareError( |
|||
f"Number of headers differs: " |
|||
f"{len(source_headers)} in source vs {len(target_headers)} in target." |
|||
) |
|||
|
|||
for i, ((src_hashes, *_), (tgt_hashes, tgt_line_no, *_)) in enumerate( |
|||
zip(source_headers, target_headers), 1 |
|||
): |
|||
if src_hashes != tgt_hashes: |
|||
raise CompareError( |
|||
f"Header level mismatch in #{i} (line {tgt_line_no}): " |
|||
"'{src_hashes}' vs '{tgt_hashes}'" |
|||
) |
|||
|
|||
# Sequentially replace each header permalink in target with the one from source |
|||
for src_header, tgt_header in zip(source_headers, target_headers): |
|||
src_permalink = src_header[2] |
|||
tgt_line_no = tgt_header[1] - 1 # Convert from 1-based to 0-based |
|||
header_match = header_with_permalink_pattern.match(target_lines[tgt_line_no]) |
|||
if header_match: |
|||
hashes, title, _ = header_match.groups() |
|||
target_lines[tgt_line_no] = ( |
|||
f"{hashes} {title}{src_permalink or ' (ERROR - MISSING PERMALINK)'}" |
|||
) |
|||
|
|||
target_lines.append("") # To preserve the empty line in the end of the file |
|||
return "\n".join(target_lines) |
|||
|
|||
|
|||
# =================================================================================== |
|||
# Links |
|||
|
|||
MARKDOWN_LINK_RE = re.compile( |
|||
r"(?<!\!)" # not an image ![...] |
|||
r"\[(?P<text>.*?)\]" # link text (non-greedy) |
|||
r"\(" |
|||
r"(?P<url>\S+?)" # url (no spaces, non-greedy) |
|||
r'(?:\s+["\'](?P<title>.*?)["\'])?' # optional title in "" or '' |
|||
r"\)" |
|||
) |
|||
|
|||
|
|||
def extract_markdown_links(lines: list[str]) -> list[tuple[str, int]]: |
|||
links = [] |
|||
for line_no, line in enumerate(lines, start=1): |
|||
for m in MARKDOWN_LINK_RE.finditer(line): |
|||
url = m.group("url") |
|||
links.append((url, line_no)) |
|||
return links |
|||
|
|||
|
|||
def replace_markdown_links(source_text: str, target_text: str, lang: str) -> str: |
|||
target_lines = target_text.splitlines() |
|||
source_links = extract_markdown_links(source_text.splitlines()) |
|||
target_links = extract_markdown_links(target_lines) |
|||
|
|||
if len(source_links) != len(target_links): |
|||
raise CompareError( |
|||
f"Number of markdown links differs: " |
|||
f"{len(source_links)} in source vs {len(target_links)} in target." |
|||
) |
|||
|
|||
# Sequentially replace each link URL in target with the one from source |
|||
for (src_link, _), (tgt_link, tgt_line_no) in zip(source_links, target_links): |
|||
real_line_no = tgt_line_no - 1 # Convert to zero-based |
|||
line = target_lines[real_line_no] |
|||
link_replace = add_lang_code_if_needed(src_link, tgt_link, lang) |
|||
target_lines[real_line_no] = line.replace(tgt_link, link_replace) |
|||
|
|||
target_lines.append("") # To preserve the empty line in the end of the file |
|||
return "\n".join(target_lines) |
|||
|
|||
|
|||
HTML_LINK_RE = re.compile(r"<a\s+[^>]*>.*?</a>") |
|||
HTML_LINK_TEXT = re.compile(r"<a\b([^>]*)>(.*?)</a>") |
|||
HTML_LINK_OPEN_TAG_RE = re.compile(r"<a\b([^>]*)>") |
|||
HTML_ATTR_RE = re.compile(r'(\w+)\s*=\s*([\'"])(.*?)\2') |
|||
|
|||
|
|||
def extract_html_links( |
|||
lines: list[str], |
|||
) -> list[tuple[tuple[str, list[tuple[str, str, str]], str], int]]: |
|||
links = [] |
|||
for line_no, line in enumerate(lines, start=1): |
|||
for html_link in HTML_LINK_RE.finditer(line): |
|||
link_str = html_link.group(0) |
|||
link_text = cast(re.Match, HTML_LINK_TEXT.match(link_str)).group(2) |
|||
link_data = (link_str, [], link_text) |
|||
link_open_tag = cast(re.Match, HTML_LINK_OPEN_TAG_RE.match(link_str)).group( |
|||
1 |
|||
) |
|||
attributes = re.findall(HTML_ATTR_RE, link_open_tag) |
|||
for attr_data in attributes: |
|||
link_data[1].append(attr_data) |
|||
links.append((link_data, line_no)) |
|||
return links |
|||
|
|||
|
|||
TIANGOLO_COM = "https://fastapi.tiangolo.com" |
|||
|
|||
|
|||
def add_lang_code_if_needed(url: str, prev_url: str, lang_code: str) -> str: |
|||
if url.startswith(TIANGOLO_COM): |
|||
if prev_url.startswith(f"{TIANGOLO_COM}/{lang_code}"): |
|||
url = url.replace(TIANGOLO_COM, f"{TIANGOLO_COM}/{lang_code}") |
|||
return url |
|||
|
|||
|
|||
def reconstruct_html_link( |
|||
attributes: list[tuple[str, str, str]], |
|||
link_text: str, |
|||
prev_attributes: list[tuple[str, str, str]], |
|||
lang_code: str, |
|||
) -> str: |
|||
prev_attributes_dict = {attr[0]: attr[2] for attr in prev_attributes} |
|||
prev_url = prev_attributes_dict["href"] |
|||
attributes_upd = [] |
|||
for attr_name, attr_quotes, attr_value in attributes: |
|||
if attr_name == "href": |
|||
attr_value = add_lang_code_if_needed(attr_value, prev_url, lang_code) |
|||
attributes_upd.append((attr_name, attr_quotes, attr_value)) |
|||
|
|||
attrs_str = " ".join( |
|||
f"{name}={quetes}{value}{quetes}" for name, quetes, value in attributes_upd |
|||
) |
|||
return f"<a {attrs_str}>{link_text}</a>" |
|||
|
|||
|
|||
def replace_html_links(source_text: str, target_text: str, lang: str) -> str: |
|||
target_lines = target_text.splitlines() |
|||
source_links = extract_html_links(source_text.splitlines()) |
|||
target_links = extract_html_links(target_lines) |
|||
|
|||
if len(source_links) != len(target_links): |
|||
raise CompareError( |
|||
f"Number of HTML links differs: " |
|||
f"{len(source_links)} in source vs {len(target_links)} in target." |
|||
) |
|||
|
|||
# Sequentially replace attributes of each link URL in target with the one from source |
|||
for (src_link_data, _), (tgt_link_data, tgt_line_no) in zip( |
|||
source_links, target_links |
|||
): |
|||
real_line_no = tgt_line_no - 1 # Convert to zero-based |
|||
line = target_lines[real_line_no] |
|||
tgt_link_text = tgt_link_data[2] |
|||
|
|||
tgt_link_original = tgt_link_data[0] |
|||
tgt_link_override = reconstruct_html_link( |
|||
src_link_data[1], tgt_link_text, tgt_link_data[1], lang |
|||
) |
|||
target_lines[real_line_no] = line.replace(tgt_link_original, tgt_link_override) |
|||
|
|||
target_lines.append("") # To preserve the empty line in the end of the file |
|||
return "\n".join(target_lines) |
|||
|
|||
|
|||
# =================================================================================== |
|||
# Images |
|||
|
|||
|
|||
# =================================================================================== |
|||
# Helper functions |
|||
|
|||
|
|||
def get_lang_doc_root_dir(lang: str) -> Path: |
|||
return ROOT / DOCS_ROOT / lang / "docs" |
|||
|
|||
|
|||
def iter_all_lang_paths(lang_path_root: Path) -> Iterable[Path]: |
|||
""" |
|||
Iterate on the markdown files to translate in order of priority. |
|||
""" |
|||
|
|||
first_dirs = [ |
|||
lang_path_root / "learn", |
|||
lang_path_root / "tutorial", |
|||
lang_path_root / "advanced", |
|||
lang_path_root / "about", |
|||
lang_path_root / "how-to", |
|||
] |
|||
first_parent = lang_path_root |
|||
yield from first_parent.glob("*.md") |
|||
for dir_path in first_dirs: |
|||
yield from dir_path.rglob("*.md") |
|||
first_dirs_str = tuple(str(d) for d in first_dirs) |
|||
for path in lang_path_root.rglob("*.md"): |
|||
if str(path).startswith(first_dirs_str): |
|||
continue |
|||
if path.parent == first_parent: |
|||
continue |
|||
yield path |
|||
|
|||
|
|||
def get_all_paths(lang: str): |
|||
res: list[str] = [] |
|||
lang_docs_root = get_lang_doc_root_dir(lang) |
|||
for path in iter_all_lang_paths(lang_docs_root): |
|||
relpath = path.relative_to(lang_docs_root) |
|||
if not str(relpath).startswith(non_translated_sections): |
|||
res.append(str(relpath)) |
|||
return res |
|||
|
|||
|
|||
# =================================================================================== |
|||
# Main |
|||
|
|||
|
|||
def process_one_file_with_retry(document_path: str, config: Config) -> bool: |
|||
en_docs_root_path = Path(get_lang_doc_root_dir("en")) |
|||
lang_docs_root_path = Path(get_lang_doc_root_dir(config.lang)) |
|||
while True: |
|||
try: |
|||
return process_one_file( |
|||
en_docs_root_path / document_path, |
|||
lang_docs_root_path / document_path, |
|||
config=config, |
|||
) |
|||
except Retry: # Retry is only raised in interactive mode |
|||
pass |
|||
|
|||
|
|||
def process_one_file( |
|||
en_doc_path_str: Path, lang_doc_path_str: Path, config: Config |
|||
) -> bool: |
|||
en_doc_path = Path(en_doc_path_str) |
|||
lang_doc_path = Path(lang_doc_path_str) |
|||
if not en_doc_path.exists(): |
|||
print(f"❌🔎 {en_doc_path_str} - doesn't exist") |
|||
return False |
|||
|
|||
en_doc_text = en_doc_path.read_text(encoding="utf-8") |
|||
lang_doc_text = lang_doc_path.read_text(encoding="utf-8") |
|||
lang_doc_text_orig = lang_doc_text |
|||
|
|||
try: |
|||
if config.check_code_includes: |
|||
lang_doc_text = replace_code_includes( |
|||
source_text=en_doc_text, |
|||
target_text=lang_doc_text, |
|||
) |
|||
if config.check_multiline_blocks: |
|||
lang_doc_text = replace_blocks( |
|||
source_text=en_doc_text, |
|||
target_text=lang_doc_text, |
|||
) |
|||
if config.check_headers_and_permalinks: |
|||
lang_doc_text = replace_headers_and_permalinks( |
|||
source_text=en_doc_text, |
|||
target_text=lang_doc_text, |
|||
) |
|||
if config.check_markdown_links: |
|||
lang_doc_text = replace_markdown_links( |
|||
source_text=en_doc_text, |
|||
target_text=lang_doc_text, |
|||
lang=config.lang, |
|||
) |
|||
if config.check_html_links: |
|||
lang_doc_text = replace_html_links( |
|||
source_text=en_doc_text, |
|||
target_text=lang_doc_text, |
|||
lang=config.lang, |
|||
) |
|||
|
|||
except CompareError as e: |
|||
print(f"❔❌ {lang_doc_path_str} Error: {e}") |
|||
if not config.interactive: |
|||
return False |
|||
subprocess.run([VSCODE_COMMAND, "--diff", lang_doc_path_str, en_doc_path_str]) |
|||
resp = "" |
|||
while resp not in ("f", "e"): |
|||
resp = input( |
|||
" Check the diff, fix the problem, and then type F if it's fixed or E to mark as invalid and skip: " |
|||
) |
|||
if resp.lower() == "e": |
|||
print(f"❌ {lang_doc_path_str} skipped with error") |
|||
return |
|||
print(f"Check {lang_doc_path_str} again") |
|||
raise Retry() from None |
|||
|
|||
if lang_doc_text_orig != lang_doc_text: |
|||
print(f"❔🆚 {lang_doc_path_str} - non-empty diff") |
|||
if not config.interactive: |
|||
return False |
|||
tmp_path = ROOT / TMP_DOCS_PATH / Path(lang_doc_path_str) |
|||
tmp_path.parent.mkdir(parents=True, exist_ok=True) |
|||
tmp_path.write_text(lang_doc_text, encoding="utf-8") |
|||
subprocess.run( |
|||
[VSCODE_COMMAND, "--diff", str(lang_doc_path_str), str(tmp_path)] |
|||
) |
|||
resp = "" |
|||
while resp not in ("f", "e"): |
|||
resp = input( |
|||
" Check the diff, fix the problem, and then type F to mark it as fixed or E to to mark as invalid and skip: " |
|||
).lower() |
|||
if resp == "e": |
|||
print(f"❌ {lang_doc_path_str} skipped with non-empty diff") |
|||
return |
|||
|
|||
print(f"✅ {lang_doc_path_str}") |
|||
return True |
|||
|
|||
|
|||
# =================================================================================== |
|||
# Typer app |
|||
|
|||
cli = typer.Typer() |
|||
|
|||
|
|||
@cli.callback() |
|||
def callback(): |
|||
pass |
|||
|
|||
|
|||
@cli.callback() |
|||
def main( |
|||
ctx: typer.Context, |
|||
lang: Annotated[LANGS, typer.Option()], |
|||
interactive: Annotated[ |
|||
bool, |
|||
typer.Option( |
|||
help="If True, will open VSCode diffs for each change to fix and confirm.", |
|||
), |
|||
] = True, |
|||
full_paths: Annotated[ |
|||
bool, |
|||
typer.Option( |
|||
help="If True, the provided document paths are treated as full paths.", |
|||
), |
|||
] = False, |
|||
check_code_includes: Annotated[ |
|||
bool, |
|||
typer.Option( |
|||
help="If True, will compare code includes blocks.", |
|||
), |
|||
] = True, |
|||
check_multiline_blocks: Annotated[ |
|||
bool, |
|||
typer.Option( |
|||
help="If True, will compare multiline code blocks.", |
|||
), |
|||
] = True, |
|||
check_headers_and_permalinks: Annotated[ |
|||
bool, |
|||
typer.Option( |
|||
help="If True, will compare headers and permalinks.", |
|||
), |
|||
] = True, |
|||
check_markdown_links: Annotated[ |
|||
bool, |
|||
typer.Option( |
|||
help="If True, will compare markdown links.", |
|||
), |
|||
] = True, |
|||
check_html_links: Annotated[ |
|||
bool, |
|||
typer.Option( |
|||
help="If True, will compare HTML links.", |
|||
), |
|||
] = True, |
|||
): |
|||
ctx.obj = Config( |
|||
lang=lang, |
|||
interactive=interactive, |
|||
full_paths=full_paths, |
|||
check_code_includes=check_code_includes, |
|||
check_multiline_blocks=check_multiline_blocks, |
|||
check_headers_and_permalinks=check_headers_and_permalinks, |
|||
check_markdown_links=check_markdown_links, |
|||
check_html_links=check_html_links, |
|||
) |
|||
|
|||
|
|||
@cli.command() |
|||
def process_all( |
|||
ctx: typer.Context, |
|||
): |
|||
""" |
|||
Go through all documents of language and compare special blocks with the corresponding |
|||
blocks in English versions of those documents. |
|||
""" |
|||
config = cast(Config, ctx.obj) |
|||
lang_docs_root_path = get_lang_doc_root_dir(config.lang) |
|||
docs = get_all_paths(config.lang) |
|||
|
|||
all_good = True |
|||
pages_with_errors: list[str] = [] |
|||
for doc in docs: |
|||
res = process_one_file_with_retry(document_path=doc, config=config) |
|||
all_good = all_good and res |
|||
if not res: |
|||
pages_with_errors.append(doc) |
|||
|
|||
if not all_good: |
|||
print("Some documents had errors:") |
|||
docs_path = lang_docs_root_path.relative_to(ROOT) |
|||
for page in pages_with_errors: |
|||
print(f" - {docs_path / page}") |
|||
raise typer.Exit(code=1) |
|||
|
|||
|
|||
@cli.command() |
|||
def process_pages( |
|||
doc_paths: Annotated[ |
|||
list[str], |
|||
typer.Argument( |
|||
help="List of relative paths to the EN documents. Should be relative to docs/en/docs/", |
|||
), |
|||
], |
|||
ctx: typer.Context, |
|||
): |
|||
""" |
|||
Compare special blocks of specified EN documents with the corresponding blocks in |
|||
translated versions of those documents. |
|||
""" |
|||
|
|||
config = cast(Config, ctx.obj) |
|||
lang_docs_root_path = get_lang_doc_root_dir(config.lang) |
|||
|
|||
all_good = True |
|||
pages_with_errors: list[str] = [] |
|||
for doc_path in doc_paths: |
|||
if config.full_paths: |
|||
path = ROOT / doc_path.lstrip("/") |
|||
doc_path = str(path.relative_to(lang_docs_root_path)) |
|||
res = process_one_file_with_retry(document_path=doc_path, config=config) |
|||
all_good = all_good and res |
|||
if not res: |
|||
pages_with_errors.append(doc_path) |
|||
|
|||
if not all_good: |
|||
print("Some documents had errors:") |
|||
docs_path = lang_docs_root_path.relative_to(ROOT) |
|||
for page in pages_with_errors: |
|||
print(f" - {docs_path / page}") |
|||
raise typer.Exit(code=1) |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
cli() |
|||
Loading…
Reference in new issue