1
0
Fork 0
mirror of https://github.com/tldr-pages/tldr.git synced 2025-03-28 21:16:20 +01:00
tldr/scripts/set-alias-page.py
Chooooo 65c3aa9d5e
scripts/set-alias-page.py: Improve sync translations (#15389)
Co-authored-by: Sebastiaan Speck <12570668+sebastiaanspeck@users.noreply.github.com>
2025-02-16 07:09:55 +05:30

498 lines
15 KiB
Python
Executable file

#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""
A Python script to generate or update alias pages.
Disclaimer: This script generates a lot of false positives so it isn't suggested to use the sync option. If used, only stage changes and commit verified changes for your language by using -l LANGUAGE.
Note: If the current directory or one of its parents is called "tldr", the script will assume it is the tldr root, i.e., the directory that contains a clone of https://github.com/tldr-pages/tldr
If you aren't, the script will use TLDR_ROOT as the tldr root. Also, ensure 'git' is available.
Note: This script uses an interactive prompt instead of positional arguments to:
- Prevent argument parsing errors with command names containing dashes (e.g. 'pacman -S')
- Provide clearer guidance for required inputs
- Allow for input validation before page creation
Usage:
python3 scripts/set-alias-page.py [-p PAGE] [-S] [-l LANGUAGE] [-s] [-n]
Options:
-p, --page PAGE
Specify the alias page in the format "platform/alias_command.md".
This will start an interactive prompt to create/update the page.
-S, --sync
Synchronize each translation's alias page (if exists) with that of the English page.
-l, --language LANGUAGE
Specify the language, a POSIX Locale Name in the form of "ll" or "ll_CC" (e.g. "fr" or "pt_BR").
-s, --stage
Stage modified pages (requires 'git' on $PATH and TLDR_ROOT to be a Git repository).
-n, --dry-run
Show what changes would be made without actually modifying the page.
Examples:
1. Create a new alias page interactively:
python3 scripts/set-alias-page.py -p osx/gsum
python3 scripts/set-alias-page.py --page osx/gsum
This will start a wizard that guides you through creating the page.
2. Read English alias pages and synchronize them into all translations:
python3 scripts/set-alias-page.py -S
python3 scripts/set-alias-page.py --sync
3. Read English alias pages and synchronize them for Brazilian Portuguese pages only:
python3 scripts/set-alias-page.py -S -l pt_BR
python3 scripts/set-alias-page.py --sync --language pt_BR
4. Read English alias pages, synchronize them into all translations and stage modified pages for commit:
python3 scripts/set-alias-page.py -Ss
python3 scripts/set-alias-page.py --sync --stage
5. Read English alias pages and show what changes would be made:
python3 scripts/set-alias-page.py -Sn
python3 scripts/set-alias-page.py --sync --dry-run
"""
import re
from pathlib import Path
from dataclasses import dataclass
from _common import (
IGNORE_FILES,
Colors,
get_tldr_root,
get_pages_dir,
get_target_paths,
get_locale,
get_status,
stage,
create_colored_line,
create_argument_parser,
)
@dataclass
class Config:
"""Global configuration for the script"""
root: Path
pages_dirs: list[Path]
templates: dict[str, str]
dry_run: bool = False
language: str = ""
@dataclass
class AliasPageContent:
"""Content of an alias page"""
title: str
original_command: str
documentation_command: str
@dataclass
class AliasPage:
"""Represents an alias page with its path and content"""
page_path: str
content: AliasPageContent
IGNORE_FILES += ("tldr.md", "aria2.md")
def test_ignore_files():
assert IGNORE_FILES == (
".DS_Store",
"tldr.md",
"aria2.md",
)
assert ".DS_Store" in IGNORE_FILES
assert "tldr.md" in IGNORE_FILES
def get_templates(root: Path):
"""
Get all alias page translation templates from
TLDR_ROOT/contributing-guides/translation-templates/alias-pages.md.
Parameters:
root (Path): The path of local tldr repository, i.e., TLDR_ROOT.
Returns:
dict of (str, str): Language labels map to alias page templates.
"""
template_file = root / "contributing-guides/translation-templates/alias-pages.md"
with template_file.open(encoding="utf-8") as f:
lines = f.readlines()
# Parse alias-pages.md
templates = {}
i = 0
while i < len(lines):
if lines[i].startswith("###"):
lang = lines[i][4:].strip("\n").strip(" ")
while True:
i = i + 1
if lines[i].startswith("Not translated yet."):
is_translated = False
break
elif lines[i].startswith("```markdown"):
i = i + 1
is_translated = True
break
if is_translated:
text = ""
while not lines[i].startswith("```"):
text += lines[i]
i = i + 1
templates[lang] = text
i = i + 1
return templates
def generate_alias_page_content(
template_content: str,
page_content: AliasPageContent,
) -> str:
"""
Generate alias page content by replacing placeholders in the template.
Parameters:
template_content (str): The markdown template for the specific language.
page_content (AliasPageContent): The content of the alias page
Returns:
str: The complete markdown content for the alias page.
"""
template_command = "example"
# Replace placeholders in template with actual values
result = template_content.replace(template_command, page_content.title, 1)
result = result.replace(template_command, page_content.original_command, 1)
result = result.replace(template_command, page_content.documentation_command)
return result
def set_alias_page(
path: Path,
page_content: AliasPageContent,
) -> str:
"""
Write an alias page to disk.
Parameters:
path (Path): Path to an alias page
page_content (AliasPageContent): The content to write to the page
Returns:
str: Execution status
"" if the alias page standing for the same command already exists or if the locale does not match language_to_update.
"\x1b[36mpage added"
"\x1b[34mpage updated"
"\x1b[36mpage would be added"
"\x1b[34mpage would updated"
"""
locale = get_locale(path)
if locale not in config.templates or (
config.language != "" and locale != config.language
):
return ""
# Get existing alias command from the locale page
existing_locale_page_content = get_alias_command_in_page(
path, get_locale_alias_pattern(locale)
)
if (
existing_locale_page_content.documentation_command
== page_content.documentation_command
):
return ""
new_locale_page_content = generate_alias_page_content(
config.templates[locale],
page_content,
)
# Determine status and write file
status = get_status(
"added" if not path.exists() else "updated",
config.dry_run,
"page",
)
if not config.dry_run:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
f.write(new_locale_page_content)
return status
def get_locale_alias_pattern(locale: str) -> str:
"""Get alias pattern from template"""
template_line = re.search(r">.*`example`", config.templates[locale]).group(0)
locale_alias_pattern = template_line[2 : template_line.find("`example`")].strip()
return locale_alias_pattern
def get_alias_command_in_page(path: Path, alias_pattern: str) -> AliasPageContent:
"""
Determine whether the given path is an alias page.
Returns:
AliasPageContent: The page content, or empty strings if not an alias page
"""
if not path.exists():
return AliasPageContent(title="", original_command="", documentation_command="")
with path.open(encoding="utf-8") as f:
content = f.read()
lines = content.splitlines()
title = next((line.strip("# \n") for line in lines if line.startswith("# ")), "")
command_lines = [line for line in lines if "`" in line]
if len(command_lines) != 2 or not title:
return AliasPageContent(title="", original_command="", documentation_command="")
original_command = ""
documentation_command = ""
alias_line = next((line for line in command_lines if alias_pattern in line), None)
if alias_line:
description_match = re.search(r"`([^`]+)`", alias_line)
if description_match:
original_command = description_match[1]
tldr_line = next(
(line for line in command_lines if line.strip().startswith("`tldr")), None
)
if tldr_line:
tldr_match = re.search(r"`tldr (.+)`", tldr_line.strip())
if tldr_match:
documentation_command = tldr_match[1]
return AliasPageContent(
title=title,
original_command=original_command,
documentation_command=documentation_command,
)
def sync_alias_page_to_locale(pages_dir: Path, alias_page: AliasPage) -> list[Path]:
"""
Synchronize an alias page into a specific locale directory.
Parameters:
pages_dir (Path): Directory containing pages for a specific locale
alias_page (AliasPage): The alias page to sync
Returns:
list[Path]: List of paths that were modified
"""
paths = []
path = config.root / pages_dir / alias_page.page_path
status = set_alias_page(path, alias_page.content)
if status != "":
rel_path = "/".join(path.parts[-3:])
paths.append(rel_path)
print(create_colored_line(Colors.GREEN, f"{rel_path} {status}"))
return paths
def get_english_alias_pages(en_path: Path) -> list[AliasPage]:
"""
Get all English alias pages with their commands.
Parameters:
en_path (Path): Path to English pages directory
Returns:
list[AliasPage]: List of alias pages with their content
"""
alias_pages = []
alias_pattern = get_locale_alias_pattern("en")
# Get all platform directories (common, linux, etc.)
platforms = [
page.name for page in en_path.iterdir() if page.name not in IGNORE_FILES
]
# Iterate through each platform
for platform in platforms:
platform_path = en_path / platform
page_paths = [
f"{platform}/{page.name}"
for page in platform_path.iterdir()
if page.name not in IGNORE_FILES
]
# Check each command if it's an alias
for page_path in page_paths:
page_content = get_alias_command_in_page(en_path / page_path, alias_pattern)
if page_content.original_command:
alias_pages.append(AliasPage(page_path=page_path, content=page_content))
return alias_pages
def prompt_alias_page_info(page_path: str) -> AliasPageContent:
"""
Prompt user for alias page content.
Returns:
AliasPageContent: The collected page content
"""
en_path = config.root / "pages"
if not page_path.lower().endswith(".md"):
page_path = f"{page_path}.md"
exists = (en_path / page_path).exists()
print(f"\n{'Updating' if exists else 'Creating new'} alias page...")
print(create_colored_line(Colors.CYAN, f"Page path: {page_path}"))
print(
create_colored_line(
Colors.BLUE,
"\nThe title will be used in the first line of the page after '#'",
)
)
print(create_colored_line(Colors.GREEN, "Example: npm run-script"))
title = input(create_colored_line(Colors.CYAN, "Enter page title: ")).strip()
if not title:
raise SystemExit(create_colored_line(Colors.RED, "Title cannot be empty"))
print(
create_colored_line(
Colors.BLUE,
"\nThe original command will appear in 'This command is an alias of `command`'",
)
)
print(create_colored_line(Colors.GREEN, "Example: npm run"))
original_command = input(
create_colored_line(Colors.CYAN, "Enter original command: ")
).strip()
if not original_command:
raise SystemExit(
create_colored_line(Colors.RED, "Original command cannot be empty")
)
print(
create_colored_line(
Colors.BLUE,
"\nThe documentation command will be used in 'tldr command' line",
)
)
print(create_colored_line(Colors.GREEN, "Example: npm run"))
documentation_command = input(
create_colored_line(
Colors.CYAN,
f"Enter documentation command (press Enter to use {original_command}): ",
)
).strip()
if not documentation_command:
documentation_command = original_command
print("\nSummary:")
print(f"* Title: {create_colored_line(Colors.CYAN, title)}")
print(f"* Original command: {create_colored_line(Colors.CYAN, original_command)}")
print(
f"* Documentation command: {create_colored_line(Colors.CYAN, documentation_command)}"
)
print(create_colored_line(Colors.BLUE, "\nThis will create a page like:"))
print(create_colored_line(Colors.GREEN, f"# {title}"))
print(
create_colored_line(
Colors.GREEN, f"\n> This command is an alias of `{original_command}`."
)
)
print(
create_colored_line(
Colors.GREEN, "\n- View documentation for the original command:"
)
)
print(create_colored_line(Colors.GREEN, f"\n`tldr {documentation_command}`"))
response = (
input(create_colored_line(Colors.CYAN, "\nProceed? [Y/n] ")).lower().strip()
)
if response and response not in ["y", "yes"]:
raise SystemExit(create_colored_line(Colors.RED, "Cancelled by user"))
return AliasPageContent(
title=title,
original_command=original_command,
documentation_command=documentation_command,
)
def main():
parser = create_argument_parser(
"Sets the alias page for all translations of a page"
)
args = parser.parse_args()
root = get_tldr_root()
pages_dirs = get_pages_dir(root)
templates = get_templates(root)
global config
config = Config(
root=root,
pages_dirs=pages_dirs,
templates=templates,
dry_run=args.dry_run,
language=args.language,
)
target_paths = []
# Use '--page' option
if args.page != "":
page_info = prompt_alias_page_info(args.page)
target_paths += get_target_paths(
args.page, config.pages_dirs, check_exists=False
)
for path in target_paths:
rel_path = "/".join(path.parts[-3:])
status = set_alias_page(path, page_info)
if status != "":
print(create_colored_line(Colors.GREEN, f"{rel_path} {status}"))
# Use '--sync' option
elif args.sync:
en_path = config.root / "pages"
pages_dirs = config.pages_dirs.copy()
pages_dirs.remove(en_path)
alias_pages = get_english_alias_pages(en_path)
for alias_page in alias_pages:
for pages_dir in pages_dirs:
target_paths.extend(sync_alias_page_to_locale(pages_dir, alias_page))
# Use '--stage' option
if args.stage and not config.dry_run and len(target_paths) > 0:
stage(target_paths)
if __name__ == "__main__":
main()