From 9b8f76a6bd3f05ee1bc890b8011d6376bb21e1fe Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Sun, 1 Mar 2026 14:23:01 +0700 Subject: [PATCH] update cli.md, cli.py, docs.py and markdown.py --- documentation/cli.md | 52 +++++++++++ mkpy/cli.py | 210 +++++++++++++++++++++++++++++++++++++++++++ mkpy/docs.py | 114 +++++++++++++++++++++++ mkpy/markdown.py | 7 +- 4 files changed, 382 insertions(+), 1 deletion(-) diff --git a/documentation/cli.md b/documentation/cli.md index 227e279..682c0c0 100644 --- a/documentation/cli.md +++ b/documentation/cli.md @@ -38,6 +38,20 @@ mkpy build mkpy build --folder docs --output site --theme dark ``` +### mkpy deploy + +Генерирует сайт и создаёт GitHub Actions workflow для деплоя на GitHub Pages. + +```bash +mkpy deploy +``` + +С параметрами: + +```bash +mkpy deploy --folder docs --output site --theme dark +``` + ### mkpy version Показывает версию mkpy. @@ -67,6 +81,26 @@ mkpy version | `--theme` | | Тема: light или dark | light | | `--no-nav` | | Отключить навигацию | false | +## Опции deploy + +| Опция | Кратко | Описание | По умолчанию | +|-------|--------|----------|--------------| +| `--folder` | `-f` | Папка с markdown файлами | docs | +| `--output` | `-o` | Выходная папка для HTML | project | +| `--title` | `-t` | Заголовок документации | MKPY | +| `--theme` | | Тема: light или dark | light | +| `--no-nav` | | Отключить навигацию | false | +| `--github` | `-g` | Создать GitHub Actions workflow | true | +| `--repo` | `-r` | GitHub репозиторий (owner/repo) | - | + +| Опция | Кратко | Описание | По умолчанию | +|-------|--------|----------|--------------| +| `--folder` | `-f` | Папка с markdown файлами | docs | +| `--output` | `-o` | Выходная папка для HTML | project | +| `--title` | `-t` | Заголовок документации | MKPY | +| `--theme` | | Тема: light или dark | light | +| `--no-nav` | | Отключить навигацию | false | + ## Примеры ### Генерация статического сайта @@ -85,6 +119,24 @@ mkpy build --theme dark mkpy build --folder docs --output site --theme dark --title "My Docs" ``` +### Деплой на GitHub Pages + +```bash +# Базовое использование - билд + создание GitHub Actions +mkpy deploy + +# С указанием параметров +mkpy deploy --folder docs --output site --theme dark + +# Без создания GitHub Actions (только билд) +mkpy deploy --no-github +``` + +После запуска `mkpy deploy`: +1. Создаётся папка `project` с HTML файлами +2. Создаётся `.github/workflows/deploy.yml` +3. Запушьте изменения на GitHub и включите Pages в настройках + ### Запуск с параметрами ```bash diff --git a/mkpy/cli.py b/mkpy/cli.py index 837059c..3fd5fd4 100644 --- a/mkpy/cli.py +++ b/mkpy/cli.py @@ -106,6 +106,10 @@ def build( bool, typer.Option("--no-nav", help="Disable navigation menu"), ] = False, + rtd: Annotated[ + bool, + typer.Option("--rtd", help="Generate readthedocs.yaml config"), + ] = False, ) -> None: from rich.console import Console from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn @@ -176,6 +180,212 @@ def build( border_style="green", )) + # Generate readthedocs.yaml if requested + if rtd: + console.print() + console.print("[cyan]Generating readthedocs.yaml...[/cyan]") + + rtd_config = f"""# Read the Docs configuration +# https://docs.readthedocs.io/en/stable/config-file/v2.html + +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "3.11" + commands: + - mkpy build -f {folder} -o _build -t {title} --theme {theme} + +python: + install: + - method: pip + path: . +""" + + rtd_path = os.path.join(output, "readthedocs.yaml") + with open(rtd_path, "w", encoding="utf-8") as f: + f.write(rtd_config) + + console.print(f"[green]✓[/green] Created readthedocs.yaml") + console.print() + console.print("[bold yellow]Deploy to Read the Docs:[/bold yellow]") + console.print(" 1. Push your code to GitHub") + console.print(" 2. Go to https://readthedocs.org/dashboard/import/") + console.print(" 3. Add your repository") + console.print(" 4. Your docs will be live!") + + +@app.command() +def deploy( + folder: Annotated[ + str, + typer.Option("--folder", "-f", help="Path to folder containing markdown files"), + ] = "docs", + output: Annotated[ + str, + typer.Option("--output", "-o", help="Output directory for static files"), + ] = "project", + title: Annotated[ + str, + typer.Option("--title", "-t", help="Documentation title"), + ] = "MKPY", + theme: Annotated[ + str, + typer.Option("--theme", help="Theme: light or dark"), + ] = "light", + no_nav: Annotated[ + bool, + typer.Option("--no-nav", help="Disable navigation menu"), + ] = False, + github: Annotated[ + bool, + typer.Option("--github", "-g", help="Create GitHub Actions workflow for deployment"), + ] = True, + repo: Annotated[ + str | None, + typer.Option("--repo", "-r", help="GitHub repository (owner/repo)"), + ] = None, +) -> None: + """Build and deploy documentation to GitHub Pages.""" + from rich.console import Console + from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn + from rich.table import Table + from rich.panel import Panel + from rich import box + + console = Console() + + # Build first + console.print(Panel.fit( + f"[bold cyan]MKPY Deploy[/bold cyan]\n" + f"Building and deploying to GitHub Pages", + border_style="cyan", + )) + + os.makedirs(output, exist_ok=True) + docs = Docs( + folder=folder, + title=title, + theme=theme, + show_nav=not no_nav, + ) + + routes = list(docs.routes.items()) + total = len(routes) + + table = Table(box=box.ROUNDED, show_header=True, header_style="bold magenta") + table.add_column("Status", style="green", width=8) + table.add_column("Source", style="cyan") + table.add_column("Output", style="yellow") + + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TaskProgressColumn(), + console=console, + ) as progress: + task = progress.add_task("[cyan]Building...", total=total) + + for route, md_path in routes: + html_content = docs.render(md_path) + + if route == "/": + html_filename = "index.html" + else: + html_filename = f"{route.lstrip('/')}.html" + + output_path = os.path.join(output, html_filename) + os.makedirs(os.path.dirname(output_path), exist_ok=True) + + with open(output_path, "w", encoding="utf-8") as f: + f.write(html_content) + + relative_md = os.path.relpath(md_path, folder) + table.add_row("✓", relative_md, html_filename) + progress.advance(task) + + console.print() + console.print(table) + + # Create GitHub Actions workflow + if github: + console.print() + console.print("[cyan]Creating GitHub Actions workflow...[/cyan]") + + workflow_dir = os.path.join(output, ".github", "workflows") + os.makedirs(workflow_dir, exist_ok=True) + + workflow_content = f"""name: Deploy to GitHub Pages + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{{{ steps.deployment.outputs.page_url }}}} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: '.' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 +""" + workflow_path = os.path.join(workflow_dir, "deploy.yml") + with open(workflow_path, "w", encoding="utf-8") as f: + f.write(workflow_content) + + console.print(f"[green]✓[/green] Created {workflow_path}") + + # Summary + console.print() + console.print(Panel.fit( + f"[bold green]✓ Deploy ready![/bold green]\n" + f"Output: [yellow]{os.path.abspath(output)}[/yellow]\n" + f"Files: [cyan]{total}[/cyan]\n" + f"GitHub Actions: [green]{'✓ Created' if github else '✗ Skipped'}[/green]", + border_style="green", + )) + + if github: + console.print() + console.print("[bold yellow]Next steps:[/bold yellow]") + console.print(" 1. Push to GitHub:") + console.print(" [cyan]git add .[/cyan]") + console.print(" [cyan]git commit -m 'Add mkpy docs'[/cyan]") + console.print(" [cyan]git push origin main[/cyan]") + console.print() + console.print(" 2. Enable GitHub Pages:") + console.print(" → Repository Settings → Pages") + console.print(" → Source: [yellow]GitHub Actions[/yellow]") + console.print() + console.print(" 3. Your docs will be live at:") + console.print(" [cyan]https://.github.io//[/cyan]") + @app.command() def version() -> None: diff --git a/mkpy/docs.py b/mkpy/docs.py index c8f6060..3e8bf2d 100644 --- a/mkpy/docs.py +++ b/mkpy/docs.py @@ -373,3 +373,117 @@ def generate_sitemap(self, host: Annotated[ def run(self) -> None: run_server(self) + + def build_static( + self, + output: Annotated[ + str, + Doc( + """ + Output directory for static files. + """ + ), + ] = "build", + ) -> None: + """ + Build static HTML files for deployment. + + Generates a complete static website that can be deployed + to any static hosting service (GitHub Pages, Read the Docs, etc.). + + Args: + output: Output directory path. + """ + import shutil + + # Create output directory + os.makedirs(output, exist_ok=True) + + # Copy assets if they exist + css_src = os.path.join(self.folder, "css") + if os.path.isdir(css_src): + css_dest = os.path.join(output, "css") + if os.path.exists(css_dest): + shutil.rmtree(css_dest) + shutil.copytree(css_src, css_dest) + + js_src = os.path.join(self.folder, "js") + if os.path.isdir(js_src): + js_dest = os.path.join(output, "js") + if os.path.exists(js_dest): + shutil.rmtree(js_dest) + shutil.copytree(js_src, js_dest) + + # Generate HTML files + for route, file_path in self.routes.items(): + html_content = self.render(file_path) + + # Determine output file path + if route == "/": + out_file = os.path.join(output, "index.html") + else: + # Remove leading slash and add .html + clean_route = route.lstrip("/") + if not clean_route: + out_file = os.path.join(output, "index.html") + else: + out_file = os.path.join(output, clean_route + ".html") + + # Create directories if needed + os.makedirs(os.path.dirname(out_file), exist_ok=True) + + # Write HTML file + with open(out_file, "w", encoding="utf-8") as f: + f.write(html_content) + + # Generate sitemap + sitemap = self.generate_sitemap() + with open(os.path.join(output, "sitemap.xml"), "w", encoding="utf-8") as f: + f.write(sitemap) + + def generate_readthedocs_config( + self, + output: Annotated[ + str, + Doc( + """ + Output directory for config file. + """ + ), + ] = ".", + ) -> str: + """ + Generate readthedocs.yaml configuration file. + + Creates a configuration file for automatic deployment + to Read the Docs. + + Args: + output: Directory where to save the config. + + Returns: + Path to generated config file. + """ + config = """# Read the Docs configuration +# https://docs.readthedocs.io/en/stable/config-file/v2.html + +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "3.11" + commands: + - python -m mkpy build -o _build + +python: + install: + - method: pip + path: . +""" + + config_path = os.path.join(output, "readthedocs.yaml") + with open(config_path, "w", encoding="utf-8") as f: + f.write(config) + + return config_path diff --git a/mkpy/markdown.py b/mkpy/markdown.py index ae38a00..03fcf38 100644 --- a/mkpy/markdown.py +++ b/mkpy/markdown.py @@ -63,8 +63,13 @@ def render(md: str) -> str: """ import markdown - return markdown.markdown( + html = markdown.markdown( md, extensions=["extra", "tables", "fenced_code", "toc"], output_format="html5", ) + + # Convert .md links to .html + html = re.sub(r'href="([^"]*)\.md"', r'href="\1.html"', html) + + return html