b183237d59
- Updated Plot scene template with YAML frontmatter for metadata tracking - Updated First Draft scene template with YAML frontmatter - Added _template.md files in both Plot and First Draft folders - Added create_scene.py script for automatic file creation and linking - Planning files reference draft files via 'draft_file' field - Draft files reference planning files via 'planning_template' field - Word count targets tracked in planning, current count in draft - Added comprehensive scripts/README.md with usage guide - Supports --from-planning and --from-draft for creating missing counterparts Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
396 lines
12 KiB
Python
396 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Scene Creation Script
|
|
|
|
Creates linked planning and draft files for a new scene.
|
|
|
|
Usage:
|
|
python create_scene.py --act 1 --chapter 1 --scene 2 --title "Chapter Two: The Journey"
|
|
|
|
Or with minimal args (auto-generates title):
|
|
python create_scene.py --act 1 --chapter 1 --scene 2
|
|
|
|
Or create from existing planning file:
|
|
python create_scene.py --from-planning 05-Plot/act_1/scene_01.md
|
|
|
|
Or create from existing draft file:
|
|
python create_scene.py --from-draft "06-First Draft/Act 1/scene01.md"
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# Get the project root (parent of scripts directory)
|
|
SCRIPTS_DIR = Path(__file__).parent
|
|
PROJECT_ROOT = SCRIPTS_DIR.parent
|
|
|
|
# Template paths
|
|
PLANNING_TEMPLATE = PROJECT_ROOT / "05-Plot" / "_template.md"
|
|
DRAFT_TEMPLATE = PROJECT_ROOT / "06-First Draft" / "_template.md"
|
|
|
|
|
|
def slugify(text):
|
|
"""Convert text to URL-friendly slug."""
|
|
if not text:
|
|
return ""
|
|
# Convert to lowercase
|
|
text = text.lower()
|
|
# Remove special characters
|
|
text = re.sub(r'[^\w\s-]', '', text)
|
|
# Replace spaces with hyphens
|
|
text = re.sub(r'[\s_]+', '-', text)
|
|
# Remove leading/trailing hyphens
|
|
text = text.strip('-')
|
|
return text
|
|
|
|
|
|
def generate_title(act, chapter, scene):
|
|
"""Generate a default title based on act/chapter/scene."""
|
|
return f"Chapter {chapter}: Scene {scene}"
|
|
|
|
|
|
def parse_yaml_frontmatter(content):
|
|
"""Parse YAML frontmatter from markdown file."""
|
|
import yaml
|
|
|
|
if not content.startswith('---'):
|
|
return {}
|
|
|
|
# Find the frontmatter
|
|
lines = content.split('\n')
|
|
if lines[0].strip() == '---':
|
|
frontmatter_lines = []
|
|
for line in lines[1:]:
|
|
if line.strip() == '---':
|
|
break
|
|
frontmatter_lines.append(line)
|
|
|
|
try:
|
|
return yaml.safe_load('\n'.join(frontmatter_lines)) or {}
|
|
except yaml.YAMLError:
|
|
return {}
|
|
return {}
|
|
|
|
|
|
def update_frontmatter(content, updates):
|
|
"""Update YAML frontmatter in markdown content."""
|
|
lines = content.split('\n')
|
|
|
|
if not lines[0].strip() == '---':
|
|
# No frontmatter, add it
|
|
yaml_content = "\n".join(f"{k}: {v}" for k, v in updates.items())
|
|
return f"---\n{yaml_content}\n---\n\n" + content
|
|
|
|
# Find and replace frontmatter
|
|
in_frontmatter = False
|
|
frontmatter_start = 0
|
|
frontmatter_end = 0
|
|
|
|
for i, line in enumerate(lines):
|
|
if line.strip() == '---':
|
|
if not in_frontmatter:
|
|
frontmatter_start = i
|
|
in_frontmatter = True
|
|
else:
|
|
frontmatter_end = i
|
|
break
|
|
|
|
if frontmatter_end == 0:
|
|
return content
|
|
|
|
# Parse existing frontmatter
|
|
existing = parse_yaml_frontmatter(content)
|
|
existing.update(updates)
|
|
|
|
# Generate new frontmatter
|
|
yaml_lines = ["---"]
|
|
for key, value in existing.items():
|
|
if isinstance(value, str):
|
|
yaml_lines.append(f"{key}: \"{value}\"")
|
|
elif isinstance(value, list):
|
|
yaml_lines.append(f"{key}: {value}")
|
|
else:
|
|
yaml_lines.append(f"{key}: {value}")
|
|
yaml_lines.append("---")
|
|
|
|
# Reconstruct content
|
|
new_content = "\n".join(yaml_lines) + "\n\n" + "\n".join(lines[frontmatter_end + 1:])
|
|
return new_content
|
|
|
|
|
|
def create_planning_file(act, chapter, scene, title, slug=None, **kwargs):
|
|
"""Create a new planning file."""
|
|
if slug is None:
|
|
slug = slugify(title)
|
|
|
|
# Create path
|
|
act_dir = PROJECT_ROOT / "05-Plot" / f"act_{act}"
|
|
act_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
filename = act_dir / f"scene_{scene:02d}.md"
|
|
|
|
# Read template
|
|
with open(PLANNING_TEMPLATE, 'r') as f:
|
|
template_content = f.read()
|
|
|
|
# Generate ID
|
|
scene_id = f"scene_{act:02d}_{chapter:02d}_{scene:02d}"
|
|
|
|
# Prepare frontmatter updates
|
|
frontmatter = {
|
|
'id': scene_id,
|
|
'title': title,
|
|
'slug': slug,
|
|
'act': act,
|
|
'chapter': chapter,
|
|
'scene': scene,
|
|
'order': scene,
|
|
'created': datetime.now().strftime("%Y-%m-%d"),
|
|
'updated': datetime.now().strftime("%Y-%m-%d"),
|
|
}
|
|
|
|
# Update with any additional kwargs
|
|
frontmatter.update(kwargs)
|
|
|
|
# Update draft_file path if not provided
|
|
if 'draft_file' not in frontmatter:
|
|
draft_path = f"../../06-First Draft/Act {act}/scene{scene:02d}.md"
|
|
frontmatter['draft_file'] = draft_path
|
|
|
|
# Update content
|
|
content = update_frontmatter(template_content, frontmatter)
|
|
|
|
# Write file
|
|
with open(filename, 'w') as f:
|
|
f.write(content)
|
|
|
|
return filename
|
|
|
|
|
|
def create_draft_file(act, chapter, scene, title, slug=None, planning_path=None, **kwargs):
|
|
"""Create a new draft file."""
|
|
if slug is None:
|
|
slug = slugify(title)
|
|
|
|
# Create path
|
|
act_dir = PROJECT_ROOT / "06-First Draft" / f"Act {act}"
|
|
act_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
filename = act_dir / f"scene{scene:02d}.md"
|
|
|
|
# Read template
|
|
with open(DRAFT_TEMPLATE, 'r') as f:
|
|
template_content = f.read()
|
|
|
|
# Generate ID
|
|
scene_id = f"draft_scene_{act:02d}_{chapter:02d}_{scene:02d}"
|
|
|
|
# Prepare frontmatter updates
|
|
frontmatter = {
|
|
'id': scene_id,
|
|
'title': title,
|
|
'slug': slug,
|
|
'act': act,
|
|
'chapter': chapter,
|
|
'scene': scene,
|
|
'created': datetime.now().strftime("%Y-%m-%d"),
|
|
'updated': datetime.now().strftime("%Y-%m-%d"),
|
|
}
|
|
|
|
# Update with any additional kwargs
|
|
frontmatter.update(kwargs)
|
|
|
|
# Set planning_template path if not provided
|
|
if 'planning_template' not in frontmatter:
|
|
if planning_path:
|
|
# Use the provided planning path
|
|
relative_path = os.path.relpath(planning_path, act_dir)
|
|
frontmatter['planning_template'] = relative_path
|
|
else:
|
|
# Generate default path
|
|
planning_path = f"../../05-Plot/act_{act}/scene_{scene:02d}.md"
|
|
frontmatter['planning_template'] = planning_path
|
|
|
|
# Update content
|
|
content = update_frontmatter(template_content, frontmatter)
|
|
|
|
# Write file
|
|
with open(filename, 'w') as f:
|
|
f.write(content)
|
|
|
|
return filename
|
|
|
|
|
|
def link_files(planning_path, draft_path):
|
|
"""Ensure both files are linked to each other."""
|
|
# Update planning file with draft path
|
|
with open(planning_path, 'r') as f:
|
|
planning_content = f.read()
|
|
|
|
relative_draft = os.path.relpath(draft_path, Path(planning_path).parent)
|
|
planning_content = update_frontmatter(planning_content, {
|
|
'draft_file': relative_draft
|
|
})
|
|
|
|
with open(planning_path, 'w') as f:
|
|
f.write(planning_content)
|
|
|
|
# Update draft file with planning path
|
|
with open(draft_path, 'r') as f:
|
|
draft_content = f.read()
|
|
|
|
relative_planning = os.path.relpath(planning_path, Path(draft_path).parent)
|
|
draft_content = update_frontmatter(draft_content, {
|
|
'planning_template': relative_planning
|
|
})
|
|
|
|
with open(draft_path, 'w') as f:
|
|
f.write(draft_content)
|
|
|
|
|
|
def create_scene_pair(act, chapter, scene, title=None, **kwargs):
|
|
"""Create both planning and draft files."""
|
|
if title is None:
|
|
title = generate_title(act, chapter, scene)
|
|
|
|
slug = slugify(title)
|
|
|
|
# Create planning file
|
|
planning_path = create_planning_file(
|
|
act, chapter, scene, title, slug, **kwargs
|
|
)
|
|
|
|
# Create draft file (without planning_path initially)
|
|
draft_path = create_draft_file(
|
|
act, chapter, scene, title, slug, **kwargs
|
|
)
|
|
|
|
# Link them together
|
|
link_files(planning_path, draft_path)
|
|
|
|
print(f"✓ Created planning file: {planning_path.relative_to(PROJECT_ROOT)}")
|
|
print(f"✓ Created draft file: {draft_path.relative_to(PROJECT_ROOT)}")
|
|
print(f"✓ Linked files together")
|
|
|
|
return planning_path, draft_path
|
|
|
|
|
|
def create_from_planning(planning_path_str):
|
|
"""Create a draft file from an existing planning file."""
|
|
planning_path = Path(planning_path_str)
|
|
|
|
if not planning_path.exists():
|
|
print(f"Error: Planning file not found: {planning_path}")
|
|
sys.exit(1)
|
|
|
|
# Parse planning file
|
|
with open(planning_path, 'r') as f:
|
|
planning_content = f.read()
|
|
|
|
frontmatter = parse_yaml_frontmatter(planning_content)
|
|
|
|
if not frontmatter:
|
|
print(f"Error: No valid frontmatter in {planning_path}")
|
|
sys.exit(1)
|
|
|
|
act = frontmatter.get('act', 1)
|
|
chapter = frontmatter.get('chapter', 1)
|
|
scene = frontmatter.get('scene', 1)
|
|
title = frontmatter.get('title', generate_title(act, chapter, scene))
|
|
|
|
# Create draft file
|
|
draft_path = create_draft_file(
|
|
act, chapter, scene, title,
|
|
planning_path=planning_path
|
|
)
|
|
|
|
# Link files
|
|
link_files(planning_path, draft_path)
|
|
|
|
print(f"✓ Created draft file: {draft_path.relative_to(PROJECT_ROOT)}")
|
|
print(f"✓ Linked to planning file: {planning_path.relative_to(PROJECT_ROOT)}")
|
|
|
|
return draft_path
|
|
|
|
|
|
def create_from_draft(draft_path_str):
|
|
"""Create a planning file from an existing draft file."""
|
|
draft_path = Path(draft_path_str)
|
|
|
|
if not draft_path.exists():
|
|
print(f"Error: Draft file not found: {draft_path}")
|
|
sys.exit(1)
|
|
|
|
# Parse draft file
|
|
with open(draft_path, 'r') as f:
|
|
draft_content = f.read()
|
|
|
|
frontmatter = parse_yaml_frontmatter(draft_content)
|
|
|
|
if not frontmatter:
|
|
print(f"Error: No valid frontmatter in {draft_path}")
|
|
sys.exit(1)
|
|
|
|
act = frontmatter.get('act', 1)
|
|
chapter = frontmatter.get('chapter', 1)
|
|
scene = frontmatter.get('scene', 1)
|
|
title = frontmatter.get('title', generate_title(act, chapter, scene))
|
|
|
|
# Create planning file
|
|
planning_path = create_planning_file(
|
|
act, chapter, scene, title,
|
|
draft_file=draft_path
|
|
)
|
|
|
|
# Link files
|
|
link_files(planning_path, draft_path)
|
|
|
|
print(f"✓ Created planning file: {planning_path.relative_to(PROJECT_ROOT)}")
|
|
print(f"✓ Linked to draft file: {draft_path.relative_to(PROJECT_ROOT)}")
|
|
|
|
return planning_path
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Create linked planning and draft files for scenes.'
|
|
)
|
|
|
|
# For creating new scene pair
|
|
parser.add_argument('--act', type=int, default=1, help='Act number')
|
|
parser.add_argument('--chapter', type=int, default=1, help='Chapter number')
|
|
parser.add_argument('--scene', type=int, required=True, help='Scene number')
|
|
parser.add_argument('--title', type=str, default=None, help='Scene title')
|
|
parser.add_argument('--type', type=str, default='scene',
|
|
choices=['scene', 'chapter', 'interlude'],
|
|
help='Scene type')
|
|
|
|
# For creating from existing files
|
|
parser.add_argument('--from-planning', type=str, default=None,
|
|
help='Create draft from existing planning file')
|
|
parser.add_argument('--from-draft', type=str, default=None,
|
|
help='Create planning from existing draft file')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.from_planning:
|
|
create_from_planning(args.from_planning)
|
|
elif args.from_draft:
|
|
create_from_draft(args.from_draft)
|
|
else:
|
|
create_scene_pair(
|
|
act=args.act,
|
|
chapter=args.chapter,
|
|
scene=args.scene,
|
|
title=args.title,
|
|
type=args.type
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|