123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- #!/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()
|