================================================================================
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'