#!/usr/bin/env python3 import os import re import shutil # Paths ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) BUILD_DIR = os.path.join(ROOT_DIR, "build") # Helper to normalize URL paths by removing './' def normalize_url(url): url_parts = url.split('#', 1) path = url_parts[0] anchor = '#' + url_parts[1] if len(url_parts) > 1 else '' # Remove leading './' if path.startswith('./'): path = path[2:] return path, anchor def determine_target_path(filename): """ Determines where an HTML file should go, relative to the ROOT_DIR. """ if filename.startswith("ch") and "-" in filename: # e.g., ch01-00-getting-started.html -> ch01/ch01-00-getting-started.html prefix = filename.split("-", 1)[0] # e.g. "ch01" return os.path.join(prefix, filename) elif filename.startswith("appendix-"): # e.g. appendix-01-keywords.html -> appendix/appendix-01-keywords.html return os.path.join("appendix", filename) else: # Standard pages (toc.html, print.html, index.html, 404.html, foreword.html, etc.) -> root return filename def build_mappings(): """ Scans the build directory and maps old relative file paths to new relative target paths. """ mapping = {} # Check all files in the build root for file in os.listdir(BUILD_DIR): if file.endswith(".html"): target_rel = determine_target_path(file) mapping[file] = target_rel return mapping def clean_html_content(content): """ Cleans the HTML document by extracting title and main content, and stripping scripts, styles, and decorative Ferris images. """ # 1. Extract title title = "Rust Book" title_match = re.search(r"(.*?)", content, re.IGNORECASE | re.DOTALL) if title_match: raw_title = title_match.group(1).strip() # Remove common suffix suffix = " - The Rust Programming Language" if raw_title.endswith(suffix): title = raw_title[:-len(suffix)].strip() else: title = raw_title # 2. Extract content from
or fall back to main_match = re.search(r"]*)?>(.*?)
", content, re.IGNORECASE | re.DOTALL) if main_match: body_content = main_match.group(1).strip() else: body_match = re.search(r"]*)?>(.*?)", content, re.IGNORECASE | re.DOTALL) if body_match: body_content = body_match.group(1).strip() else: body_content = content.strip() # 3. Strip nested scripts or styles body_content = re.sub(r")<[^<]*)*", "", body_content, flags=re.IGNORECASE | re.DOTALL) body_content = re.sub(r")<[^<]*)*", "", body_content, flags=re.IGNORECASE | re.DOTALL) # 4. Strip decorative Ferris image tags to prevent broken image links body_content = re.sub(r"]*src=\"[^\"]*img/ferris/[^\"]*\"[^>]*>", "", body_content, flags=re.IGNORECASE) # 5. Construct clean minimal HTML structure return title, body_content def update_links(html_content, current_rel_path, mapping): """ Updates all relative href and src attributes to point to the new organized folder structure. """ current_new_dir = os.path.dirname(mapping.get(current_rel_path, current_rel_path)) def replace_link(match): attr = match.group(1) # href or src url = match.group(2) # the raw url path, anchor = normalize_url(url) # Check mapping for HTML files if path in mapping: target_new_path = mapping[path] # Calculate the relative path from current_new_dir to target_new_path new_rel_path = os.path.relpath(target_new_path, current_new_dir) return f'{attr}="{new_rel_path}{anchor}"' # Check assets (like images) if path.startswith("img/"): # Image paths are originally relative to root. Move relative path. # e.g., if current is in ch01/, relpath to img/trpl01-01.png is ../img/trpl01-01.png new_rel_path = os.path.relpath(path, current_new_dir) return f'{attr}="{new_rel_path}{anchor}"' return match.group(0) # Regex matching: href="..." or src="..." html_content = re.sub(r'(href|src)="([^"]*)"', replace_link, html_content) return html_content def clean_and_organize(): print(f"Reading from raw build: {BUILD_DIR}") # 1. Map all HTML files to their new organized destinations mapping = build_mappings() if not mapping: print("No HTML files found to process.") return # 2. Identify directories to clean in the workspace root # We will delete any existing organized folders so we don't have stale files dirs_to_clean = set(os.path.dirname(p) for p in mapping.values() if os.path.dirname(p)) dirs_to_clean.add("img") # we will recreate root img directory for d in dirs_to_clean: d_path = os.path.join(ROOT_DIR, d) if os.path.exists(d_path): print(f"Cleaning existing organized folder: {d}/") shutil.rmtree(d_path) # Also clean existing loose HTML files in the root that are in our mapping for file_rel in mapping.values(): if os.path.dirname(file_rel) == "": root_file_path = os.path.join(ROOT_DIR, file_rel) if os.path.exists(root_file_path): print(f"Removing old root file: {file_rel}") os.remove(root_file_path) # 3. Process and write cleaned HTML files to organized paths for old_rel, new_rel in mapping.items(): src_path = os.path.join(BUILD_DIR, old_rel) dest_path = os.path.join(ROOT_DIR, new_rel) # Ensure destination folder exists os.makedirs(os.path.dirname(dest_path), exist_ok=True) with open(src_path, "r", encoding="utf-8") as f: raw_content = f.read() # Clean the HTML content title, clean_body = clean_html_content(raw_content) # Update hrefs and srcs updated_content = update_links(clean_body, old_rel, mapping) clean_html = f""" {title} {updated_content} """ with open(dest_path, "w", encoding="utf-8") as f: f.write(clean_html) print(f"Saved: {new_rel}") # 4. Copy conceptual images inside img/ (excluding img/ferris/) src_img_dir = os.path.join(BUILD_DIR, "img") dest_img_dir = os.path.join(ROOT_DIR, "img") if os.path.exists(src_img_dir): os.makedirs(dest_img_dir, exist_ok=True) for item in os.listdir(src_img_dir): src_item = os.path.join(src_img_dir, item) dest_item = os.path.join(dest_img_dir, item) if os.path.isdir(src_item): # Skip ferris directory if item == "ferris": continue shutil.copytree(src_item, dest_item) else: shutil.copy2(src_item, dest_item) print("Copied content diagrams to root img/ directory.") # 5. Delete raw build folder completely print(f"Removing build/ folder...") shutil.rmtree(BUILD_DIR) print("Reorganization and cleanup completed successfully.") if __name__ == "__main__": if not os.path.exists(BUILD_DIR): print(f"Error: Build directory {BUILD_DIR} does not exist.") print("If you have already organized the files, please rebuild first if you want to clean again.") else: clean_and_organize()