diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..17e3532 --- /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 +- Report generation for a single spec in automated workflows +- 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..732795e --- /dev/null +++ b/scripts/generate_reports.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Generate reports from an MSML specification. + +This script generates various reports (Markdown, 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...") + ms.write_markdown_reports(output_dir) + print(f" ✓ Markdown reports generated") + + if report_format in ('obsidian', 'all'): + if verbose: + print(" Generating Obsidian vault...") + 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..dd886df --- /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, spec_path=spec_path) + + 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()