julee 0.1.3__py3-none-any.whl → 0.1.4__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.
@@ -10,13 +10,12 @@ Provides directives:
10
10
  """
11
11
 
12
12
  import yaml
13
- from pathlib import Path
14
13
  from docutils import nodes
15
- from sphinx.util.docutils import SphinxDirective
16
14
  from sphinx.util import logging
15
+ from sphinx.util.docutils import SphinxDirective
17
16
 
18
17
  from .config import get_config
19
- from .utils import normalize_name, slugify, path_to_root
18
+ from .utils import normalize_name, path_to_root, slugify
20
19
 
21
20
  logger = logging.getLogger(__name__)
22
21
 
@@ -31,7 +30,7 @@ def get_app_registry() -> dict:
31
30
 
32
31
  def get_documented_apps(env) -> set:
33
32
  """Get documented apps set from env, creating if needed."""
34
- if not hasattr(env, 'documented_apps'):
33
+ if not hasattr(env, "documented_apps"):
35
34
  env.documented_apps = set()
36
35
  return env.documented_apps
37
36
 
@@ -42,11 +41,12 @@ def scan_app_manifests(app):
42
41
  _app_registry = {}
43
42
 
44
43
  config = get_config()
45
- project_root = config.project_root
46
- apps_dir = config.get_path('app_manifests')
44
+ apps_dir = config.get_path("app_manifests")
47
45
 
48
46
  if not apps_dir.exists():
49
- logger.info(f"apps/ directory not found at {apps_dir} - no app manifests to index")
47
+ logger.info(
48
+ f"apps/ directory not found at {apps_dir} - no app manifests to index"
49
+ )
50
50
  return
51
51
 
52
52
  # Scan for app.yaml files
@@ -68,13 +68,13 @@ def scan_app_manifests(app):
68
68
  continue
69
69
 
70
70
  _app_registry[app_slug] = {
71
- 'slug': app_slug,
72
- 'name': manifest.get('name', app_slug.replace('-', ' ').title()),
73
- 'type': manifest.get('type', 'unknown'),
74
- 'status': manifest.get('status'),
75
- 'description': manifest.get('description', '').strip(),
76
- 'accelerators': manifest.get('accelerators', []),
77
- 'manifest_path': str(manifest_path),
71
+ "slug": app_slug,
72
+ "name": manifest.get("name", app_slug.replace("-", " ").title()),
73
+ "type": manifest.get("type", "unknown"),
74
+ "status": manifest.get("status"),
75
+ "description": manifest.get("description", "").strip(),
76
+ "accelerators": manifest.get("accelerators", []),
77
+ "manifest_path": str(manifest_path),
78
78
  }
79
79
 
80
80
  logger.info(f"Indexed {len(_app_registry)} apps from manifests")
@@ -118,47 +118,51 @@ def get_personas_for_app(app_slug: str, story_registry: list) -> list[str]:
118
118
  personas = set()
119
119
  app_normalized = normalize_name(app_slug)
120
120
  for story in story_registry:
121
- if story['app_normalized'] == app_normalized:
122
- personas.add(story['persona'])
121
+ if story["app_normalized"] == app_normalized:
122
+ personas.add(story["persona"])
123
123
  return sorted(personas)
124
124
 
125
125
 
126
- def get_journeys_for_app(app_slug: str, story_registry: list, journey_registry: dict) -> list[str]:
126
+ def get_journeys_for_app(
127
+ app_slug: str, story_registry: list, journey_registry: dict
128
+ ) -> list[str]:
127
129
  """Get journeys that include stories from this app."""
128
130
  # Get story titles for this app
129
131
  app_normalized = normalize_name(app_slug)
130
132
  app_story_titles = {
131
- normalize_name(s['feature'])
133
+ normalize_name(s["feature"])
132
134
  for s in story_registry
133
- if s['app_normalized'] == app_normalized
135
+ if s["app_normalized"] == app_normalized
134
136
  }
135
137
 
136
138
  # Find journeys that reference these stories
137
139
  journeys = []
138
140
  for slug, journey in journey_registry.items():
139
- for step in journey.get('steps', []):
140
- if step.get('type') == 'story':
141
- if normalize_name(step['ref']) in app_story_titles:
141
+ for step in journey.get("steps", []):
142
+ if step.get("type") == "story":
143
+ if normalize_name(step["ref"]) in app_story_titles:
142
144
  journeys.append(slug)
143
145
  break
144
146
 
145
147
  return sorted(set(journeys))
146
148
 
147
149
 
148
- def get_epics_for_app(app_slug: str, story_registry: list, epic_registry: dict) -> list[str]:
150
+ def get_epics_for_app(
151
+ app_slug: str, story_registry: list, epic_registry: dict
152
+ ) -> list[str]:
149
153
  """Get epics that include stories from this app."""
150
154
  # Get story titles for this app
151
155
  app_normalized = normalize_name(app_slug)
152
156
  app_story_titles = {
153
- normalize_name(s['feature'])
157
+ normalize_name(s["feature"])
154
158
  for s in story_registry
155
- if s['app_normalized'] == app_normalized
159
+ if s["app_normalized"] == app_normalized
156
160
  }
157
161
 
158
162
  # Find epics that reference these stories
159
163
  epics = []
160
164
  for slug, epic in epic_registry.items():
161
- for story_title in epic.get('stories', []):
165
+ for story_title in epic.get("stories", []):
162
166
  if normalize_name(story_title) in app_story_titles:
163
167
  epics.append(slug)
164
168
  break
@@ -184,12 +188,13 @@ class DefineAppDirective(SphinxDirective):
184
188
 
185
189
  # Return placeholder - actual rendering in doctree-resolved
186
190
  node = DefineAppPlaceholder()
187
- node['app_slug'] = app_slug
191
+ node["app_slug"] = app_slug
188
192
  return [node]
189
193
 
190
194
 
191
195
  class DefineAppPlaceholder(nodes.General, nodes.Element):
192
196
  """Placeholder node for define-app, replaced at doctree-resolved."""
197
+
193
198
  pass
194
199
 
195
200
 
@@ -208,6 +213,7 @@ class AppIndexDirective(SphinxDirective):
208
213
 
209
214
  class AppIndexPlaceholder(nodes.General, nodes.Element):
210
215
  """Placeholder node for app-index, replaced at doctree-resolved."""
216
+
211
217
  pass
212
218
 
213
219
 
@@ -224,17 +230,24 @@ class AppsForPersonaDirective(SphinxDirective):
224
230
 
225
231
  def run(self):
226
232
  node = AppsForPersonaPlaceholder()
227
- node['persona'] = self.arguments[0]
233
+ node["persona"] = self.arguments[0]
228
234
  return [node]
229
235
 
230
236
 
231
237
  class AppsForPersonaPlaceholder(nodes.General, nodes.Element):
232
238
  """Placeholder node for apps-for-persona, replaced at doctree-resolved."""
239
+
233
240
  pass
234
241
 
235
242
 
236
- def build_app_content(app_slug: str, docname: str, story_registry: list,
237
- journey_registry: dict, epic_registry: dict, known_personas: set):
243
+ def build_app_content(
244
+ app_slug: str,
245
+ docname: str,
246
+ story_registry: list,
247
+ journey_registry: dict,
248
+ epic_registry: dict,
249
+ known_personas: set,
250
+ ):
238
251
  """Build the content nodes for an app."""
239
252
  from sphinx.addnodes import seealso
240
253
 
@@ -251,13 +264,15 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
251
264
  prefix = path_to_root(docname)
252
265
 
253
266
  # Description first
254
- if app_data['description']:
267
+ if app_data["description"]:
255
268
  desc_para = nodes.paragraph()
256
- desc_para += nodes.Text(app_data['description'])
269
+ desc_para += nodes.Text(app_data["description"])
257
270
  result_nodes.append(desc_para)
258
271
 
259
272
  # Stories count and link
260
- app_stories = [s for s in story_registry if s['app_normalized'] == normalize_name(app_slug)]
273
+ app_stories = [
274
+ s for s in story_registry if s["app_normalized"] == normalize_name(app_slug)
275
+ ]
261
276
 
262
277
  if app_stories:
263
278
  story_count = len(app_stories)
@@ -275,20 +290,20 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
275
290
 
276
291
  # Type
277
292
  type_labels = {
278
- 'staff': 'Staff Application',
279
- 'external': 'External Application',
280
- 'member-tool': 'Member Tool',
293
+ "staff": "Staff Application",
294
+ "external": "External Application",
295
+ "member-tool": "Member Tool",
281
296
  }
282
297
  type_para = nodes.paragraph()
283
298
  type_para += nodes.strong(text="Type: ")
284
- type_para += nodes.Text(type_labels.get(app_data['type'], app_data['type']))
299
+ type_para += nodes.Text(type_labels.get(app_data["type"], app_data["type"]))
285
300
  seealso_node += type_para
286
301
 
287
302
  # Status (if present)
288
- if app_data['status']:
303
+ if app_data["status"]:
289
304
  status_para = nodes.paragraph()
290
305
  status_para += nodes.strong(text="Status: ")
291
- status_para += nodes.Text(app_data['status'])
306
+ status_para += nodes.Text(app_data["status"])
292
307
  seealso_node += status_para
293
308
 
294
309
  # Personas (derived from stories)
@@ -298,7 +313,9 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
298
313
  persona_para += nodes.strong(text="Personas: ")
299
314
  for i, persona in enumerate(personas):
300
315
  persona_slug = slugify(persona)
301
- persona_path = f"{prefix}{config.get_doc_path('personas')}/{persona_slug}.html"
316
+ persona_path = (
317
+ f"{prefix}{config.get_doc_path('personas')}/{persona_slug}.html"
318
+ )
302
319
  persona_normalized = normalize_name(persona)
303
320
 
304
321
  if persona_normalized in known_personas:
@@ -319,7 +336,9 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
319
336
  journey_para = nodes.paragraph()
320
337
  journey_para += nodes.strong(text="Journeys: ")
321
338
  for i, journey_slug in enumerate(journeys):
322
- journey_path = f"{prefix}{config.get_doc_path('journeys')}/{journey_slug}.html"
339
+ journey_path = (
340
+ f"{prefix}{config.get_doc_path('journeys')}/{journey_slug}.html"
341
+ )
323
342
  ref = nodes.reference("", "", refuri=journey_path)
324
343
  ref += nodes.Text(journey_slug.replace("-", " ").title())
325
344
  journey_para += ref
@@ -348,19 +367,15 @@ def build_app_content(app_slug: str, docname: str, story_registry: list,
348
367
 
349
368
  def build_app_index(docname: str, story_registry: list):
350
369
  """Build the app index grouped by type."""
351
- config = get_config()
352
-
353
370
  if not _app_registry:
354
371
  para = nodes.paragraph()
355
372
  para += nodes.emphasis(text="No apps defined")
356
373
  return [para]
357
374
 
358
- prefix = path_to_root(docname)
359
-
360
375
  # Group apps by type
361
- by_type = {'staff': [], 'external': [], 'member-tool': []}
376
+ by_type = {"staff": [], "external": [], "member-tool": []}
362
377
  for slug, app_data in _app_registry.items():
363
- app_type = app_data['type']
378
+ app_type = app_data["type"]
364
379
  if app_type in by_type:
365
380
  by_type[app_type].append((slug, app_data))
366
381
  else:
@@ -369,9 +384,9 @@ def build_app_index(docname: str, story_registry: list):
369
384
  result_nodes = []
370
385
 
371
386
  type_sections = [
372
- ('staff', 'Staff Applications'),
373
- ('external', 'External Applications'),
374
- ('member-tool', 'Member Tools'),
387
+ ("staff", "Staff Applications"),
388
+ ("external", "External Applications"),
389
+ ("member-tool", "Member Tools"),
375
390
  ]
376
391
 
377
392
  for type_key, type_label in type_sections:
@@ -387,14 +402,14 @@ def build_app_index(docname: str, story_registry: list):
387
402
  # App list
388
403
  app_list = nodes.bullet_list()
389
404
 
390
- for slug, app_data in sorted(apps, key=lambda x: x[1]['name']):
405
+ for slug, app_data in sorted(apps, key=lambda x: x[1]["name"]):
391
406
  item = nodes.list_item()
392
407
  para = nodes.paragraph()
393
408
 
394
409
  # Link to app
395
410
  app_path = f"{slug}.html"
396
411
  ref = nodes.reference("", "", refuri=app_path)
397
- ref += nodes.Text(app_data['name'])
412
+ ref += nodes.Text(app_data["name"])
398
413
  para += ref
399
414
 
400
415
  # Personas
@@ -432,13 +447,13 @@ def build_apps_for_persona(docname: str, persona_arg: str, story_registry: list)
432
447
 
433
448
  bullet_list = nodes.bullet_list()
434
449
 
435
- for slug, app_data in sorted(matching_apps, key=lambda x: x[1]['name']):
450
+ for slug, app_data in sorted(matching_apps, key=lambda x: x[1]["name"]):
436
451
  item = nodes.list_item()
437
452
  para = nodes.paragraph()
438
453
 
439
454
  app_path = f"{prefix}{config.get_doc_path('applications')}/{slug}.html"
440
455
  ref = nodes.reference("", "", refuri=app_path)
441
- ref += nodes.Text(app_data['name'])
456
+ ref += nodes.Text(app_data["name"])
442
457
  para += ref
443
458
 
444
459
  item += para
@@ -449,7 +464,7 @@ def build_apps_for_persona(docname: str, persona_arg: str, story_registry: list)
449
464
 
450
465
  def process_app_placeholders(app, doctree, docname):
451
466
  """Replace app placeholders with rendered content."""
452
- from . import stories, journeys, epics
467
+ from . import epics, journeys, stories
453
468
 
454
469
  env = app.env
455
470
 
@@ -460,10 +475,14 @@ def process_app_placeholders(app, doctree, docname):
460
475
 
461
476
  # Process define-app placeholders
462
477
  for node in doctree.traverse(DefineAppPlaceholder):
463
- app_slug = node['app_slug']
478
+ app_slug = node["app_slug"]
464
479
  content = build_app_content(
465
- app_slug, docname, _story_registry,
466
- journey_registry, epic_registry, _known_personas
480
+ app_slug,
481
+ docname,
482
+ _story_registry,
483
+ journey_registry,
484
+ epic_registry,
485
+ _known_personas,
467
486
  )
468
487
  node.replace_self(content)
469
488
 
@@ -474,7 +493,7 @@ def process_app_placeholders(app, doctree, docname):
474
493
 
475
494
  # Process apps-for-persona placeholders
476
495
  for node in doctree.traverse(AppsForPersonaPlaceholder):
477
- persona = node['persona']
496
+ persona = node["persona"]
478
497
  content = build_apps_for_persona(docname, persona, _story_registry)
479
498
  node.replace_self(content)
480
499
 
@@ -8,25 +8,25 @@ from copy import deepcopy
8
8
  from pathlib import Path
9
9
 
10
10
  DEFAULT_CONFIG = {
11
- 'paths': {
11
+ "paths": {
12
12
  # Where to find Gherkin feature files: {app}/features/*.feature
13
- 'feature_files': 'tests/e2e/',
13
+ "feature_files": "tests/e2e/",
14
14
  # Where to find app manifests: */app.yaml
15
- 'app_manifests': 'apps/',
15
+ "app_manifests": "apps/",
16
16
  # Where to find integration manifests: */integration.yaml
17
- 'integration_manifests': 'src/integrations/',
17
+ "integration_manifests": "src/integrations/",
18
18
  # Where to find bounded context code: {slug}/ directories
19
- 'bounded_contexts': 'src/',
19
+ "bounded_contexts": "src/",
20
20
  },
21
- 'docs_structure': {
21
+ "docs_structure": {
22
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',
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
30
  },
31
31
  }
32
32
 
@@ -76,7 +76,7 @@ class HCDConfig:
76
76
  self._project_root = self._docs_dir.parent
77
77
 
78
78
  # Merge user config with defaults
79
- user_config = getattr(app.config, 'sphinx_hcd', {}) or {}
79
+ user_config = getattr(app.config, "sphinx_hcd", {}) or {}
80
80
  self._config = _deep_merge(DEFAULT_CONFIG, user_config)
81
81
 
82
82
  @property
@@ -98,7 +98,7 @@ class HCDConfig:
98
98
  Returns:
99
99
  Absolute Path resolved relative to project root
100
100
  """
101
- rel_path = self._config['paths'].get(key, '')
101
+ rel_path = self._config["paths"].get(key, "")
102
102
  return self._project_root / rel_path
103
103
 
104
104
  def get_doc_path(self, key: str) -> str:
@@ -110,7 +110,7 @@ class HCDConfig:
110
110
  Returns:
111
111
  Relative path string for use in doc references
112
112
  """
113
- return self._config['docs_structure'].get(key, key)
113
+ return self._config["docs_structure"].get(key, key)
114
114
 
115
115
 
116
116
  # Module-level config instance, set by setup()