docwrap 0.1.0__tar.gz → 0.3.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.
- {docwrap-0.1.0 → docwrap-0.3.0}/.gitignore +1 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/PKG-INFO +28 -23
- {docwrap-0.1.0 → docwrap-0.3.0}/README.md +19 -14
- {docwrap-0.1.0 → docwrap-0.3.0}/_build.py +11 -0
- docwrap-0.3.0/plugins/agy/_docwrap/_adapter.py +5 -0
- docwrap-0.3.0/plugins/agy/tests/test_agy_adapter.py +50 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/claude/.claude-plugin/plugin.json +1 -1
- docwrap-0.3.0/plugins/claude/_docwrap/_adapter.py +5 -0
- docwrap-0.3.0/plugins/claude/tests/test_claude_adapter.py +129 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/codex/.codex-plugin/plugin.json +1 -1
- docwrap-0.3.0/plugins/codex/_docwrap/_adapter.py +5 -0
- docwrap-0.3.0/plugins/codex/tests/test_codex_adapter.py +50 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/pyproject.toml +24 -21
- docwrap-0.3.0/src/docwrap/__init__.py +93 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/__main__.py +1 -1
- docwrap-0.3.0/src/docwrap/_arguments.py +20 -0
- docwrap-0.3.0/src/docwrap/_canonical.py +56 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_cli.py +614 -554
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_config.py +199 -168
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_convert.py +22 -31
- docwrap-0.3.0/src/docwrap/_core/__init__.py +25 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_codeblock.py +22 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_discover.py +99 -51
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_escape.py +6 -17
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_incremental.py +30 -22
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_normalize.py +7 -1
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_parse.py +1 -1
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_process.py +115 -97
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_wrap.py +116 -107
- docwrap-0.3.0/src/docwrap/_display/__init__.py +7 -0
- docwrap-0.3.0/src/docwrap/_display/_color.py +48 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_json.py +9 -26
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_print.py +5 -4
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_recommend.py +15 -34
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_skips.py +4 -4
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_stats.py +15 -22
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_summary.py +50 -35
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_exceptions.py +7 -2
- docwrap-0.3.0/src/docwrap/_files.py +207 -0
- docwrap-0.3.0/src/docwrap/_git.py +167 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_git_utils.py +1 -21
- docwrap-0.3.0/src/docwrap/_hook.py +1210 -0
- docwrap-0.3.0/src/docwrap/_hook_front.py +91 -0
- docwrap-0.3.0/src/docwrap/_incremental_git.py +122 -0
- docwrap-0.1.0/src/docwrap/__init__.py → docwrap-0.3.0/src/docwrap/_library.py +3 -7
- docwrap-0.3.0/src/docwrap/_main.py +45 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_stats.py +124 -110
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_styles/__init__.py +42 -18
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_styles/_google.py +33 -16
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_styles/_numpy.py +17 -5
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_styles/_sphinx.py +27 -29
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_types.py +44 -54
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_validate.py +4 -5
- docwrap-0.3.0/src/docwrap/_vendor/__init__.py +1 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/__init__.py +1 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_adds.py +29 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_blobs.py +200 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_config.py +30 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_discovery.py +296 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_index.py +64 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_listings.py +235 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_pathspecs.py +153 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_repo.py +127 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_run.py +97 -0
- docwrap-0.3.0/src/docwrap/_vendor/gitfiles/manifest.json +27 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/__init__.py +1 -0
- docwrap-0.1.0/plugins/agy/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_agy.py +59 -17
- docwrap-0.1.0/plugins/claude/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_claude.py +60 -12
- docwrap-0.1.0/plugins/codex/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_codex.py +60 -11
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_color.py +72 -0
- docwrap-0.1.0/src/docwrap/hooks/__init__.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_contract.py +38 -34
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_diff.py +238 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_front.py +510 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_hints.py +17 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_loader.py +101 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_notes.py +97 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_parser.py +184 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_streams.py +218 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_terminal.py +46 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_tool.py +36 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_version.py +29 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_writes.py +76 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/manifest.json +37 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookshell/__init__.py +7 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookshell/_classify.py +277 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookshell/_detector.py +500 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookshell/_forms.py +695 -0
- {docwrap-0.1.0/src/docwrap → docwrap-0.3.0/src/docwrap/_vendor/hookshell}/_grammar.py +154 -114
- {docwrap-0.1.0/src/docwrap → docwrap-0.3.0/src/docwrap/_vendor/hookshell}/_shell.py +5 -5
- docwrap-0.3.0/src/docwrap/_vendor/hookshell/_shellparse.py +1021 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookshell/_source.py +130 -0
- docwrap-0.3.0/src/docwrap/_vendor/hookshell/manifest.json +22 -0
- docwrap-0.3.0/src/docwrap/_vendor/srcfiles/__init__.py +58 -0
- docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_errors.py +56 -0
- docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_keys.py +128 -0
- docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_labels.py +79 -0
- docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_paths.py +69 -0
- docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_recovery.py +123 -0
- docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_signals.py +82 -0
- docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_write.py +212 -0
- docwrap-0.3.0/src/docwrap/_vendor/srcfiles/manifest.json +22 -0
- docwrap-0.3.0/src/docwrap/hooks/__init__.py +60 -0
- docwrap-0.1.0/plugins/agy/tests/test_agy_adapter.py +0 -135
- docwrap-0.1.0/plugins/claude/tests/test_claude_adapter.py +0 -209
- docwrap-0.1.0/plugins/codex/tests/test_codex_adapter.py +0 -125
- docwrap-0.1.0/src/docwrap/_canonical.py +0 -66
- docwrap-0.1.0/src/docwrap/_classify.py +0 -56
- docwrap-0.1.0/src/docwrap/_color.py +0 -135
- docwrap-0.1.0/src/docwrap/_core/__init__.py +0 -27
- docwrap-0.1.0/src/docwrap/_detector.py +0 -166
- docwrap-0.1.0/src/docwrap/_display/__init__.py +0 -17
- docwrap-0.1.0/src/docwrap/_display/_diff.py +0 -62
- docwrap-0.1.0/src/docwrap/_downstream.py +0 -181
- docwrap-0.1.0/src/docwrap/_files.py +0 -304
- docwrap-0.1.0/src/docwrap/_git.py +0 -764
- docwrap-0.1.0/src/docwrap/_hook.py +0 -1001
- docwrap-0.1.0/src/docwrap/_hosts/__init__.py +0 -50
- docwrap-0.1.0/src/docwrap/_shellparse.py +0 -643
- {docwrap-0.1.0 → docwrap-0.3.0}/LICENSE +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/agy/_docwrap/__init__.py +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/agy/adapter.toml +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/agy/hooks.json +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/agy/plugin.json +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/claude/_docwrap/__init__.py +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/claude/adapter.toml +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/claude/hooks/hooks.json +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/codex/_docwrap/__init__.py +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/codex/adapter.toml +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/plugins/codex/hooks/hooks.json +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_detect.py +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_text.py +0 -0
- {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: docwrap
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Wrap Python docstrings with support for Google, NumPy, and Sphinx styles
|
|
5
5
|
Project-URL: Documentation, https://github.com/dactylo/docwrap/tree/main/docs
|
|
6
6
|
Project-URL: Homepage, https://github.com/dactylo/docwrap
|
|
@@ -19,24 +19,22 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
|
|
20
20
|
# docwrap
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Docwrap applies configurable formatting rules to Python docstrings. It makes wrapping and layout
|
|
22
|
+
docwrap applies configurable formatting rules to Python docstrings. It makes wrapping and layout
|
|
25
23
|
deterministic, with controls for line length, paragraph reflow, and summary placement. It supports
|
|
26
24
|
Google, NumPy, and Sphinx/reST conventions.
|
|
27
25
|
|
|
28
26
|
Run `docwrap` to see which docstrings would change across your project:
|
|
29
27
|
|
|
30
|
-

|
|
28
|
+

|
|
31
29
|
|
|
32
30
|
Then use `docwrap --preview` to compare the original and proposed formatting. For example, using
|
|
33
31
|
the default 79-column limit:
|
|
34
32
|
|
|
35
|
-

|
|
33
|
+

|
|
36
34
|
|
|
37
35
|
## Installation
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
docwrap requires CPython 3.13 or later. From a checkout, install the command with
|
|
40
38
|
[uv](https://docs.astral.sh/uv/):
|
|
41
39
|
|
|
42
40
|
```bash
|
|
@@ -78,17 +76,17 @@ line-length = 88
|
|
|
78
76
|
```
|
|
79
77
|
|
|
80
78
|
Configuration is discovered from each file's `pyproject.toml`. The
|
|
81
|
-
[wrapping policy guide](docs/design.md#choosing-a-wrapping-policy) explains the tradeoffs between
|
|
82
|
-
reflowing prose and preserving authored layout. See the [CLI reference](docs/cli.md) for output
|
|
79
|
+
[wrapping policy guide](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/design.md#choosing-a-wrapping-policy) explains the tradeoffs between
|
|
80
|
+
reflowing prose and preserving authored layout. See the [CLI reference](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/cli.md) for output
|
|
83
81
|
modes, configuration precedence, and advanced options.
|
|
84
82
|
|
|
85
83
|
## Integrations and library use
|
|
86
84
|
|
|
87
85
|
A local [pre-commit](https://pre-commit.com/) hook can run docwrap when you commit. See the
|
|
88
|
-
[pre-commit setup](docs/hook.md#pre-commit) for a writing hook and its report-only variant. Agent
|
|
86
|
+
[pre-commit setup](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/hook.md#pre-commit) for a writing hook and its report-only variant. Agent
|
|
89
87
|
hooks for Claude Code, Codex, and Antigravity can run before commits or after edits. Their
|
|
90
88
|
installation, host settings, commit behavior, and composition limits are in the
|
|
91
|
-
[agent hook guide](docs/hook.md).
|
|
89
|
+
[agent hook guide](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/hook.md).
|
|
92
90
|
|
|
93
91
|
For Python callers, `wrap_source()` returns rewritten source, `check()` reports whether it would
|
|
94
92
|
change, and `wrap_source_report()` supplies structured results:
|
|
@@ -101,26 +99,33 @@ result = docwrap.check(source)
|
|
|
101
99
|
wrapped = docwrap.wrap_source(source)
|
|
102
100
|
```
|
|
103
101
|
|
|
104
|
-
See the [library reference](docs/library.md) for file operations, configuration, result types, and
|
|
102
|
+
See the [library reference](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/library.md) for file operations, configuration, result types, and
|
|
105
103
|
safety skips.
|
|
106
104
|
|
|
107
105
|
## Documentation
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
| If you want to... | Read |
|
|
108
|
+
| ------------------------------------------------------ | ------------------------------------------ |
|
|
109
|
+
| Look up commands, output, configuration, or exit codes | [CLI reference](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/cli.md) |
|
|
110
|
+
| Set up agent hooks, commit handling, or host responses | [Agent hook guide](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/hook.md) |
|
|
111
|
+
| Wrap only changed files or definitions | [Incremental mode](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/incremental.md) |
|
|
112
|
+
| Call docwrap from Python | [Library reference](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/library.md) |
|
|
113
|
+
| Choose a style and understand what docwrap preserves | [Concepts and safety](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/design.md) |
|
|
114
|
+
| Check supported environments and file behavior | [Platform compatibility](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/platform.md) |
|
|
115
115
|
|
|
116
116
|
## Development
|
|
117
117
|
|
|
118
|
-
See the [development guide](docs/development.md) for pinned CI dependencies and lock updates.
|
|
118
|
+
See the [development guide](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/development.md) for pinned CI dependencies and lock updates.
|
|
119
|
+
|
|
120
|
+
Run `make setup` with uv 0.13.6 to create the pinned `.venv` before project commands.
|
|
121
|
+
`make install` and `make reinstall` install only the CLI through uv.
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
```bash
|
|
124
|
+
make quality # read-only quality checks
|
|
125
|
+
make check # quality checks and one full test run
|
|
126
|
+
make format # apply repository formatters
|
|
127
|
+
```
|
|
123
128
|
|
|
124
129
|
## License
|
|
125
130
|
|
|
126
|
-
|
|
131
|
+
docwrap is available under the [MIT License](https://github.com/dactylo/docwrap/blob/v0.3.0/LICENSE).
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# docwrap
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Docwrap applies configurable formatting rules to Python docstrings. It makes wrapping and layout
|
|
3
|
+
docwrap applies configurable formatting rules to Python docstrings. It makes wrapping and layout
|
|
6
4
|
deterministic, with controls for line length, paragraph reflow, and summary placement. It supports
|
|
7
5
|
Google, NumPy, and Sphinx/reST conventions.
|
|
8
6
|
|
|
@@ -17,7 +15,7 @@ the default 79-column limit:
|
|
|
17
15
|
|
|
18
16
|
## Installation
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
docwrap requires CPython 3.13 or later. From a checkout, install the command with
|
|
21
19
|
[uv](https://docs.astral.sh/uv/):
|
|
22
20
|
|
|
23
21
|
```bash
|
|
@@ -87,21 +85,28 @@ safety skips.
|
|
|
87
85
|
|
|
88
86
|
## Documentation
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
| If you want to... | Read |
|
|
89
|
+
| ------------------------------------------------------ | ------------------------------------------ |
|
|
90
|
+
| Look up commands, output, configuration, or exit codes | [CLI reference](docs/cli.md) |
|
|
91
|
+
| Set up agent hooks, commit handling, or host responses | [Agent hook guide](docs/hook.md) |
|
|
92
|
+
| Wrap only changed files or definitions | [Incremental mode](docs/incremental.md) |
|
|
93
|
+
| Call docwrap from Python | [Library reference](docs/library.md) |
|
|
94
|
+
| Choose a style and understand what docwrap preserves | [Concepts and safety](docs/design.md) |
|
|
95
|
+
| Check supported environments and file behavior | [Platform compatibility](docs/platform.md) |
|
|
96
96
|
|
|
97
97
|
## Development
|
|
98
98
|
|
|
99
99
|
See the [development guide](docs/development.md) for pinned CI dependencies and lock updates.
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
Run `make setup` with uv 0.13.6 to create the pinned `.venv` before project commands.
|
|
102
|
+
`make install` and `make reinstall` install only the CLI through uv.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
make quality # read-only quality checks
|
|
106
|
+
make check # quality checks and one full test run
|
|
107
|
+
make format # apply repository formatters
|
|
108
|
+
```
|
|
104
109
|
|
|
105
110
|
## License
|
|
106
111
|
|
|
107
|
-
|
|
112
|
+
docwrap is available under the [MIT License](LICENSE).
|
|
@@ -12,6 +12,17 @@ class PluginMetadataHook(MetadataHookInterface):
|
|
|
12
12
|
|
|
13
13
|
@override
|
|
14
14
|
def update(self, metadata: dict[str, object]) -> None:
|
|
15
|
+
"""Replace distribution entry points with the bundled plugin registrations.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
metadata: Distribution metadata to update in place.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
OSError: If a plugin manifest cannot be read.
|
|
22
|
+
tomllib.TOMLDecodeError: If a plugin manifest is invalid TOML.
|
|
23
|
+
ValueError: If two plugins register the same entry-point group and
|
|
24
|
+
name.
|
|
25
|
+
"""
|
|
15
26
|
metadata['entry-points'] = _plugin_entry_points(Path(self.root))
|
|
16
27
|
|
|
17
28
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import shlex
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from docwrap._hook_front import _TOOL, _build_hook_parser
|
|
10
|
+
from docwrap._vendor.hookfront._loader import load_adapter
|
|
11
|
+
|
|
12
|
+
_ROOT = Path(__file__).resolve().parents[3]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.usefixtures('registered_hosts')
|
|
16
|
+
def test_native_manifest_command_loads_this_adapter() -> None:
|
|
17
|
+
argv = shlex.split('docwrap hook --agent agy --event PreToolUse')
|
|
18
|
+
args = _build_hook_parser().parse_args(argv[2:])
|
|
19
|
+
adapter = load_adapter(_TOOL, args.agent)
|
|
20
|
+
assert adapter.name == 'agy'
|
|
21
|
+
assert args.event == 'PreToolUse'
|
|
22
|
+
assert adapter.requires_event
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_native_manifest_is_unchanged() -> None:
|
|
26
|
+
assert json.loads((_ROOT / 'plugins' / 'agy' / 'hooks.json').read_text(encoding='utf-8')) == {
|
|
27
|
+
'docwrap': {
|
|
28
|
+
'PreToolUse': [
|
|
29
|
+
{
|
|
30
|
+
'matcher': 'run_command',
|
|
31
|
+
'hooks': [
|
|
32
|
+
{
|
|
33
|
+
'type': 'command',
|
|
34
|
+
'command': 'docwrap hook --agent agy --event PreToolUse',
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_native_plugin_identity_and_layout() -> None:
|
|
44
|
+
plugin = json.loads((_ROOT / 'plugins' / 'agy' / 'plugin.json').read_text(encoding='utf-8'))
|
|
45
|
+
assert set(plugin) == {'$schema', 'name', 'description'}
|
|
46
|
+
assert plugin['$schema'] == 'https://antigravity.google/schemas/v1/plugin.json'
|
|
47
|
+
assert plugin['name'] == 'docwrap-agy'
|
|
48
|
+
assert isinstance(plugin['description'], str)
|
|
49
|
+
assert plugin['description'].strip()
|
|
50
|
+
assert not (_ROOT / 'plugins' / 'agy' / 'hooks').exists()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import shlex
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from docwrap._hook_front import _TOOL, _build_hook_parser
|
|
11
|
+
from docwrap._hook_front import _run_hook as run_hook
|
|
12
|
+
from docwrap._vendor.hookfront._loader import load_adapter
|
|
13
|
+
from tests.helpers import decode_text, git_commit_all, run_setup
|
|
14
|
+
|
|
15
|
+
_ROOT = Path(__file__).resolve().parents[3]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestClaudeAdapter:
|
|
19
|
+
@pytest.mark.usefixtures('example_hosts')
|
|
20
|
+
@pytest.mark.parametrize('host', ['claude', 'example'])
|
|
21
|
+
@pytest.mark.parametrize(
|
|
22
|
+
('command', 'outcome'),
|
|
23
|
+
[
|
|
24
|
+
pytest.param('ls', 'unrelated', id='unrelated'),
|
|
25
|
+
pytest.param('git commit --future-option -m x', 'denied', id='denied'),
|
|
26
|
+
pytest.param('git commit -m x', 'wrapped', id='supported'),
|
|
27
|
+
],
|
|
28
|
+
)
|
|
29
|
+
def test_hook_decisions_preserve_adapter_independence(
|
|
30
|
+
self,
|
|
31
|
+
tmp_path: Path,
|
|
32
|
+
capsys: pytest.CaptureFixture[str],
|
|
33
|
+
host: str,
|
|
34
|
+
command: str,
|
|
35
|
+
outcome: Literal['unrelated', 'denied', 'wrapped'],
|
|
36
|
+
) -> None:
|
|
37
|
+
run_setup(['git', 'init'], cwd=tmp_path)
|
|
38
|
+
run_setup(['git', 'config', 'docwrap.hook', 'true'], cwd=tmp_path)
|
|
39
|
+
(tmp_path / 'pyproject.toml').write_text(
|
|
40
|
+
'[tool.docwrap]\nline-length = 40\nfill-style = "greedy"\n', encoding='utf-8'
|
|
41
|
+
)
|
|
42
|
+
target = tmp_path / 'sample.py'
|
|
43
|
+
target.write_text('pass\n', encoding='utf-8')
|
|
44
|
+
git_commit_all(tmp_path)
|
|
45
|
+
source = (
|
|
46
|
+
'def f():\n """Summary.\n\n'
|
|
47
|
+
' one two three four five six seven eight nine ten eleven twelve\n'
|
|
48
|
+
' """\n'
|
|
49
|
+
)
|
|
50
|
+
target.write_text(source, encoding='utf-8')
|
|
51
|
+
run_setup(['git', 'add', 'sample.py'], cwd=tmp_path)
|
|
52
|
+
payload = json.dumps(
|
|
53
|
+
_payload('PreToolUse', 'Bash', {'command': command}) | {'cwd': str(tmp_path)}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
status = run_hook(payload, host=host)
|
|
57
|
+
|
|
58
|
+
captured = capsys.readouterr()
|
|
59
|
+
expected_decision = 'deny' if outcome == 'denied' else None
|
|
60
|
+
assert (status, _decision(captured.out)) == (0, expected_decision)
|
|
61
|
+
if expected_decision == 'deny':
|
|
62
|
+
assert 'unsupported commit command' in captured.err
|
|
63
|
+
else:
|
|
64
|
+
assert captured.err == ''
|
|
65
|
+
expected = (
|
|
66
|
+
'def f():\n """Summary.\n\n'
|
|
67
|
+
' one two three four five six seven\n'
|
|
68
|
+
' eight nine ten eleven twelve\n'
|
|
69
|
+
' """\n'
|
|
70
|
+
if outcome == 'wrapped'
|
|
71
|
+
else source
|
|
72
|
+
)
|
|
73
|
+
staged = run_setup(['git', 'show', ':sample.py'], cwd=tmp_path)
|
|
74
|
+
assert decode_text(staged.stdout) == expected
|
|
75
|
+
assert target.read_text(encoding='utf-8') == expected
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _decision(stdout: str) -> str | None:
|
|
79
|
+
if not stdout:
|
|
80
|
+
return None
|
|
81
|
+
data = json.loads(stdout)
|
|
82
|
+
if 'systemMessage' in data:
|
|
83
|
+
return None
|
|
84
|
+
if 'hookSpecificOutput' in data:
|
|
85
|
+
return str(data['hookSpecificOutput']['permissionDecision'])
|
|
86
|
+
return str(data['example']['decision'])
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _payload(event: str, tool: str, tool_input: dict[str, object]) -> dict[str, object]:
|
|
90
|
+
return {'hook_event_name': event, 'tool_name': tool, 'tool_input': tool_input, 'cwd': '/r'}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.mark.usefixtures('registered_hosts')
|
|
94
|
+
def test_native_manifest_command_loads_this_adapter() -> None:
|
|
95
|
+
argv = shlex.split('docwrap hook --agent claude')
|
|
96
|
+
args = _build_hook_parser().parse_args(argv[2:])
|
|
97
|
+
adapter = load_adapter(_TOOL, args.agent)
|
|
98
|
+
assert adapter.name == 'claude'
|
|
99
|
+
assert args.event is None
|
|
100
|
+
assert not adapter.requires_event
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_native_manifest_is_unchanged() -> None:
|
|
104
|
+
manifest = _ROOT / 'plugins' / 'claude' / 'hooks' / 'hooks.json'
|
|
105
|
+
assert json.loads(manifest.read_text(encoding='utf-8')) == {
|
|
106
|
+
'hooks': {
|
|
107
|
+
'PreToolUse': [
|
|
108
|
+
{
|
|
109
|
+
'matcher': '^Bash$',
|
|
110
|
+
'hooks': [{'type': 'command', 'command': 'docwrap hook --agent claude'}],
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_native_plugin_identity_and_marketplace_source() -> None:
|
|
118
|
+
plugin = json.loads(
|
|
119
|
+
(_ROOT / 'plugins' / 'claude' / '.claude-plugin' / 'plugin.json').read_text(
|
|
120
|
+
encoding='utf-8'
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
marketplace = json.loads(
|
|
124
|
+
(_ROOT / '.claude-plugin' / 'marketplace.json').read_text(encoding='utf-8')
|
|
125
|
+
)
|
|
126
|
+
assert plugin['name'] == 'docwrap'
|
|
127
|
+
assert marketplace['plugins'][0]['source'] == './plugins/claude'
|
|
128
|
+
assert not (_ROOT / 'plugins' / 'claude' / 'hooks.json').exists()
|
|
129
|
+
assert not (_ROOT / 'plugins' / 'claude' / '.codex-plugin').exists()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import shlex
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from docwrap._hook_front import _TOOL, _build_hook_parser
|
|
10
|
+
from docwrap._vendor.hookfront._loader import load_adapter
|
|
11
|
+
|
|
12
|
+
_ROOT = Path(__file__).resolve().parents[3]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.usefixtures('registered_hosts')
|
|
16
|
+
def test_native_manifest_command_loads_this_adapter() -> None:
|
|
17
|
+
argv = shlex.split('docwrap hook --agent codex')
|
|
18
|
+
args = _build_hook_parser().parse_args(argv[2:])
|
|
19
|
+
adapter = load_adapter(_TOOL, args.agent)
|
|
20
|
+
assert adapter.name == 'codex'
|
|
21
|
+
assert args.event is None
|
|
22
|
+
assert not adapter.requires_event
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_native_manifest_is_unchanged() -> None:
|
|
26
|
+
manifest = _ROOT / 'plugins' / 'codex' / 'hooks' / 'hooks.json'
|
|
27
|
+
assert json.loads(manifest.read_text(encoding='utf-8')) == {
|
|
28
|
+
'hooks': {
|
|
29
|
+
'PreToolUse': [
|
|
30
|
+
{
|
|
31
|
+
'matcher': '^Bash$',
|
|
32
|
+
'hooks': [{'type': 'command', 'command': 'docwrap hook --agent codex'}],
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_native_plugin_identity_and_marketplace_source() -> None:
|
|
40
|
+
plugin = json.loads(
|
|
41
|
+
(_ROOT / 'plugins' / 'codex' / '.codex-plugin' / 'plugin.json').read_text(encoding='utf-8')
|
|
42
|
+
)
|
|
43
|
+
marketplace = json.loads(
|
|
44
|
+
(_ROOT / '.agents' / 'plugins' / 'marketplace.json').read_text(encoding='utf-8')
|
|
45
|
+
)
|
|
46
|
+
assert plugin['name'] == 'docwrap'
|
|
47
|
+
assert 'hooks' not in plugin
|
|
48
|
+
assert marketplace['plugins'][0]['source'] == {'source': 'local', 'path': './plugins/codex'}
|
|
49
|
+
assert not (_ROOT / 'plugins' / 'codex' / 'hooks.json').exists()
|
|
50
|
+
assert not (_ROOT / 'plugins' / 'codex' / '.claude-plugin').exists()
|
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
# ==========================================================
|
|
4
4
|
[build-system]
|
|
5
5
|
build-backend = "hatchling.build"
|
|
6
|
-
requires = [ "editables~=0.3", "hatchling>=1.27" ]
|
|
6
|
+
requires = [ "editables~=0.3", "hatch-fancy-pypi-readme>=25.1", "hatchling>=1.27" ]
|
|
7
7
|
|
|
8
8
|
# ==========================================================
|
|
9
9
|
# Project Metadata
|
|
10
10
|
# ==========================================================
|
|
11
11
|
[project]
|
|
12
12
|
name = "docwrap"
|
|
13
|
-
version = "0.
|
|
13
|
+
version = "0.3.0"
|
|
14
14
|
description = "Wrap Python docstrings with support for Google, NumPy, and Sphinx styles"
|
|
15
|
-
readme = "README.md"
|
|
16
15
|
keywords = [ "docstring", "formatter", "google", "numpy", "sphinx", "wrap" ]
|
|
17
16
|
license = "MIT"
|
|
18
17
|
license-files = [ "LICENSE" ]
|
|
@@ -24,12 +23,12 @@ classifiers = [
|
|
|
24
23
|
"Programming Language :: Python :: 3.15",
|
|
25
24
|
"Typing :: Typed",
|
|
26
25
|
]
|
|
27
|
-
dynamic = [ "entry-points" ]
|
|
26
|
+
dynamic = [ "entry-points", "readme" ]
|
|
28
27
|
urls.Documentation = "https://github.com/dactylo/docwrap/tree/main/docs"
|
|
29
28
|
urls.Homepage = "https://github.com/dactylo/docwrap"
|
|
30
29
|
urls.Issues = "https://github.com/dactylo/docwrap/issues"
|
|
31
30
|
urls.Repository = "https://github.com/dactylo/docwrap"
|
|
32
|
-
scripts.docwrap = "docwrap.
|
|
31
|
+
scripts.docwrap = "docwrap._main:main"
|
|
33
32
|
|
|
34
33
|
[dependency-groups]
|
|
35
34
|
dev = [
|
|
@@ -45,9 +44,11 @@ dev = [
|
|
|
45
44
|
"pyright",
|
|
46
45
|
"ruff",
|
|
47
46
|
"vulture",
|
|
47
|
+
"zizmor",
|
|
48
48
|
{ include-group = "test" },
|
|
49
49
|
]
|
|
50
50
|
test = [
|
|
51
|
+
"hypothesis",
|
|
51
52
|
"pytest",
|
|
52
53
|
"pytest-xdist>=3.2",
|
|
53
54
|
]
|
|
@@ -59,9 +60,22 @@ ci-build = [
|
|
|
59
60
|
[tool.hatch]
|
|
60
61
|
metadata.hooks.custom.path = "_build.py"
|
|
61
62
|
build.targets.wheel.include = [ "/plugins/*/_docwrap/", "/src/docwrap/" ]
|
|
62
|
-
build.targets.sdist.include = [ "/_build.py", "/plugins", "/src" ]
|
|
63
|
+
build.targets.sdist.include = [ "/README.md", "/_build.py", "/plugins", "/src" ]
|
|
63
64
|
build.targets.wheel.sources = [ "src" ]
|
|
64
65
|
|
|
66
|
+
[tool.hatch.metadata.hooks.fancy-pypi-readme]
|
|
67
|
+
content-type = "text/markdown"
|
|
68
|
+
fragments = [ { path = "README.md" } ]
|
|
69
|
+
|
|
70
|
+
# Keep checkout links relative; package links target this release.
|
|
71
|
+
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
|
|
72
|
+
pattern = '(!\[[^\]\n]*\]\()(?![A-Za-z][A-Za-z0-9+.-]*:|[/#])(?:\./)?([^\s)]+)(\))'
|
|
73
|
+
replacement = '\1https://raw.githubusercontent.com/dactylo/docwrap/v$HFPR_VERSION/\2\3'
|
|
74
|
+
|
|
75
|
+
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
|
|
76
|
+
pattern = '(?<!!)(\[[^\]\n]+\]\()(?![A-Za-z][A-Za-z0-9+.-]*:|[/#])(?:\./)?([^\s)]+)(\))'
|
|
77
|
+
replacement = '\1https://github.com/dactylo/docwrap/blob/v$HFPR_VERSION/\2\3'
|
|
78
|
+
|
|
65
79
|
[tool.uv]
|
|
66
80
|
# CI supplies the native Taplo CLI; its Python package lacks Linux/Windows ARM wheels.
|
|
67
81
|
exclude-dependencies = [
|
|
@@ -81,26 +95,17 @@ lint.select = [ "ALL" ]
|
|
|
81
95
|
lint.ignore = [
|
|
82
96
|
"COM812", # Conflicts with the Ruff formatter
|
|
83
97
|
"CPY001", # Copyright notices are not required
|
|
84
|
-
"D203", # Conflicts with the Google docstring convention
|
|
85
|
-
"D213", # Conflicts with the Google docstring convention
|
|
86
98
|
"EM101", # Exception must not use string literal
|
|
87
99
|
"EM102", # Exception must not use f-string literal
|
|
88
100
|
"FBT001", # Boolean positional arg in function definition
|
|
89
101
|
"FBT002", # Boolean default value in function definition
|
|
90
|
-
"ISC001", # Implicit string concatenation
|
|
91
102
|
"Q000", # Conflicts with the Ruff formatter
|
|
92
|
-
"Q001", # Conflicts with the Ruff formatter
|
|
93
|
-
"Q002", # Conflicts with the Ruff formatter
|
|
94
103
|
"Q003", # Conflicts with the Ruff formatter
|
|
95
104
|
"T201", # print() found
|
|
96
105
|
"TRY003", # Avoid specifying long messages outside exception class
|
|
97
106
|
]
|
|
98
|
-
# Same-file private-member collaboration follows the module privacy boundary.
|
|
99
|
-
lint.per-file-ignores."src/docwrap/_grammar.py" = [ "SLF001" ]
|
|
100
|
-
lint.per-file-ignores."src/docwrap/_shellparse.py" = [ "SLF001" ]
|
|
101
107
|
lint.per-file-ignores."{tests,plugins/*/tests}/**/*.py" = [
|
|
102
108
|
"D", # No docstrings required in tests
|
|
103
|
-
"PLC2701", # Tests intentionally import private implementation symbols
|
|
104
109
|
"PLR2004", # Allow magic values in tests
|
|
105
110
|
"S101", # Allow asserts in tests
|
|
106
111
|
"S603", # Allow subprocess calls in tests
|
|
@@ -116,17 +121,12 @@ lint.pydocstyle.convention = "google"
|
|
|
116
121
|
[tool.vulture]
|
|
117
122
|
ignore_names = [
|
|
118
123
|
"_enable_hook",
|
|
119
|
-
"
|
|
120
|
-
"_isolate_git_config", # Autouse pytest fixture
|
|
121
|
-
"commenters",
|
|
124
|
+
"_isolate_git_config", # Autouse pytest fixture
|
|
122
125
|
"median",
|
|
123
126
|
"p75",
|
|
124
127
|
"p90",
|
|
125
128
|
"p95",
|
|
126
129
|
"p99",
|
|
127
|
-
"sphinx_field",
|
|
128
|
-
"whitespace_split",
|
|
129
|
-
"wordchars",
|
|
130
130
|
]
|
|
131
131
|
|
|
132
132
|
[tool.pyproject-fmt]
|
|
@@ -137,6 +137,8 @@ max_supported_python = "3.15"
|
|
|
137
137
|
# ==========================================================
|
|
138
138
|
[tool.pyright]
|
|
139
139
|
pythonVersion = "3.13" # Keep in sync with tool.ruff.target-version
|
|
140
|
+
venv = ".venv" # Created by make setup
|
|
141
|
+
venvPath = "."
|
|
140
142
|
typeCheckingMode = "strict"
|
|
141
143
|
extraPaths = [ ".", "src" ]
|
|
142
144
|
executionEnvironments = [
|
|
@@ -152,6 +154,7 @@ ini_options.testpaths = [ "plugins", "tests" ]
|
|
|
152
154
|
ini_options.pythonpath = [ ".", "src" ]
|
|
153
155
|
ini_options.addopts = [ "-q", "--strict-markers", "--tb=short" ]
|
|
154
156
|
ini_options.markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"')" ]
|
|
157
|
+
ini_options.faulthandler_timeout = 300
|
|
155
158
|
|
|
156
159
|
# ==========================================================
|
|
157
160
|
# Mdformat Configuration (Requires mdformat-pyproject)
|