================================================================================
PRACTICAL PYTHON SCRIPTING - CHALLENGE 3 SOLUTION
Chapter 1: Paths & File System Navigation
Challenge: Complex Path Navigation (rebuild_typescript_course.py pattern)
================================================================================
PROBLEM:
Create a script that mirrors the structure of rebuild_typescript_course.py:
1. Use Path(__file__).parent to find the script location
2. Navigate to a nested directory structure (courses/Programming/TypeScript/html/)
3. Find all chapter HTML files (ts1-*.html)
4. Extract chapter numbers and titles from the HTML
5. Display a summary of what was found
This teaches the actual pattern used in production code.
SOLUTION:
================================================================================
from pathlib import Path
import re
# Find the script's directory
SCRIPT_DIR = Path(__file__).resolve().parent
print(f"Script location: {SCRIPT_DIR}\n")
# Navigate to nested directory using / operator
CHAPTERS_DIR = SCRIPT_DIR / "courses" / "Programming" / "TypeScript" / "html"
print(f"Looking for chapters in: {CHAPTERS_DIR}")
# Validate the path exists
if not CHAPTERS_DIR.exists():
print(f"❌ Chapters directory not found!")
print(f" Expected: {CHAPTERS_DIR}")
print(f"\nTo test this script:")
print(f" 1. Create the directory structure:")
print(f" mkdir -p {CHAPTERS_DIR}")
print(f" 2. Add HTML files like ts1-1.html, ts1-2.html, etc.")
exit(1)
# Find all chapter HTML files using glob()
html_files = sorted(CHAPTERS_DIR.glob("ts1-*.html"))
if not html_files:
print(f"⚠️ No chapter files found (pattern: ts1-*.html)")
exit(1)
print(f"✅ Found {len(html_files)} chapter file(s)\n")
# Process each chapter file
chapters = []
for html_file in html_files:
try:
# 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))
# Read the HTML file
with open(html_file, 'r', encoding='utf-8') as f:
html_content = f.read()
# Extract chapter title from
tag
title_match = re.search(r'(.*?)
', html_content)
chapter_title = title_match.group(1) if title_match else f"Chapter {chapter_num}"
# Store chapter info
chapters.append({
'number': chapter_num,
'title': chapter_title,
'filename': html_file.name,
'path': html_file,
'size': html_file.stat().st_size
})
print(f"✅ Chapter {chapter_num}: {chapter_title}")
print(f" File: {html_file.name}")
print(f" Size: {html_file.stat().st_size:,} bytes\n")
except Exception as e:
print(f"❌ Error processing {html_file.name}: {e}\n")
# Display summary
print("=" * 60)
print(f"SUMMARY")
print("=" * 60)
print(f"Total chapters found: {len(chapters)}")
print(f"Location: {CHAPTERS_DIR}\n")
if chapters:
print("Chapter list:")
for ch in chapters:
print(f" {ch['number']:2d}. {ch['title']}")
total_size = sum(ch['size'] for ch in chapters)
print(f"\nTotal content size: {total_size:,} bytes ({total_size / 1024 / 1024:.2f} MB)")
print("=" * 60)
================================================================================
HOW IT WORKS:
================================================================================
1. FINDING THE SCRIPT LOCATION
SCRIPT_DIR = Path(__file__).resolve().parent
- __file__ = path to this script
- .resolve() = convert to absolute path (handles symlinks)
- .parent = the directory containing the script
2. NAVIGATING TO NESTED DIRECTORIES
CHAPTERS_DIR = SCRIPT_DIR / "courses" / "Programming" / "TypeScript" / "html"
- Use / to append each directory level
- Each / appends one directory
- Result: /full/path/to/script/courses/Programming/TypeScript/html
3. VALIDATING THE PATH
if not CHAPTERS_DIR.exists():
# Handle error
- Check if the nested directory actually exists
- Helpful error message if it doesn't
- Prevents confusing errors later
4. FINDING FILES WITH PATTERNS
html_files = sorted(CHAPTERS_DIR.glob("ts1-*.html"))
- .glob("ts1-*.html") finds files matching pattern
- Patterns: "ts1-*" means starts with "ts1-"
- "*.html" means ends with ".html"
- So "ts1-*.html" matches: ts1-1.html, ts1-2.html, etc.
- sorted() puts them in order
5. EXTRACTING INFORMATION
- html_file.stem = filename without extension
- Re.match(r"ts1-(\d+)", stem) = extract chapter number
- (\d+) captures one or more digits
- open(html_file, 'r') = read the file
- re.search() = find text matching a pattern in the content
6. STORING AND REPORTING
- Store chapter info in a list of dictionaries
- Display formatted output
- Calculate statistics (total size, count)
================================================================================
PATH MANIPULATION TECHNIQUES:
================================================================================
Technique 1: Building paths step-by-step
script_dir = Path(__file__).parent
# Add one level at a time
level1 = script_dir / "courses"
level2 = level1 / "Programming"
level3 = level2 / "TypeScript"
level4 = level3 / "html"
# Or all at once:
final = script_dir / "courses" / "Programming" / "TypeScript" / "html"
Both are equivalent!
---
Technique 2: Getting parts of paths
full_path = Path("/var/www/myapp/courses/Programming/TypeScript/html/ts1-1.html")
full_path.parent # /var/www/myapp/courses/Programming/TypeScript/html
full_path.name # ts1-1.html
full_path.stem # ts1-1 (filename without extension)
full_path.suffix # .html (just the extension)
parts = full_path.parts
# ('/','var','www','myapp','courses','Programming','TypeScript','html','ts1-1.html')
---
Technique 3: Relative paths
script_dir = Path(__file__).parent
# This works:
chapters_dir = script_dir / "courses" / "Programming" / "TypeScript" / "html"
# But what if you know the relative path from script to chapters?
# You can navigate backwards then forwards:
chapters_dir = script_dir / "../debserver/chapters"
# Or more safely:
chapters_dir = script_dir.parent.parent / "debserver" / "chapters"
================================================================================
UNDERSTANDING GLOB PATTERNS:
================================================================================
Glob Pattern Matches
* Any characters (except /)
*.html file.html, chapter.html, ts1-1.html
ts1-*.html ts1-1.html, ts1-2.html, ts1-10.html
ts1-[1-3].html ts1-1.html, ts1-2.html, ts1-3.html (1 through 3)
[abc]*.html a.html, bfile.html, chart.html (starts with a, b, or c)
**/*.html Recursively find all .html files in subdirectories
Examples:
source_dir.glob("*.txt") # Find all .txt files
source_dir.glob("test_*.py") # Find test_*.py files
source_dir.glob("**/config.json") # Find config.json anywhere inside
================================================================================
REGEX PATTERN EXPLANATION:
================================================================================
Pattern: r"ts1-(\d+)"
r"..." = raw string (backslashes are literal, not escape characters)
ts1- = Match literally "ts1-"
(\d+) = Capture one or more digits
\d = Any digit (0-9)
+ = One or more times
() = Capture this group for extraction
Examples:
"ts1-1" → Matches, captures: "1"
"ts1-15" → Matches, captures: "15"
"ts1-abc" → No match (not digits)
"ts2-1" → No match (says ts2, not ts1)
In the code:
match = re.match(r"ts1-(\d+)", "ts1-1")
match.group(0) # "ts1-1" (the whole match)
match.group(1) # "1" (the first captured group)
================================================================================
FILE OPERATIONS USED:
================================================================================
open(path, 'r', encoding='utf-8')
- Open file for reading ('r')
- Specify UTF-8 encoding (important for compatibility)
- Returns a file object
- Use with 'with' statement to auto-close
f.read()
- Read entire file contents as a string
- For large files, use .readlines() or iterate
html_file.stat()
- Get file statistics (size, timestamps, permissions)
- .stat().st_size = file size in bytes
sorted(list_of_paths)
- Sort Path objects in alphabetical order
- Helpful for processing files in order
================================================================================
ERROR HANDLING:
================================================================================
try/except block handles:
- FileNotFoundError: File doesn't exist
- UnicodeDecodeError: File encoding issue
- Regular expression errors: Malformed pattern
- IOError: Permission denied
Specific handling:
except FileNotFoundError:
print(f"File not found: {file}")
except UnicodeDecodeError:
print(f"Could not decode file (wrong encoding): {file}")
except Exception as e:
print(f"Unexpected error: {e}")
The solution uses "except Exception" which catches all errors. For
production code, you'd handle specific errors. But for learning and
general utility scripts, this is appropriate.
================================================================================
TESTING THE SOLUTION:
================================================================================
To test this script:
1. Create the directory structure:
mkdir -p courses/Programming/TypeScript/html
2. Create some test HTML files:
cat > courses/Programming/TypeScript/html/ts1-1.html << 'EOF'
Chapter 1: Introduction
This is the first chapter.
EOF
cat > courses/Programming/TypeScript/html/ts1-2.html << 'EOF'
Chapter 2: Advanced Types
This is the second chapter.
EOF
3. Run the script:
python chapter_finder.py
Expected output:
Script location: /home/philip/scripts
Looking for chapters in: /home/philip/scripts/courses/Programming/TypeScript/html
✅ Found 2 chapter file(s)
✅ Chapter 1: Chapter 1: Introduction
File: ts1-1.html
Size: 58 bytes
✅ Chapter 2: Chapter 2: Advanced Types
File: ts1-2.html
Size: 56 bytes
============================================================
SUMMARY
============================================================
Total chapters found: 2
Location: /home/philip/scripts/courses/Programming/TypeScript/html
Chapter list:
1. Chapter 1: Introduction
2. Chapter 2: Advanced Types
Total content size: 114 bytes (0.00 MB)
============================================================
================================================================================
REAL-WORLD CONNECTION:
================================================================================
This solution demonstrates the actual pattern used in
rebuild_typescript_course.py:
1. Find script location using Path(__file__)
2. Navigate to chapters directory
3. Validate the path exists
4. Find files using glob()
5. Extract information from each file
6. Store results for processing
7. Generate a summary
The difference in rebuild_typescript_course.py:
- Also loads database utilities
- Inserts chapters into a database
- Uses try/except for database errors
- Reports database operation results
But the PATH NAVIGATION pattern is identical! That's why this chapter
is the foundation for everything else.
================================================================================
KEY TAKEAWAYS:
================================================================================
✅ Path(__file__).parent finds the script's directory
✅ .resolve() handles symlinks and creates absolute paths
✅ Use / to build nested paths step-by-step
✅ .exists() validates paths before using them
✅ .glob() finds files matching patterns
✅ .stat() gets file information
✅ sorted() puts results in order
✅ Try/except handles errors gracefully
✅ Path properties (.stem, .name, .suffix) extract parts
✅ This pattern scales from simple scripts to complex utilities
The concepts you've learned here are the FOUNDATION for all practical
Python scripting. Every utility script follows this basic pattern:
1. Find files relative to the script location
2. Validate paths exist
3. Process files
4. Report results
Master this pattern, and you can write any utility script!
================================================================================