Skip to content

Commit 8e252e4

Browse files
authored
Merge pull request #227 from dwolfson/Dr.E-Refactoring
Dr.e refactoring
2 parents 776fd05 + 9469d7b commit 8e252e4

322 files changed

Lines changed: 16958 additions & 8095 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎commands/cat/Dr-Egeria_md-orig.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
This is an ongoing experiment in parsing and playing with Freddie docs
2+
This is an ongoing experiment in parsing and playing with Dr.Egeria docs
33
"""
44
import json
55
import os

‎commands/cat/dr_egeria.py‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
This is an ongoing experiment in parsing and playing with Freddie docs
2+
This is an ongoing experiment in parsing and playing with Dr.Egeria docs
33
"""
44
import os
55
import sys
@@ -35,8 +35,7 @@
3535
console = Console(width=EGERIA_WIDTH)
3636

3737
@click.command("process_markdown_file", help="Process a markdown file and return the output as a string.")
38-
@click.option("--input-file", help="Markdown file to process.", default="dr_egeria_intro_part1.md",
39-
prompt="Markdown file to process")
38+
@click.argument("input_file", default="dr_egeria_intro_part1.md", required=False)
4039
@click.option("--output-folder", help="Output folder.", default="", required=False)
4140
@click.option("--directive", default="validate", help="How to process the file (display/validate/process). "
4241
"Overridden by --validate or --process flags.",
@@ -109,14 +108,4 @@ def _running_in_pycharm_debugger() -> bool:
109108
return sys.gettrace() is not None or os.environ.get("PYCHARM_HOSTED") is not None
110109

111110
if __name__ == "__main__":
112-
# if _running_in_pycharm_debugger():
113-
input_file = Prompt.ask("Markdown File name to process:", default="dr_egeria_intro_part1.md")
114-
directive = Prompt.ask("Directive (display/validate/process):", default="validate")
115-
advanced = Prompt.ask("Use advanced usage level? [y/N]:", default="N").strip().lower() in ("y", "yes")
116-
process_markdown_file.callback(input_file, "", directive,
117-
do_validate=False, do_process=False,
118-
server=EGERIA_VIEW_SERVER, url=EGERIA_VIEW_SERVER_URL, userid=EGERIA_USER,
119-
user_pass=EGERIA_USER_PASSWORD, parse_summary="all", attribute_logs="debug",
120-
advanced=advanced, summary_only=False, debug=False)
121-
# else:
122-
# process_markdown_file()
111+
process_markdown_file()

‎commands/cat/dr_egeria_jupyter.py‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
This is an ongoing experiment in parsing and playing with Freddie docs
2+
This is an ongoing experiment in parsing and playing with Dr.Egeria docs
33
"""
44

55
import nbformat
@@ -45,8 +45,7 @@
4545

4646

4747
@click.command("process-jupyter")
48-
@click.option("--file-path", help="File path to notebook",
49-
default="glossary_creation_experiment.ipynb")
48+
@click.argument("input_file", default="glossary_creation_experiment.ipynb", required=False)
5049
@click.option("--directive", default="display-only", help="How to process the file")
5150
@click.option("--server", default=EGERIA_VIEW_SERVER, help="Egeria view server to use.")
5251
@click.option(
@@ -55,7 +54,7 @@
5554
@click.option("--userid", default=EGERIA_USER, help="Egeria user")
5655
@click.option("--user_pass", default=EGERIA_USER_PASSWORD, help="Egeria user password")
5756
def process_jupyter_notebook(
58-
file_path: str,
57+
input_file: str,
5958
directive: str,
6059
server: str,
6160
url: str,
@@ -69,7 +68,7 @@ def process_jupyter_notebook(
6968

7069
try:
7170
updated = False
72-
full_file_path = os.path.join(EGERIA_ROOT_PATH, EGERIA_INBOX_PATH, file_path)
71+
full_file_path = os.path.join(EGERIA_ROOT_PATH, EGERIA_INBOX_PATH, input_file)
7372
click.echo(f"Processing notebook: {full_file_path}")
7473
with open(full_file_path, 'r') as f:
7574
nb = nbformat.read(f, as_version=4)
@@ -101,7 +100,7 @@ def process_jupyter_notebook(
101100

102101

103102
if updated:
104-
path, filename = os.path.split(file_path) # Get both parts
103+
path, filename = os.path.split(input_file) # Get both parts
105104
new_filename = f"processed-{get_current_datetime_string()}-{filename}" # Create the new filename
106105
new_file_path = os.path.join(EGERIA_ROOT_PATH, EGERIA_OUTBOX_PATH, new_filename) # Construct the new path
107106
os.makedirs(os.path.dirname(new_file_path), exist_ok=True)

‎commands/tech/gen_report_specs.py‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,26 @@ def _load_command_specs(path: Path | str) -> dict:
275275
path_str = str(path)
276276
try:
277277
from md_processing.md_processing_utils.compact_loader import load_compact_specs_from_dir
278-
specs = load_compact_specs_from_dir(path_str)
279-
if specs:
280-
logger.info(f"Loaded compact specs from {path_str} with {len(specs)} commands")
281-
return specs
278+
if os.path.isdir(path_str):
279+
specs = load_compact_specs_from_dir(path_str)
280+
if specs:
281+
logger.info(f"Loaded compact specs from {path_str} with {len(specs)} commands")
282+
return specs
283+
elif os.path.isfile(path_str):
284+
# Fallback for single file (used in tests)
285+
from md_processing.md_processing_utils.parse_compact_export import load_compact_json, expand_command
286+
from md_processing.md_processing_utils.compact_loader import _command_to_spec
287+
data = load_compact_json(path_str)
288+
all_attr_defs = data.get("attribute_definitions", {})
289+
all_bundles = data.get("bundles", {})
290+
specs = {}
291+
for cname, cdef in data.get("commands", {}).items():
292+
expanded = expand_command(cdef, all_bundles, all_attr_defs)
293+
specs[cname] = _command_to_spec(cname, cdef, expanded)
294+
if specs:
295+
return specs
282296
except Exception as e:
283-
logger.error(f"Failed to use compact_loader on dir {path_str}: {e}")
297+
logger.error(f"Failed to load specs from {path_str}: {e}")
284298
return {}
285299

286300
def build_format_sets_from_commands(

‎commands/tech/generate_dr_help.py‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,10 @@ def create_help_terms(client: Optional[ServerClient] = None, advanced: bool = Fa
180180
"attributes": columns}
181181
}
182182

183-
term_entry = f"""# Create Glossary\n
184-
## Display Name\n\n{glossary_name}\n
185-
## Qualified Name\n\n{glossary_qn}\n
186-
## Description\n\nDr.Egeria command help glossary.\n\n___\n\n"""
183+
term_entry = f"""## Create Glossary\n
184+
### Display Name\n\n{glossary_name}\n
185+
### Qualified Name\n\n{glossary_qn}\n
186+
### Description\n\nDr.Egeria command help glossary.\n\n___\n\n"""
187187

188188
families = set()
189189
for command, values in commands.items():
@@ -194,7 +194,7 @@ def create_help_terms(client: Optional[ServerClient] = None, advanced: bool = Fa
194194

195195
for family in families:
196196
family_qn = f"CollectionFolder::dr-egeria:{family.lower().replace(' ', '-')}"
197-
term_entry += f"# Create Collection Folder\n## Display Name\n\n{family}\n\n## Qualified Name\n\n{family_qn}\n\n## Purpose\n\nDr-Egeria Definitions\n\n## Parent ID\n\n{glossary_qn}\n\n## Parent Relationship Type Name\n\nCollectionMembership\n\n___\n\n"
197+
term_entry += f"## Create Collection Folder\n### Display Name\n\n{family}\n\n### Qualified Name\n\n{family_qn}\n\n### Purpose\n\nDr-Egeria Definitions\n\n### Parent ID\n\n{glossary_qn}\n\n### Parent Relationship Type Name\n\nCollectionMembership\n\n___\n\n"
198198

199199
for command, values in commands.items():
200200
if command == "exported":
@@ -206,17 +206,17 @@ def create_help_terms(client: Optional[ServerClient] = None, advanced: bool = Fa
206206
family = commands[command].get("family", "General")
207207
family_qn = f"CollectionFolder::dr-egeria:{family.lower().replace(' ', '-')}"
208208

209-
term_entry+= "# Create Term\n"
210-
term_entry+= f"## Display Name\n\n{command}\n\n"
211-
term_entry+= f"## Description\n\n{command_description}\n\n"
212-
term_entry+= f"## Glossary Name\n\n{glossary_qn}\n\n"
213-
term_entry+= f"## Folders\n\n{family_qn}\n\n"
209+
term_entry+= "## Create Term\n"
210+
term_entry+= f"### Display Name\n\n{command}\n\n"
211+
term_entry+= f"### Description\n\n{command_description}\n\n"
212+
term_entry+= f"### Glossary Name\n\n{glossary_qn}\n\n"
213+
term_entry+= f"### Folders\n\n{family_qn}\n\n"
214214

215215

216216
du = _extract_help_fields(command, client=client, advanced=advanced)
217217
output = generate_entity_md_table(du, "", "", _extract_help_function, columns_struct, None, "help" )
218218

219-
term_entry+= f"## Usage\n\n{output}\n\n___\n\n"
219+
term_entry+= f"### Usage\n\n{output}\n\n___\n\n"
220220

221221
print(term_entry)
222222
# Generate filename with current date and time in ISO 8601 format

‎commands/tech/generate_md_cmd_templates.py‎

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ def _level_visible(attr_level: str, usage_level: str = "Advanced") -> bool:
7777
# ---------------------------------------------------------------------------
7878

7979
def _write_attr_block(out: io.StringIO, key: str, value: dict, client: Optional[ServerClient] = None) -> None:
80-
"""Write a single ## attribute block into the StringIO buffer."""
81-
out.write(f"\n## {key}\n")
80+
"""Write a single ### attribute block into the StringIO buffer."""
81+
out.write(f"\n### {key}\n")
8282
out.write(f">\t**Input Required**: {value.get('input_required', 'false')}\n\n")
8383

8484
attr_type = value.get("data_type") or value.get("style") or ""
@@ -124,10 +124,15 @@ def _write_attr_block(out: io.StringIO, key: str, value: dict, client: Optional[
124124
if str(default_value).strip():
125125
out.write(f">\t**Default Value**: {default_value}\n\n")
126126

127+
if attr_type and attr_type.strip().lower() in ("dictionary", "keyvalue", "named dict"):
128+
out.write(">\t| Parameter Name | Parameter Value |\n")
129+
out.write(">\t|---|---|\n")
130+
out.write(">\t| example_key | example_value |\n\n")
131+
127132

128133
def _print_attr(key: str, value: dict, client: Optional[ServerClient] = None) -> None:
129134
"""Mirror of _write_attr_block for console output."""
130-
print(f"\n## {key}")
135+
print(f"\n### {key}")
131136
print(f">\tInput Required: {value.get('input_required', 'false')}")
132137

133138
attr_type = value.get("data_type") or value.get("style") or ""
@@ -172,30 +177,35 @@ def _print_attr(key: str, value: dict, client: Optional[ServerClient] = None) ->
172177
if str(default_value).strip():
173178
print(f">\tDefault Value: {default_value}")
174179

180+
if attr_type and attr_type.strip().lower() in ("dictionary", "keyvalue", "named dict"):
181+
print(">\t| Parameter Name | Parameter Value |")
182+
print(">\t|---|---|")
183+
print(">\t| example_key | example_value |\n")
184+
175185

176186
@logger.catch
177187
def main():
178188
"""Generate markdown command templates organised by family.
179189
180190
Each command file is structured as:
181191
182-
# <Command Name>
192+
## <Command Name>
183193
> <description>
184194
185-
# Required
186-
## <attr> ... (input_required=true, any visible level)
195+
## Required
196+
### <attr> ... (input_required=true, any visible level)
187197
188-
# Common Properties
189-
## <attr> ... (level=Common, optional)
198+
## Common Properties
199+
### <attr> ... (level=Common, optional)
190200
191-
# <DisplayName> Properties
192-
## <attr> ... (level=Domain, optional; heading uses command display_name)
201+
## <DisplayName> Properties
202+
### <attr> ... (level=Domain, optional; heading uses command display_name)
193203
194-
# Additional Properties
195-
## <attr> ... (level=Basic, optional)
204+
## Additional Properties
205+
### <attr> ... (level=Basic, optional)
196206
197-
# Advanced Properties
198-
## <attr> ... (level=Advanced, optional)
207+
## Advanced Properties
208+
### <attr> ... (level=Advanced, optional)
199209
200210
Sections with no attributes are omitted entirely.
201211
Link commands only ever produce Required and Advanced sections from defaults;
@@ -258,7 +268,7 @@ def main():
258268

259269
family_dir = os.path.join(base_output_dir, family)
260270
os.makedirs(family_dir, exist_ok=True)
261-
print(f"\n# Family: {family}\n")
271+
print(f"\n## Family: {family}\n")
262272

263273
for command in sorted(command_list):
264274
values = commands[command]
@@ -326,13 +336,14 @@ def main():
326336
# Build the output buffer
327337
# -----------------------------------------------------------
328338
command_output = io.StringIO()
329-
command_output.write(f"# {command}\n")
339+
command_output.write("___\n\n")
340+
command_output.write(f"## {command}\n")
330341
if command_description:
331342
command_output.write(f"> {command_description}\n")
332343
if alternate_names:
333344
command_output.write(f">\n>\t**Alternative Names**: {alternate_names}\n")
334345

335-
print(f"\n# {command}")
346+
print(f"\n## {command}")
336347
if command_description:
337348
print(f"> {command_description}")
338349
if alternate_names:
@@ -352,13 +363,15 @@ def main():
352363
if not attr_list:
353364
continue # omit empty sections entirely
354365

355-
# command_output.write(f"\n# {section_title}\n")
356-
# print(f"\n# {section_title}")
366+
# command_output.write(f"\n## {section_title}\n")
367+
# print(f"\n## {section_title}")
357368

358369
for key, value in attr_list:
359370
_write_attr_block(command_output, key, value, client=client)
360371
_print_attr(key, value, client=client)
361372

373+
command_output.write("\n___\n")
374+
362375
# -----------------------------------------------------------
363376
# Save to file
364377
# -----------------------------------------------------------

‎docs/design/dr-egeria-execution-engine-walkthrough.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ I verified the v2 engine's compatibility with legacy DrE markdown samples using
4242
- **Infrastructure Fixes**: Resolved bugs in `processors.py` regarding parser initialization and `DrECommand.raw_block` attribute naming.
4343

4444
```bash
45-
PYTHONPATH=. EGERIA_ROOT_PATH=sample-data python commands/cat/dr_egeria_md.py --input-file Simple-Data-Lens.md --directive validate
45+
PYTHONPATH=. EGERIA_ROOT_PATH=sample-data python commands/cat/dr_egeria.py Simple-Data-Lens.md --directive validate
4646
```
4747

4848
## Phase Status Summary
@@ -78,22 +78,22 @@ PYTHONPATH=. EGERIA_ROOT_PATH=sample-data python commands/cat/dr_egeria_md.py --
7878

7979
```bash
8080
# Sequential execution of multiple commands in feedback.md
81-
PYTHONPATH=. EGERIA_ROOT_PATH=sample-data python commands/cat/dr_egeria_md.py --input-file feedback.md --directive validate
81+
PYTHONPATH=. EGERIA_ROOT_PATH=sample-data python commands/cat/dr_egeria.py feedback.md --directive validate
8282
```
8383

8484
### 5. Invocation & Dependency Fixes
8585

8686
- **Unified Entry Point**: Renamed the CLI script to `commands/cat/dr_egeria.py` and added a `dr_egeria` script alias in `pyproject.toml`.
8787
- **Direct Execution**: Restored the `if __name__ == "__main__"` block in `md_processing/dr_egeria.py` for manual invocation.
8888
- **Dependency Management**: Updated the `v2/README.md` with explicit installation instructions (`pip install -e .`) to ensure all dependencies like `loguru` are correctly resolved.
89-
- **Consistency**: Standardized command-line arguments to use `--input-file` across all entry points.
89+
- **Consistency**: Standardized command-line arguments to use a positional input file across all entry points.
9090

9191
```bash
9292
# Correct usage with installed script
93-
EGERIA_ROOT_PATH=sample-data dr_egeria --input-file project.md --directive validate
93+
EGERIA_ROOT_PATH=sample-data dr_egeria project.md --directive validate
9494

9595
# Direct execution of the command in /commands/cat
96-
EGERIA_ROOT_PATH=sample-data python commands/cat/dr_egeria.py --input-file project.md --directive validate
96+
EGERIA_ROOT_PATH=sample-data python commands/cat/dr_egeria.py project.md --directive validate
9797
```
9898

9999
### 6. Configuration & Path Robustness
@@ -104,7 +104,7 @@ EGERIA_ROOT_PATH=sample-data python commands/cat/dr_egeria.py --input-file proje
104104

105105
```bash
106106
# Works out of the box from the project root
107-
dr_egeria --input-file project.md --directive validate
107+
dr_egeria project.md --directive validate
108108
```
109109

110110
## Final Status Summary

‎docs/design/dr-egeria-module-structure.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ sequenceDiagram
7979
participant SDK as EgeriaTech / OMVS
8080
participant EGERIA as Egeria Server
8181
82-
User->>CLI: hey_egeria cat process-markdown-file --input-file X.md [--validate|--process] [--advanced] [--debug]
82+
User->>CLI: dr_egeria X.md [--validate|--process] [--advanced] [--debug]
8383
CLI->>ORCH: asyncio.run(process_md_file_v2(..., debug=...))
8484
8585
opt debug=True
@@ -244,7 +244,7 @@ flowchart LR
244244

245245
| File | Role |
246246
|------|------|
247-
| `commands/cat/dr_egeria.py` | Click CLI entry-point; `--validate` / `--process` shortcut flags, `--advanced`, `--debug` |
247+
| `commands/cat/dr_egeria.py` | Click CLI entry-point: `dr_egeria <file.md>`. Shortcuts: `--validate` / `--process`, `--advanced`, `--debug` |
248248
| `md_processing/dr_egeria.py` | Async orchestrator: extraction → dispatch → output; owns debug patch/restore |
249249
| `md_processing/v2/extraction.py` | `UniversalExtractor` — produces `DrECommand` list from raw Markdown |
250250
| `md_processing/v2/parsing.py` | `AttributeFirstParser` — async, spec-driven, with server-side validation cache |
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Proposal: Shift Dr.Egeria Markdown Syntax Hierarchy
2+
3+
This plan outlines the changes required to adjust the Dr.Egeria parsing and generation syntax so that:
4+
- **`#` (Single Hash)**: Ignored by the parser; treated as standard Markdown document headers or comments.
5+
- **`##` (Double Hash)**: Defines a Dr.Egeria Command.
6+
- **`###` (Triple Hash)**: Defines a Command Attribute.
7+
8+
## Motivation
9+
This change will allow users to comfortably use single-hash `#` headers to title their overarching Markdown documents or sections, reserving the `##` and `###` headers strictly for Dr.Egeria execution contexts.
10+
11+
## Proposed Changes
12+
13+
### 1. Update the Extraction Logic (`md_processing/v2/extraction.py`)
14+
The `UniversalExtractor` determines how markdown text is split into command blocks and attributes.
15+
- **Command Header Matching**:
16+
- Update `self.cmd_header_rx` to match `## <Verb> <Object>`.
17+
- Update `_split_into_blocks()` so that it looks for `line.startswith("## ")` instead of `line.startswith("# ")` when identifying block boundaries.
18+
- **Attribute Header Matching**:
19+
- Update `_extract_attributes_from_block()` to use `^###\s+` in its regular expression instead of `^##\s+`.
20+
21+
### 2. Update Template Generation (`commands/tech/generate_md_cmd_templates.py`)
22+
The generator needs to produce templates in the new syntax.
23+
- **Command Titles**: Update `_generate_md_file_content()` and the console output equivalent to prefix commands with `## ` instead of `# `.
24+
- **Attribute Titles**: Update `_write_attr_block()` and `_print_attr()` to prefix attributes with `### ` instead of `## `.
25+
26+
### 3. Documentation & Sample Data Migration
27+
Existing Markdown files (smoke tests, templates, inbox documentation) will no longer parse correctly.
28+
- Run `uv run gen_md_cmd_templates --usage-level Basic` and `--usage-level Advanced` to forcefully regenerate all built-in template files in the new syntax.
29+
- Manually (or via script) update `sample-data/smoke_test_gov_officer.md` and `sample-data/smoke_test_terms_and_conditions.md` to shift all hashes down one level.
30+
- Any Markdown files provided to users in the inbox (e.g. `dr_egeria_intro_*.md`) may need an update to reflect the new expected format.
31+
32+
## Verification Plan
33+
34+
### Automated Verification
35+
- Re-run Pytest unit tests. Many tests under `tests/micro-tests/` that feed string literals to `UniversalExtractor` will fail and need to be updated to the new `##` and `###` syntax.
36+
- Specifically, the parser tests in `tests/micro-tests/md_processing/v2/test_extraction.py` will serve as our primary validation that the single hash `#` is ignored and `##`/`###` parse correctly.
37+
38+
### Manual Verification
39+
- Re-run validation on the smoke tests: `dr_egeria sample-data/smoke_test_gov_officer.md --validate` to ensure it parses successfully.
40+
41+
> [!WARNING]
42+
> **Breaking Change**: This is a breaking change for any existing user Markdown scripts. Users who have created their own `dr_egeria` markdown documents will need to run a quick search-and-replace to shift their headers down one level.
43+
44+
Does this approach and scope of changes align with what you had in mind?

0 commit comments

Comments
 (0)