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.
Files changed (132) hide show
  1. {docwrap-0.1.0 → docwrap-0.3.0}/.gitignore +1 -0
  2. {docwrap-0.1.0 → docwrap-0.3.0}/PKG-INFO +28 -23
  3. {docwrap-0.1.0 → docwrap-0.3.0}/README.md +19 -14
  4. {docwrap-0.1.0 → docwrap-0.3.0}/_build.py +11 -0
  5. docwrap-0.3.0/plugins/agy/_docwrap/_adapter.py +5 -0
  6. docwrap-0.3.0/plugins/agy/tests/test_agy_adapter.py +50 -0
  7. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/claude/.claude-plugin/plugin.json +1 -1
  8. docwrap-0.3.0/plugins/claude/_docwrap/_adapter.py +5 -0
  9. docwrap-0.3.0/plugins/claude/tests/test_claude_adapter.py +129 -0
  10. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/codex/.codex-plugin/plugin.json +1 -1
  11. docwrap-0.3.0/plugins/codex/_docwrap/_adapter.py +5 -0
  12. docwrap-0.3.0/plugins/codex/tests/test_codex_adapter.py +50 -0
  13. {docwrap-0.1.0 → docwrap-0.3.0}/pyproject.toml +24 -21
  14. docwrap-0.3.0/src/docwrap/__init__.py +93 -0
  15. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/__main__.py +1 -1
  16. docwrap-0.3.0/src/docwrap/_arguments.py +20 -0
  17. docwrap-0.3.0/src/docwrap/_canonical.py +56 -0
  18. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_cli.py +614 -554
  19. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_config.py +199 -168
  20. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_convert.py +22 -31
  21. docwrap-0.3.0/src/docwrap/_core/__init__.py +25 -0
  22. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_codeblock.py +22 -0
  23. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_discover.py +99 -51
  24. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_escape.py +6 -17
  25. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_incremental.py +30 -22
  26. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_normalize.py +7 -1
  27. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_parse.py +1 -1
  28. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_process.py +115 -97
  29. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_core/_wrap.py +116 -107
  30. docwrap-0.3.0/src/docwrap/_display/__init__.py +7 -0
  31. docwrap-0.3.0/src/docwrap/_display/_color.py +48 -0
  32. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_json.py +9 -26
  33. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_print.py +5 -4
  34. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_recommend.py +15 -34
  35. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_skips.py +4 -4
  36. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_stats.py +15 -22
  37. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_summary.py +50 -35
  38. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_exceptions.py +7 -2
  39. docwrap-0.3.0/src/docwrap/_files.py +207 -0
  40. docwrap-0.3.0/src/docwrap/_git.py +167 -0
  41. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_git_utils.py +1 -21
  42. docwrap-0.3.0/src/docwrap/_hook.py +1210 -0
  43. docwrap-0.3.0/src/docwrap/_hook_front.py +91 -0
  44. docwrap-0.3.0/src/docwrap/_incremental_git.py +122 -0
  45. docwrap-0.1.0/src/docwrap/__init__.py → docwrap-0.3.0/src/docwrap/_library.py +3 -7
  46. docwrap-0.3.0/src/docwrap/_main.py +45 -0
  47. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_stats.py +124 -110
  48. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_styles/__init__.py +42 -18
  49. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_styles/_google.py +33 -16
  50. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_styles/_numpy.py +17 -5
  51. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_styles/_sphinx.py +27 -29
  52. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_types.py +44 -54
  53. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_validate.py +4 -5
  54. docwrap-0.3.0/src/docwrap/_vendor/__init__.py +1 -0
  55. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/__init__.py +1 -0
  56. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_adds.py +29 -0
  57. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_blobs.py +200 -0
  58. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_config.py +30 -0
  59. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_discovery.py +296 -0
  60. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_index.py +64 -0
  61. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_listings.py +235 -0
  62. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_pathspecs.py +153 -0
  63. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_repo.py +127 -0
  64. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/_run.py +97 -0
  65. docwrap-0.3.0/src/docwrap/_vendor/gitfiles/manifest.json +27 -0
  66. docwrap-0.3.0/src/docwrap/_vendor/hookfront/__init__.py +1 -0
  67. docwrap-0.1.0/plugins/agy/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_agy.py +59 -17
  68. docwrap-0.1.0/plugins/claude/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_claude.py +60 -12
  69. docwrap-0.1.0/plugins/codex/_docwrap/_adapter.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_codex.py +60 -11
  70. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_color.py +72 -0
  71. docwrap-0.1.0/src/docwrap/hooks/__init__.py → docwrap-0.3.0/src/docwrap/_vendor/hookfront/_contract.py +38 -34
  72. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_diff.py +238 -0
  73. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_front.py +510 -0
  74. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_hints.py +17 -0
  75. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_loader.py +101 -0
  76. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_notes.py +97 -0
  77. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_parser.py +184 -0
  78. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_streams.py +218 -0
  79. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_terminal.py +46 -0
  80. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_tool.py +36 -0
  81. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_version.py +29 -0
  82. docwrap-0.3.0/src/docwrap/_vendor/hookfront/_writes.py +76 -0
  83. docwrap-0.3.0/src/docwrap/_vendor/hookfront/manifest.json +37 -0
  84. docwrap-0.3.0/src/docwrap/_vendor/hookshell/__init__.py +7 -0
  85. docwrap-0.3.0/src/docwrap/_vendor/hookshell/_classify.py +277 -0
  86. docwrap-0.3.0/src/docwrap/_vendor/hookshell/_detector.py +500 -0
  87. docwrap-0.3.0/src/docwrap/_vendor/hookshell/_forms.py +695 -0
  88. {docwrap-0.1.0/src/docwrap → docwrap-0.3.0/src/docwrap/_vendor/hookshell}/_grammar.py +154 -114
  89. {docwrap-0.1.0/src/docwrap → docwrap-0.3.0/src/docwrap/_vendor/hookshell}/_shell.py +5 -5
  90. docwrap-0.3.0/src/docwrap/_vendor/hookshell/_shellparse.py +1021 -0
  91. docwrap-0.3.0/src/docwrap/_vendor/hookshell/_source.py +130 -0
  92. docwrap-0.3.0/src/docwrap/_vendor/hookshell/manifest.json +22 -0
  93. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/__init__.py +58 -0
  94. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_errors.py +56 -0
  95. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_keys.py +128 -0
  96. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_labels.py +79 -0
  97. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_paths.py +69 -0
  98. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_recovery.py +123 -0
  99. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_signals.py +82 -0
  100. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/_write.py +212 -0
  101. docwrap-0.3.0/src/docwrap/_vendor/srcfiles/manifest.json +22 -0
  102. docwrap-0.3.0/src/docwrap/hooks/__init__.py +60 -0
  103. docwrap-0.1.0/plugins/agy/tests/test_agy_adapter.py +0 -135
  104. docwrap-0.1.0/plugins/claude/tests/test_claude_adapter.py +0 -209
  105. docwrap-0.1.0/plugins/codex/tests/test_codex_adapter.py +0 -125
  106. docwrap-0.1.0/src/docwrap/_canonical.py +0 -66
  107. docwrap-0.1.0/src/docwrap/_classify.py +0 -56
  108. docwrap-0.1.0/src/docwrap/_color.py +0 -135
  109. docwrap-0.1.0/src/docwrap/_core/__init__.py +0 -27
  110. docwrap-0.1.0/src/docwrap/_detector.py +0 -166
  111. docwrap-0.1.0/src/docwrap/_display/__init__.py +0 -17
  112. docwrap-0.1.0/src/docwrap/_display/_diff.py +0 -62
  113. docwrap-0.1.0/src/docwrap/_downstream.py +0 -181
  114. docwrap-0.1.0/src/docwrap/_files.py +0 -304
  115. docwrap-0.1.0/src/docwrap/_git.py +0 -764
  116. docwrap-0.1.0/src/docwrap/_hook.py +0 -1001
  117. docwrap-0.1.0/src/docwrap/_hosts/__init__.py +0 -50
  118. docwrap-0.1.0/src/docwrap/_shellparse.py +0 -643
  119. {docwrap-0.1.0 → docwrap-0.3.0}/LICENSE +0 -0
  120. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/agy/_docwrap/__init__.py +0 -0
  121. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/agy/adapter.toml +0 -0
  122. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/agy/hooks.json +0 -0
  123. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/agy/plugin.json +0 -0
  124. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/claude/_docwrap/__init__.py +0 -0
  125. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/claude/adapter.toml +0 -0
  126. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/claude/hooks/hooks.json +0 -0
  127. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/codex/_docwrap/__init__.py +0 -0
  128. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/codex/adapter.toml +0 -0
  129. {docwrap-0.1.0 → docwrap-0.3.0}/plugins/codex/hooks/hooks.json +0 -0
  130. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_detect.py +0 -0
  131. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/_display/_text.py +0 -0
  132. {docwrap-0.1.0 → docwrap-0.3.0}/src/docwrap/py.typed +0 -0
