Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -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
133 changes: 133 additions & 0 deletions scripts/generate_reports.py
Original file line number Diff line number Diff line change
@@ -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()
106 changes: 106 additions & 0 deletions scripts/load_and_validate_spec.py
Original file line number Diff line number Diff line change
@@ -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()