wikiops 1.0.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.
- wikiops-1.0.0/LICENSE +21 -0
- wikiops-1.0.0/PKG-INFO +196 -0
- wikiops-1.0.0/README.md +169 -0
- wikiops-1.0.0/pyproject.toml +60 -0
- wikiops-1.0.0/src/wikiops/__init__.py +8 -0
- wikiops-1.0.0/src/wikiops/cli/__init__.py +0 -0
- wikiops-1.0.0/src/wikiops/cli/app.py +81 -0
- wikiops-1.0.0/src/wikiops/core/__init__.py +0 -0
- wikiops-1.0.0/src/wikiops/core/apply_engine.py +10 -0
- wikiops-1.0.0/src/wikiops/core/config_loader.py +98 -0
- wikiops-1.0.0/src/wikiops/core/diff_engine.py +42 -0
- wikiops-1.0.0/src/wikiops/core/document_loader.py +16 -0
- wikiops-1.0.0/src/wikiops/core/exceptions.py +14 -0
- wikiops-1.0.0/src/wikiops/core/orchestrator.py +162 -0
- wikiops-1.0.0/src/wikiops/core/plugin_manager.py +144 -0
- wikiops-1.0.0/src/wikiops/core/plugin_resources.py +69 -0
- wikiops-1.0.0/src/wikiops/core/provider_manager.py +117 -0
- wikiops-1.0.0/src/wikiops/core/reference_resolver.py +15 -0
- wikiops-1.0.0/src/wikiops/providers/__init__.py +0 -0
- wikiops-1.0.0/src/wikiops/providers/azure_devops/__init__.py +3 -0
- wikiops-1.0.0/src/wikiops/providers/azure_devops/provider.py +299 -0
- wikiops-1.0.0/src/wikiops/utils/__init__.py +0 -0
wikiops-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sebastian Granda Gallego
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
wikiops-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wikiops
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Extensible CLI to automate documentation in wiki platforms (Azure DevOps, Confluence, etc.) using Markdown. Built on a plugin and provider architecture, it enables generating, updating, and structuring documentation as code.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Sebastian Granda Gallego
|
|
8
|
+
Author-email: sgg10.develop@gmail.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
17
|
+
Requires-Dist: pydantic (>=2.12.5,<3.0.0)
|
|
18
|
+
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
19
|
+
Requires-Dist: typer (>=0.24.1,<0.25.0)
|
|
20
|
+
Requires-Dist: wikiops-sdk (>=1.0.0,<2.0.0)
|
|
21
|
+
Project-URL: Documentation, https://github.com/sgg10/wikiops/tree/main/docs
|
|
22
|
+
Project-URL: Homepage, https://github.com/sgg10/wikiops
|
|
23
|
+
Project-URL: Issues, https://github.com/sgg10/wikiops/issues
|
|
24
|
+
Project-URL: Repository, https://github.com/sgg10/wikiops
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# wikiops
|
|
28
|
+
|
|
29
|
+
`wikiops` is the host application and CLI runtime for the WikiOps ecosystem.
|
|
30
|
+
|
|
31
|
+
It orchestrates documentation automation workflows around `wikiops-sdk` by loading configuration, discovering plugins and providers, building execution context, rendering previews, and applying planned changes.
|
|
32
|
+
|
|
33
|
+
## What This Repository Contains
|
|
34
|
+
|
|
35
|
+
- A command-line interface for WikiOps execution workflows.
|
|
36
|
+
- The host orchestrator that coordinates planning and apply flows.
|
|
37
|
+
- Runtime loading for plugins and providers through Python entry points.
|
|
38
|
+
- YAML configuration loading for providers, profiles, refs, and plugin config.
|
|
39
|
+
- Document loading, diff rendering, and apply delegation.
|
|
40
|
+
- A built-in Azure DevOps Wiki provider.
|
|
41
|
+
- A strict `pytest` suite with coverage enforcement.
|
|
42
|
+
|
|
43
|
+
## What This Repository Does Not Contain
|
|
44
|
+
|
|
45
|
+
- The public extension contracts and shared domain models.
|
|
46
|
+
- A stable SDK-level API surface for plugins and providers.
|
|
47
|
+
- Built-in documentation plugins.
|
|
48
|
+
- Project-specific documentation business logic.
|
|
49
|
+
|
|
50
|
+
Those concerns belong to `wikiops-sdk` and external plugin repositories.
|
|
51
|
+
|
|
52
|
+
## Relationship To `wikiops-sdk`
|
|
53
|
+
|
|
54
|
+
The WikiOps ecosystem is intentionally split across repositories:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
plugin -> sdk <- host
|
|
58
|
+
provider -> sdk <- host
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `wikiops-sdk`
|
|
62
|
+
- defines the shared contracts, domain models, and compatibility helpers
|
|
63
|
+
- `wikiops`
|
|
64
|
+
- orchestrates execution around those SDK contracts
|
|
65
|
+
- plugins
|
|
66
|
+
- implement documentation planning logic
|
|
67
|
+
- providers
|
|
68
|
+
- implement persistence and read-side infrastructure
|
|
69
|
+
|
|
70
|
+
Canonical SDK documentation lives in the separate SDK repository:
|
|
71
|
+
|
|
72
|
+
- [`wikiops-sdk README`](https://github.com/sgg10/wikiops-sdk/blob/main/README.md)
|
|
73
|
+
- [`wikiops-sdk architecture`](https://github.com/sgg10/wikiops-sdk/blob/main/docs/architecture.md)
|
|
74
|
+
- [`wikiops-sdk contracts API`](https://github.com/sgg10/wikiops-sdk/blob/main/docs/api/contracts.md)
|
|
75
|
+
- [`wikiops-sdk domain API`](https://github.com/sgg10/wikiops-sdk/blob/main/docs/api/domain.md)
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
Install the host and its runtime dependencies:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
poetry install --with test
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Inspect the currently available extensions:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
poetry run wikiops plugins
|
|
89
|
+
poetry run wikiops providers
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The host currently ships with a built-in provider implementation:
|
|
93
|
+
|
|
94
|
+
- `azure_devops_wiki`
|
|
95
|
+
|
|
96
|
+
Plugins are expected to be installed separately through Python packages that expose the `wikiops.plugins` entry point group.
|
|
97
|
+
|
|
98
|
+
### Example Configuration
|
|
99
|
+
|
|
100
|
+
The host reads YAML configuration with provider definitions, profiles, refs, and plugin-specific settings.
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
providers:
|
|
104
|
+
azdo:
|
|
105
|
+
type: azure_devops_wiki
|
|
106
|
+
organization: acme
|
|
107
|
+
project: engineering
|
|
108
|
+
wiki: platform
|
|
109
|
+
|
|
110
|
+
profiles:
|
|
111
|
+
default:
|
|
112
|
+
provider: azdo
|
|
113
|
+
refs:
|
|
114
|
+
docs_root:
|
|
115
|
+
provider: azdo
|
|
116
|
+
kind: path
|
|
117
|
+
locator:
|
|
118
|
+
path: /Engineering/Teams
|
|
119
|
+
plugins:
|
|
120
|
+
acme.team-docs:
|
|
121
|
+
parent_alias: docs_root
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Example Input
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
team_name: Platform
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Plan A Run
|
|
131
|
+
|
|
132
|
+
Replace `acme.team-docs` with an installed plugin ID shown by `wikiops plugins`.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
poetry run wikiops run \
|
|
136
|
+
--config wikiops.yaml \
|
|
137
|
+
--profile default \
|
|
138
|
+
--plugin acme.team-docs \
|
|
139
|
+
--input input.yaml
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
This prints:
|
|
143
|
+
|
|
144
|
+
- the planned `ChangeSet` as JSON
|
|
145
|
+
- a preview diff
|
|
146
|
+
|
|
147
|
+
### Apply A Run
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
poetry run wikiops run \
|
|
151
|
+
--config wikiops.yaml \
|
|
152
|
+
--profile default \
|
|
153
|
+
--plugin acme.team-docs \
|
|
154
|
+
--input input.yaml \
|
|
155
|
+
--apply
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
On apply, the CLI also prints the provider `ApplyResult` and exits with code `1` if any operation failed.
|
|
159
|
+
|
|
160
|
+
## Plugin Resources
|
|
161
|
+
|
|
162
|
+
`wikiops` injects a `PluginResourceProvider` when loading plugin entry points.
|
|
163
|
+
|
|
164
|
+
- Resource paths are relative to the plugin package root.
|
|
165
|
+
- The host does not assume a fixed `resources/` directory.
|
|
166
|
+
- If a plugin stores assets under `resources/`, it should request them as `resources/...`.
|
|
167
|
+
- If a plugin already provides its own `resources` object, the host leaves it untouched.
|
|
168
|
+
|
|
169
|
+
## Documentation Map
|
|
170
|
+
|
|
171
|
+
Start here depending on your role:
|
|
172
|
+
|
|
173
|
+
- New to the host: [`docs/getting-started.md`](docs/getting-started.md)
|
|
174
|
+
- Need the host architecture: [`docs/architecture.md`](docs/architecture.md)
|
|
175
|
+
- Need the runtime execution lifecycle: [`docs/execution-flow.md`](docs/execution-flow.md)
|
|
176
|
+
- Need the YAML configuration model: [`docs/configuration.md`](docs/configuration.md)
|
|
177
|
+
- Need the host/SDK boundary: [`docs/sdk-relationship.md`](docs/sdk-relationship.md)
|
|
178
|
+
- Using the CLI: [`docs/guides/using-the-cli.md`](docs/guides/using-the-cli.md)
|
|
179
|
+
- Building a plugin for this host: [`docs/guides/build-a-plugin.md`](docs/guides/build-a-plugin.md)
|
|
180
|
+
- Building a provider for this host: [`docs/guides/build-a-provider.md`](docs/guides/build-a-provider.md)
|
|
181
|
+
- Need module-level runtime reference: [`docs/reference/core-modules.md`](docs/reference/core-modules.md)
|
|
182
|
+
- Need the built-in Azure DevOps provider reference: [`docs/reference/azure-devops-wiki.md`](docs/reference/azure-devops-wiki.md)
|
|
183
|
+
- Need host internal boundaries: [`docs/reference/internal-boundaries.md`](docs/reference/internal-boundaries.md)
|
|
184
|
+
|
|
185
|
+
The full host documentation index lives at [`docs/index.md`](docs/index.md).
|
|
186
|
+
|
|
187
|
+
## Tests
|
|
188
|
+
|
|
189
|
+
This repository ships with a strict `pytest` suite and coverage threshold.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
poetry run pytest
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The tests are also useful as executable examples of the current host behavior.
|
|
196
|
+
|
wikiops-1.0.0/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# wikiops
|
|
2
|
+
|
|
3
|
+
`wikiops` is the host application and CLI runtime for the WikiOps ecosystem.
|
|
4
|
+
|
|
5
|
+
It orchestrates documentation automation workflows around `wikiops-sdk` by loading configuration, discovering plugins and providers, building execution context, rendering previews, and applying planned changes.
|
|
6
|
+
|
|
7
|
+
## What This Repository Contains
|
|
8
|
+
|
|
9
|
+
- A command-line interface for WikiOps execution workflows.
|
|
10
|
+
- The host orchestrator that coordinates planning and apply flows.
|
|
11
|
+
- Runtime loading for plugins and providers through Python entry points.
|
|
12
|
+
- YAML configuration loading for providers, profiles, refs, and plugin config.
|
|
13
|
+
- Document loading, diff rendering, and apply delegation.
|
|
14
|
+
- A built-in Azure DevOps Wiki provider.
|
|
15
|
+
- A strict `pytest` suite with coverage enforcement.
|
|
16
|
+
|
|
17
|
+
## What This Repository Does Not Contain
|
|
18
|
+
|
|
19
|
+
- The public extension contracts and shared domain models.
|
|
20
|
+
- A stable SDK-level API surface for plugins and providers.
|
|
21
|
+
- Built-in documentation plugins.
|
|
22
|
+
- Project-specific documentation business logic.
|
|
23
|
+
|
|
24
|
+
Those concerns belong to `wikiops-sdk` and external plugin repositories.
|
|
25
|
+
|
|
26
|
+
## Relationship To `wikiops-sdk`
|
|
27
|
+
|
|
28
|
+
The WikiOps ecosystem is intentionally split across repositories:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
plugin -> sdk <- host
|
|
32
|
+
provider -> sdk <- host
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `wikiops-sdk`
|
|
36
|
+
- defines the shared contracts, domain models, and compatibility helpers
|
|
37
|
+
- `wikiops`
|
|
38
|
+
- orchestrates execution around those SDK contracts
|
|
39
|
+
- plugins
|
|
40
|
+
- implement documentation planning logic
|
|
41
|
+
- providers
|
|
42
|
+
- implement persistence and read-side infrastructure
|
|
43
|
+
|
|
44
|
+
Canonical SDK documentation lives in the separate SDK repository:
|
|
45
|
+
|
|
46
|
+
- [`wikiops-sdk README`](https://github.com/sgg10/wikiops-sdk/blob/main/README.md)
|
|
47
|
+
- [`wikiops-sdk architecture`](https://github.com/sgg10/wikiops-sdk/blob/main/docs/architecture.md)
|
|
48
|
+
- [`wikiops-sdk contracts API`](https://github.com/sgg10/wikiops-sdk/blob/main/docs/api/contracts.md)
|
|
49
|
+
- [`wikiops-sdk domain API`](https://github.com/sgg10/wikiops-sdk/blob/main/docs/api/domain.md)
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
Install the host and its runtime dependencies:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
poetry install --with test
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Inspect the currently available extensions:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
poetry run wikiops plugins
|
|
63
|
+
poetry run wikiops providers
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The host currently ships with a built-in provider implementation:
|
|
67
|
+
|
|
68
|
+
- `azure_devops_wiki`
|
|
69
|
+
|
|
70
|
+
Plugins are expected to be installed separately through Python packages that expose the `wikiops.plugins` entry point group.
|
|
71
|
+
|
|
72
|
+
### Example Configuration
|
|
73
|
+
|
|
74
|
+
The host reads YAML configuration with provider definitions, profiles, refs, and plugin-specific settings.
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
providers:
|
|
78
|
+
azdo:
|
|
79
|
+
type: azure_devops_wiki
|
|
80
|
+
organization: acme
|
|
81
|
+
project: engineering
|
|
82
|
+
wiki: platform
|
|
83
|
+
|
|
84
|
+
profiles:
|
|
85
|
+
default:
|
|
86
|
+
provider: azdo
|
|
87
|
+
refs:
|
|
88
|
+
docs_root:
|
|
89
|
+
provider: azdo
|
|
90
|
+
kind: path
|
|
91
|
+
locator:
|
|
92
|
+
path: /Engineering/Teams
|
|
93
|
+
plugins:
|
|
94
|
+
acme.team-docs:
|
|
95
|
+
parent_alias: docs_root
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Example Input
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
team_name: Platform
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Plan A Run
|
|
105
|
+
|
|
106
|
+
Replace `acme.team-docs` with an installed plugin ID shown by `wikiops plugins`.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
poetry run wikiops run \
|
|
110
|
+
--config wikiops.yaml \
|
|
111
|
+
--profile default \
|
|
112
|
+
--plugin acme.team-docs \
|
|
113
|
+
--input input.yaml
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This prints:
|
|
117
|
+
|
|
118
|
+
- the planned `ChangeSet` as JSON
|
|
119
|
+
- a preview diff
|
|
120
|
+
|
|
121
|
+
### Apply A Run
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
poetry run wikiops run \
|
|
125
|
+
--config wikiops.yaml \
|
|
126
|
+
--profile default \
|
|
127
|
+
--plugin acme.team-docs \
|
|
128
|
+
--input input.yaml \
|
|
129
|
+
--apply
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
On apply, the CLI also prints the provider `ApplyResult` and exits with code `1` if any operation failed.
|
|
133
|
+
|
|
134
|
+
## Plugin Resources
|
|
135
|
+
|
|
136
|
+
`wikiops` injects a `PluginResourceProvider` when loading plugin entry points.
|
|
137
|
+
|
|
138
|
+
- Resource paths are relative to the plugin package root.
|
|
139
|
+
- The host does not assume a fixed `resources/` directory.
|
|
140
|
+
- If a plugin stores assets under `resources/`, it should request them as `resources/...`.
|
|
141
|
+
- If a plugin already provides its own `resources` object, the host leaves it untouched.
|
|
142
|
+
|
|
143
|
+
## Documentation Map
|
|
144
|
+
|
|
145
|
+
Start here depending on your role:
|
|
146
|
+
|
|
147
|
+
- New to the host: [`docs/getting-started.md`](docs/getting-started.md)
|
|
148
|
+
- Need the host architecture: [`docs/architecture.md`](docs/architecture.md)
|
|
149
|
+
- Need the runtime execution lifecycle: [`docs/execution-flow.md`](docs/execution-flow.md)
|
|
150
|
+
- Need the YAML configuration model: [`docs/configuration.md`](docs/configuration.md)
|
|
151
|
+
- Need the host/SDK boundary: [`docs/sdk-relationship.md`](docs/sdk-relationship.md)
|
|
152
|
+
- Using the CLI: [`docs/guides/using-the-cli.md`](docs/guides/using-the-cli.md)
|
|
153
|
+
- Building a plugin for this host: [`docs/guides/build-a-plugin.md`](docs/guides/build-a-plugin.md)
|
|
154
|
+
- Building a provider for this host: [`docs/guides/build-a-provider.md`](docs/guides/build-a-provider.md)
|
|
155
|
+
- Need module-level runtime reference: [`docs/reference/core-modules.md`](docs/reference/core-modules.md)
|
|
156
|
+
- Need the built-in Azure DevOps provider reference: [`docs/reference/azure-devops-wiki.md`](docs/reference/azure-devops-wiki.md)
|
|
157
|
+
- Need host internal boundaries: [`docs/reference/internal-boundaries.md`](docs/reference/internal-boundaries.md)
|
|
158
|
+
|
|
159
|
+
The full host documentation index lives at [`docs/index.md`](docs/index.md).
|
|
160
|
+
|
|
161
|
+
## Tests
|
|
162
|
+
|
|
163
|
+
This repository ships with a strict `pytest` suite and coverage threshold.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
poetry run pytest
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The tests are also useful as executable examples of the current host behavior.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wikiops"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Extensible CLI to automate documentation in wiki platforms (Azure DevOps, Confluence, etc.) using Markdown. Built on a plugin and provider architecture, it enables generating, updating, and structuring documentation as code."
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "Sebastian Granda Gallego",email = "sgg10.develop@gmail.com"}
|
|
11
|
+
]
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
requires-python = ">=3.10,<4.0"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"pydantic (>=2.12.5,<3.0.0)",
|
|
18
|
+
"typer (>=0.24.1,<0.25.0)",
|
|
19
|
+
"pyyaml (>=6.0.3,<7.0.0)",
|
|
20
|
+
"httpx (>=0.28.1,<0.29.0)",
|
|
21
|
+
"wikiops-sdk (>=1.0.0,<2.0.0)"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/sgg10/wikiops"
|
|
26
|
+
Repository = "https://github.com/sgg10/wikiops"
|
|
27
|
+
Documentation = "https://github.com/sgg10/wikiops/tree/main/docs"
|
|
28
|
+
Issues = "https://github.com/sgg10/wikiops/issues"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
wikiops = "wikiops.cli.app:app"
|
|
32
|
+
|
|
33
|
+
[project.entry-points."wikiops.providers"]
|
|
34
|
+
azure_devops_wiki = "wikiops.providers.azure_devops:AzureDevOpsWikiProviderFactory"
|
|
35
|
+
|
|
36
|
+
[tool.poetry]
|
|
37
|
+
packages = [
|
|
38
|
+
{include = "wikiops", from = "src"}
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
test = [
|
|
43
|
+
"pytest (>=9.0.3,<10.0.0)",
|
|
44
|
+
"pytest-cov (>=6.0.0,<7.0.0)"
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
addopts = "--strict-config --strict-markers -ra --cov=wikiops --cov-branch --cov-report=term-missing --cov-report=xml --cov-fail-under=90"
|
|
49
|
+
testpaths = ["tests"]
|
|
50
|
+
pythonpath = ["src"]
|
|
51
|
+
xfail_strict = true
|
|
52
|
+
required_plugins = ["pytest-cov"]
|
|
53
|
+
|
|
54
|
+
[tool.coverage.run]
|
|
55
|
+
branch = true
|
|
56
|
+
source = ["wikiops"]
|
|
57
|
+
|
|
58
|
+
[tool.coverage.report]
|
|
59
|
+
show_missing = true
|
|
60
|
+
skip_covered = false
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from wikiops.core.orchestrator import DefaultDocumentationOrchestrator
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(
|
|
10
|
+
help="WikiOps CLI - A tool for Markdown-based documentation automation."
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _load_yaml(path: str | Path) -> dict[str, Any]:
|
|
15
|
+
return yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command("plugins")
|
|
19
|
+
def list_plugins() -> None:
|
|
20
|
+
"""List discovered plugins."""
|
|
21
|
+
|
|
22
|
+
orchestrator = DefaultDocumentationOrchestrator()
|
|
23
|
+
for plugin in orchestrator.plugin_manager.list():
|
|
24
|
+
typer.echo(
|
|
25
|
+
f"- {plugin.manifest.plugin_id} :: {plugin.manifest.display_name} ({plugin.manifest.version})"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command("providers")
|
|
30
|
+
def list_providers() -> None:
|
|
31
|
+
"""List discovered provider types."""
|
|
32
|
+
|
|
33
|
+
orchestrator = DefaultDocumentationOrchestrator()
|
|
34
|
+
for provider_type in orchestrator.provider_manager.list_types():
|
|
35
|
+
typer.echo(f"- {provider_type}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.command("run")
|
|
39
|
+
def run(
|
|
40
|
+
config: str = typer.Option(
|
|
41
|
+
..., "--config", "-c", help="Path to the YAML config file."
|
|
42
|
+
),
|
|
43
|
+
profile: str = typer.Option(
|
|
44
|
+
..., "--profile", "-p", help="Profile name to execute."
|
|
45
|
+
),
|
|
46
|
+
plugin: str = typer.Option(
|
|
47
|
+
..., "--plugin", help="Plugin identifier or entry point name."
|
|
48
|
+
),
|
|
49
|
+
input: str = typer.Option(
|
|
50
|
+
..., "--input", "-i", help="Path to the YAML input file."
|
|
51
|
+
),
|
|
52
|
+
apply: bool = typer.Option(False, "--apply", help="Persist the planned changes."),
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Plan or apply a documentation use case."""
|
|
55
|
+
|
|
56
|
+
orchestrator = DefaultDocumentationOrchestrator()
|
|
57
|
+
raw_input = _load_yaml(input)
|
|
58
|
+
|
|
59
|
+
if apply:
|
|
60
|
+
change_set, apply_result, diff = orchestrator.apply_from_file(
|
|
61
|
+
config, profile, plugin, raw_input
|
|
62
|
+
)
|
|
63
|
+
typer.echo("=== CHANGESET ===")
|
|
64
|
+
typer.echo(change_set.model_dump_json(indent=2))
|
|
65
|
+
typer.echo("\n=== DIFF ===")
|
|
66
|
+
typer.echo(diff or "(No diff available)")
|
|
67
|
+
typer.echo("\n=== APPLY RESULT ===")
|
|
68
|
+
typer.echo(apply_result.model_dump_json(indent=2))
|
|
69
|
+
raise typer.Exit(code=1 if apply_result.has_failures() else 0)
|
|
70
|
+
|
|
71
|
+
_, _, change_set, diff = orchestrator.plan_from_file(
|
|
72
|
+
config, profile, plugin, raw_input, dry_run=True
|
|
73
|
+
)
|
|
74
|
+
typer.echo("=== CHANGESET ===")
|
|
75
|
+
typer.echo(change_set.model_dump_json(indent=2))
|
|
76
|
+
typer.echo("\n=== DIFF ===")
|
|
77
|
+
typer.echo(diff or "(No diff generated)")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
app()
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from wikiops_sdk.contracts import DocumentProvider
|
|
2
|
+
from wikiops_sdk.domain import ApplyResult, ChangeSet
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ApplyEngine:
|
|
6
|
+
"""Persists a planned change set through a provider."""
|
|
7
|
+
|
|
8
|
+
def apply(self, provider: DocumentProvider, change_set: ChangeSet) -> ApplyResult:
|
|
9
|
+
"""Applies the given change set using the provided document provider."""
|
|
10
|
+
return provider.apply_changes(change_set)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, Union
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from wikiops.core.exceptions import ConfigurationError
|
|
8
|
+
from wikiops_sdk.domain import DocumentRef
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProviderDefinition(BaseModel):
|
|
12
|
+
"""Provider configuration entry from the YAML file."""
|
|
13
|
+
|
|
14
|
+
type: str
|
|
15
|
+
settings: Dict[str, Any] = Field(default_factory=dict)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProfileDefinition(BaseModel):
|
|
19
|
+
"""Execution profile entry from the YAML file."""
|
|
20
|
+
|
|
21
|
+
provider: str
|
|
22
|
+
refs: Dict[str, DocumentRef] = Field(default_factory=dict)
|
|
23
|
+
plugins: Dict[str, Dict[str, Any]] = Field(default_factory=dict)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AppConfig(BaseModel):
|
|
27
|
+
"""Top-level application configuration."""
|
|
28
|
+
|
|
29
|
+
providers: Dict[str, ProviderDefinition] = Field(default_factory=dict)
|
|
30
|
+
profiles: Dict[str, ProfileDefinition] = Field(default_factory=dict)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ConfigLoader:
|
|
34
|
+
"""Loads and validates YAML application configuration."""
|
|
35
|
+
|
|
36
|
+
def _prepare_providers(
|
|
37
|
+
self, raw_providers: Dict[str, Any]
|
|
38
|
+
) -> Dict[str, ProviderDefinition]:
|
|
39
|
+
"""Validates and transforms raw provider definitions into structured ProviderDefinition instances."""
|
|
40
|
+
providers = {}
|
|
41
|
+
|
|
42
|
+
for name, provider in raw_providers.items():
|
|
43
|
+
provider_type = provider.get("type")
|
|
44
|
+
|
|
45
|
+
if not provider_type:
|
|
46
|
+
raise ConfigurationError(f"Provider '{name}' is missing 'type' field.")
|
|
47
|
+
|
|
48
|
+
settings = {k: v for k, v in provider.items() if k != "type"}
|
|
49
|
+
settings.setdefault("provider_name", name)
|
|
50
|
+
|
|
51
|
+
providers[name] = ProviderDefinition(type=provider_type, settings=settings)
|
|
52
|
+
|
|
53
|
+
return providers
|
|
54
|
+
|
|
55
|
+
def _prepare_profiles(
|
|
56
|
+
self, raw_profiles: Dict[str, Any]
|
|
57
|
+
) -> Dict[str, ProfileDefinition]:
|
|
58
|
+
"""Validates and transforms raw profile definitions into structured ProfileDefinition instances."""
|
|
59
|
+
profiles = {}
|
|
60
|
+
|
|
61
|
+
for name, profile in raw_profiles.items():
|
|
62
|
+
provider_name = profile.get("provider")
|
|
63
|
+
|
|
64
|
+
if not provider_name:
|
|
65
|
+
raise ConfigurationError(
|
|
66
|
+
f"Profile '{name}' is missing 'provider' field."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
refs_raw = profile.get("refs", {})
|
|
70
|
+
plugins_raw = profile.get("plugins", {})
|
|
71
|
+
|
|
72
|
+
refs = {
|
|
73
|
+
alias: DocumentRef.model_validate(ref_raw)
|
|
74
|
+
for alias, ref_raw in refs_raw.items()
|
|
75
|
+
}
|
|
76
|
+
profiles[name] = ProfileDefinition(
|
|
77
|
+
provider=provider_name, refs=refs, plugins=plugins_raw
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return profiles
|
|
81
|
+
|
|
82
|
+
def load(self, path: Union[str, Path]) -> AppConfig:
|
|
83
|
+
config_path = Path(path)
|
|
84
|
+
|
|
85
|
+
if not config_path.exists():
|
|
86
|
+
raise ConfigurationError(f"Configuration file not found: {config_path}")
|
|
87
|
+
|
|
88
|
+
if not config_path.is_file():
|
|
89
|
+
raise ConfigurationError(f"Configuration path is not a file: {config_path}")
|
|
90
|
+
|
|
91
|
+
raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
92
|
+
providers_section = raw.get("providers", {})
|
|
93
|
+
profiles_section = raw.get("profiles", {})
|
|
94
|
+
|
|
95
|
+
providers = self._prepare_providers(providers_section)
|
|
96
|
+
profiles = self._prepare_profiles(profiles_section)
|
|
97
|
+
|
|
98
|
+
return AppConfig(providers=providers, profiles=profiles)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
from difflib import unified_diff
|
|
3
|
+
|
|
4
|
+
from wikiops_sdk.domain import ChangeSet, UpdateDocumentOperation
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DiffEngine:
|
|
8
|
+
"""Builds preview diffs for update operations."""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def _find_document(documents: Dict[str, object], ref: object):
|
|
12
|
+
for document in documents.values():
|
|
13
|
+
if getattr(document, "ref", None) == ref:
|
|
14
|
+
return document
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
def render(self, documents: Dict[str, object], change_set: ChangeSet) -> str:
|
|
18
|
+
chunks: List[str] = []
|
|
19
|
+
for op in change_set.operations:
|
|
20
|
+
if not isinstance(op, UpdateDocumentOperation):
|
|
21
|
+
chunks.append(
|
|
22
|
+
f"# Operation {op.operation_id}\n"
|
|
23
|
+
f"Type: {op.operation}\n"
|
|
24
|
+
f"This operation creates a new content and has no line diff against an existing document."
|
|
25
|
+
)
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
current_doc = self._find_document(documents, op.ref)
|
|
29
|
+
current_lines = (current_doc.content if current_doc else "").splitlines()
|
|
30
|
+
new_lines = op.new_content.splitlines()
|
|
31
|
+
|
|
32
|
+
diff = unified_diff(
|
|
33
|
+
current_lines,
|
|
34
|
+
new_lines,
|
|
35
|
+
fromfile="current",
|
|
36
|
+
tofile="planned",
|
|
37
|
+
lineterm="",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
chunks.append(f"# Operation {op.operation_id}\n" + "\n".join(diff))
|
|
41
|
+
|
|
42
|
+
return "\n\n".join(chunks)
|