123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- #!/usr/bin/env python3
- # SPDX-License-Identifier: MIT
- """
- A Python script to update the common contents of a command example across all languages.
- Usage:
- python3 scripts/update-command.py [-c] [-u] [-n] <PLATFORM> <FILENAME>
- Options:
- -c, --common-part COMMON_PART
- Specify the common part to be modified (any content between double brackets will be ignored).
- -u, --updated-common-part UPDATED_COMMON_PART
- Specify the updated common part (any content between double brackets will be ignored).
- -n, --dry-run
- Show what changes would be made without actually modifying the page.
- Examples:
- 1. Update 'cargo' page interactively:
- python3 scripts/update-command.py common cargo
- Enter the command examples (any content between double curly brackets will be ignored):
- Enter the common part to modify: cargo search {{}}
- Enter the change to be made: cargo search --limit 1 {{}}
- 2. Show what changes would be made by updating `sudo apt install {{}}` in 'apt' page to `sudo apt install {{}} --no-confirm`:
- python3 scripts/update-command.py --dry-run -c "sudo apt install {{}}" -u "sudo apt install {{}} --no-confirm" linux apt
- """
- from pathlib import Path
- import os
- import re
- import argparse
- import sys
- from functools import reduce
- import logging
- class MyFormatter(logging.Formatter):
- grey = "\x1b[0;30m"
- yellow = "\x1b[33;20m"
- red = "\x1b[31;20m"
- bold_red = "\x1b[31;1m"
- reset = "\x1b[0m"
- format = "%(levelname)s: %(message)s (%(filename)s:%(lineno)d)"
- FORMATS = {
- logging.INFO: grey + format + reset,
- logging.WARNING: yellow + format + reset,
- logging.ERROR: red + format + reset,
- }
- def format(self, record):
- log_fmt = self.FORMATS.get(record.levelno)
- formatter = logging.Formatter(log_fmt)
- return formatter.format(record)
- logger = logging.getLogger(__name__)
- logger.propagate = False
- ch = logging.StreamHandler()
- ch.setFormatter(MyFormatter())
- logger.addHandler(ch)
- def get_locales(base_path: Path) -> list[str]:
- return [
- d.name.split(".")[1]
- for d in base_path.iterdir()
- if d.is_dir() and d.name.startswith("pages.")
- ]
- def take_cmd_example_with_common_part(cmd_examples: list[str], common_part: str) -> str:
- return next(
- (
- f"`{cmd_example}`"
- for cmd_example in cmd_examples
- if remove_placeholders(cmd_example) == common_part
- ),
- None,
- )
- def get_cmd_examples_of_page(page_text: str) -> list[str]:
- command_pattern = re.compile(r"`([^`]+)`")
- return re.findall(command_pattern, page_text)
- def find_cmd_example_with_common_part(common_part: str, page_text: str) -> list[str]:
- cmd_examples = get_cmd_examples_of_page(page_text)
- return take_cmd_example_with_common_part(cmd_examples, common_part)
- def get_page_path(tldr_root: Path, locale: str, platform: str, filename: str):
- if locale == "":
- return tldr_root / "pages" / platform / filename
- return tldr_root / f"pages.{locale}" / platform / filename
- def split_by_curly_brackets(s: str) -> list[str]:
- return re.split(r"(\{\{.*?\}\})", s)
- def parse_placeholders(cmd_example: str) -> list[str]:
- return [
- part.strip("{}")
- for part in split_by_curly_brackets(cmd_example)
- if part.startswith("{{") and part.endswith("}}")
- ]
- def place_placeholders(cmd_example: str, placeholders: list[str]) -> str:
- return reduce(
- lambda cmd, ph: cmd.replace("{{}}", "{{" + ph + "}}", 1),
- placeholders,
- cmd_example,
- )
- def remove_placeholders(cmd_example: str) -> str:
- return re.sub(r"\{\{.*?\}\}", "{{}}", cmd_example)
- def add_backticks(cmd_example: str) -> str:
- return "`" + cmd_example.strip("`") + "`"
- def update_page(
- page_path: Path,
- old_common_part: str,
- new_common_part: str,
- dry_run: bool,
- ) -> None:
- with page_path.open("r", encoding="utf-8") as file:
- page_text = file.read()
- logger.info(f"Processing page: {page_path}")
- cmd_example = find_cmd_example_with_common_part(old_common_part, page_text)
- if not cmd_example:
- logger.warning(f"Common part '{old_common_part}' not found in '{page_path}'.")
- return False
- logger.info(f"Found command example: {cmd_example}")
- new_cmd_example = add_backticks(
- place_placeholders(new_common_part, parse_placeholders(cmd_example))
- )
- logger.info(f"{cmd_example} -> {new_cmd_example}")
- if not dry_run:
- new_page_text = page_text.replace(cmd_example, new_cmd_example)
- with page_path.open("w", encoding="utf-8") as file:
- file.write(new_page_text)
- return True
- def parse_arguments() -> argparse.Namespace:
- parser = argparse.ArgumentParser(description="Update tldr pages.")
- parser.add_argument(
- "platform", help="Relative path to the page from the repository root"
- )
- parser.add_argument("filename", help="Page file name (without .md)")
- parser.add_argument(
- "-c", "--common-part", help="Common part to be modified", required=False
- )
- parser.add_argument(
- "-u", "--updated-common-part", help="Updated common part", required=False
- )
- parser.add_argument(
- "-n",
- "--dry-run",
- action="store_true",
- help="Show what changes would be made without actually modifying the pages",
- )
- parser.add_argument(
- "-v",
- "--verbose",
- action="count",
- default=0,
- help="Increase verbosity level (use -v, -vv)",
- )
- args = parser.parse_args()
- if args.verbose > 0:
- log_levels = [logging.WARNING, logging.INFO]
- log_level = log_levels[min(args.verbose, len(log_levels) - 1)]
- else:
- log_level = logging.ERROR
- logging.basicConfig(level=log_level)
- return args
- def update_pages(
- tldr_root: str,
- platform: str,
- filename: str,
- locales: list[str],
- old_common_part: str,
- updated_common_part: str,
- dry_run: bool,
- ) -> None:
- for locale in locales:
- page_path = get_page_path(tldr_root, locale, platform, filename)
- if page_path.exists() and page_path.is_file():
- exists = update_page(
- page_path,
- old_common_part,
- updated_common_part,
- dry_run,
- )
- if not exists and locale == "":
- logger.warning(
- f"Common part '{old_common_part}' not found in '{page_path}'."
- )
- def clean_cmd_example(cmd_example: str) -> str:
- return remove_placeholders(cmd_example).strip("`")
- def get_tldr_root() -> Path:
- f = Path("update-command.py").resolve()
- return next(path for path in f.parents if path.name == "tldr")
- if "TLDR_ROOT" in os.environ:
- return Path(os.environ["TLDR_ROOT"])
- logger.error(
- "Please set TLDR_ROOT to the location of a clone of https://github.com/tldr-pages/tldr."
- )
- sys.exit(1)
- def main():
- args = parse_arguments()
- print(
- "Enter the command examples (any content between double curly brackets will be ignored):"
- )
- common_part = (
- args.common_part
- if args.common_part
- else clean_cmd_example(input("Enter the common part to modify: "))
- )
- updated_common_part = (
- args.updated_common_part
- if args.updated_common_part
- else clean_cmd_example(input("Enter the change to be made: "))
- )
- tldr_root = get_tldr_root()
- locales = [""]
- locales.extend(get_locales(tldr_root))
- update_pages(
- tldr_root,
- args.platform,
- args.filename + ".md",
- locales,
- common_part,
- updated_common_part,
- args.dry_run,
- )
- if __name__ == "__main__":
- main()
|