readwright 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {readwright-0.4.0 → readwright-0.5.0}/.agents/skills/readwright/SKILL.md +1 -0
- {readwright-0.4.0 → readwright-0.5.0}/AGENTS.md +1 -1
- {readwright-0.4.0 → readwright-0.5.0}/PKG-INFO +15 -3
- {readwright-0.4.0 → readwright-0.5.0}/README.md +14 -2
- {readwright-0.4.0 → readwright-0.5.0}/README.md.j2 +11 -0
- {readwright-0.4.0 → readwright-0.5.0}/pyproject.toml +1 -1
- readwright-0.5.0/src/readwright/__init__.py +12 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/cli.py +100 -42
- {readwright-0.4.0 → readwright-0.5.0}/tests/conftest.py +8 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_cli.py +34 -0
- {readwright-0.4.0 → readwright-0.5.0}/uv.lock +1 -1
- readwright-0.4.0/src/readwright/__init__.py +0 -8
- {readwright-0.4.0 → readwright-0.5.0}/.agents/skills/readwright/helpers.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/.github/workflows/ci.yml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/.github/workflows/release.yml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/.gitignore +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/.pre-commit-config.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/.pre-commit-hooks.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/.python-version +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/CLAUDE.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/LICENSE +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/action.yml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/README.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/config-only/README.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/config-only/docs/screenshots/CREDITS.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/config-only/docs/screenshots/captions.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/config-only/docs/screenshots/dashboard.jpg +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/config-only/docs/screenshots/editor-dark.jpg +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/config-only/docs/screenshots/editor-light.jpg +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/config-only/pyproject.toml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/config-only/readme.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/dotnet-tool/DotTool.csproj +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/dotnet-tool/README.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/dotnet-tool/readme.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/go-cli/README.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/go-cli/go.mod +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/go-cli/readme.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/.all-contributorsrc +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/.env.example +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/CHANGELOG.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/LICENSE +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/README.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/README.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/demo_tool/__init__.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/demo_tool/cli.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/logo.svg +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/CREDITS.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/captions.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/main.jpg +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/mobile/captions.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/mobile/phone.jpg +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/settings-dark.jpg +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/settings-light.jpg +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/usage.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/pyproject.toml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/readme.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/templates/partials/contributing.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/node-cli/README.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/node-cli/package.json +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/node-cli/readme.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/rust-cli/Cargo.toml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/rust-cli/README.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/examples/rust-cli/readme.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/readme.yaml +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/badges.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/changelog.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/config.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/helpers.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/images.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/metadata.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/py.typed +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/renderer.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/templates/base.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/templates/partials/badges.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/templates/partials/contributing.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/templates/partials/donate.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/templates/partials/header.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/templates/partials/install.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/templates/partials/license.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/templates/partials/screenshots.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/templates/partials/usage.md.j2 +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/src/readwright/toc.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/golden/base.md +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_badges.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_changelog.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_config.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_examples.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_helpers.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_images.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_metadata.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_renderer.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tests/test_toc.py +0 -0
- {readwright-0.4.0 → readwright-0.5.0}/tox.ini +0 -0
|
@@ -37,6 +37,7 @@ template/config, then re-render.
|
|
|
37
37
|
| `readwright blocks` | List overridable template blocks/partials |
|
|
38
38
|
| `readwright skill [--install] [--dest DIR] [--force]` | Print the bundled copy of this skill, or install it into a project's `.agents/skills/` (or `--dest`) |
|
|
39
39
|
| `readwright show <template>` | Print a packaged template (`base.md.j2` for the block order, or e.g. `partials/install.md.j2`) to copy and customize |
|
|
40
|
+
| `readwright completion <shell>` | Print a bash/zsh/fish/PowerShell completion script |
|
|
40
41
|
|
|
41
42
|
All accept `-C/--root` (repo root) and `-c/--config` (explicit config path) as
|
|
42
43
|
options *after* the subcommand, e.g. `readwright render -C /path/to/repo` —
|
|
@@ -51,7 +51,7 @@ regenerating rather than hand-editing expected output.
|
|
|
51
51
|
## Layout
|
|
52
52
|
|
|
53
53
|
- `src/readwright/cli.py` — Typer commands: `init`, `render`, `check`, `badges`,
|
|
54
|
-
`blocks`, `show`, `skill`.
|
|
54
|
+
`blocks`, `show`, `skill`, `completion`.
|
|
55
55
|
- `src/readwright/renderer.py` — Jinja2 environment setup and rendering pipeline.
|
|
56
56
|
- `src/readwright/helpers.py`, `badges.py`, `images.py`, `toc.py`, `changelog.py`,
|
|
57
57
|
`metadata.py` — the template helper functions exposed to `README.md.j2`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: readwright
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Render GitHub READMEs from Jinja2 templates with badge and screenshot helpers
|
|
5
5
|
Project-URL: Homepage, https://github.com/Garulf/readwright
|
|
6
6
|
Project-URL: Issues, https://github.com/Garulf/readwright/issues
|
|
@@ -36,6 +36,7 @@ Render GitHub READMEs from Jinja2 templates with badge and screenshot helpers.
|
|
|
36
36
|
- [Template helpers](#template-helpers)
|
|
37
37
|
- [Configuration](#configuration)
|
|
38
38
|
- [pre-commit and GitHub Actions](#pre-commit-and-github-actions)
|
|
39
|
+
- [Shell completion](#shell-completion)
|
|
39
40
|
- [Agent skill](#agent-skill)
|
|
40
41
|
- [Contributing](#contributing)
|
|
41
42
|
- [License](#license)
|
|
@@ -168,18 +169,29 @@ stays reproducible in CI. `readwright render --user-config` merges them ad hoc.
|
|
|
168
169
|
```yaml
|
|
169
170
|
# .pre-commit-config.yaml
|
|
170
171
|
- repo: https://github.com/Garulf/readwright
|
|
171
|
-
rev: v0.
|
|
172
|
+
rev: v0.5.0
|
|
172
173
|
hooks:
|
|
173
174
|
- id: readwright-check
|
|
174
175
|
```
|
|
175
176
|
|
|
176
177
|
```yaml
|
|
177
178
|
# .github/workflows/ci.yml
|
|
178
|
-
- uses: Garulf/readwright@v0.
|
|
179
|
+
- uses: Garulf/readwright@v0.5.0
|
|
179
180
|
with:
|
|
180
181
|
mode: check # or render
|
|
181
182
|
```
|
|
182
183
|
|
|
184
|
+
## Shell completion
|
|
185
|
+
|
|
186
|
+
`readwright completion <shell>` prints a completion script for bash, zsh, fish or PowerShell.
|
|
187
|
+
It completes subcommands, options and the template names `readwright show` accepts.
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
eval "$(readwright completion bash)" # add to ~/.bashrc
|
|
191
|
+
eval "$(readwright completion zsh)" # add to ~/.zshrc, after compinit
|
|
192
|
+
readwright completion fish > ~/.config/fish/completions/readwright.fish
|
|
193
|
+
```
|
|
194
|
+
|
|
183
195
|
## Agent skill
|
|
184
196
|
|
|
185
197
|
The repo ships an [agent skill](https://agentskills.io) at `.agents/skills/readwright/`
|
|
@@ -10,6 +10,7 @@ Render GitHub READMEs from Jinja2 templates with badge and screenshot helpers.
|
|
|
10
10
|
- [Template helpers](#template-helpers)
|
|
11
11
|
- [Configuration](#configuration)
|
|
12
12
|
- [pre-commit and GitHub Actions](#pre-commit-and-github-actions)
|
|
13
|
+
- [Shell completion](#shell-completion)
|
|
13
14
|
- [Agent skill](#agent-skill)
|
|
14
15
|
- [Contributing](#contributing)
|
|
15
16
|
- [License](#license)
|
|
@@ -142,18 +143,29 @@ stays reproducible in CI. `readwright render --user-config` merges them ad hoc.
|
|
|
142
143
|
```yaml
|
|
143
144
|
# .pre-commit-config.yaml
|
|
144
145
|
- repo: https://github.com/Garulf/readwright
|
|
145
|
-
rev: v0.
|
|
146
|
+
rev: v0.5.0
|
|
146
147
|
hooks:
|
|
147
148
|
- id: readwright-check
|
|
148
149
|
```
|
|
149
150
|
|
|
150
151
|
```yaml
|
|
151
152
|
# .github/workflows/ci.yml
|
|
152
|
-
- uses: Garulf/readwright@v0.
|
|
153
|
+
- uses: Garulf/readwright@v0.5.0
|
|
153
154
|
with:
|
|
154
155
|
mode: check # or render
|
|
155
156
|
```
|
|
156
157
|
|
|
158
|
+
## Shell completion
|
|
159
|
+
|
|
160
|
+
`readwright completion <shell>` prints a completion script for bash, zsh, fish or PowerShell.
|
|
161
|
+
It completes subcommands, options and the template names `readwright show` accepts.
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
eval "$(readwright completion bash)" # add to ~/.bashrc
|
|
165
|
+
eval "$(readwright completion zsh)" # add to ~/.zshrc, after compinit
|
|
166
|
+
readwright completion fish > ~/.config/fish/completions/readwright.fish
|
|
167
|
+
```
|
|
168
|
+
|
|
157
169
|
## Agent skill
|
|
158
170
|
|
|
159
171
|
The repo ships an [agent skill](https://agentskills.io) at `.agents/skills/readwright/`
|
|
@@ -134,6 +134,17 @@ stays reproducible in CI. `readwright render --user-config` merges them ad hoc.
|
|
|
134
134
|
mode: check # or render
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
+
## Shell completion
|
|
138
|
+
|
|
139
|
+
`readwright completion <shell>` prints a completion script for bash, zsh, fish or PowerShell.
|
|
140
|
+
It completes subcommands, options and the template names `readwright show` accepts.
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
eval "$(readwright completion bash)" # add to ~/.bashrc
|
|
144
|
+
eval "$(readwright completion zsh)" # add to ~/.zshrc, after compinit
|
|
145
|
+
readwright completion fish > ~/.config/fish/completions/readwright.fish
|
|
146
|
+
```
|
|
147
|
+
|
|
137
148
|
## Agent skill
|
|
138
149
|
|
|
139
150
|
The repo ships an [agent skill](https://agentskills.io) at `.agents/skills/readwright/`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Render GitHub READMEs from Jinja2 templates."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def __getattr__(name: str) -> str:
|
|
5
|
+
if name != "__version__":
|
|
6
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
return version("readwright")
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
return "0.0.0"
|
|
@@ -6,35 +6,44 @@ import difflib
|
|
|
6
6
|
import re
|
|
7
7
|
import shutil
|
|
8
8
|
import sys
|
|
9
|
+
from functools import cache
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import Annotated
|
|
11
|
+
from typing import TYPE_CHECKING, Annotated
|
|
11
12
|
|
|
12
13
|
import typer
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
config_sources,
|
|
25
|
-
deep_merge,
|
|
26
|
-
load_user_config,
|
|
27
|
-
resolve,
|
|
28
|
-
)
|
|
29
|
-
from readwright.renderer import BASE_TEMPLATE, MARKER_PREFIX, Renderer, RenderResult
|
|
14
|
+
from typer.completion import Shells, completion_init, get_completion_script
|
|
15
|
+
|
|
16
|
+
# Every shell Tab press imports this module, so heavy imports live in the functions that use them.
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
|
|
21
|
+
from readwright.config import Config
|
|
22
|
+
from readwright.renderer import RenderResult
|
|
23
|
+
|
|
24
|
+
completion_init()
|
|
30
25
|
|
|
31
26
|
app = typer.Typer(
|
|
32
27
|
help="Render GitHub READMEs from Jinja2 templates.",
|
|
33
28
|
no_args_is_help=True,
|
|
34
29
|
add_completion=False,
|
|
35
30
|
)
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@cache
|
|
34
|
+
def _console(stderr: bool) -> Console:
|
|
35
|
+
from rich.console import Console
|
|
36
|
+
|
|
37
|
+
return Console(stderr=stderr)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def out() -> Console:
|
|
41
|
+
return _console(stderr=False)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def err() -> Console:
|
|
45
|
+
return _console(stderr=True)
|
|
46
|
+
|
|
38
47
|
|
|
39
48
|
RootOpt = Annotated[Path, typer.Option("--root", "-C", help="Repository root.")]
|
|
40
49
|
ConfigOpt = Annotated[Path | None, typer.Option("--config", "-c", help="Config file path.")]
|
|
@@ -52,7 +61,9 @@ VerboseOpt = Annotated[
|
|
|
52
61
|
|
|
53
62
|
def version_callback(value: bool) -> None:
|
|
54
63
|
if value:
|
|
55
|
-
|
|
64
|
+
from readwright import __version__
|
|
65
|
+
|
|
66
|
+
out().print(f"readwright {__version__}")
|
|
56
67
|
raise typer.Exit()
|
|
57
68
|
|
|
58
69
|
|
|
@@ -66,17 +77,19 @@ def main(
|
|
|
66
77
|
|
|
67
78
|
|
|
68
79
|
def warn(message: str) -> None:
|
|
69
|
-
err.print("[yellow]warning:[/] ", end="")
|
|
70
|
-
err.print(message, markup=False, highlight=False)
|
|
80
|
+
err().print("[yellow]warning:[/] ", end="")
|
|
81
|
+
err().print(message, markup=False, highlight=False)
|
|
71
82
|
|
|
72
83
|
|
|
73
84
|
def fail(message: str, code: int = 1) -> None:
|
|
74
|
-
err.print("[red]error:[/] ", end="")
|
|
75
|
-
err.print(message, markup=False, highlight=False)
|
|
85
|
+
err().print("[red]error:[/] ", end="")
|
|
86
|
+
err().print(message, markup=False, highlight=False)
|
|
76
87
|
raise typer.Exit(code)
|
|
77
88
|
|
|
78
89
|
|
|
79
90
|
def _load(root: Path, config: Path | None, user_config: bool, strict: bool) -> Config:
|
|
91
|
+
from readwright.config import config_sources, resolve
|
|
92
|
+
|
|
80
93
|
if config is None and len(sources := config_sources(root)) > 1:
|
|
81
94
|
warn(f"both {' and '.join(sources)} exist; using {sources[0]}")
|
|
82
95
|
try:
|
|
@@ -87,13 +100,15 @@ def _load(root: Path, config: Path | None, user_config: bool, strict: bool) -> C
|
|
|
87
100
|
|
|
88
101
|
|
|
89
102
|
def _render_with(root: Path, cfg: Config, verbose: bool) -> RenderResult:
|
|
103
|
+
from readwright.renderer import Renderer
|
|
104
|
+
|
|
90
105
|
try:
|
|
91
106
|
result = Renderer(root, cfg, warn=warn).render()
|
|
92
107
|
except Exception as exc:
|
|
93
108
|
fail(f"{type(exc).__name__}: {exc}")
|
|
94
109
|
if verbose:
|
|
95
110
|
for name, source in result.sources.items():
|
|
96
|
-
err.print(f"[dim]template {name} <- {source}[/]")
|
|
111
|
+
err().print(f"[dim]template {name} <- {source}[/]")
|
|
97
112
|
return result
|
|
98
113
|
|
|
99
114
|
|
|
@@ -105,6 +120,8 @@ def _render(
|
|
|
105
120
|
|
|
106
121
|
|
|
107
122
|
def _is_managed(path: Path) -> bool:
|
|
123
|
+
from readwright.renderer import MARKER_PREFIX
|
|
124
|
+
|
|
108
125
|
return path.read_text(encoding="utf-8").startswith(MARKER_PREFIX)
|
|
109
126
|
|
|
110
127
|
|
|
@@ -112,7 +129,7 @@ def _write_output(root: Path, cfg: Config, result: RenderResult, force: bool) ->
|
|
|
112
129
|
target = root / cfg.output
|
|
113
130
|
if target.is_file():
|
|
114
131
|
if target.read_text(encoding="utf-8") == result.text:
|
|
115
|
-
out.print(f"{cfg.output} unchanged")
|
|
132
|
+
out().print(f"{cfg.output} unchanged")
|
|
116
133
|
return
|
|
117
134
|
if not force and not _is_managed(target):
|
|
118
135
|
fail(
|
|
@@ -121,10 +138,12 @@ def _write_output(root: Path, cfg: Config, result: RenderResult, force: bool) ->
|
|
|
121
138
|
)
|
|
122
139
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
123
140
|
target.write_text(result.text, encoding="utf-8")
|
|
124
|
-
out.print(f"{cfg.output} updated from {result.template_name}")
|
|
141
|
+
out().print(f"{cfg.output} updated from {result.template_name}")
|
|
125
142
|
|
|
126
143
|
|
|
127
144
|
def watch_paths(root: Path, cfg: Config) -> list[Path]:
|
|
145
|
+
from readwright.config import DEFAULT_CONFIG_NAME
|
|
146
|
+
|
|
128
147
|
candidates = [
|
|
129
148
|
root / cfg.template,
|
|
130
149
|
root / DEFAULT_CONFIG_NAME,
|
|
@@ -180,7 +199,7 @@ def _watch_loop(
|
|
|
180
199
|
|
|
181
200
|
cfg = _load(root, config, user_config, strict)
|
|
182
201
|
paths = watch_paths(root, cfg)
|
|
183
|
-
err.print(
|
|
202
|
+
err().print(
|
|
184
203
|
f"[dim]watching {', '.join(str(p.relative_to(root)) for p in paths)} (ctrl-c to stop)[/]"
|
|
185
204
|
)
|
|
186
205
|
try:
|
|
@@ -205,6 +224,8 @@ def check(
|
|
|
205
224
|
verbose: VerboseOpt = False,
|
|
206
225
|
) -> None:
|
|
207
226
|
"""Exit 1 if the output file is out of date with its template."""
|
|
227
|
+
from readwright.renderer import MARKER_PREFIX
|
|
228
|
+
|
|
208
229
|
cfg, result = _render(root, config, user_config, strict, verbose)
|
|
209
230
|
for name in result.user_templates:
|
|
210
231
|
warn(f"user-level template '{name}' was used; CI renders will differ")
|
|
@@ -216,7 +237,7 @@ def check(
|
|
|
216
237
|
warn(f"{cfg.output} is not managed by readwright (no marker); skipping")
|
|
217
238
|
return
|
|
218
239
|
if current == result.text:
|
|
219
|
-
out.print(f"{cfg.output} is up to date")
|
|
240
|
+
out().print(f"{cfg.output} is up to date")
|
|
220
241
|
return
|
|
221
242
|
diff = difflib.unified_diff(
|
|
222
243
|
current.splitlines(keepends=True),
|
|
@@ -224,7 +245,7 @@ def check(
|
|
|
224
245
|
fromfile=cfg.output,
|
|
225
246
|
tofile=f"{cfg.output} (rendered)",
|
|
226
247
|
)
|
|
227
|
-
out.print("".join(diff), end="", highlight=False, markup=False)
|
|
248
|
+
out().print("".join(diff), end="", highlight=False, markup=False)
|
|
228
249
|
fail(f"{cfg.output} is out of date; run `readwright render`")
|
|
229
250
|
|
|
230
251
|
|
|
@@ -266,6 +287,8 @@ JINJA_SYNTAX = re.compile(r"{[{%#]")
|
|
|
266
287
|
|
|
267
288
|
|
|
268
289
|
def _init_config_data(root: Path) -> dict:
|
|
290
|
+
from readwright.config import deep_merge, load_user_config, resolve
|
|
291
|
+
|
|
269
292
|
cfg = resolve(root)
|
|
270
293
|
data = cfg.model_dump(exclude_defaults=True, exclude_none=True)
|
|
271
294
|
if (user := load_user_config()) is not None:
|
|
@@ -308,6 +331,10 @@ def init(
|
|
|
308
331
|
] = False,
|
|
309
332
|
) -> None:
|
|
310
333
|
"""Scaffold readme.yaml and README.md.j2 in the repository."""
|
|
334
|
+
import yaml
|
|
335
|
+
|
|
336
|
+
from readwright.config import DEFAULT_CONFIG_NAME, DEFAULT_TEMPLATE
|
|
337
|
+
|
|
311
338
|
config_path = root / DEFAULT_CONFIG_NAME
|
|
312
339
|
template_path = root / DEFAULT_TEMPLATE
|
|
313
340
|
existing = [
|
|
@@ -339,14 +366,19 @@ def init(
|
|
|
339
366
|
if from_readme:
|
|
340
367
|
cfg = _load(root, None, False, False)
|
|
341
368
|
_write_output(root, cfg, _render_with(root, cfg, False), force=True)
|
|
342
|
-
out.print(f"created {config_name} and {DEFAULT_TEMPLATE}; README.md is now managed")
|
|
369
|
+
out().print(f"created {config_name} and {DEFAULT_TEMPLATE}; README.md is now managed")
|
|
343
370
|
return
|
|
344
|
-
out.print(f"created {config_name} and {DEFAULT_TEMPLATE}; run `readwright render`")
|
|
371
|
+
out().print(f"created {config_name} and {DEFAULT_TEMPLATE}; run `readwright render`")
|
|
345
372
|
|
|
346
373
|
|
|
347
374
|
@app.command()
|
|
348
375
|
def badges(root: RootOpt = Path("."), config: ConfigOpt = None) -> None:
|
|
349
376
|
"""List available badge presets."""
|
|
377
|
+
from rich.table import Table
|
|
378
|
+
|
|
379
|
+
from readwright.badges import BadgeRegistry
|
|
380
|
+
from readwright.config import Config, ProjectInfo, resolve
|
|
381
|
+
|
|
350
382
|
try:
|
|
351
383
|
cfg = resolve(root, config_path=config)
|
|
352
384
|
except ValueError:
|
|
@@ -361,38 +393,53 @@ def badges(root: RootOpt = Path("."), config: ConfigOpt = None) -> None:
|
|
|
361
393
|
except ValueError as exc:
|
|
362
394
|
example = f"[dim]{exc}[/]"
|
|
363
395
|
table.add_row(name, example)
|
|
364
|
-
out.print(table)
|
|
396
|
+
out().print(table)
|
|
365
397
|
|
|
366
398
|
|
|
367
399
|
@app.command()
|
|
368
400
|
def blocks(root: RootOpt = Path("."), config: ConfigOpt = None) -> None:
|
|
369
401
|
"""List the blocks of base.md.j2 and the partials that can be shadowed."""
|
|
402
|
+
from readwright.renderer import BASE_TEMPLATE, Renderer
|
|
403
|
+
|
|
370
404
|
cfg = _load(root, config, False, False)
|
|
371
405
|
renderer = Renderer(root, cfg)
|
|
372
406
|
template = renderer.env.get_template(BASE_TEMPLATE)
|
|
373
|
-
out.print(f"[bold]blocks in {BASE_TEMPLATE}[/]")
|
|
407
|
+
out().print(f"[bold]blocks in {BASE_TEMPLATE}[/]")
|
|
374
408
|
for name in template.blocks:
|
|
375
|
-
out.print(f" {name}")
|
|
376
|
-
out.print("[bold]partials[/] (shadow with templates/partials/<name> in the repo)")
|
|
409
|
+
out().print(f" {name}")
|
|
410
|
+
out().print("[bold]partials[/] (shadow with templates/partials/<name> in the repo)")
|
|
377
411
|
for name in sorted(renderer.env.list_templates()):
|
|
378
412
|
if name.startswith("partials/"):
|
|
379
|
-
out.print(f" {name} [dim]<- {renderer.source_label(name)}[/]")
|
|
413
|
+
out().print(f" {name} [dim]<- {renderer.source_label(name)}[/]")
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def bundled_template_names(incomplete: str) -> list[str]:
|
|
417
|
+
templates = Path(__file__).resolve().parent / "templates"
|
|
418
|
+
names = sorted(p.relative_to(templates).as_posix() for p in templates.rglob("*") if p.is_file())
|
|
419
|
+
return [name for name in names if name.startswith(incomplete)]
|
|
380
420
|
|
|
381
421
|
|
|
382
422
|
@app.command()
|
|
383
423
|
def show(
|
|
384
|
-
name: Annotated[
|
|
424
|
+
name: Annotated[
|
|
425
|
+
str,
|
|
426
|
+
typer.Argument(
|
|
427
|
+
help="Template name, e.g. base.md.j2", autocompletion=bundled_template_names
|
|
428
|
+
),
|
|
429
|
+
],
|
|
385
430
|
root: RootOpt = Path("."),
|
|
386
431
|
config: ConfigOpt = None,
|
|
387
432
|
) -> None:
|
|
388
433
|
"""Print a template's source (useful for copying a partial to override it)."""
|
|
434
|
+
from readwright.renderer import Renderer
|
|
435
|
+
|
|
389
436
|
cfg = _load(root, config, False, False)
|
|
390
437
|
renderer = Renderer(root, cfg)
|
|
391
438
|
try:
|
|
392
439
|
source, _, _ = renderer.env.loader.get_source(renderer.env, name)
|
|
393
440
|
except Exception:
|
|
394
441
|
fail(f"template '{name}' not found")
|
|
395
|
-
err.print(f"[dim]# {name} <- {renderer.source_label(name)}[/]")
|
|
442
|
+
err().print(f"[dim]# {name} <- {renderer.source_label(name)}[/]")
|
|
396
443
|
sys.stdout.write(source)
|
|
397
444
|
|
|
398
445
|
|
|
@@ -428,7 +475,7 @@ def skill(
|
|
|
428
475
|
"""Print the bundled agent skill's location, or --install it into a project."""
|
|
429
476
|
source = bundled_skill_dir()
|
|
430
477
|
if not install:
|
|
431
|
-
out.print(str(source), highlight=False, soft_wrap=True)
|
|
478
|
+
out().print(str(source), highlight=False, soft_wrap=True)
|
|
432
479
|
return
|
|
433
480
|
skills_dir = dest if dest is not None else root / DEFAULT_SKILL_DEST
|
|
434
481
|
target = skills_dir / SKILL_NAME
|
|
@@ -437,7 +484,18 @@ def skill(
|
|
|
437
484
|
fail(f"{target} already exists (use --force to replace it)")
|
|
438
485
|
shutil.rmtree(target)
|
|
439
486
|
shutil.copytree(source, target)
|
|
440
|
-
out.print(f"[green]installed[/] skill to {target}", highlight=False, soft_wrap=True)
|
|
487
|
+
out().print(f"[green]installed[/] skill to {target}", highlight=False, soft_wrap=True)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@app.command()
|
|
491
|
+
def completion(
|
|
492
|
+
shell: Annotated[Shells, typer.Argument(help="Shell to generate the script for.")],
|
|
493
|
+
) -> None:
|
|
494
|
+
"""Print a shell completion script, e.g. eval "$(readwright completion zsh)"."""
|
|
495
|
+
script = get_completion_script(
|
|
496
|
+
prog_name="readwright", complete_var="_READWRIGHT_COMPLETE", shell=shell.value
|
|
497
|
+
)
|
|
498
|
+
sys.stdout.write(script + "\n")
|
|
441
499
|
|
|
442
500
|
|
|
443
501
|
if __name__ == "__main__":
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import subprocess
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
@@ -19,6 +20,13 @@ classifiers = [
|
|
|
19
20
|
PNG_BYTES = b"\x89PNG\r\n\x1a\n"
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
@pytest.fixture(autouse=True)
|
|
24
|
+
def isolated_git_env(monkeypatch):
|
|
25
|
+
# git hooks export GIT_DIR, which would point test repos at the real repository
|
|
26
|
+
for name in [name for name in os.environ if name.startswith("GIT_")]:
|
|
27
|
+
monkeypatch.delenv(name)
|
|
28
|
+
|
|
29
|
+
|
|
22
30
|
@pytest.fixture
|
|
23
31
|
def tmp_repo(tmp_path: Path) -> Path:
|
|
24
32
|
subprocess.run(["git", "init", "-q"], cwd=tmp_path, check=True)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
1
3
|
from pathlib import Path
|
|
2
4
|
|
|
3
5
|
import yaml
|
|
@@ -237,3 +239,35 @@ def test_skill_install_custom_dest_and_refuses_overwrite(tmp_path):
|
|
|
237
239
|
assert again.exit_code == 1
|
|
238
240
|
assert "already exists" in again.output
|
|
239
241
|
assert run("skill", "--install", "--dest", dest, "--force").exit_code == 0
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_completion_prints_script_for_shell():
|
|
245
|
+
for shell in ("bash", "zsh", "fish"):
|
|
246
|
+
result = run("completion", shell)
|
|
247
|
+
assert result.exit_code == 0, result.output
|
|
248
|
+
assert "_READWRIGHT_COMPLETE=complete_" in result.output
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_completion_rejects_unknown_shell():
|
|
252
|
+
assert run("completion", "tcsh").exit_code != 0
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_completion_suggests_commands_and_templates():
|
|
256
|
+
def complete(words, cword):
|
|
257
|
+
env = {"_READWRIGHT_COMPLETE": "complete_bash", "COMP_WORDS": words, "COMP_CWORD": cword}
|
|
258
|
+
return runner.invoke(app, [], env=env, prog_name="readwright").output.split()
|
|
259
|
+
|
|
260
|
+
assert {"render", "completion"} <= set(complete("readwright ", "1"))
|
|
261
|
+
assert "partials/install.md.j2" in complete("readwright show partials/", "2")
|
|
262
|
+
assert complete("readwright show base", "2") == ["base.md.j2"]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def test_import_skips_heavy_dependencies():
|
|
266
|
+
code = (
|
|
267
|
+
"import sys, readwright.cli; "
|
|
268
|
+
"print(sorted(m for m in ('pydantic', 'jinja2', 'rich', 'yaml') if m in sys.modules))"
|
|
269
|
+
)
|
|
270
|
+
result = subprocess.run(
|
|
271
|
+
[sys.executable, "-c", code], capture_output=True, text=True, check=True
|
|
272
|
+
)
|
|
273
|
+
assert result.stdout.strip() == "[]"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{readwright-0.4.0 → readwright-0.5.0}/examples/config-only/docs/screenshots/editor-light.jpg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/mobile/captions.yaml
RENAMED
|
File without changes
|
{readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/mobile/phone.jpg
RENAMED
|
File without changes
|
{readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/settings-dark.jpg
RENAMED
|
File without changes
|
{readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/docs/screenshots/settings-light.jpg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{readwright-0.4.0 → readwright-0.5.0}/examples/kitchen-sink/templates/partials/contributing.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|