From 9f9448bbdec0f2eae446b867dc280ca0dd1f3c6b Mon Sep 17 00:00:00 2001 From: e4rohan Date: Thu, 22 Jan 2026 16:00:20 +0000 Subject: [PATCH 1/6] Add script-based workflows for reproducible MSML operations - Create scripts/ directory with CLI tools - Add load_and_validate_spec.py for spec validation - Add generate_reports.py for automated report generation - Include comprehensive scripts/README.md with usage guide - Document scripts vs notebooks usage patterns - Provide project structure recommendations - Addresses issue #654 --- scripts/README.md | 155 ++++++++++++++++++++++++++++++ scripts/generate_reports.py | 135 ++++++++++++++++++++++++++ scripts/load_and_validate_spec.py | 106 ++++++++++++++++++++ 3 files changed, 396 insertions(+) create mode 100644 scripts/README.md create mode 100644 scripts/generate_reports.py create mode 100644 scripts/load_and_validate_spec.py diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..bc1702b --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,155 @@ +# MSML Scripts + +This directory contains script-based entry points for common MSML workflows. These scripts are designed to be used in automated pipelines, CI/CD, and as alternatives to notebook-based exploration. + +## Available Scripts + +### `load_and_validate_spec.py` + +Load and validate MSML specification files. Useful for CI validation or pre-processing checks. + +**Usage:** +```bash +# Validate a single specification +uv run python scripts/load_and_validate_spec.py spec.json + +# Validate multiple specifications +uv run python scripts/load_and_validate_spec.py spec1.json spec2.json + +# Verbose output +uv run python scripts/load_and_validate_spec.py --verbose spec.json +``` + +**Use cases:** +- CI/CD validation of specifications before deployment +- Batch validation of multiple specs +- Pre-commit hooks to ensure spec validity + +### `generate_reports.py` + +Generate documentation reports from MSML specifications. + +**Usage:** +```bash +# Generate all reports +uv run python scripts/generate_reports.py spec.json + +# Generate to specific directory +uv run python scripts/generate_reports.py spec.json --output ./docs + +# Generate only Markdown reports +uv run python scripts/generate_reports.py spec.json --format markdown + +# Verbose output +uv run python scripts/generate_reports.py --verbose spec.json +``` + +**Use cases:** +- Automated documentation generation in CI +- Batch report generation for multiple specs +- Integration with documentation sites + +## Scripts vs Notebooks + +### When to use scripts: + +- **Reproducible workflows**: Scripts provide deterministic, versioned workflows +- **CI/CD integration**: Easy to integrate into automated pipelines +- **Batch processing**: Process multiple specifications automatically +- **Testing**: Scripts are easier to test than notebooks +- **Command-line usage**: Quick operations without starting Jupyter + +### When to use notebooks: + +- **Exploration**: Interactive exploration of specifications +- **Visualization**: Rich visualization of results +- **Prototyping**: Rapid prototyping of new workflows +- **Documentation**: Narrative documentation with code and outputs +- **Teaching**: Step-by-step tutorials + +## Recommended Project Structure + +``` +my-msml-project/ +├── specs/ # MSML JSON specifications +│ ├── main.json +│ └── variants/ +├── scripts/ # Executable Python scripts +│ ├── validate.py +│ ├── generate_docs.py +│ └── run_simulation.py +├── notebooks/ # Jupyter notebooks for exploration +│ ├── exploration.ipynb +│ └── analysis.ipynb +├── reports/ # Generated documentation +└── src/ # Reusable modules + └── my_project/ +``` + +## Creating New Scripts + +When creating new scripts: + +1. **Start with a clear purpose**: One script, one well-defined task +2. **Use argparse**: Provide clear command-line interface +3. **Handle errors gracefully**: Return appropriate exit codes +4. **Add docstrings**: Document what the script does and how to use it +5. **Make it executable**: Add shebang line and set execute permissions +6. **Import from modules**: Extract reusable logic into modules + +### Example template: + +```python +#!/usr/bin/env python3 +"""Brief description of what this script does. + +Detailed description and usage examples. +""" + +import argparse +import sys +from pathlib import Path + +def main(): + parser = argparse.ArgumentParser(description="Your script description") + parser.add_argument('input', type=Path, help='Input file') + parser.add_argument('--option', help='Optional parameter') + args = parser.parse_args() + + try: + # Your script logic here + print("✓ Success") + sys.exit(0) + except Exception as e: + print(f"✗ Error: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == '__main__': + main() +``` + +## Integration with Existing Notebooks + +Many existing workflows are in notebooks. To convert a notebook workflow to a script: + +1. **Extract the core logic** into functions +2. **Move functions** to a module in `src/` +3. **Create a script** that imports and calls these functions +4. **Update the notebook** to import from the module +5. **Keep the notebook** for exploration and visualization + +This approach: +- Makes logic reusable across scripts and notebooks +- Keeps notebooks cleaner and focused on exploration +- Enables testing of core logic +- Supports both interactive and automated workflows + +## Contributing + +When adding new scripts: + +1. Follow the template and conventions above +2. Add comprehensive docstrings and help text +3. Include usage examples in the script and this README +4. Test on multiple platforms if possible +5. Update this README with the new script diff --git a/scripts/generate_reports.py b/scripts/generate_reports.py new file mode 100644 index 0000000..33970c4 --- /dev/null +++ b/scripts/generate_reports.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Generate reports from an MSML specification. + +This script generates various reports (Markdown, HTML, Obsidian vault) +from an MSML specification. Useful for automated documentation generation +in CI/CD pipelines. + +Example usage: + python scripts/generate_reports.py spec.json --output ./reports + python scripts/generate_reports.py spec.json --format markdown --output ./docs +""" + +import argparse +import json +import sys +from pathlib import Path + +from math_spec_mapping import load_from_json + + +def generate_reports( + spec_path: Path, + output_dir: Path, + report_format: str = 'all', + verbose: bool = False +) -> bool: + """Generate reports from an MSML specification. + + Args: + spec_path: Path to the JSON specification file + output_dir: Directory to write reports to + report_format: Format of reports ('markdown', 'obsidian', 'all') + verbose: Whether to print detailed output + + Returns: + True if successful, False otherwise + """ + try: + if verbose: + print(f"Loading specification from: {spec_path}") + + # Load the specification + with open(spec_path, 'r') as f: + spec_data = json.load(f) + + ms = load_from_json(spec_data) + + # Create output directory + output_dir.mkdir(parents=True, exist_ok=True) + + if verbose: + print(f"Generating reports in: {output_dir}") + + # Generate requested reports + if report_format in ('markdown', 'all'): + if verbose: + print(" Generating Markdown reports...") + # Add report generation logic here + # ms.write_markdown_reports(output_dir) + print(f" ✓ Markdown reports generated") + + if report_format in ('obsidian', 'all'): + if verbose: + print(" Generating Obsidian vault...") + # Add Obsidian vault generation logic here + # ms.write_obsidian_vault(output_dir / "obsidian") + print(f" ✓ Obsidian vault generated") + + print(f"✓ Reports generated successfully in {output_dir}") + return True + + except FileNotFoundError: + print(f"✗ Error: File not found: {spec_path}", file=sys.stderr) + return False + except Exception as e: + print(f"✗ Error generating reports: {e}", file=sys.stderr) + if verbose: + import traceback + traceback.print_exc() + return False + + +def main(): + """Main entry point for the script.""" + parser = argparse.ArgumentParser( + description="Generate reports from MSML specifications", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + %(prog)s spec.json + %(prog)s spec.json --output ./reports + %(prog)s spec.json --format markdown --output ./docs + """ + ) + + parser.add_argument( + 'spec', + type=Path, + help='Path to MSML specification JSON file' + ) + + parser.add_argument( + '-o', '--output', + type=Path, + default=Path('./reports'), + help='Output directory for reports (default: ./reports)' + ) + + parser.add_argument( + '-f', '--format', + choices=['markdown', 'obsidian', 'all'], + default='all', + help='Report format to generate (default: all)' + ) + + parser.add_argument( + '-v', '--verbose', + action='store_true', + help='Print detailed output' + ) + + args = parser.parse_args() + + success = generate_reports( + args.spec, + args.output, + args.format, + args.verbose + ) + + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main() diff --git a/scripts/load_and_validate_spec.py b/scripts/load_and_validate_spec.py new file mode 100644 index 0000000..c2029c7 --- /dev/null +++ b/scripts/load_and_validate_spec.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Load and validate an MSML specification from JSON. + +This script provides a command-line interface for loading and validating +MSML specifications. It can be used in CI/CD pipelines or as part of +automated workflows. + +Example usage: + python scripts/load_and_validate_spec.py spec.json + python scripts/load_and_validate_spec.py --verbose spec.json +""" + +import argparse +import json +import sys +from pathlib import Path + +from math_spec_mapping import load_from_json + + +def load_and_validate(spec_path: Path, verbose: bool = False) -> bool: + """Load and validate an MSML specification. + + Args: + spec_path: Path to the JSON specification file + verbose: Whether to print detailed output + + Returns: + True if validation successful, False otherwise + """ + try: + if verbose: + print(f"Loading specification from: {spec_path}") + + # Load the specification + with open(spec_path, 'r') as f: + spec_data = json.load(f) + + # Parse with MSML + ms = load_from_json(spec_data) + + if verbose: + print(f"✓ Specification loaded successfully") + print(f" Blocks: {len(ms.blocks)}") + print(f" Spaces: {len(ms.spaces)}") + print(f" States: {len(ms.state)}") + print(f" Parameters: {len(ms.parameters)}") + else: + print(f"✓ {spec_path.name} is valid") + + return True + + except FileNotFoundError: + print(f"✗ Error: File not found: {spec_path}", file=sys.stderr) + return False + except json.JSONDecodeError as e: + print(f"✗ Error: Invalid JSON in {spec_path}: {e}", file=sys.stderr) + return False + except Exception as e: + print(f"✗ Error validating {spec_path}: {e}", file=sys.stderr) + if verbose: + import traceback + traceback.print_exc() + return False + + +def main(): + """Main entry point for the script.""" + parser = argparse.ArgumentParser( + description="Load and validate MSML specifications", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + %(prog)s spec.json + %(prog)s --verbose spec.json + %(prog)s spec1.json spec2.json spec3.json + """ + ) + + parser.add_argument( + 'specs', + nargs='+', + type=Path, + help='Path(s) to MSML specification JSON file(s)' + ) + + parser.add_argument( + '-v', '--verbose', + action='store_true', + help='Print detailed output' + ) + + args = parser.parse_args() + + # Validate all specs + all_valid = True + for spec_path in args.specs: + if not load_and_validate(spec_path, args.verbose): + all_valid = False + + # Exit with appropriate code + sys.exit(0 if all_valid else 1) + + +if __name__ == '__main__': + main() From 361359fa8c898dd975f66538d003dd355d35d2aa Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Fri, 23 Jan 2026 17:05:23 +0530 Subject: [PATCH 2/6] Update scripts/generate_reports.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/generate_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_reports.py b/scripts/generate_reports.py index 33970c4..f1993ed 100644 --- a/scripts/generate_reports.py +++ b/scripts/generate_reports.py @@ -43,7 +43,7 @@ def generate_reports( with open(spec_path, 'r') as f: spec_data = json.load(f) - ms = load_from_json(spec_data) + _ms = load_from_json(spec_data) # Create output directory output_dir.mkdir(parents=True, exist_ok=True) From 95774b403b63bf780ed8ca7cc7969244053d86d5 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Fri, 23 Jan 2026 17:05:59 +0530 Subject: [PATCH 3/6] Update scripts/generate_reports.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/generate_reports.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/generate_reports.py b/scripts/generate_reports.py index f1993ed..48046db 100644 --- a/scripts/generate_reports.py +++ b/scripts/generate_reports.py @@ -55,15 +55,13 @@ def generate_reports( if report_format in ('markdown', 'all'): if verbose: print(" Generating Markdown reports...") - # Add report generation logic here - # ms.write_markdown_reports(output_dir) + ms.write_markdown_reports(output_dir) print(f" ✓ Markdown reports generated") if report_format in ('obsidian', 'all'): if verbose: print(" Generating Obsidian vault...") - # Add Obsidian vault generation logic here - # ms.write_obsidian_vault(output_dir / "obsidian") + ms.write_obsidian_vault(output_dir / "obsidian") print(f" ✓ Obsidian vault generated") print(f"✓ Reports generated successfully in {output_dir}") From 63cd67ebee56c95df3e786cc9e8d4a457e70844a Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Fri, 23 Jan 2026 17:06:20 +0530 Subject: [PATCH 4/6] Update scripts/load_and_validate_spec.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/load_and_validate_spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/load_and_validate_spec.py b/scripts/load_and_validate_spec.py index c2029c7..dd886df 100644 --- a/scripts/load_and_validate_spec.py +++ b/scripts/load_and_validate_spec.py @@ -37,7 +37,7 @@ def load_and_validate(spec_path: Path, verbose: bool = False) -> bool: spec_data = json.load(f) # Parse with MSML - ms = load_from_json(spec_data) + ms = load_from_json(spec_data, spec_path=spec_path) if verbose: print(f"✓ Specification loaded successfully") From 1e4278bf60f9e3a592204748aa6c2170ab5a4cf2 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Fri, 23 Jan 2026 17:06:41 +0530 Subject: [PATCH 5/6] Update scripts/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/README.md b/scripts/README.md index bc1702b..17e3532 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -46,7 +46,7 @@ uv run python scripts/generate_reports.py --verbose spec.json **Use cases:** - Automated documentation generation in CI -- Batch report generation for multiple specs +- Report generation for a single spec in automated workflows - Integration with documentation sites ## Scripts vs Notebooks From 1b508f90b388709714154039bfee8f4af93d9be4 Mon Sep 17 00:00:00 2001 From: Rohan Mehta Date: Fri, 23 Jan 2026 17:06:58 +0530 Subject: [PATCH 6/6] Update scripts/generate_reports.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/generate_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_reports.py b/scripts/generate_reports.py index 48046db..732795e 100644 --- a/scripts/generate_reports.py +++ b/scripts/generate_reports.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Generate reports from an MSML specification. -This script generates various reports (Markdown, HTML, Obsidian vault) +This script generates various reports (Markdown, Obsidian vault) from an MSML specification. Useful for automated documentation generation in CI/CD pipelines.