|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import sys |
3 | 4 | from pathlib import Path |
4 | 5 |
|
5 | 6 | import typer |
6 | 7 |
|
7 | | -from .errors import ( |
8 | | - AssetMissingError, |
9 | | - DependencyMissingError, |
10 | | - HTMLBuildError, |
11 | | - InvalidFrontmatterError, |
12 | | - PDFBuildError, |
13 | | - ThemeLoadError, |
14 | | - TrustforgeError, |
15 | | -) |
| 8 | +from .common.errors import TrustforgeError |
16 | 9 | from .pipeline.metadata import build_index_csv |
17 | 10 | from .pipeline.render_html import render_html |
18 | 11 | from .pipeline.render_pdf import render_pdf |
19 | 12 |
|
20 | 13 | app = typer.Typer(help="Trustforge Policy Foundry") |
21 | 14 |
|
22 | 15 |
|
23 | | -def _bail(msg: str, code: int = 1) -> None: |
24 | | - typer.secho(msg, fg=typer.colors.RED, err=True) |
25 | | - raise typer.Exit(code) |
| 16 | +def _echo_err(msg: str) -> None: |
| 17 | + typer.echo(msg, err=True) |
26 | 18 |
|
27 | 19 |
|
28 | 20 | @app.command() |
29 | 21 | def html(policy: str) -> None: |
30 | | - """Render a policy Markdown file to themed HTML.""" |
| 22 | + """ |
| 23 | + Render a policy Markdown file to themed HTML. |
| 24 | + """ |
31 | 25 | try: |
32 | 26 | out = render_html(Path(policy)) |
33 | | - typer.secho(f"HTML -> {out}", fg=typer.colors.GREEN) |
34 | | - except InvalidFrontmatterError as e: |
35 | | - _bail(f"[frontmatter] {e}") |
36 | | - except (AssetMissingError, ThemeLoadError) as e: |
37 | | - _bail(f"[theme/assets] {e}") |
38 | | - except HTMLBuildError as e: |
39 | | - _bail(f"[html] {e}") |
40 | 27 | except TrustforgeError as e: |
41 | | - _bail(f"[trustforge] {e}") |
| 28 | + _echo_err(str(e)) |
| 29 | + raise typer.Exit(code=2) |
42 | 30 | except Exception as e: # safety net |
43 | | - _bail(f"[unexpected] {e.__class__.__name__}: {e}") |
| 31 | + _echo_err(f"Unexpected error: {e}") |
| 32 | + raise typer.Exit(code=1) |
| 33 | + typer.echo(f"HTML -> {out}") |
44 | 34 |
|
45 | 35 |
|
46 | 36 | @app.command() |
47 | 37 | def pdf(policy: str) -> None: |
48 | | - """Render a policy Markdown file to themed PDF (xelatex required).""" |
| 38 | + """ |
| 39 | + Render a policy Markdown file to themed PDF (XeLaTeX required). |
| 40 | + """ |
49 | 41 | try: |
50 | 42 | out = render_pdf(Path(policy)) |
51 | | - typer.secho(f"PDF -> {out}", fg=typer.colors.GREEN) |
52 | | - except InvalidFrontmatterError as e: |
53 | | - _bail(f"[frontmatter] {e}") |
54 | | - except DependencyMissingError as e: |
55 | | - _bail(f"[deps] {e}\nHint: install TeX Live (XeLaTeX).") |
56 | | - except AssetMissingError as e: |
57 | | - _bail(f"[assets] {e}") |
58 | | - except ThemeLoadError as e: |
59 | | - _bail(f"[theme] {e}") |
60 | | - except PDFBuildError as e: |
61 | | - _bail(f"[pdf] {e}") |
62 | 43 | except TrustforgeError as e: |
63 | | - _bail(f"[trustforge] {e}") |
64 | | - except Exception as e: # safety net |
65 | | - _bail(f"[unexpected] {e.__class__.__name__}: {e}") |
| 44 | + _echo_err(str(e)) |
| 45 | + raise typer.Exit(code=2) |
| 46 | + except Exception as e: |
| 47 | + _echo_err(f"Unexpected error: {e}") |
| 48 | + raise typer.Exit(code=1) |
| 49 | + typer.echo(f"PDF -> {out}") |
66 | 50 |
|
67 | 51 |
|
68 | 52 | @app.command() |
69 | 53 | def index(out: str = "out/policies.csv", policies_dir: str = "policies") -> None: |
70 | | - """Build CSV index of policy frontmatter across all markdown files.""" |
| 54 | + """ |
| 55 | + Build CSV index of policy frontmatter across all markdown files. |
| 56 | + """ |
71 | 57 | try: |
72 | 58 | p = build_index_csv(Path(policies_dir), Path(out)) |
73 | | - typer.secho(f"Index -> {p}", fg=typer.colors.GREEN) |
74 | | - except InvalidFrontmatterError as e: |
75 | | - _bail(f"[frontmatter] {e}") |
76 | 59 | except TrustforgeError as e: |
77 | | - _bail(f"[trustforge] {e}") |
78 | | - except FileNotFoundError: |
79 | | - _bail(f"[inputs] Policies dir not found: {policies_dir}") |
80 | | - except Exception as e: # safety net |
81 | | - _bail(f"[unexpected] {e.__class__.__name__}: {e}") |
| 60 | + _echo_err(str(e)) |
| 61 | + raise typer.Exit(code=2) |
| 62 | + except Exception as e: |
| 63 | + _echo_err(f"Unexpected error: {e}") |
| 64 | + raise typer.Exit(code=1) |
| 65 | + typer.echo(f"Index -> {p}") |
82 | 66 |
|
83 | 67 |
|
84 | 68 | if __name__ == "__main__": |
85 | | - app() |
| 69 | + # Typer uses SystemExit; this guard ensures clean non-zero codes propagate. |
| 70 | + try: |
| 71 | + app() |
| 72 | + except SystemExit as e: |
| 73 | + # Allow GH Actions to display a non-zero exit when appropriate. |
| 74 | + sys.exit(e.code) |
0 commit comments