feat: add CI-friendly example generation and verification

- Add 'make examples' to regenerate README images
- Add 'make verify-examples' for CI verification
- Create GitHub Action to verify examples are up to date
- Improve generate_examples.py with better error handling
- Add update-examples.sh convenience script
- Document development workflow in README

This ensures README examples always match the actual code output.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-09-09 15:55:53 -05:00
parent 3c9c06acc9
commit 1adbd1a5ff
5 changed files with 156 additions and 6 deletions

52
.github/workflows/verify-examples.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Verify Examples
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
paths:
- 'src/**'
- 'templates/**'
- 'docs/images/**'
- '.github/workflows/verify-examples.yml'
jobs:
verify-examples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y poppler-utils qpdf
- name: Install Typst
run: |
# Download and install Typst
wget https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz
tar -xf typst-x86_64-unknown-linux-musl.tar.xz
sudo mv typst-x86_64-unknown-linux-musl/typst /usr/local/bin/
typst --version
- name: Install Python dependencies
run: |
pip install pyyaml
- name: Verify examples are up to date
run: |
make verify-examples
- name: Upload example images if changed
if: failure()
uses: actions/upload-artifact@v3
with:
name: updated-examples
path: docs/images/

View File

@ -1,4 +1,4 @@
.PHONY: all clean install test samples help check-deps .PHONY: all clean install test samples help check-deps examples verify-examples
# Default target # Default target
all: out/flashcards.pdf out/flashcards_linear.pdf all: out/flashcards.pdf out/flashcards_linear.pdf
@ -42,6 +42,23 @@ test: check-deps
@command -v qpdf >/dev/null 2>&1 && qpdf --check out/test.pdf || echo "PDF generated (validation skipped)" @command -v qpdf >/dev/null 2>&1 && qpdf --check out/test.pdf || echo "PDF generated (validation skipped)"
@echo "Test completed successfully" @echo "Test completed successfully"
# Generate README example images
examples: check-deps
@echo "Generating example images for README..."
@python3 src/generate_examples.py
@echo "✓ Example images generated in docs/images/"
# Verify examples are up to date (for CI)
verify-examples: examples
@echo "Verifying example images are up to date..."
@if git diff --quiet docs/images/; then \
echo "✓ Example images are up to date"; \
else \
echo "✗ Example images have changed - please run 'make examples' and commit the changes"; \
git diff --stat docs/images/; \
exit 1; \
fi
# Clean output files # Clean output files
clean: clean:
rm -rf out/ rm -rf out/
@ -53,6 +70,8 @@ help:
@echo " make Generate default flashcards (0-9)" @echo " make Generate default flashcards (0-9)"
@echo " make samples Generate all sample configurations" @echo " make samples Generate all sample configurations"
@echo " make test Run a quick test build" @echo " make test Run a quick test build"
@echo " make examples Generate README example images"
@echo " make verify-examples Verify examples are up to date (CI)"
@echo " make install Install dependencies (macOS)" @echo " make install Install dependencies (macOS)"
@echo " make clean Remove all generated files" @echo " make clean Remove all generated files"
@echo " make help Show this help message" @echo " make help Show this help message"

View File

@ -394,6 +394,38 @@ The `SorobanGenerator` class provides:
All methods use clean TypeScript interfaces with proper types - no shell command building required! All methods use clean TypeScript interfaces with proper types - no shell command building required!
## Development
### Updating Example Images
If you make changes that affect the visual output, please update the example images:
```bash
# Regenerate all example images
make examples
# Or use the update script
./scripts/update-examples.sh
# Verify examples are up to date (CI will also check this)
make verify-examples
```
The CI pipeline will automatically verify that example images are up to date with the code.
### Running Tests
```bash
# Quick test build
make test
# Generate all samples
make samples
# Full CI verification
make verify-examples
```
## License ## License
MIT License - see LICENSE file for details. MIT License - see LICENSE file for details.

25
scripts/update-examples.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# Update README example images
# Run this script when you make changes that affect the visual output
set -e
echo "🔄 Updating README example images..."
# Run the example generator
if ! python3 src/generate_examples.py; then
echo "❌ Failed to generate examples"
exit 1
fi
# Check if there are changes
if git diff --quiet docs/images/; then
echo "✅ No changes to example images"
else
echo "📝 Example images have been updated:"
git diff --stat docs/images/
echo ""
echo "Please review the changes and commit them if they look correct:"
echo " git add docs/images/"
echo " git commit -m 'docs: update example images'"
fi

View File

@ -183,8 +183,25 @@ def main():
examples = generate_example_pdfs() examples = generate_example_pdfs()
# Check if we have a PDF to PNG converter available
converters = []
for cmd in ['pdftoppm', 'convert', 'gs']:
if subprocess.run(['which', cmd], capture_output=True).returncode == 0:
converters.append(cmd)
if not converters:
print("ERROR: No PDF to PNG converter found. Please install one of:")
print(" - poppler-utils (for pdftoppm)")
print(" - imagemagick (for convert)")
print(" - ghostscript (for gs)")
print("\nOn Ubuntu/Debian: sudo apt-get install poppler-utils")
print("On macOS: brew install poppler")
return 1
print(f"Using PDF converters: {', '.join(converters)}")
print("Generating example images...") print("Generating example images...")
failed = []
for example in examples: for example in examples:
print(f" - {example['name']}: {example['desc']}") print(f" - {example['name']}: {example['desc']}")
@ -207,7 +224,8 @@ def main():
if pdf_to_png(str(pdf_path), str(front_png), dpi=150, page=1): if pdf_to_png(str(pdf_path), str(front_png), dpi=150, page=1):
print(f" ✓ Generated {front_png.name}") print(f" ✓ Generated {front_png.name}")
else: else:
print(f" WARNING: Could not convert to PNG (install pdftoppm, ImageMagick, or Ghostscript)") print(f" ✗ Failed to convert {front_png.name}")
failed.append(example['name'])
# For single cards, also generate back page # For single cards, also generate back page
if '--cards-per-page' in example['args'] and example['args'][example['args'].index('--cards-per-page') + 1] == '1': if '--cards-per-page' in example['args'] and example['args'][example['args'].index('--cards-per-page') + 1] == '1':
@ -215,9 +233,13 @@ def main():
if pdf_to_png(str(pdf_path), str(back_png), dpi=150, page=2): if pdf_to_png(str(pdf_path), str(back_png), dpi=150, page=2):
print(f" ✓ Generated {back_png.name}") print(f" ✓ Generated {back_png.name}")
print("\nExample images generated in docs/images/") if failed:
print("\nTo use in README:") print(f"\n✗ Failed to generate {len(failed)} images: {', '.join(failed)}")
print("![Description](docs/images/example-name-front.png)") return 1
else:
print(f"\n✓ Successfully generated all {len(examples)} examples in docs/images/")
return 0
if __name__ == '__main__': if __name__ == '__main__':
main() import sys
sys.exit(main())