#!/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()