🎨 manage comics via yaml files
This commit is contained in:
67
CLAUDE.md
67
CLAUDE.md
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
Sunday Comics is a Flask-based webcomic website with server-side rendering and client-side navigation. Comics are stored as simple Python dictionaries in `comics_data.py`, making the system easy to manage without a database.
|
||||
Sunday Comics is a Flask-based webcomic website with server-side rendering and client-side navigation. Comics are stored as individual YAML files in `data/comics/`, making them easy to manage without a database. Each comic gets its own file for clean organization and version control.
|
||||
|
||||
## Development Commands
|
||||
|
||||
@@ -24,7 +24,13 @@ python app.py
|
||||
```bash
|
||||
python scripts/add_comic.py
|
||||
```
|
||||
This creates a new entry in `comics_data.py` with defaults. Edit the file afterwards to customize title, alt_text, author_note, etc.
|
||||
This creates a new YAML file in `data/comics/` with the next comic number and reasonable defaults. Edit the generated file to customize title, alt_text, author_note, etc.
|
||||
|
||||
**Add a new comic with markdown author note:**
|
||||
```bash
|
||||
python scripts/add_comic.py -m
|
||||
```
|
||||
This also creates a markdown file in `content/author_notes/` for the author note.
|
||||
|
||||
**Generate RSS feed:**
|
||||
```bash
|
||||
@@ -40,8 +46,23 @@ Run this after adding/updating comics to regenerate `static/sitemap.xml` for sea
|
||||
|
||||
## Architecture
|
||||
|
||||
### Data Layer: comics_data.py
|
||||
Comics are stored as a Python list called `COMICS`. Each comic is a dictionary with:
|
||||
### Data Layer: YAML Files in data/comics/
|
||||
|
||||
Comics are stored as individual YAML files in the `data/comics/` directory. The `data_loader.py` module automatically loads all `.yaml` files (except `TEMPLATE.yaml` and `README.yaml`), sorts them by comic number, and builds the `COMICS` list.
|
||||
|
||||
**File structure:**
|
||||
- `data/comics/001.yaml` - Comic #1
|
||||
- `data/comics/002.yaml` - Comic #2
|
||||
- `data/comics/003.yaml` - Comic #3
|
||||
- `data/comics/TEMPLATE.yaml` - Template for new comics (ignored by loader)
|
||||
- `data/comics/README.md` - Documentation for comic files
|
||||
|
||||
**Adding a new comic:**
|
||||
1. Use `python scripts/add_comic.py` to auto-generate the next comic file
|
||||
2. OR manually copy `TEMPLATE.yaml` and rename it
|
||||
3. Edit the YAML file to set comic properties
|
||||
|
||||
Each comic YAML file contains:
|
||||
- `number` (required): Sequential comic number
|
||||
- `filename` (required): Image filename in `static/images/comics/` OR list of filenames for multi-image comics (webtoon style)
|
||||
- `date` (required): Publication date in YYYY-MM-DD format
|
||||
@@ -53,11 +74,19 @@ Comics are stored as a Python list called `COMICS`. Each comic is a dictionary w
|
||||
- `plain` (optional): Override global PLAIN_DEFAULT setting (hides header/border)
|
||||
- `section` (optional): Section/chapter title (e.g., "Chapter 1: Origins"). Add to first comic of a new section.
|
||||
|
||||
**Multi-image comics (webtoon style):**
|
||||
- Set `filename` to a list of image filenames: `['page1.png', 'page2.png', 'page3.png']`
|
||||
- Set `alt_text` to either:
|
||||
- A single string (applies to all images): `'A three-part vertical story'`
|
||||
- A list matching each image: `['Description 1', 'Description 2', 'Description 3']`
|
||||
**Multi-image comics (webtoon style) in YAML:**
|
||||
```yaml
|
||||
filename:
|
||||
- page1.png
|
||||
- page2.png
|
||||
- page3.png
|
||||
alt_text:
|
||||
- "Description 1"
|
||||
- "Description 2"
|
||||
- "Description 3"
|
||||
```
|
||||
- Set `filename` to a list of image filenames
|
||||
- Set `alt_text` to either a single string (applies to all images) or a list matching each image
|
||||
- If `alt_text` is a list but doesn't match `filename` length, a warning is logged
|
||||
- Images display vertically with seamless stacking (no gaps)
|
||||
- First image loads immediately; subsequent images lazy-load as user scrolls
|
||||
@@ -154,21 +183,25 @@ Global context variables injected into all templates:
|
||||
|
||||
## Important Implementation Details
|
||||
|
||||
1. **Comic ordering**: COMICS list order determines comic sequence. Last item is the "latest" comic.
|
||||
1. **Comic loading**: The `data_loader.py` module scans `data/comics/` for `.yaml` files, loads them, validates required fields, and sorts by comic number. TEMPLATE.yaml and README.yaml are automatically ignored.
|
||||
|
||||
2. **Enrichment pattern**: Always use `enrich_comic()` before passing comics to templates or APIs. This adds computed properties like `full_width`, `plain`, and `formatted_date`.
|
||||
2. **Comic ordering**: COMICS list order (determined by the `number` field in each YAML file) determines comic sequence. Last item is the "latest" comic.
|
||||
|
||||
3. **Date formatting**: The `format_comic_date()` function uses `%d` with lstrip('0') for cross-platform compatibility (not all systems support `%-d`).
|
||||
3. **Enrichment pattern**: Always use `enrich_comic()` before passing comics to templates or APIs. This adds computed properties like `full_width`, `plain`, and `formatted_date`.
|
||||
|
||||
4. **Author notes hierarchy**: If `author_note_md` field is specified, the markdown file is loaded and rendered as HTML, taking precedence over the plain text `author_note` field. When markdown is used, `author_note_is_html` is set to True.
|
||||
4. **Date formatting**: The `format_comic_date()` function uses `%d` with lstrip('0') for cross-platform compatibility (not all systems support `%-d`).
|
||||
|
||||
5. **Settings cascade**: Global settings (FULL_WIDTH_DEFAULT, PLAIN_DEFAULT) apply unless overridden per-comic with `full_width` or `plain` keys.
|
||||
5. **Author notes hierarchy**: If `author_note_md` field is specified, the markdown file is loaded and rendered as HTML, taking precedence over the plain text `author_note` field. When markdown is used, `author_note_is_html` is set to True.
|
||||
|
||||
6. **Navigation state**: Client-side navigation reads `data-total-comics` and `data-comic-number` from the `.comic-container` element to manage button states.
|
||||
6. **Settings cascade**: Global settings (FULL_WIDTH_DEFAULT, PLAIN_DEFAULT) apply unless overridden per-comic with `full_width` or `plain` keys in the YAML file.
|
||||
|
||||
7. **Comic icon navigation**: When `USE_COMIC_NAV_ICONS` is True, templates use `.btn-icon-nav` class with icon images instead of text buttons. JavaScript automatically detects icon mode and applies appropriate classes. Disabled icons have reduced opacity (0.3).
|
||||
7. **Navigation state**: Client-side navigation reads `data-total-comics` and `data-comic-number` from the `.comic-container` element to manage button states.
|
||||
|
||||
8. **Archive sections**: When `SECTIONS_ENABLED` is True, comics with a `section` field will start a new section on the archive page. Only add the `section` field to the first comic of each new section. All subsequent comics belong to that section until a new `section` field is encountered.
|
||||
8. **Comic icon navigation**: When `USE_COMIC_NAV_ICONS` is True, templates use `.btn-icon-nav` class with icon images instead of text buttons. JavaScript automatically detects icon mode and applies appropriate classes. Disabled icons have reduced opacity (0.3).
|
||||
|
||||
9. **Archive sections**: When `SECTIONS_ENABLED` is True, comics with a `section` field will start a new section on the archive page. Only add the `section` field to the first comic of each new section. All subsequent comics belong to that section until a new `section` field is encountered.
|
||||
|
||||
10. **YAML validation**: The data loader validates each comic file and logs warnings for missing required fields (`number`, `filename`, `date`, `alt_text`). Invalid files are skipped.
|
||||
|
||||
## Production Deployment
|
||||
|
||||
|
||||
@@ -97,34 +97,17 @@ EMBED_ENABLED = True
|
||||
# When enabled, users can easily copy a direct link to the current comic
|
||||
PERMALINK_ENABLED = True
|
||||
|
||||
COMICS = [
|
||||
{
|
||||
'number': 1,
|
||||
'title': 'First Comic',
|
||||
'filename': 'comic-001.jpg',
|
||||
'mobile_filename': 'comic-001-mobile.jpg', # Optional: mobile version of the comic
|
||||
'date': '2025-01-01',
|
||||
'alt_text': 'The very first comic',
|
||||
'author_note': 'This is where your comic journey begins!',
|
||||
'author_note_md': '2025-01-01.md', # Optional: use markdown from content/author_notes/2025-01-01.md (overrides author_note)
|
||||
'full_width': True, # Optional: override FULL_WIDTH_DEFAULT for this comic
|
||||
'plain': True, # Optional: override PLAIN_DEFAULT for this comic
|
||||
'section': 'Chapter 1: The Beginning', # Optional: start a new section on archive page
|
||||
},
|
||||
{
|
||||
'number': 2,
|
||||
'filename': 'comic-002.jpg',
|
||||
'date': '2025-01-08',
|
||||
'alt_text': 'The second comic',
|
||||
'full_width': True,
|
||||
'plain': True,
|
||||
},
|
||||
{
|
||||
'number': 3,
|
||||
'title': 'Third Comic',
|
||||
'filename': 'comic-003.jpg',
|
||||
'date': '2025-01-15',
|
||||
'alt_text': 'The third comic',
|
||||
'author_note': 'Things are getting interesting!',
|
||||
},
|
||||
]
|
||||
# Load comics from YAML files
|
||||
from data_loader import load_comics_from_yaml, validate_comics
|
||||
|
||||
COMICS = load_comics_from_yaml('data/comics')
|
||||
|
||||
# Validate loaded comics
|
||||
if not validate_comics(COMICS):
|
||||
print("Warning: Comic validation failed. Please check your YAML files.")
|
||||
|
||||
# Show loaded comics count
|
||||
if COMICS:
|
||||
print(f"Loaded {len(COMICS)} comics from data/comics/")
|
||||
else:
|
||||
print("Warning: No comics loaded! Please add .yaml files to data/comics/")
|
||||
|
||||
23
data/comics/001.yaml
Normal file
23
data/comics/001.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
# Comic #1
|
||||
number: 1
|
||||
title: "First Comic"
|
||||
filename: comic-001.jpg
|
||||
mobile_filename: comic-001-mobile.jpg # Optional: mobile version of the comic
|
||||
date: "2025-01-01"
|
||||
alt_text: "The very first comic"
|
||||
|
||||
# Author notes (choose one method):
|
||||
# Option 1: Plain text note
|
||||
author_note: "This is where your comic journey begins!"
|
||||
|
||||
# Option 2: Markdown file (overrides author_note if present)
|
||||
# Just a filename looks in content/author_notes/
|
||||
# Or use a path like "special/note.md" relative to content/
|
||||
author_note_md: "2025-01-01.md"
|
||||
|
||||
# Display settings (override global defaults)
|
||||
full_width: true
|
||||
plain: true
|
||||
|
||||
# Section header (optional - only add to first comic of a new section)
|
||||
section: "Chapter 1: The Beginning"
|
||||
9
data/comics/002.yaml
Normal file
9
data/comics/002.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
# Comic #2
|
||||
number: 2
|
||||
filename: comic-002.jpg
|
||||
date: "2025-01-08"
|
||||
alt_text: "The second comic"
|
||||
|
||||
# Display settings
|
||||
full_width: true
|
||||
plain: true
|
||||
7
data/comics/003.yaml
Normal file
7
data/comics/003.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# Comic #3
|
||||
number: 3
|
||||
title: "Third Comic"
|
||||
filename: comic-003.jpg
|
||||
date: "2025-01-15"
|
||||
alt_text: "The third comic"
|
||||
author_note: "Things are getting interesting!"
|
||||
89
data/comics/README.md
Normal file
89
data/comics/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Comic Data Directory
|
||||
|
||||
This directory contains YAML files for managing individual comics. Each comic gets its own `.yaml` file.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Adding a New Comic
|
||||
|
||||
1. **Copy the template:**
|
||||
```bash
|
||||
cp TEMPLATE.yaml 004.yaml
|
||||
```
|
||||
|
||||
2. **Edit the file** with your comic's information:
|
||||
- Update `number`, `filename`, `date`, and `alt_text` (required)
|
||||
- Add optional fields like `title`, `author_note`, etc.
|
||||
|
||||
3. **Save the file** and restart your application
|
||||
|
||||
The comics will be automatically loaded and sorted by comic number.
|
||||
|
||||
## File Naming
|
||||
|
||||
You can name files anything you want (e.g., `001.yaml`, `first-comic.yaml`, `2025-01-01.yaml`), but using the comic number is recommended for easy organization.
|
||||
|
||||
## Required Fields
|
||||
|
||||
Every comic MUST have:
|
||||
- `number` - Sequential comic number (integer)
|
||||
- `filename` - Image filename (string) or list of filenames for multi-image comics
|
||||
- `date` - Publication date in YYYY-MM-DD format (string)
|
||||
- `alt_text` - Accessibility description (string or list for multi-image)
|
||||
|
||||
## Optional Fields
|
||||
|
||||
- `title` - Comic title (defaults to "#X" if not provided)
|
||||
- `mobile_filename` - Mobile-optimized version
|
||||
- `author_note` - Plain text note below the comic
|
||||
- `author_note_md` - Markdown file for author note (overrides `author_note`)
|
||||
- `full_width` - Override global width setting (boolean)
|
||||
- `plain` - Override global plain mode (boolean)
|
||||
- `section` - Start a new section/chapter (string, add only to first comic of section)
|
||||
|
||||
## Multi-Image Comics (Webtoon Style)
|
||||
|
||||
For vertical scrolling comics with multiple images:
|
||||
|
||||
```yaml
|
||||
number: 42
|
||||
filename:
|
||||
- page1.png
|
||||
- page2.png
|
||||
- page3.png
|
||||
alt_text:
|
||||
- "First panel description"
|
||||
- "Second panel description"
|
||||
- "Third panel description"
|
||||
date: "2025-01-01"
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
number: 4
|
||||
title: "The Adventure Begins"
|
||||
filename: comic-004.jpg
|
||||
date: "2025-01-22"
|
||||
alt_text: "A hero stands at the edge of a cliff, looking at the horizon"
|
||||
author_note: "This is where things get interesting!"
|
||||
full_width: true
|
||||
section: "Chapter 2: The Journey"
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
The data loader will:
|
||||
- Skip files with missing required fields (with warnings)
|
||||
- Check for duplicate comic numbers
|
||||
- Warn about gaps in numbering
|
||||
- Sort comics by number automatically
|
||||
|
||||
## Testing Your Changes
|
||||
|
||||
Test the loader directly:
|
||||
```bash
|
||||
python data_loader.py
|
||||
```
|
||||
|
||||
This will show you all loaded comics and any validation warnings.
|
||||
51
data/comics/TEMPLATE.yaml
Normal file
51
data/comics/TEMPLATE.yaml
Normal file
@@ -0,0 +1,51 @@
|
||||
# Template for creating new comics
|
||||
# Copy this file and rename it to match your comic number (e.g., 004.yaml, 005.yaml)
|
||||
# Fields marked as REQUIRED must be included
|
||||
# All other fields are optional
|
||||
|
||||
# REQUIRED: Sequential comic number
|
||||
number: 999
|
||||
|
||||
# REQUIRED: Image filename(s) in static/images/comics/
|
||||
# Single image:
|
||||
filename: comic-999.jpg
|
||||
# OR multi-image (webtoon style):
|
||||
# filename:
|
||||
# - page1.png
|
||||
# - page2.png
|
||||
# - page3.png
|
||||
|
||||
# Optional: Mobile-optimized version of the comic
|
||||
# mobile_filename: comic-999-mobile.jpg
|
||||
|
||||
# REQUIRED: Publication date (YYYY-MM-DD format)
|
||||
date: "2025-01-01"
|
||||
|
||||
# REQUIRED: Accessibility text for screen readers
|
||||
# Single alt text (for single or multi-image):
|
||||
alt_text: "Description of what happens in this comic"
|
||||
# OR individual alt texts for multi-image comics:
|
||||
# alt_text:
|
||||
# - "Description of first image"
|
||||
# - "Description of second image"
|
||||
# - "Description of third image"
|
||||
|
||||
# Optional: Comic title (defaults to "#X" if not provided)
|
||||
title: "Title of Your Comic"
|
||||
|
||||
# Optional: Plain text author note
|
||||
author_note: "Your thoughts about this comic."
|
||||
|
||||
# Optional: Markdown author note file (overrides author_note if present)
|
||||
# Just filename looks in content/author_notes/
|
||||
# Or use path like "special/note.md" relative to content/
|
||||
# author_note_md: "2025-01-01.md"
|
||||
|
||||
# Optional: Override global FULL_WIDTH_DEFAULT setting
|
||||
# full_width: true
|
||||
|
||||
# Optional: Override global PLAIN_DEFAULT setting (hides header/border)
|
||||
# plain: true
|
||||
|
||||
# Optional: Section/chapter title (only add to first comic of a new section)
|
||||
# section: "Chapter 2: New Adventures"
|
||||
121
data_loader.py
Normal file
121
data_loader.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Comic data loader for YAML-based comic management.
|
||||
|
||||
This module scans the data/comics/ directory for .yaml files,
|
||||
loads each comic's configuration, and builds the COMICS list.
|
||||
"""
|
||||
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_comics_from_yaml(comics_dir='data/comics'):
|
||||
"""
|
||||
Load all comic data from YAML files in the specified directory.
|
||||
|
||||
Args:
|
||||
comics_dir: Path to directory containing comic YAML files
|
||||
|
||||
Returns:
|
||||
List of comic dictionaries, sorted by comic number
|
||||
"""
|
||||
comics = []
|
||||
comics_path = Path(comics_dir)
|
||||
|
||||
if not comics_path.exists():
|
||||
print(f"Warning: Comics directory '{comics_dir}' does not exist. Creating it...")
|
||||
comics_path.mkdir(parents=True, exist_ok=True)
|
||||
return []
|
||||
|
||||
# Find all .yaml and .yml files
|
||||
yaml_files = list(comics_path.glob('*.yaml')) + list(comics_path.glob('*.yml'))
|
||||
|
||||
# Filter out template and README files
|
||||
yaml_files = [f for f in yaml_files if f.stem.upper() not in ('TEMPLATE', 'README')]
|
||||
|
||||
if not yaml_files:
|
||||
print(f"Warning: No YAML files found in '{comics_dir}'")
|
||||
return []
|
||||
|
||||
for yaml_file in yaml_files:
|
||||
try:
|
||||
with open(yaml_file, 'r', encoding='utf-8') as f:
|
||||
comic_data = yaml.safe_load(f)
|
||||
|
||||
if comic_data is None:
|
||||
print(f"Warning: '{yaml_file.name}' is empty, skipping")
|
||||
continue
|
||||
|
||||
if 'number' not in comic_data:
|
||||
print(f"Warning: '{yaml_file.name}' missing required 'number' field, skipping")
|
||||
continue
|
||||
|
||||
if 'filename' not in comic_data:
|
||||
print(f"Warning: '{yaml_file.name}' missing required 'filename' field, skipping")
|
||||
continue
|
||||
|
||||
if 'date' not in comic_data:
|
||||
print(f"Warning: '{yaml_file.name}' missing required 'date' field, skipping")
|
||||
continue
|
||||
|
||||
if 'alt_text' not in comic_data:
|
||||
print(f"Warning: '{yaml_file.name}' missing required 'alt_text' field, skipping")
|
||||
continue
|
||||
|
||||
comics.append(comic_data)
|
||||
|
||||
except yaml.YAMLError as e:
|
||||
print(f"Error parsing '{yaml_file.name}': {e}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Error loading '{yaml_file.name}': {e}")
|
||||
continue
|
||||
|
||||
# Sort by comic number
|
||||
comics.sort(key=lambda c: c['number'])
|
||||
|
||||
return comics
|
||||
|
||||
|
||||
def validate_comics(comics):
|
||||
"""
|
||||
Validate the loaded comics for common issues.
|
||||
|
||||
Args:
|
||||
comics: List of comic dictionaries
|
||||
|
||||
Returns:
|
||||
True if validation passes, False otherwise
|
||||
"""
|
||||
if not comics:
|
||||
return True
|
||||
|
||||
numbers = [c['number'] for c in comics]
|
||||
|
||||
# Check for duplicate comic numbers
|
||||
if len(numbers) != len(set(numbers)):
|
||||
duplicates = [n for n in numbers if numbers.count(n) > 1]
|
||||
print(f"Warning: Duplicate comic numbers found: {set(duplicates)}")
|
||||
return False
|
||||
|
||||
# Check for gaps in comic numbering (optional warning)
|
||||
for i in range(len(comics) - 1):
|
||||
if comics[i+1]['number'] - comics[i]['number'] > 1:
|
||||
print(f"Info: Gap in comic numbering between {comics[i]['number']} and {comics[i+1]['number']}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Test the loader
|
||||
print("Loading comics from data/comics/...")
|
||||
comics = load_comics_from_yaml()
|
||||
print(f"Loaded {len(comics)} comics")
|
||||
|
||||
if validate_comics(comics):
|
||||
print("Validation passed!")
|
||||
for comic in comics:
|
||||
title = comic.get('title', f"#{comic['number']}")
|
||||
print(f" - Comic {comic['number']}: {title} ({comic['date']})")
|
||||
else:
|
||||
print("Validation failed!")
|
||||
@@ -1,2 +1,3 @@
|
||||
Flask==3.0.0
|
||||
markdown==3.5.1
|
||||
markdown==3.5.1
|
||||
PyYAML==6.0.3
|
||||
@@ -4,7 +4,7 @@
|
||||
# Licensed under the MIT License - see LICENSE file for details
|
||||
|
||||
"""
|
||||
Script to add a new comic entry to comics_data.py with reasonable defaults
|
||||
Script to add a new comic entry as a YAML file with reasonable defaults
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
@@ -51,7 +51,7 @@ Write your author note here using markdown formatting.
|
||||
|
||||
def main():
|
||||
"""Add a new comic entry with defaults"""
|
||||
parser = argparse.ArgumentParser(description='Add a new comic entry to comics_data.py')
|
||||
parser = argparse.ArgumentParser(description='Add a new comic entry as a YAML file')
|
||||
parser.add_argument('-m', '--markdown', action='store_true',
|
||||
help='Generate a markdown file for author notes and add author_note_md field to comic entry')
|
||||
args = parser.parse_args()
|
||||
@@ -59,53 +59,75 @@ def main():
|
||||
# Get next number
|
||||
number = max(comic['number'] for comic in COMICS) + 1 if COMICS else 1
|
||||
|
||||
# Get today's date
|
||||
date_str = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
# Create entry with defaults
|
||||
comic = {
|
||||
comic_data = {
|
||||
'number': number,
|
||||
'filename': f'comic-{number:03d}.png',
|
||||
'date': datetime.now().strftime('%Y-%m-%d'),
|
||||
'date': date_str,
|
||||
'alt_text': f'Comic #{number}',
|
||||
}
|
||||
|
||||
# Get path to comics_data.py
|
||||
# Add markdown reference if requested
|
||||
if args.markdown:
|
||||
comic_data['author_note_md'] = f'{date_str}.md'
|
||||
|
||||
# Get paths
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
parent_dir = os.path.dirname(script_dir)
|
||||
comics_file = os.path.join(parent_dir, 'comics_data.py')
|
||||
comics_dir = os.path.join(parent_dir, 'data', 'comics')
|
||||
yaml_file = os.path.join(comics_dir, f'{number:03d}.yaml')
|
||||
|
||||
# Read file
|
||||
with open(comics_file, 'r') as f:
|
||||
content = f.read()
|
||||
# Create comics directory if it doesn't exist
|
||||
os.makedirs(comics_dir, exist_ok=True)
|
||||
|
||||
# Check if file already exists
|
||||
if os.path.exists(yaml_file):
|
||||
print(f"Error: Comic file already exists: {yaml_file}")
|
||||
sys.exit(1)
|
||||
|
||||
# Create YAML file with comments
|
||||
yaml_content = f"""# Comic #{number}
|
||||
number: {number}
|
||||
filename: {comic_data['filename']}
|
||||
date: "{date_str}"
|
||||
alt_text: "{comic_data['alt_text']}"
|
||||
"""
|
||||
|
||||
# Format new entry
|
||||
if args.markdown:
|
||||
entry_str = f""" {{
|
||||
'number': {comic['number']},
|
||||
'filename': {repr(comic['filename'])},
|
||||
'date': {repr(comic['date'])},
|
||||
'alt_text': {repr(comic['alt_text'])},
|
||||
'author_note_md': {repr(comic['date'] + '.md')}
|
||||
}}"""
|
||||
yaml_content += f'\n# Markdown author note (overrides author_note if present)\nauthor_note_md: "{date_str}.md"\n'
|
||||
else:
|
||||
entry_str = f""" {{
|
||||
'number': {comic['number']},
|
||||
'filename': {repr(comic['filename'])},
|
||||
'date': {repr(comic['date'])},
|
||||
'alt_text': {repr(comic['alt_text'])}
|
||||
}}"""
|
||||
yaml_content += '\n# Optional: Add author note\n# author_note: "Your thoughts about this comic."\n'
|
||||
|
||||
# Insert before closing bracket
|
||||
insert_pos = content.rfind(']')
|
||||
new_content = content[:insert_pos] + entry_str + ",\n" + content[insert_pos:]
|
||||
yaml_content += """
|
||||
# Optional: Add a title
|
||||
# title: "Title of Your Comic"
|
||||
|
||||
# Write back
|
||||
with open(comics_file, 'w') as f:
|
||||
f.write(new_content)
|
||||
# Optional: Override global settings
|
||||
# full_width: true
|
||||
# plain: true
|
||||
|
||||
print(f"Added comic #{number}")
|
||||
# Optional: Start a new section (only add to first comic of section)
|
||||
# section: "Chapter X: Title"
|
||||
"""
|
||||
|
||||
# Write YAML file
|
||||
with open(yaml_file, 'w') as f:
|
||||
f.write(yaml_content)
|
||||
|
||||
print(f"Created comic #{number}: {yaml_file}")
|
||||
|
||||
# Create markdown file if requested
|
||||
if args.markdown:
|
||||
create_markdown_file(comic['date'], parent_dir)
|
||||
create_markdown_file(date_str, parent_dir)
|
||||
|
||||
print(f"\nNext steps:")
|
||||
print(f"1. Add your comic image as: static/images/comics/{comic_data['filename']}")
|
||||
print(f"2. Edit {yaml_file} to customize the comic metadata")
|
||||
if args.markdown:
|
||||
print(f"3. Edit content/author_notes/{date_str}.md to write your author note")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user