================================================================================ PRACTICAL PYTHON SCRIPTING - CHALLENGE 2 SOLUTION Chapter 5: Building rebuild_typescript_course.py Challenge: Parse Chapter Metadata ================================================================================ PROBLEM: Create a script that: 1. Finds all chapter HTML files 2. For each file, extract: chapter number, title from

3. Display a table with all chapters 4. Show chapter count SOLUTION: ================================================================================ from pathlib import Path import re def parse_chapters(): """Parse chapter metadata from HTML files.""" print("=" * 80) print("CHAPTER METADATA PARSER") print("=" * 80 + "\n") # Find script directory script_dir = Path(__file__).resolve().parent chapters_dir = script_dir / "courses" / "Programming" / "TypeScript" / "html" if not chapters_dir.exists(): print(f"āŒ Directory not found: {chapters_dir}") return # Find all chapter files print("šŸ“š Finding chapter files...\n") html_files = sorted(chapters_dir.glob("ts1-*.html")) if not html_files: print("āš ļø No chapter files found!") return print(f"āœ… Found {len(html_files)} files\n") # Parse metadata from each file chapters = [] for html_file in html_files: try: # Read file content html_content = html_file.read_text(encoding='utf-8') # Extract chapter number from filename (ts1-1.html → 1) match = re.match(r"ts1-(\d+)", html_file.stem) if not match: continue chapter_num = int(match.group(1)) # Extract title from

tag title_match = re.search(r'

(.*?)

', html_content) if title_match: chapter_title = title_match.group(1).strip() else: chapter_title = f"Chapter {chapter_num}" # Get file size file_size = html_file.stat().st_size chapters.append({ 'number': chapter_num, 'title': chapter_title, 'filename': html_file.name, 'size': file_size }) print(f"āœ… Chapter {chapter_num}: {chapter_title}") except Exception as e: print(f"āŒ Error parsing {html_file.name}: {e}") continue # Display results as table print("\n" + "=" * 80) print("CHAPTER SUMMARY") print("=" * 80 + "\n") # Print header print(f"{'#':<3} {'Title':<40} {'File':<20} {'Size':<10}") print("-" * 80) # Print rows for ch in chapters: size_kb = ch['size'] / 1024 print(f"{ch['number']:<3} {ch['title']:<40} {ch['filename']:<20} {size_kb:>8.1f} KB") # Print footer print("-" * 80) print(f"\nšŸ“Š Total chapters: {len(chapters)}") total_size = sum(ch['size'] for ch in chapters) total_size_mb = total_size / 1024 / 1024 print(f"šŸ“Š Total size: {total_size_mb:.2f} MB") if __name__ == "__main__": parse_chapters() ================================================================================ HOW IT WORKS: ================================================================================ 1. EXTRACT CHAPTER NUMBER match = re.match(r"ts1-(\d+)", html_file.stem) chapter_num = int(match.group(1)) - (\d+) captures one or more digits - group(1) extracts the captured digits - int() converts string to integer 2. EXTRACT TITLE FROM HTML title_match = re.search(r'

(.*?)

', html_content) chapter_title = title_match.group(1).strip() - r'

(.*?)

' matches:

ANYTHING

- (.*?) is a non-greedy match (stops at first ) - .strip() removes leading/trailing whitespace 3. BUILD TABLE print(f"{'#':<3} {'Title':<40} {'File':<20} {'Size':<10}") - {value:width} right-aligns text - {value:^width} center-aligns text 4. CALCULATE TOTALS total_size = sum(ch['size'] for ch in chapters) - List comprehension: sum all file sizes - Divide by 1024 twice to get MB ================================================================================ TESTING: ================================================================================ python parse_metadata.py Output: ================================================================================ CHAPTER METADATA PARSER ================================================================================ šŸ“š Finding chapter files... āœ… Found 12 files āœ… Chapter 1: Why TypeScript & Setup āœ… Chapter 2: Basic Types & Type System āœ… Chapter 3: Objects, Arrays & Tuples ... āœ… Chapter 12: Building Real Projects ================================================================================ CHAPTER SUMMARY ================================================================================ # Title File Size -------------------------------------------------------------------------------- 1 Why TypeScript & Setup ts1-1.html 45.2 KB 2 Basic Types & Type System ts1-2.html 52.1 KB 3 Objects, Arrays & Tuples ts1-3.html 48.5 KB ... 12 Building Real Projects ts1-12.html 51.8 KB -------------------------------------------------------------------------------- šŸ“Š Total chapters: 12 šŸ“Š Total size: 0.61 MB ================================================================================ KEY CONCEPTS: ================================================================================ Regular Expression Pattern: r'

(.*?)

'

- Match literal "

" (.*?) - Capture any characters (non-greedy)

- Match literal "" Examples:

Chapter 1

→ Captures: "Chapter 1"

Advanced Types

→ Captures: "Advanced Types" Why non-greedy (.*?) instead of greedy (.*)? āœ… Non-greedy (.*?):

Title 1

Title 2

Matches: "Title 1" (stops at first ) āŒ Greedy (.*):

Title 1

Title 2

Matches: "Title 1

Title 2" (goes to last

) Non-greedy is usually what you want! ================================================================================ EXTENDING THIS SCRIPT: ================================================================================ You could add: 1. Word count: word_count = len(html_content.split()) print(f"Words: {word_count}") 2. Code block count: code_blocks = html_content.count('') print(f"Code blocks: {code_blocks}") 3. Link count: links = len(re.findall(r'