julee-viewpoints 0.1.0__py3-none-any.whl
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.
- julee_viewpoints/__init__.py +26 -0
- julee_viewpoints/py.typed +0 -0
- julee_viewpoints/sphinx_hcd/__init__.py +209 -0
- julee_viewpoints/sphinx_hcd/config.py +149 -0
- julee_viewpoints/sphinx_hcd/domain/__init__.py +5 -0
- julee_viewpoints/sphinx_hcd/domain/models/__init__.py +32 -0
- julee_viewpoints/sphinx_hcd/domain/models/accelerator.py +158 -0
- julee_viewpoints/sphinx_hcd/domain/models/app.py +152 -0
- julee_viewpoints/sphinx_hcd/domain/models/code_info.py +124 -0
- julee_viewpoints/sphinx_hcd/domain/models/epic.py +86 -0
- julee_viewpoints/sphinx_hcd/domain/models/integration.py +226 -0
- julee_viewpoints/sphinx_hcd/domain/models/journey.py +231 -0
- julee_viewpoints/sphinx_hcd/domain/models/persona.py +112 -0
- julee_viewpoints/sphinx_hcd/domain/models/story.py +141 -0
- julee_viewpoints/sphinx_hcd/domain/repositories/__init__.py +25 -0
- julee_viewpoints/sphinx_hcd/domain/repositories/accelerator.py +98 -0
- julee_viewpoints/sphinx_hcd/domain/repositories/app.py +57 -0
- julee_viewpoints/sphinx_hcd/domain/repositories/base.py +89 -0
- julee_viewpoints/sphinx_hcd/domain/repositories/code_info.py +69 -0
- julee_viewpoints/sphinx_hcd/domain/repositories/epic.py +62 -0
- julee_viewpoints/sphinx_hcd/domain/repositories/integration.py +79 -0
- julee_viewpoints/sphinx_hcd/domain/repositories/journey.py +106 -0
- julee_viewpoints/sphinx_hcd/domain/repositories/story.py +68 -0
- julee_viewpoints/sphinx_hcd/parsers/__init__.py +48 -0
- julee_viewpoints/sphinx_hcd/parsers/ast.py +152 -0
- julee_viewpoints/sphinx_hcd/parsers/gherkin.py +155 -0
- julee_viewpoints/sphinx_hcd/parsers/yaml.py +185 -0
- julee_viewpoints/sphinx_hcd/repositories/__init__.py +4 -0
- julee_viewpoints/sphinx_hcd/repositories/memory/__init__.py +25 -0
- julee_viewpoints/sphinx_hcd/repositories/memory/accelerator.py +86 -0
- julee_viewpoints/sphinx_hcd/repositories/memory/app.py +45 -0
- julee_viewpoints/sphinx_hcd/repositories/memory/base.py +107 -0
- julee_viewpoints/sphinx_hcd/repositories/memory/code_info.py +59 -0
- julee_viewpoints/sphinx_hcd/repositories/memory/epic.py +54 -0
- julee_viewpoints/sphinx_hcd/repositories/memory/integration.py +70 -0
- julee_viewpoints/sphinx_hcd/repositories/memory/journey.py +96 -0
- julee_viewpoints/sphinx_hcd/repositories/memory/story.py +63 -0
- julee_viewpoints/sphinx_hcd/sphinx/__init__.py +28 -0
- julee_viewpoints/sphinx_hcd/sphinx/adapters.py +116 -0
- julee_viewpoints/sphinx_hcd/sphinx/context.py +163 -0
- julee_viewpoints/sphinx_hcd/sphinx/directives/__init__.py +160 -0
- julee_viewpoints/sphinx_hcd/sphinx/directives/accelerator.py +581 -0
- julee_viewpoints/sphinx_hcd/sphinx/directives/app.py +349 -0
- julee_viewpoints/sphinx_hcd/sphinx/directives/base.py +212 -0
- julee_viewpoints/sphinx_hcd/sphinx/directives/epic.py +439 -0
- julee_viewpoints/sphinx_hcd/sphinx/directives/integration.py +220 -0
- julee_viewpoints/sphinx_hcd/sphinx/directives/journey.py +652 -0
- julee_viewpoints/sphinx_hcd/sphinx/directives/persona.py +347 -0
- julee_viewpoints/sphinx_hcd/sphinx/directives/story.py +575 -0
- julee_viewpoints/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
- julee_viewpoints/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
- julee_viewpoints/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
- julee_viewpoints/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
- julee_viewpoints/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
- julee_viewpoints/sphinx_hcd/sphinx/initialization.py +139 -0
- julee_viewpoints/sphinx_hcd/tests/__init__.py +9 -0
- julee_viewpoints/sphinx_hcd/tests/conftest.py +6 -0
- julee_viewpoints/sphinx_hcd/tests/domain/__init__.py +1 -0
- julee_viewpoints/sphinx_hcd/tests/domain/models/__init__.py +1 -0
- julee_viewpoints/sphinx_hcd/tests/domain/models/test_accelerator.py +272 -0
- julee_viewpoints/sphinx_hcd/tests/domain/models/test_app.py +261 -0
- julee_viewpoints/sphinx_hcd/tests/domain/models/test_code_info.py +229 -0
- julee_viewpoints/sphinx_hcd/tests/domain/models/test_epic.py +177 -0
- julee_viewpoints/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
- julee_viewpoints/sphinx_hcd/tests/domain/models/test_journey.py +258 -0
- julee_viewpoints/sphinx_hcd/tests/domain/models/test_persona.py +187 -0
- julee_viewpoints/sphinx_hcd/tests/domain/models/test_story.py +216 -0
- julee_viewpoints/sphinx_hcd/tests/integration/__init__.py +1 -0
- julee_viewpoints/sphinx_hcd/tests/parsers/__init__.py +1 -0
- julee_viewpoints/sphinx_hcd/tests/parsers/test_ast.py +279 -0
- julee_viewpoints/sphinx_hcd/tests/parsers/test_gherkin.py +280 -0
- julee_viewpoints/sphinx_hcd/tests/parsers/test_yaml.py +480 -0
- julee_viewpoints/sphinx_hcd/tests/repositories/__init__.py +1 -0
- julee_viewpoints/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
- julee_viewpoints/sphinx_hcd/tests/repositories/test_app.py +218 -0
- julee_viewpoints/sphinx_hcd/tests/repositories/test_base.py +151 -0
- julee_viewpoints/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
- julee_viewpoints/sphinx_hcd/tests/repositories/test_epic.py +237 -0
- julee_viewpoints/sphinx_hcd/tests/repositories/test_integration.py +268 -0
- julee_viewpoints/sphinx_hcd/tests/repositories/test_journey.py +296 -0
- julee_viewpoints/sphinx_hcd/tests/repositories/test_story.py +236 -0
- julee_viewpoints/sphinx_hcd/tests/sphinx/__init__.py +1 -0
- julee_viewpoints/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
- julee_viewpoints/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
- julee_viewpoints/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
- julee_viewpoints/sphinx_hcd/tests/sphinx/test_context.py +257 -0
- julee_viewpoints/sphinx_hcd/tests/usecases/__init__.py +1 -0
- julee_viewpoints/sphinx_hcd/tests/usecases/test_derive_personas.py +314 -0
- julee_viewpoints/sphinx_hcd/tests/usecases/test_resolve_accelerator_references.py +489 -0
- julee_viewpoints/sphinx_hcd/tests/usecases/test_resolve_app_references.py +276 -0
- julee_viewpoints/sphinx_hcd/tests/usecases/test_resolve_story_references.py +240 -0
- julee_viewpoints/sphinx_hcd/usecases/__init__.py +83 -0
- julee_viewpoints/sphinx_hcd/usecases/derive_personas.py +203 -0
- julee_viewpoints/sphinx_hcd/usecases/resolve_accelerator_references.py +275 -0
- julee_viewpoints/sphinx_hcd/usecases/resolve_app_references.py +168 -0
- julee_viewpoints/sphinx_hcd/usecases/resolve_story_references.py +147 -0
- julee_viewpoints/sphinx_hcd/utils.py +185 -0
- julee_viewpoints-0.1.0.dist-info/METADATA +34 -0
- julee_viewpoints-0.1.0.dist-info/RECORD +102 -0
- julee_viewpoints-0.1.0.dist-info/WHEEL +5 -0
- julee_viewpoints-0.1.0.dist-info/entry_points.txt +2 -0
- julee_viewpoints-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Code-outward documentation for julee solutions.
|
|
2
|
+
|
|
3
|
+
This kit projects a solution into documentation: its personas, journeys,
|
|
4
|
+
epics and stories, its applications and integrations, and the bounded
|
|
5
|
+
contexts its code already describes (ADR 006).
|
|
6
|
+
|
|
7
|
+
It is a viewpoint kit. Its bounded contexts describe a solution rather
|
|
8
|
+
than implement part of one, which is why the manifest sets
|
|
9
|
+
``viewpoint=True``.
|
|
10
|
+
|
|
11
|
+
Use it in a solution's ``conf.py``::
|
|
12
|
+
|
|
13
|
+
extensions = ["julee_viewpoints.sphinx_hcd"]
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from julee.core.entities.kit import Kit
|
|
17
|
+
|
|
18
|
+
kit = Kit(
|
|
19
|
+
slug="viewpoints",
|
|
20
|
+
name="Code-outward documentation",
|
|
21
|
+
package="julee_viewpoints",
|
|
22
|
+
viewpoint=True,
|
|
23
|
+
contributes={"sphinx.extension": "julee_viewpoints.sphinx_hcd"},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = ["kit"]
|
|
File without changes
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Sphinx HCD (Human-Centered Design) Extensions for Julee Solutions.
|
|
2
|
+
|
|
3
|
+
This package provides Sphinx extensions for documenting Julee-based solutions
|
|
4
|
+
using Human-Centered Design patterns. It supports:
|
|
5
|
+
|
|
6
|
+
- Stories: User stories derived from Gherkin .feature files
|
|
7
|
+
- Journeys: User journeys composed of stories and epics
|
|
8
|
+
- Epics: Collections of related stories
|
|
9
|
+
- Apps: Application documentation with manifest-based metadata
|
|
10
|
+
- Accelerators: Domain accelerator documentation with bounded context scanning
|
|
11
|
+
- Integrations: External integration documentation
|
|
12
|
+
- Personas: Auto-generated UML diagrams showing persona-epic-app relationships
|
|
13
|
+
|
|
14
|
+
Usage in conf.py::
|
|
15
|
+
|
|
16
|
+
extensions = ["julee_viewpoints.sphinx_hcd"]
|
|
17
|
+
|
|
18
|
+
# Optional configuration (defaults match standard Julee layout)
|
|
19
|
+
sphinx_hcd = {
|
|
20
|
+
'paths': {
|
|
21
|
+
'feature_files': 'tests/e2e/',
|
|
22
|
+
'app_manifests': 'apps/',
|
|
23
|
+
'integration_manifests': 'src/integrations/',
|
|
24
|
+
'bounded_contexts': 'src/',
|
|
25
|
+
},
|
|
26
|
+
'docs_structure': {
|
|
27
|
+
'applications': 'applications',
|
|
28
|
+
'personas': 'users/personas',
|
|
29
|
+
'journeys': 'users/journeys',
|
|
30
|
+
'epics': 'users/epics',
|
|
31
|
+
'accelerators': 'domain/accelerators',
|
|
32
|
+
'integrations': 'integrations',
|
|
33
|
+
'stories': 'users/stories',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from sphinx.util import logging
|
|
39
|
+
|
|
40
|
+
from .config import init_config
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def setup(app):
|
|
46
|
+
"""Set up all HCD extensions for Sphinx."""
|
|
47
|
+
from .sphinx.directives import (
|
|
48
|
+
AcceleratorDependencyDiagramDirective,
|
|
49
|
+
AcceleratorDependencyDiagramPlaceholder,
|
|
50
|
+
AcceleratorIndexDirective,
|
|
51
|
+
AcceleratorIndexPlaceholder,
|
|
52
|
+
AcceleratorsForAppDirective,
|
|
53
|
+
AcceleratorsForAppPlaceholder,
|
|
54
|
+
AcceleratorStatusDirective,
|
|
55
|
+
AppIndexDirective,
|
|
56
|
+
AppIndexPlaceholder,
|
|
57
|
+
AppsForPersonaDirective,
|
|
58
|
+
AppsForPersonaPlaceholder,
|
|
59
|
+
# Accelerator directives
|
|
60
|
+
DefineAcceleratorDirective,
|
|
61
|
+
DefineAcceleratorPlaceholder,
|
|
62
|
+
# App directives
|
|
63
|
+
DefineAppDirective,
|
|
64
|
+
DefineAppPlaceholder,
|
|
65
|
+
# Epic directives
|
|
66
|
+
DefineEpicDirective,
|
|
67
|
+
# Integration directives
|
|
68
|
+
DefineIntegrationDirective,
|
|
69
|
+
DefineIntegrationPlaceholder,
|
|
70
|
+
# Journey directives
|
|
71
|
+
DefineJourneyDirective,
|
|
72
|
+
DependentAcceleratorsDirective,
|
|
73
|
+
DependentAcceleratorsPlaceholder,
|
|
74
|
+
EpicIndexDirective,
|
|
75
|
+
EpicIndexPlaceholder,
|
|
76
|
+
EpicsForPersonaDirective,
|
|
77
|
+
EpicsForPersonaPlaceholder,
|
|
78
|
+
EpicStoryDirective,
|
|
79
|
+
GherkinAppStoriesDirective,
|
|
80
|
+
GherkinStoriesDirective,
|
|
81
|
+
GherkinStoriesForAppDirective,
|
|
82
|
+
GherkinStoriesForPersonaDirective,
|
|
83
|
+
GherkinStoriesIndexDirective,
|
|
84
|
+
# Story deprecated aliases
|
|
85
|
+
GherkinStoryDirective,
|
|
86
|
+
IntegrationIndexDirective,
|
|
87
|
+
IntegrationIndexPlaceholder,
|
|
88
|
+
JourneyDependencyGraphDirective,
|
|
89
|
+
JourneyDependencyGraphPlaceholder,
|
|
90
|
+
JourneyIndexDirective,
|
|
91
|
+
JourneysForPersonaDirective,
|
|
92
|
+
# Persona directives
|
|
93
|
+
PersonaDiagramDirective,
|
|
94
|
+
PersonaDiagramPlaceholder,
|
|
95
|
+
PersonaIndexDiagramDirective,
|
|
96
|
+
PersonaIndexDiagramPlaceholder,
|
|
97
|
+
StepEpicDirective,
|
|
98
|
+
StepPhaseDirective,
|
|
99
|
+
StepStoryDirective,
|
|
100
|
+
StoriesDirective,
|
|
101
|
+
# Story directives
|
|
102
|
+
StoryAppDirective,
|
|
103
|
+
StoryIndexDirective,
|
|
104
|
+
StoryListForAppDirective,
|
|
105
|
+
StoryListForPersonaDirective,
|
|
106
|
+
StoryRefDirective,
|
|
107
|
+
StorySeeAlsoPlaceholder,
|
|
108
|
+
)
|
|
109
|
+
from .sphinx.event_handlers import (
|
|
110
|
+
on_builder_inited,
|
|
111
|
+
on_doctree_read,
|
|
112
|
+
on_doctree_resolved,
|
|
113
|
+
on_env_purge_doc,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Register configuration value first
|
|
117
|
+
app.add_config_value("sphinx_hcd", {}, "env")
|
|
118
|
+
|
|
119
|
+
# Initialize config when builder starts (after conf.py is loaded)
|
|
120
|
+
app.connect("builder-inited", _init_config_handler, priority=0)
|
|
121
|
+
|
|
122
|
+
# Connect event handlers
|
|
123
|
+
app.connect("builder-inited", on_builder_inited, priority=100)
|
|
124
|
+
app.connect("doctree-read", on_doctree_read)
|
|
125
|
+
app.connect("doctree-resolved", on_doctree_resolved)
|
|
126
|
+
app.connect("env-purge-doc", on_env_purge_doc)
|
|
127
|
+
|
|
128
|
+
# Register story directives
|
|
129
|
+
app.add_directive("story", StoryRefDirective)
|
|
130
|
+
app.add_directive("stories", StoriesDirective)
|
|
131
|
+
app.add_directive("story-list-for-persona", StoryListForPersonaDirective)
|
|
132
|
+
app.add_directive("story-list-for-app", StoryListForAppDirective)
|
|
133
|
+
app.add_directive("story-index", StoryIndexDirective)
|
|
134
|
+
app.add_directive("story-app", StoryAppDirective)
|
|
135
|
+
app.add_node(StorySeeAlsoPlaceholder)
|
|
136
|
+
|
|
137
|
+
# Register deprecated story aliases
|
|
138
|
+
app.add_directive("gherkin-story", GherkinStoryDirective)
|
|
139
|
+
app.add_directive("gherkin-stories", GherkinStoriesDirective)
|
|
140
|
+
app.add_directive("gherkin-stories-for-persona", GherkinStoriesForPersonaDirective)
|
|
141
|
+
app.add_directive("gherkin-stories-for-app", GherkinStoriesForAppDirective)
|
|
142
|
+
app.add_directive("gherkin-stories-index", GherkinStoriesIndexDirective)
|
|
143
|
+
app.add_directive("gherkin-app-stories", GherkinAppStoriesDirective)
|
|
144
|
+
|
|
145
|
+
# Register journey directives
|
|
146
|
+
app.add_directive("define-journey", DefineJourneyDirective)
|
|
147
|
+
app.add_directive("step-story", StepStoryDirective)
|
|
148
|
+
app.add_directive("step-epic", StepEpicDirective)
|
|
149
|
+
app.add_directive("step-phase", StepPhaseDirective)
|
|
150
|
+
app.add_directive("journey-index", JourneyIndexDirective)
|
|
151
|
+
app.add_directive("journey-dependency-graph", JourneyDependencyGraphDirective)
|
|
152
|
+
app.add_directive("journeys-for-persona", JourneysForPersonaDirective)
|
|
153
|
+
app.add_node(JourneyDependencyGraphPlaceholder)
|
|
154
|
+
|
|
155
|
+
# Register epic directives
|
|
156
|
+
app.add_directive("define-epic", DefineEpicDirective)
|
|
157
|
+
app.add_directive("epic-story", EpicStoryDirective)
|
|
158
|
+
app.add_directive("epic-index", EpicIndexDirective)
|
|
159
|
+
app.add_directive("epics-for-persona", EpicsForPersonaDirective)
|
|
160
|
+
app.add_node(EpicIndexPlaceholder)
|
|
161
|
+
app.add_node(EpicsForPersonaPlaceholder)
|
|
162
|
+
|
|
163
|
+
# Register app directives
|
|
164
|
+
app.add_directive("define-app", DefineAppDirective)
|
|
165
|
+
app.add_directive("app-index", AppIndexDirective)
|
|
166
|
+
app.add_directive("apps-for-persona", AppsForPersonaDirective)
|
|
167
|
+
app.add_node(DefineAppPlaceholder)
|
|
168
|
+
app.add_node(AppIndexPlaceholder)
|
|
169
|
+
app.add_node(AppsForPersonaPlaceholder)
|
|
170
|
+
|
|
171
|
+
# Register accelerator directives
|
|
172
|
+
app.add_directive("define-accelerator", DefineAcceleratorDirective)
|
|
173
|
+
app.add_directive("accelerator-index", AcceleratorIndexDirective)
|
|
174
|
+
app.add_directive("accelerators-for-app", AcceleratorsForAppDirective)
|
|
175
|
+
app.add_directive("dependent-accelerators", DependentAcceleratorsDirective)
|
|
176
|
+
app.add_directive(
|
|
177
|
+
"accelerator-dependency-diagram", AcceleratorDependencyDiagramDirective
|
|
178
|
+
)
|
|
179
|
+
app.add_directive("accelerator-status", AcceleratorStatusDirective)
|
|
180
|
+
app.add_node(DefineAcceleratorPlaceholder)
|
|
181
|
+
app.add_node(AcceleratorIndexPlaceholder)
|
|
182
|
+
app.add_node(AcceleratorsForAppPlaceholder)
|
|
183
|
+
app.add_node(DependentAcceleratorsPlaceholder)
|
|
184
|
+
app.add_node(AcceleratorDependencyDiagramPlaceholder)
|
|
185
|
+
|
|
186
|
+
# Register integration directives
|
|
187
|
+
app.add_directive("define-integration", DefineIntegrationDirective)
|
|
188
|
+
app.add_directive("integration-index", IntegrationIndexDirective)
|
|
189
|
+
app.add_node(DefineIntegrationPlaceholder)
|
|
190
|
+
app.add_node(IntegrationIndexPlaceholder)
|
|
191
|
+
|
|
192
|
+
# Register persona directives
|
|
193
|
+
app.add_directive("persona-diagram", PersonaDiagramDirective)
|
|
194
|
+
app.add_directive("persona-index-diagram", PersonaIndexDiagramDirective)
|
|
195
|
+
app.add_node(PersonaDiagramPlaceholder)
|
|
196
|
+
app.add_node(PersonaIndexDiagramPlaceholder)
|
|
197
|
+
|
|
198
|
+
logger.info("Loaded julee_viewpoints.sphinx_hcd extensions")
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
"version": "2.0",
|
|
202
|
+
"parallel_read_safe": False,
|
|
203
|
+
"parallel_write_safe": True,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _init_config_handler(app):
|
|
208
|
+
"""Initialize HCD config from Sphinx app config."""
|
|
209
|
+
init_config(app)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Configuration for sphinx_hcd extension.
|
|
2
|
+
|
|
3
|
+
Provides defaults matching the RBA solution layout, with ability to override
|
|
4
|
+
via sphinx_hcd config dict in conf.py.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
DEFAULT_CONFIG = {
|
|
11
|
+
"paths": {
|
|
12
|
+
# Where to find Gherkin feature files: {app}/features/*.feature
|
|
13
|
+
"feature_files": "tests/e2e/",
|
|
14
|
+
# Where to find app manifests: */app.yaml
|
|
15
|
+
"app_manifests": "apps/",
|
|
16
|
+
# Where to find integration manifests: */integration.yaml
|
|
17
|
+
"integration_manifests": "src/integrations/",
|
|
18
|
+
# Where to find bounded context code: {slug}/ directories
|
|
19
|
+
"bounded_contexts": "src/",
|
|
20
|
+
},
|
|
21
|
+
"docs_structure": {
|
|
22
|
+
# RST file locations relative to docs root
|
|
23
|
+
"applications": "applications",
|
|
24
|
+
"personas": "users/personas",
|
|
25
|
+
"journeys": "users/journeys",
|
|
26
|
+
"epics": "users/epics",
|
|
27
|
+
"accelerators": "domain/accelerators",
|
|
28
|
+
"integrations": "integrations",
|
|
29
|
+
"stories": "users/stories",
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def config_factory() -> dict:
|
|
35
|
+
"""Return a fresh config dict populated with defaults.
|
|
36
|
+
|
|
37
|
+
Usage in conf.py::
|
|
38
|
+
|
|
39
|
+
from julee_viewpoints.sphinx_hcd import config_factory
|
|
40
|
+
|
|
41
|
+
sphinx_hcd = config_factory()
|
|
42
|
+
sphinx_hcd['paths']['feature_files'] = 'tests/bdd/'
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
A deep copy of DEFAULT_CONFIG that can be modified.
|
|
46
|
+
"""
|
|
47
|
+
return deepcopy(DEFAULT_CONFIG)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _deep_merge(base: dict, override: dict) -> dict:
|
|
51
|
+
"""Deep merge override into base, returning new dict."""
|
|
52
|
+
result = deepcopy(base)
|
|
53
|
+
for key, value in override.items():
|
|
54
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
55
|
+
result[key] = _deep_merge(result[key], value)
|
|
56
|
+
else:
|
|
57
|
+
result[key] = deepcopy(value)
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class HCDConfig:
|
|
62
|
+
"""Configuration holder for sphinx_hcd extension.
|
|
63
|
+
|
|
64
|
+
Provides access to paths and doc structure settings, resolving paths
|
|
65
|
+
relative to the project root.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, app):
|
|
69
|
+
"""Initialize config from Sphinx app.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
app: Sphinx application instance
|
|
73
|
+
"""
|
|
74
|
+
self._app = app
|
|
75
|
+
self._docs_dir = Path(app.srcdir)
|
|
76
|
+
self._project_root = self._docs_dir.parent
|
|
77
|
+
|
|
78
|
+
# Merge user config with defaults
|
|
79
|
+
user_config = getattr(app.config, "sphinx_hcd", {}) or {}
|
|
80
|
+
self._config = _deep_merge(DEFAULT_CONFIG, user_config)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def project_root(self) -> Path:
|
|
84
|
+
"""Project root directory (parent of docs/)."""
|
|
85
|
+
return self._project_root
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def docs_dir(self) -> Path:
|
|
89
|
+
"""Documentation source directory."""
|
|
90
|
+
return self._docs_dir
|
|
91
|
+
|
|
92
|
+
def get_path(self, key: str) -> Path:
|
|
93
|
+
"""Get an absolute path from the paths config.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
key: Path key (e.g., 'feature_files', 'app_manifests')
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Absolute Path resolved relative to project root
|
|
100
|
+
"""
|
|
101
|
+
rel_path: str = self._config["paths"].get(key, "")
|
|
102
|
+
return self._project_root / rel_path
|
|
103
|
+
|
|
104
|
+
def get_doc_path(self, key: str) -> str:
|
|
105
|
+
"""Get a doc structure path.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
key: Doc path key (e.g., 'applications', 'personas')
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Relative path string for use in doc references
|
|
112
|
+
"""
|
|
113
|
+
doc_path: str = self._config["docs_structure"].get(key, key)
|
|
114
|
+
return doc_path
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Module-level config instance, set by setup()
|
|
118
|
+
_config: HCDConfig | None = None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_config() -> HCDConfig:
|
|
122
|
+
"""Get the current HCD configuration.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
HCDConfig instance
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
RuntimeError: If called before extension is initialized
|
|
129
|
+
"""
|
|
130
|
+
if _config is None:
|
|
131
|
+
raise RuntimeError(
|
|
132
|
+
"sphinx_hcd config not initialized. "
|
|
133
|
+
"Ensure 'julee_viewpoints.sphinx_hcd' is in your Sphinx extensions."
|
|
134
|
+
)
|
|
135
|
+
return _config
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def init_config(app) -> HCDConfig:
|
|
139
|
+
"""Initialize config from Sphinx app. Called by extension setup.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
app: Sphinx application instance
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
HCDConfig instance
|
|
146
|
+
"""
|
|
147
|
+
global _config
|
|
148
|
+
_config = HCDConfig(app)
|
|
149
|
+
return _config
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Domain models for sphinx_hcd.
|
|
2
|
+
|
|
3
|
+
Pydantic models representing HCD entities: stories, journeys, epics,
|
|
4
|
+
apps, accelerators, integrations, and personas.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .accelerator import Accelerator, IntegrationReference
|
|
8
|
+
from .app import App, AppType
|
|
9
|
+
from .code_info import BoundedContextInfo, ClassInfo
|
|
10
|
+
from .epic import Epic
|
|
11
|
+
from .integration import Direction, ExternalDependency, Integration
|
|
12
|
+
from .journey import Journey, JourneyStep, StepType
|
|
13
|
+
from .persona import Persona
|
|
14
|
+
from .story import Story
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Accelerator",
|
|
18
|
+
"App",
|
|
19
|
+
"AppType",
|
|
20
|
+
"BoundedContextInfo",
|
|
21
|
+
"ClassInfo",
|
|
22
|
+
"Direction",
|
|
23
|
+
"Epic",
|
|
24
|
+
"ExternalDependency",
|
|
25
|
+
"Integration",
|
|
26
|
+
"IntegrationReference",
|
|
27
|
+
"Journey",
|
|
28
|
+
"JourneyStep",
|
|
29
|
+
"Persona",
|
|
30
|
+
"StepType",
|
|
31
|
+
"Story",
|
|
32
|
+
]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Accelerator domain model.
|
|
2
|
+
|
|
3
|
+
Represents an accelerator (bounded context) in the HCD documentation system.
|
|
4
|
+
Accelerators are defined via RST directives and may have associated code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import Field, field_validator
|
|
8
|
+
|
|
9
|
+
from julee.core.entities.entity import Entity
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IntegrationReference(Entity):
|
|
13
|
+
"""Reference to an integration with optional description.
|
|
14
|
+
|
|
15
|
+
Used for sources_from and publishes_to relationships where
|
|
16
|
+
an accelerator may specify what data it sources or publishes.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
slug: str = Field(description='Integration slug (e.g., "pilot-data-collection")')
|
|
20
|
+
description: str = Field(
|
|
21
|
+
default="",
|
|
22
|
+
description='What is sourced/published (e.g., "Scheme documentation")',
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
@field_validator("slug", mode="before")
|
|
26
|
+
@classmethod
|
|
27
|
+
def validate_slug(cls, v: str) -> str:
|
|
28
|
+
"""Validate slug is not empty."""
|
|
29
|
+
if not v or not v.strip():
|
|
30
|
+
raise ValueError("slug cannot be empty")
|
|
31
|
+
return v.strip()
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_dict(cls, data: dict | str) -> "IntegrationReference":
|
|
35
|
+
"""Create from dict or string.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
data: Either a dict with slug/description or a plain string slug
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
IntegrationReference instance
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(data, str):
|
|
44
|
+
return cls(slug=data)
|
|
45
|
+
return cls(slug=data.get("slug", ""), description=data.get("description", ""))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Accelerator(Entity):
|
|
49
|
+
"""Accelerator entity.
|
|
50
|
+
|
|
51
|
+
An accelerator represents a bounded context that provides business
|
|
52
|
+
capabilities. It may have associated code in src/{slug}/ and is
|
|
53
|
+
exposed through one or more applications.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
slug: str = Field(description='URL-safe identifier (e.g., "vocabulary")')
|
|
57
|
+
status: str = Field(
|
|
58
|
+
default="",
|
|
59
|
+
description='Development status (e.g., "alpha", "production", "future")',
|
|
60
|
+
)
|
|
61
|
+
milestone: str | None = Field(
|
|
62
|
+
default=None, description='Target milestone (e.g., "2 (Nov 2025)")'
|
|
63
|
+
)
|
|
64
|
+
acceptance: str | None = Field(
|
|
65
|
+
default=None, description="Acceptance criteria description"
|
|
66
|
+
)
|
|
67
|
+
objective: str = Field(default="", description="Business objective/description")
|
|
68
|
+
sources_from: tuple[IntegrationReference, ...] = Field(
|
|
69
|
+
default_factory=tuple, description="Integrations this accelerator reads from"
|
|
70
|
+
)
|
|
71
|
+
feeds_into: tuple[str, ...] = Field(
|
|
72
|
+
default_factory=tuple, description="Other accelerators this one feeds data into"
|
|
73
|
+
)
|
|
74
|
+
publishes_to: tuple[IntegrationReference, ...] = Field(
|
|
75
|
+
default_factory=tuple, description="Integrations this accelerator writes to"
|
|
76
|
+
)
|
|
77
|
+
depends_on: tuple[str, ...] = Field(
|
|
78
|
+
default_factory=tuple, description="Other accelerators this one depends on"
|
|
79
|
+
)
|
|
80
|
+
docname: str = Field(
|
|
81
|
+
default="", description="RST document name (for incremental builds)"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@field_validator("slug", mode="before")
|
|
85
|
+
@classmethod
|
|
86
|
+
def validate_slug(cls, v: str) -> str:
|
|
87
|
+
"""Validate slug is not empty."""
|
|
88
|
+
if not v or not v.strip():
|
|
89
|
+
raise ValueError("slug cannot be empty")
|
|
90
|
+
return v.strip()
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def display_title(self) -> str:
|
|
94
|
+
"""Get formatted title for display."""
|
|
95
|
+
return self.slug.replace("-", " ").title()
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def status_normalized(self) -> str:
|
|
99
|
+
"""Get normalized status for grouping."""
|
|
100
|
+
return self.status.lower().strip() if self.status else ""
|
|
101
|
+
|
|
102
|
+
def has_integration_dependency(self, integration_slug: str) -> bool:
|
|
103
|
+
"""Check if accelerator depends on an integration.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
integration_slug: Integration slug to check
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
True if sources_from or publishes_to contains this integration
|
|
110
|
+
"""
|
|
111
|
+
for ref in self.sources_from:
|
|
112
|
+
if ref.slug == integration_slug:
|
|
113
|
+
return True
|
|
114
|
+
for ref in self.publishes_to:
|
|
115
|
+
if ref.slug == integration_slug:
|
|
116
|
+
return True
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
def has_accelerator_dependency(self, accelerator_slug: str) -> bool:
|
|
120
|
+
"""Check if accelerator depends on another accelerator.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
accelerator_slug: Accelerator slug to check
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
True if depends_on or feeds_into contains this accelerator
|
|
127
|
+
"""
|
|
128
|
+
return (
|
|
129
|
+
accelerator_slug in self.depends_on or accelerator_slug in self.feeds_into
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def get_sources_from_slugs(self) -> list[str]:
|
|
133
|
+
"""Get list of integration slugs this accelerator sources from."""
|
|
134
|
+
return [ref.slug for ref in self.sources_from]
|
|
135
|
+
|
|
136
|
+
def get_publishes_to_slugs(self) -> list[str]:
|
|
137
|
+
"""Get list of integration slugs this accelerator publishes to."""
|
|
138
|
+
return [ref.slug for ref in self.publishes_to]
|
|
139
|
+
|
|
140
|
+
def get_integration_description(
|
|
141
|
+
self, integration_slug: str, relationship: str
|
|
142
|
+
) -> str | None:
|
|
143
|
+
"""Get description for an integration relationship.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
integration_slug: Integration to look up
|
|
147
|
+
relationship: Either "sources_from" or "publishes_to"
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Description if found, None otherwise
|
|
151
|
+
"""
|
|
152
|
+
refs = (
|
|
153
|
+
self.sources_from if relationship == "sources_from" else self.publishes_to
|
|
154
|
+
)
|
|
155
|
+
for ref in refs:
|
|
156
|
+
if ref.slug == integration_slug:
|
|
157
|
+
return ref.description or None
|
|
158
|
+
return None
|