123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- #!/usr/bin/env python3
- # SPDX-License-Identifier: MIT
- """
- A Python file that makes some commonly used functions available for other scripts to use.
- """
- from enum import Enum
- from pathlib import Path
- from unittest.mock import patch
- import shutil
- import os
- import argparse
- import subprocess
- IGNORE_FILES = (".DS_Store",)
- class Colors(str, Enum):
- def __str__(self):
- return str(
- self.value
- ) # make str(Colors.COLOR) return the ANSI code instead of an Enum object
- RED = "\x1b[31m"
- GREEN = "\x1b[32m"
- BLUE = "\x1b[34m"
- CYAN = "\x1b[36m"
- RESET = "\x1b[0m"
- def test_ignore_files():
- assert IGNORE_FILES == (".DS_Store",)
- assert ".DS_Store" in IGNORE_FILES
- assert "tldr.md" not in IGNORE_FILES
- def get_tldr_root(lookup_path: Path = None) -> Path:
- """
- Get the path of the local tldr repository, looking for it in each part of the given path. If it is not found, the path in the environment variable TLDR_ROOT is returned.
- Parameters:
- lookup_path (Path): the path to search for the tldr root. By default, the path of the script.
- Returns:
- Path: the local tldr repository.
- """
- if lookup_path is None:
- absolute_lookup_path = Path(__file__).resolve()
- else:
- absolute_lookup_path = Path(lookup_path).resolve()
- if (
- tldr_root := next(
- (path for path in absolute_lookup_path.parents if path.name == "tldr"), None
- )
- ) is not None:
- return tldr_root
- elif "TLDR_ROOT" in os.environ:
- return Path(os.environ["TLDR_ROOT"])
- raise SystemExit(
- f"{Colors.RED}Please set the environment variable TLDR_ROOT to the location of a clone of https://github.com/tldr-pages/tldr{Colors.RESET}"
- )
- def test_get_tldr_root():
- tldr_root = get_tldr_root("/path/to/tldr/scripts/test_script.py")
- assert tldr_root == Path("/path/to/tldr")
- # Set TLDR_ROOT in the environment
- os.environ["TLDR_ROOT"] = "/path/to/tldr_clone"
- tldr_root = get_tldr_root("/tmp")
- assert tldr_root == Path("/path/to/tldr_clone")
- del os.environ["TLDR_ROOT"]
- # Remove TLDR_ROOT from the environment
- original_env = os.environ.pop("TLDR_ROOT", None)
- # Check if SystemExit is raised
- raised = False
- try:
- get_tldr_root("/tmp")
- except SystemExit:
- raised = True
- assert raised
- # Restore the original values
- if original_env is not None:
- os.environ["TLDR_ROOT"] = original_env
- def get_pages_dir(root: Path) -> list[Path]:
- """
- Get all pages directories.
- Parameters:
- root (Path): the path to search for the pages directories.
- Returns:
- list (list of Path's): Path's of page entry and platform, e.g. "page.fr/common".
- """
- return [d for d in root.iterdir() if d.name.startswith("pages")]
- def test_get_pages_dir():
- # Create temporary directories with names starting with "pages"
- root = Path("test_root")
- shutil.rmtree(root, True)
- root.mkdir(exist_ok=True)
- # Create temporary directories with names that do not start with "pages"
- (root / "other_dir_1").mkdir(exist_ok=True)
- (root / "other_dir_2").mkdir(exist_ok=True)
- # Call the function and verify that it returns an empty list
- result = get_pages_dir(root)
- assert result == []
- (root / "pages").mkdir(exist_ok=True)
- (root / "pages.fr").mkdir(exist_ok=True)
- (root / "other_dir").mkdir(exist_ok=True)
- # Call the function and verify the result
- result = get_pages_dir(root)
- expected = [root / "pages", root / "pages.fr"]
- assert result.sort() == expected.sort() # the order differs on Unix / macOS
- shutil.rmtree(root, True)
- def get_target_paths(page: Path, pages_dirs: Path) -> list[Path]:
- """
- Get all paths in all languages that match the page.
- Parameters:
- page (Path): the page to search for.
- Returns:
- list (list of Path's): A list of Path's.
- """
- target_paths = []
- if not page.lower().endswith(".md"):
- page = f"{page}.md"
- arg_platform, arg_page = page.split("/")
- for pages_dir in pages_dirs:
- page_path = pages_dir / arg_platform / arg_page
- if not page_path.exists():
- continue
- target_paths.append(page_path)
- target_paths.sort()
- return target_paths
- def test_get_target_paths():
- root = Path("test_root")
- shutil.rmtree(root, True)
- root.mkdir(exist_ok=True)
- shutil.os.makedirs(root / "pages" / "common")
- shutil.os.makedirs(root / "pages.fr" / "common")
- file_path = root / "pages" / "common" / "tldr.md"
- with open(file_path, "w"):
- pass
- file_path = root / "pages.fr" / "common" / "tldr.md"
- with open(file_path, "w"):
- pass
- target_paths = get_target_paths("common/tldr", get_pages_dir(root))
- for path in target_paths:
- rel_path = "/".join(path.parts[-3:])
- print(rel_path)
- shutil.rmtree(root, True)
- def get_locale(path: Path) -> str:
- """
- Get the locale from the path.
- Parameters:
- path (Path): the path to extract the locale.
- Returns:
- str: a POSIX Locale Name in the form of "ll" or "ll_CC" (e.g. "fr" or "pt_BR").
- """
- # compute locale
- pages_dirname = path.parents[1].name
- if "." in pages_dirname:
- _, locale = pages_dirname.split(".")
- else:
- locale = "en"
- return locale
- def test_get_locale():
- assert get_locale(Path("path/to/pages.fr/common/tldr.md")) == "fr"
- assert get_locale(Path("path/to/pages/common/tldr.md")) == "en"
- assert get_locale(Path("path/to/other/common/tldr.md")) == "en"
- def get_status(action: str, dry_run: bool, type: str) -> str:
- """
- Get a colored status line.
- Parameters:
- action (str): The action to perform.
- dry_run (bool): Whether to perform a dry-run.
- type (str): The kind of object to modify (alias, link).
- Returns:
- str: A colored line
- """
- match action:
- case "added":
- start_color = Colors.CYAN
- case "updated":
- start_color = Colors.BLUE
- case _:
- start_color = Colors.RED
- if dry_run:
- status = f"{type} would be {action}"
- else:
- status = f"{type} {action}"
- return create_colored_line(start_color, status)
- def test_get_status():
- # Test dry run status
- assert (
- get_status("added", True, "alias")
- == f"{Colors.CYAN}alias would be added{Colors.RESET}"
- )
- assert (
- get_status("updated", True, "link")
- == f"{Colors.BLUE}link would be updated{Colors.RESET}"
- )
- # Test non-dry run status
- assert (
- get_status("added", False, "alias") == f"{Colors.CYAN}alias added{Colors.RESET}"
- )
- assert (
- get_status("updated", False, "link")
- == f"{Colors.BLUE}link updated{Colors.RESET}"
- )
- # Test default color for unknown action
- assert (
- get_status("unknown", True, "alias")
- == f"{Colors.RED}alias would be unknown{Colors.RESET}"
- )
- def create_colored_line(start_color: str, text: str) -> str:
- """
- Create a colored line.
- Parameters:
- start_color (str): The color for the line.
- text (str): The text to display.
- Returns:
- str: A colored line
- """
- return f"{start_color}{text}{Colors.RESET}"
- def test_create_colored_line():
- assert (
- create_colored_line(Colors.CYAN, "TLDR") == f"{Colors.CYAN}TLDR{Colors.RESET}"
- )
- assert create_colored_line("Hello", "TLDR") == f"HelloTLDR{Colors.RESET}"
- def create_argument_parser(description: str) -> argparse.ArgumentParser:
- """
- Create an argument parser that can be extended.
- Parameters:
- description (str): The description for the argument parser
- Returns:
- ArgumentParser: an argument parser.
- """
- parser = argparse.ArgumentParser(description=description)
- parser.add_argument(
- "-p",
- "--page",
- type=str,
- default="",
- help='page name in the format "platform/alias_command.md"',
- )
- parser.add_argument(
- "-S",
- "--sync",
- action="store_true",
- default=False,
- help="synchronize each translation's alias page (if exists) with that of English page",
- )
- parser.add_argument(
- "-l",
- "--language",
- type=str,
- default="",
- help='language in the format "ll" or "ll_CC" (e.g. "fr" or "pt_BR")',
- )
- parser.add_argument(
- "-s",
- "--stage",
- action="store_true",
- default=False,
- help="stage modified pages (requires `git` to be on $PATH and TLDR_ROOT to be a Git repository)",
- )
- parser.add_argument(
- "-n",
- "--dry-run",
- action="store_true",
- default=False,
- help="show what changes would be made without actually modifying the pages",
- )
- return parser
- def test_create_argument_parser():
- description = "Test argument parser"
- parser = create_argument_parser(description)
- assert isinstance(parser, argparse.ArgumentParser)
- assert parser.description == description
- # Check if each expected argument is added with the correct configurations
- arguments = [
- ("-p", "--page", str, ""),
- ("-l", "--language", str, ""),
- ("-s", "--stage", None, False),
- ("-S", "--sync", None, False),
- ("-n", "--dry-run", None, False),
- ]
- for short_flag, long_flag, arg_type, default_value in arguments:
- action = parser._option_string_actions[short_flag] # Get action for short flag
- assert action.dest.replace("_", "-") == long_flag.lstrip(
- "-"
- ) # Check destination name
- assert action.type == arg_type # Check argument type
- assert action.default == default_value # Check default value
- def stage(paths: list[Path]):
- """
- Stage the given paths using Git.
- Parameters:
- paths (list of Paths): the list of Path's to stage using Git.
- """
- subprocess.call(["git", "add", *(path.resolve() for path in paths)])
- @patch("subprocess.call")
- def test_stage(mock_subprocess_call):
- paths = [Path("/path/to/file1"), Path("/path/to/file2")]
- # Call the stage function
- stage(paths)
- # Verify that subprocess.call was called with the correct arguments
- mock_subprocess_call.assert_called_once_with(["git", "add", *paths])
|