1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
|
import argparse
import html
import io
import tempfile
from contextlib import redirect_stdout
from difflib import unified_diff
from pathlib import Path
from typer.cli import app as typer_main
PACKAGE_REFERENCE_PATH = Path(__file__).parents[1] / "docs" / "source" / "en" / "package_reference" / "cli.md"
WARNING_HEADER = """<!--
# WARNING
# This entire file has been generated by Typer based on the `hf` CLI implementation.
# To re-generate the code, run `make style` or `python ./utils/generate_cli_reference.py --update`.
# WARNING
-->"""
def print_colored_diff(expected: str, current: str) -> None:
"""Print a colored line-by-line diff between expected and current content.
Auto-generated code by Cursor.
"""
expected_lines = expected.splitlines(keepends=True)
current_lines = current.splitlines(keepends=True)
diff = unified_diff(
current_lines, expected_lines, fromfile="Current content", tofile="Expected content", lineterm=""
)
for line in diff:
line = line.rstrip("\n")
if line.startswith("+++"):
print(f"\033[92m{line}\033[0m") # Green for additions
elif line.startswith("---"):
print(f"\033[91m{line}\033[0m") # Red for deletions
elif line.startswith("@@"):
print(f"\033[96m{line}\033[0m") # Cyan for context
elif line.startswith("+"):
print(f"\033[92m{line}\033[0m") # Green for additions
elif line.startswith("-"):
print(f"\033[91m{line}\033[0m") # Red for deletions
else:
print(line) # Default color for context
def generate_cli_reference() -> str:
with tempfile.TemporaryDirectory() as tmpdir:
tmp_file = Path(tmpdir) / "cli.md"
try:
with redirect_stdout(io.StringIO()): # catch and ignore typer.echo output
typer_main(
[
"src/huggingface_hub/cli/hf.py",
"utils",
"docs",
"--name",
"hf",
"--output",
str(tmp_file),
]
)
except SystemExit as e:
# Typer (Click) calls sys.exit() internally, so we catch it
if e.code not in (0, None):
raise # re-raise if it was an error exit
content = tmp_file.read_text()
# Decode HTML entities that Typer generates
content = html.unescape(content)
return f"{WARNING_HEADER}\n\n{content}"
def check_and_update_cli_reference(update: bool, verbose: bool = False) -> None:
new_content = generate_cli_reference()
if PACKAGE_REFERENCE_PATH.exists():
existing_content = PACKAGE_REFERENCE_PATH.read_text()
if existing_content == new_content:
print("ā
All good! (CLI reference)")
return
elif not update:
print(
f"ā `{PACKAGE_REFERENCE_PATH}` does not exist yet.\n"
" Please run `make style` or `python utils/generate_cli_reference.py --update` to generate it."
)
exit(1)
if update:
PACKAGE_REFERENCE_PATH.write_text(new_content)
print(
f"ā
CLI reference has been updated in `{PACKAGE_REFERENCE_PATH}`.\n Please make sure the changes are accurate and commit them."
)
else:
print(
f"ā Expected content mismatch in `{PACKAGE_REFERENCE_PATH}`.\n"
" It is most likely that you've modified the CLI implementation and did not re-generate the docs.\n"
" Please run `make style` or `python utils/generate_cli_reference.py --update`."
)
if verbose:
print("\nš Diff between current and expected content:")
print_colored_diff(new_content, existing_content)
exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--update",
action="store_true",
help=(f"Whether to re-generate `{PACKAGE_REFERENCE_PATH}` if a change is detected."),
)
parser.add_argument(
"--verbose",
action="store_true",
help="Show detailed diff when content mismatch is detected.",
)
args = parser.parse_args()
check_and_update_cli_reference(update=args.update, verbose=args.verbose)
|