@@ -24,6 +24,7 @@ ENV/
24
24
  *.spec
25
25
 
26
26
  # Unit test / coverage reports
27
+ .hypothesis/
27
28
  htmlcov/
28
29
  .tox/
29
30
  .nox/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docwrap
3
- Version: 0.1.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
- [![CI](https://github.com/dactylo/docwrap/actions/workflows/ci.yml/badge.svg)](https://github.com/dactylo/docwrap/actions/workflows/ci.yml)
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
- ![docwrap summary of six Python files, with 10 of 32 docstrings requiring wrapping](images/docwrap-summary.png)
28
+ ![docwrap summary of six Python files, with 10 of 32 docstrings requiring wrapping](https://raw.githubusercontent.com/dactylo/docwrap/v0.3.0/images/docwrap-summary.png)
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
- ![docwrap preview showing prose and an argument description before and after reflow](images/docwrap-preview.png)
33
+ ![docwrap preview showing prose and an argument description before and after reflow](https://raw.githubusercontent.com/dactylo/docwrap/v0.3.0/images/docwrap-preview.png)
36
34
 
37
35
  ## Installation
38
36
 
39
- Docwrap requires CPython 3.13 or later. From a checkout, install the command with
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
- - [CLI reference](docs/cli.md): commands, output, configuration, and exit codes.
110
- - [Agent hook guide](docs/hook.md): setup, commit handling, and host responses.
111
- - [Incremental mode](docs/incremental.md): wrap only changed files or definitions.
112
- - [Library reference](docs/library.md): supported Python API and result contracts.
113
- - [Concepts and safety](docs/design.md): styles, wrapping choices, and preservation rules.
114
- - [Platform compatibility](docs/platform.md): supported environments and file behavior.
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
- From the checkout, run `make check` for repository checks. Run `make format` to apply the
121
- repository's formatters. The Makefile also provides `make install`, `make reinstall`, and
122
- `make test`; install targets use uv.
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](LICENSE).
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
- [![CI](https://github.com/dactylo/docwrap/actions/workflows/ci.yml/badge.svg)](https://github.com/dactylo/docwrap/actions/workflows/ci.yml)
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
- Docwrap requires CPython 3.13 or later. From a checkout, install the command with
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
- - [CLI reference](docs/cli.md): commands, output, configuration, and exit codes.
91
- - [Agent hook guide](docs/hook.md): setup, commit handling, and host responses.
92
- - [Incremental mode](docs/incremental.md): wrap only changed files or definitions.
93
- - [Library reference](docs/library.md): supported Python API and result contracts.
94
- - [Concepts and safety](docs/design.md): styles, wrapping choices, and preservation rules.
95
- - [Platform compatibility](docs/platform.md): supported environments and file behavior.
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
- From the checkout, run `make check` for repository checks. Run `make format` to apply the
102
- repository's formatters. The Makefile also provides `make install`, `make reinstall`, and
103
- `make test`; install targets use uv.
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
- Docwrap is available under the [MIT License](LICENSE).
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,5 @@
1
+ """Translate Antigravity hook events for docwrap."""
2
+
3
+ from docwrap._vendor.hookfront._agy import AntigravityAdapter
4
+
5
+ __all__ = ['AntigravityAdapter']
@@ -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()
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docwrap",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Install the docwrap commit-time docstring wrapping hook for Claude Code.",
5
5
  "author": {
6
6
  "name": "docwrap contributors"
@@ -0,0 +1,5 @@
1
+ """Translate Claude Code hook events for docwrap."""
2
+
3
+ from docwrap._vendor.hookfront._claude import ClaudeAdapter
4
+
5
+ __all__ = ['ClaudeAdapter']
@@ -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()
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docwrap",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Install the docwrap commit-time docstring wrapping hook for Codex.",
5
5
  "author": {
6
6
  "name": "docwrap contributors"
@@ -0,0 +1,5 @@
1
+ """Translate Codex hook events for docwrap."""
2
+
3
+ from docwrap._vendor.hookfront._codex import CodexAdapter
4
+
5
+ __all__ = ['CodexAdapter']
@@ -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.1.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._cli:main"
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
- "_enable_wrap_summary",
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)