|
from __future__ import annotations |
|
|
|
import importlib |
|
import re |
|
from pathlib import Path |
|
from typing import Annotated, Any, Optional |
|
|
|
import requests |
|
import tomlkit as toml |
|
from typer import Argument, Option |
|
|
|
from gradio.analytics import custom_component_analytics |
|
from gradio.cli.commands.display import LivePanelDisplay |
|
|
|
from ._docs_assets import css |
|
from ._docs_utils import extract_docstrings, get_deep, make_markdown, make_space |
|
|
|
|
|
def _docs( |
|
path: Annotated[ |
|
Path, Argument(help="The directory of the custom component.") |
|
] = Path("."), |
|
demo_dir: Annotated[ |
|
Optional[Path], Option(help="Path to the demo directory.") |
|
] = None, |
|
demo_name: Annotated[Optional[str], Option(help="Name of the demo file.")] = None, |
|
readme_path: Annotated[ |
|
Optional[Path], Option(help="Path to the README.md file.") |
|
] = None, |
|
space_url: Annotated[ |
|
Optional[str], Option(help="URL of the Space to use for the demo.") |
|
] = None, |
|
generate_space: Annotated[ |
|
bool, |
|
Option( |
|
help="Create a documentation space for the custom compone.", is_flag=True |
|
), |
|
] = True, |
|
generate_readme: Annotated[ |
|
bool, |
|
Option(help="Create a README.md file for the custom component.", is_flag=True), |
|
] = True, |
|
suppress_demo_check: Annotated[ |
|
bool, |
|
Option( |
|
help="Suppress demo warnings and errors.", |
|
is_flag=True, |
|
), |
|
] = False, |
|
): |
|
"""Runs the documentation generator.""" |
|
custom_component_analytics( |
|
"docs", |
|
None, |
|
None, |
|
None, |
|
None, |
|
) |
|
|
|
_component_dir = Path(path).resolve() |
|
_demo_dir = Path(demo_dir).resolve() if demo_dir else Path("demo").resolve() |
|
_demo_name = demo_name if demo_name else "app.py" |
|
_demo_path = _demo_dir / _demo_name |
|
_readme_path = ( |
|
Path(readme_path).resolve() if readme_path else _component_dir / "README.md" |
|
) |
|
|
|
if not generate_space and not generate_readme: |
|
raise ValueError("Must generate at least one of space or readme") |
|
|
|
with LivePanelDisplay() as live: |
|
live.update( |
|
f":page_facing_up: Generating documentation for [orange3]{str(_component_dir.name)}[/]", |
|
add_sleep=0.2, |
|
) |
|
live.update( |
|
f":eyes: Reading project metadata from [orange3]{_component_dir}/pyproject.toml[/]\n" |
|
) |
|
|
|
if not (_component_dir / "pyproject.toml").exists(): |
|
raise ValueError( |
|
f"Cannot find pyproject.toml file in [orange3]{_component_dir}[/]" |
|
) |
|
|
|
with open(_component_dir / "pyproject.toml", encoding="utf-8") as f: |
|
data = toml.loads(f.read()) |
|
|
|
name = get_deep(data, ["project", "name"]) |
|
|
|
if not isinstance(name, str): |
|
raise ValueError("Name not found in pyproject.toml") |
|
|
|
run_command( |
|
live=live, |
|
name=name, |
|
suppress_demo_check=suppress_demo_check, |
|
pyproject_toml=data, |
|
generate_space=generate_space, |
|
generate_readme=generate_readme, |
|
type_mode="simple", |
|
_demo_path=_demo_path, |
|
_demo_dir=_demo_dir, |
|
_readme_path=_readme_path, |
|
space_url=space_url, |
|
_component_dir=_component_dir, |
|
) |
|
|
|
|
|
def run_command( |
|
live: LivePanelDisplay, |
|
name: str, |
|
pyproject_toml: dict[str, Any], |
|
suppress_demo_check: bool, |
|
generate_space: bool, |
|
generate_readme: bool, |
|
type_mode: str, |
|
_demo_path: Path, |
|
_demo_dir: Path, |
|
_readme_path: Path, |
|
space_url: str | None, |
|
_component_dir: Path, |
|
simple: bool = False, |
|
): |
|
with open(_demo_path, encoding="utf-8") as f: |
|
demo = f.read() |
|
|
|
pypi_exists = requests.get(f"https://pypi.org/pypi/{name}/json").status_code |
|
|
|
pypi_exists = pypi_exists == 200 or False |
|
|
|
local_version = get_deep(pyproject_toml, ["project", "version"]) |
|
description = str(get_deep(pyproject_toml, ["project", "description"]) or "") |
|
repo = get_deep(pyproject_toml, ["project", "urls", "repository"]) |
|
space = ( |
|
space_url |
|
if space_url |
|
else get_deep(pyproject_toml, ["project", "urls", "space"]) |
|
) |
|
|
|
if not local_version and not pypi_exists: |
|
raise ValueError( |
|
f"Cannot find version in pyproject.toml or on PyPI for [orange3]{name}[/].\nIf you have just published to PyPI, please wait a few minutes and try again." |
|
) |
|
module = importlib.import_module(name) |
|
(docs, type_mode) = extract_docstrings(module) |
|
|
|
if generate_space: |
|
if not simple: |
|
live.update(":computer: [blue]Generating space.[/]") |
|
|
|
source = make_space( |
|
docs=docs, |
|
name=name, |
|
description=description, |
|
local_version=local_version |
|
if local_version is None |
|
else str(local_version), |
|
demo=demo, |
|
space=space if space is None else str(space), |
|
repo=repo if repo is None else str(repo), |
|
pypi_exists=pypi_exists, |
|
suppress_demo_check=suppress_demo_check, |
|
) |
|
|
|
with open(_demo_dir / "space.py", "w", encoding="utf-8") as f: |
|
f.write(source) |
|
if not simple: |
|
live.update( |
|
f":white_check_mark: Space created in [orange3]{_demo_dir}/space.py[/]\n" |
|
) |
|
with open(_demo_dir / "css.css", "w", encoding="utf-8") as f: |
|
f.write(css) |
|
|
|
if generate_readme: |
|
if not simple: |
|
live.update(":pencil: [blue]Generating README.[/]") |
|
readme = make_markdown( |
|
docs, name, description, local_version, demo, space, repo, pypi_exists |
|
) |
|
|
|
readme_content = Path(_readme_path).read_text() |
|
|
|
with open(_readme_path, "w", encoding="utf-8") as f: |
|
yaml_regex = re.search( |
|
"(?:^|[\r\n])---[\n\r]+([\\S\\s]*?)[\n\r]+---([\n\r]|$)", readme_content |
|
) |
|
if yaml_regex is not None: |
|
readme = readme_content[: yaml_regex.span()[-1]] + readme |
|
f.write(readme) |
|
if not simple: |
|
live.update( |
|
f":white_check_mark: README generated in [orange3]{_readme_path}[/]" |
|
) |
|
if simple: |
|
short_readme_path = Path(_readme_path).relative_to(_component_dir) |
|
short_demo_path = Path(_demo_dir / "space.py").relative_to(_component_dir) |
|
live.update( |
|
f":white_check_mark: Documentation generated in [orange3]{short_demo_path}[/] and [orange3]{short_readme_path}[/]. Pass --no-generate-docs to disable auto documentation." |
|
) |
|
|
|
if type_mode == "simple": |
|
live.update( |
|
"\n:orange_circle: [red]The docs were generated in simple mode. Updating python to a more recent version will result in richer documentation.[/]" |
|
) |
|
|