docwrap 0.2.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.2.0 → docwrap-0.3.0}/PKG-INFO +24 -19
- {docwrap-0.2.0 → docwrap-0.3.0}/README.md +8 -3
- {docwrap-0.2.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.2.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.2.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.2.0 → docwrap-0.3.0}/pyproject.toml +4 -4
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/__init__.py +2 -0
- docwrap-0.3.0/src/docwrap/_canonical.py +56 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_cli.py +462 -374
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_config.py +180 -108
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_convert.py +22 -29
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/__init__.py +0 -1
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_codeblock.py +22 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_discover.py +74 -45
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_escape.py +6 -17
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_incremental.py +15 -11
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_normalize.py +6 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_process.py +114 -96
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_wrap.py +84 -50
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/__init__.py +0 -2
- docwrap-0.3.0/src/docwrap/_display/_color.py +48 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_print.py +5 -4
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_recommend.py +6 -12
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_skips.py +4 -4
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_stats.py +6 -14
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_summary.py +8 -6
- {docwrap-0.2.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.3.0/src/docwrap/_git_utils.py +56 -0
- 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.2.0 → docwrap-0.3.0}/src/docwrap/_library.py +2 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_main.py +1 -1
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_stats.py +90 -81
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_styles/__init__.py +31 -10
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_styles/_google.py +15 -10
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_styles/_numpy.py +2 -2
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_styles/_sphinx.py +10 -14
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_types.py +5 -1
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_validate.py +4 -5
- 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.2.0/plugins/agy/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_agy.py +59 -17
- docwrap-0.2.0/plugins/claude/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_claude.py +34 -11
- docwrap-0.2.0/plugins/codex/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_codex.py +34 -10
- docwrap-0.3.0/src/docwrap/_vendor/hookfront/_color.py +72 -0
- docwrap-0.2.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.2.0/src/docwrap/_downstream.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_streams.py +88 -28
- 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/_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.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/hookshell/_grammar.py +104 -78
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/hookshell/_shell.py +5 -5
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/hookshell/_shellparse.py +559 -230
- 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.2.0/plugins/agy/tests/test_agy_adapter.py +0 -135
- docwrap-0.2.0/plugins/claude/tests/test_claude_adapter.py +0 -268
- docwrap-0.2.0/plugins/codex/tests/test_codex_adapter.py +0 -160
- docwrap-0.2.0/src/docwrap/_canonical.py +0 -66
- docwrap-0.2.0/src/docwrap/_color.py +0 -152
- docwrap-0.2.0/src/docwrap/_display/_diff.py +0 -64
- docwrap-0.2.0/src/docwrap/_files.py +0 -316
- docwrap-0.2.0/src/docwrap/_git.py +0 -734
- docwrap-0.2.0/src/docwrap/_git_utils.py +0 -133
- docwrap-0.2.0/src/docwrap/_hook.py +0 -823
- docwrap-0.2.0/src/docwrap/_hook_front.py +0 -405
- docwrap-0.2.0/src/docwrap/_hosts/__init__.py +0 -50
- docwrap-0.2.0/src/docwrap/_vendor/hookshell/_classify.py +0 -74
- docwrap-0.2.0/src/docwrap/_vendor/hookshell/_detector.py +0 -201
- docwrap-0.2.0/src/docwrap/_vendor/hookshell/manifest.json +0 -18
- {docwrap-0.2.0 → docwrap-0.3.0}/.gitignore +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/LICENSE +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/agy/_docwrap/__init__.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/agy/adapter.toml +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/agy/hooks.json +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/agy/plugin.json +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/claude/_docwrap/__init__.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/claude/adapter.toml +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/claude/hooks/hooks.json +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/codex/_docwrap/__init__.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/codex/adapter.toml +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/plugins/codex/hooks/hooks.json +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/__main__.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_arguments.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_core/_parse.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_detect.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_json.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_display/_text.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/__init__.py +0 -0
- {docwrap-0.2.0 → docwrap-0.3.0}/src/docwrap/_vendor/hookshell/__init__.py +0 -0
- {docwrap-0.2.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
|
|
@@ -25,12 +25,12 @@ Google, NumPy, and Sphinx/reST conventions.
|
|
|
25
25
|
|
|
26
26
|
Run `docwrap` to see which docstrings would change across your project:
|
|
27
27
|
|
|
28
|
-

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

|
|
34
34
|
|
|
35
35
|
## Installation
|
|
36
36
|
|
|
@@ -76,17 +76,17 @@ line-length = 88
|
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
Configuration is discovered from each file's `pyproject.toml`. The
|
|
79
|
-
[wrapping policy guide](https://github.com/dactylo/docwrap/blob/v0.
|
|
80
|
-
reflowing prose and preserving authored layout. See the [CLI reference](https://github.com/dactylo/docwrap/blob/v0.
|
|
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
|
|
81
81
|
modes, configuration precedence, and advanced options.
|
|
82
82
|
|
|
83
83
|
## Integrations and library use
|
|
84
84
|
|
|
85
85
|
A local [pre-commit](https://pre-commit.com/) hook can run docwrap when you commit. See the
|
|
86
|
-
[pre-commit setup](https://github.com/dactylo/docwrap/blob/v0.
|
|
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
|
|
87
87
|
hooks for Claude Code, Codex, and Antigravity can run before commits or after edits. Their
|
|
88
88
|
installation, host settings, commit behavior, and composition limits are in the
|
|
89
|
-
[agent hook guide](https://github.com/dactylo/docwrap/blob/v0.
|
|
89
|
+
[agent hook guide](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/hook.md).
|
|
90
90
|
|
|
91
91
|
For Python callers, `wrap_source()` returns rewritten source, `check()` reports whether it would
|
|
92
92
|
change, and `wrap_source_report()` supplies structured results:
|
|
@@ -99,28 +99,33 @@ result = docwrap.check(source)
|
|
|
99
99
|
wrapped = docwrap.wrap_source(source)
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
See the [library reference](https://github.com/dactylo/docwrap/blob/v0.
|
|
102
|
+
See the [library reference](https://github.com/dactylo/docwrap/blob/v0.3.0/docs/library.md) for file operations, configuration, result types, and
|
|
103
103
|
safety skips.
|
|
104
104
|
|
|
105
105
|
## Documentation
|
|
106
106
|
|
|
107
107
|
| If you want to... | Read |
|
|
108
108
|
| ------------------------------------------------------ | ------------------------------------------ |
|
|
109
|
-
| Look up commands, output, configuration, or exit codes | [CLI reference](https://github.com/dactylo/docwrap/blob/v0.
|
|
110
|
-
| Set up agent hooks, commit handling, or host responses | [Agent hook guide](https://github.com/dactylo/docwrap/blob/v0.
|
|
111
|
-
| Wrap only changed files or definitions | [Incremental mode](https://github.com/dactylo/docwrap/blob/v0.
|
|
112
|
-
| Call docwrap from Python | [Library reference](https://github.com/dactylo/docwrap/blob/v0.
|
|
113
|
-
| Choose a style and understand what docwrap preserves | [Concepts and safety](https://github.com/dactylo/docwrap/blob/v0.
|
|
114
|
-
| Check supported environments and file behavior | [Platform compatibility](https://github.com/dactylo/docwrap/blob/v0.
|
|
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](https://github.com/dactylo/docwrap/blob/v0.
|
|
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
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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.
|
|
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
|
-
docwrap is available under the [MIT License](https://github.com/dactylo/docwrap/blob/v0.
|
|
131
|
+
docwrap is available under the [MIT License](https://github.com/dactylo/docwrap/blob/v0.3.0/LICENSE).
|
|
@@ -98,9 +98,14 @@ safety skips.
|
|
|
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
|
|
|
@@ -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()
|
|
@@ -10,7 +10,7 @@ requires = [ "editables~=0.3", "hatch-fancy-pypi-readme>=25.1", "hatchling>=1.27
|
|
|
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
15
|
keywords = [ "docstring", "formatter", "google", "numpy", "sphinx", "wrap" ]
|
|
16
16
|
license = "MIT"
|
|
@@ -44,6 +44,7 @@ dev = [
|
|
|
44
44
|
"pyright",
|
|
45
45
|
"ruff",
|
|
46
46
|
"vulture",
|
|
47
|
+
"zizmor",
|
|
47
48
|
{ include-group = "test" },
|
|
48
49
|
]
|
|
49
50
|
test = [
|
|
@@ -103,9 +104,6 @@ lint.ignore = [
|
|
|
103
104
|
"T201", # print() found
|
|
104
105
|
"TRY003", # Avoid specifying long messages outside exception class
|
|
105
106
|
]
|
|
106
|
-
# Same-file private-member collaboration follows the module privacy boundary.
|
|
107
|
-
lint.per-file-ignores."src/docwrap/_vendor/hookshell/_grammar.py" = [ "SLF001" ]
|
|
108
|
-
lint.per-file-ignores."src/docwrap/_vendor/hookshell/_shellparse.py" = [ "SLF001" ]
|
|
109
107
|
lint.per-file-ignores."{tests,plugins/*/tests}/**/*.py" = [
|
|
110
108
|
"D", # No docstrings required in tests
|
|
111
109
|
"PLR2004", # Allow magic values in tests
|
|
@@ -139,6 +137,8 @@ max_supported_python = "3.15"
|
|
|
139
137
|
# ==========================================================
|
|
140
138
|
[tool.pyright]
|
|
141
139
|
pythonVersion = "3.13" # Keep in sync with tool.ruff.target-version
|
|
140
|
+
venv = ".venv" # Created by make setup
|
|
141
|
+
venvPath = "."
|
|
142
142
|
typeCheckingMode = "strict"
|
|
143
143
|
extraPaths = [ ".", "src" ]
|
|
144
144
|
executionEnvironments = [
|
|
@@ -18,6 +18,7 @@ if TYPE_CHECKING:
|
|
|
18
18
|
DocwrapConfig,
|
|
19
19
|
DocwrapConfigWarning,
|
|
20
20
|
DocwrapSkipWarning,
|
|
21
|
+
FileChangedError,
|
|
21
22
|
FillStyle,
|
|
22
23
|
InvalidLineLengthError,
|
|
23
24
|
ReflowMode,
|
|
@@ -45,6 +46,7 @@ __all__ = [
|
|
|
45
46
|
'DocwrapConfig',
|
|
46
47
|
'DocwrapConfigWarning',
|
|
47
48
|
'DocwrapSkipWarning',
|
|
49
|
+
'FileChangedError',
|
|
48
50
|
'FillStyle',
|
|
49
51
|
'InvalidLineLengthError',
|
|
50
52
|
'ReflowMode',
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Canonical section registry for docstring roles."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from docwrap._types import CanonicalRole
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class _CanonicalSection:
|
|
10
|
+
"""Registry entry whose tuple position determines canonical section order."""
|
|
11
|
+
|
|
12
|
+
role: CanonicalRole
|
|
13
|
+
google_header: str | None = None
|
|
14
|
+
numpy_header: str | None = None
|
|
15
|
+
return_like: bool = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
CANONICAL_SECTIONS: tuple[_CanonicalSection, ...] = (
|
|
19
|
+
_CanonicalSection('param', google_header='Args', numpy_header='Parameters'),
|
|
20
|
+
_CanonicalSection('keyword_param', google_header='Keyword Args'),
|
|
21
|
+
_CanonicalSection('other_param', numpy_header='Other Parameters'),
|
|
22
|
+
_CanonicalSection(
|
|
23
|
+
'returns',
|
|
24
|
+
google_header='Returns',
|
|
25
|
+
numpy_header='Returns',
|
|
26
|
+
return_like=True,
|
|
27
|
+
),
|
|
28
|
+
_CanonicalSection(
|
|
29
|
+
'yields',
|
|
30
|
+
google_header='Yields',
|
|
31
|
+
numpy_header='Yields',
|
|
32
|
+
return_like=True,
|
|
33
|
+
),
|
|
34
|
+
_CanonicalSection('raises', google_header='Raises', numpy_header='Raises'),
|
|
35
|
+
_CanonicalSection('warns', google_header='Warns', numpy_header='Warns'),
|
|
36
|
+
_CanonicalSection(
|
|
37
|
+
'attributes',
|
|
38
|
+
google_header='Attributes',
|
|
39
|
+
numpy_header='Attributes',
|
|
40
|
+
),
|
|
41
|
+
_CanonicalSection('methods', google_header='Methods', numpy_header='Methods'),
|
|
42
|
+
_CanonicalSection('see_also', google_header='See Also', numpy_header='See Also'),
|
|
43
|
+
_CanonicalSection('notes', google_header='Notes', numpy_header='Notes'),
|
|
44
|
+
_CanonicalSection('references', google_header='References', numpy_header='References'),
|
|
45
|
+
_CanonicalSection('examples', google_header='Examples', numpy_header='Examples'),
|
|
46
|
+
_CanonicalSection('todo', google_header='Todo', numpy_header='Todo'),
|
|
47
|
+
_CanonicalSection('warnings', google_header='Warnings', numpy_header='Warnings'),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
CANONICAL_ORDER: tuple[CanonicalRole, ...] = tuple(section.role for section in CANONICAL_SECTIONS)
|
|
51
|
+
|
|
52
|
+
CANONICAL_ORDER_SET: frozenset[CanonicalRole] = frozenset(CANONICAL_ORDER)
|
|
53
|
+
|
|
54
|
+
RETURN_LIKE_ROLES: frozenset[CanonicalRole] = frozenset(
|
|
55
|
+
s.role for s in CANONICAL_SECTIONS if s.return_like
|
|
56
|
+
)
|