#!/usr/bin/env python3
"""
Refactor all generate_weekN.py files (N=2..32) to use math_helpers.py.
Changes applied:
1. Replace inline CSS import → `from math_helpers import *`
2. Remove inline color constants (RED, BLUE, etc.)
3. Remove inline helper functions (hdr, sec, b, bw, bl)
4. Remove SVG generators that now live in math_helpers
5. Update hdr(day, topic) → hdr(N, day, topic)
6. Add teacher guide stub functions
7. Update generate loop to use generate_simple with teacher guides
Run: python3 refactor_weeks.py
"""
import os
import re
import glob
BASE = os.path.expanduser("~/Home_School/2nd_Grade/Math")
# SVG generator function names that should be removed (now in math_helpers)
SVG_FUNCS = {
'circle_svg', 'eq_group_svg', 'array_svg', 'shape_svg',
'clock_svg', 'fraction_svg', 'coin_svg', 'grid_svg',
'bar_graph_svg', 'star_svg', 'trophy_svg', 'medal_svg',
'check_svg', 'times_table_html', 'skip_count_html',
'mini_chart_html', 'ruler_inches', 'ruler_cm',
'measure_line', 'measure_line_cm', 'fraction_bar_svg',
}
# Helper functions to remove
HELPER_FUNCS = {
'hdr', 'sec', 'b', 'bw', 'bl', 'page', 'page_multi',
}
# Color constants to remove
COLOR_VARS = {
'RED', 'BLUE', 'GREEN', 'YELLOW', 'ORANGE', 'PURPLE', 'PINK', 'TEAL', 'GOLD',
}
def read_file(path):
with open(path, 'r') as f:
return f.read()
def write_file(path, content):
with open(path, 'w') as f:
f.write(content)
def remove_css_block(content):
"""Remove the CSS = r\"\"\"...\"\"\" block."""
pattern = r'^CSS\s*=\s*r""".*?"""'
return re.sub(pattern, '', content, flags=re.DOTALL | re.MULTILINE)
def remove_color_block(content):
"""Remove color constant definitions."""
lines = content.split('\n')
result = []
skip = False
for line in lines:
stripped = line.strip()
if stripped.startswith('# Color palette'):
skip = True
continue
if skip:
if stripped == '' or stripped.startswith('#'):
continue
if any(stripped.startswith(f'{c} =') for c in COLOR_VARS):
continue
if stripped.startswith('"'):
continue
skip = False
result.append(line)
# Remove trailing blank lines that were left
while result and result[-1].strip() == '':
result.pop()
result.append('')
return '\n'.join(result)
def remove_func_block(content, func_name):
"""Remove a function definition block."""
# Match def func_name(...):\n ... (until next top-level def or non-indented line)
pattern = rf'^(?:def\s+{re.escape(func_name)}\s*\(.*?\):.*?)(?:^def\s+|^days\s*=|^generate|^for\s+|^[a-zA-Z]|$)'
result = re.sub(pattern, '', content, flags=re.DOTALL | re.MULTILINE)
# Clean up blank lines
result = re.sub(r'\n{3,}', '\n\n', result)
return result
def update_hdr_calls(content, week_num):
"""Replace hdr("Day", "Topic") → hdr(N, "Day", "Topic")."""
pattern = r'hdr\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)'
replacement = f'hdr({week_num}, "\\1", "\\2")'
return re.sub(pattern, replacement, content)
def build_import(week_num):
"""Build the import block for a refactored week file."""
return f'''import os
import glob
from weasyprint import HTML
from math_helpers import (
CSS, TEACHER_CSS,
page, page_multi, hdr, sec, b, bw, bl,
generate_simple,
tg_page, tg_page_multi, tg_sec, tg_grid_answers, tg_note, tg_section_title,
BLUE, GREEN, RED, YELLOW, ORANGE, PURPLE, PINK, TEAL, GOLD,
circle_svg, eq_group_svg, array_svg, shape_svg,
clock_svg, fraction_svg, coin_svg, grid_svg,
bar_graph_svg, star_svg, trophy_svg, medal_svg, check_svg,
times_table_html, skip_count_html, mini_chart_html,
ruler_inches, ruler_cm, measure_line, measure_line_cm,
fraction_bar_svg,
)'''
def extract_day_functions(content):
"""Extract day function names from the content."""
pattern = r'def\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\s*\('
return re.findall(pattern, content, re.IGNORECASE)
def extract_days_list(content):
"""Extract the days list definition."""
# Find the days = [...] block
pattern = r'^days\s*=\s*\[(.*?)\]'
match = re.search(pattern, content, re.DOTALL | re.MULTILINE)
if match:
return match.group(0)
return None
def build_tg_stub(day_name, week_num, topic_map):
"""Build a teacher guide stub function for a day."""
day_lower = day_name.lower()
topic = topic_map.get(day_lower, f"Week {week_num} {day_name}")
func_name = f'{day_lower}_tg'
return f'''
def {func_name}():
"""Teacher guide for {day_name}."""
return tg_page("{day_name}", "{topic}",
tg_note("Answers for {day_name} - {topic}")
)'''
def get_day_topics(content):
"""Extract topic strings from the days list."""
topics = {}
# Find patterns like ("Monday", "Topic Name", monday, ...)
pattern = r'\("([^"]+)"\s*,\s*"([^"]+)"\s*,\s*(\w+)'
for match in re.finditer(pattern, content):
day = match.group(1).lower()
topic = match.group(2)
topics[day] = topic
return topics
def refactor_week(week_num, dry_run=False):
"""Refactor a single week file."""
path = os.path.join(BASE, f'generate_week{week_num}.py')
if not os.path.exists(path):
print(f" SKIP: {path} not found")
return False
content = read_file(path)
original = content
# 1. Extract day functions and topics before modifying
day_funcs = extract_day_functions(content)
topics = get_day_topics(content)
# 2. Remove CSS block
content = remove_css_block(content)
# 3. Remove color constants
content = remove_color_block(content)
# 4. Remove helper functions
for func in HELPER_FUNCS:
content = remove_func_block(content, func)
# 5. Remove SVG generators
for func in SVG_FUNCS:
content = remove_func_block(content, func)
# 6. Update hdr calls
content = update_hdr_calls(content, week_num)
# 7. Remove old import lines
lines = content.split('\n')
new_lines = []
skip_imports = False
for line in lines:
stripped = line.strip()
# Skip old imports
if stripped.startswith('import os') or stripped.startswith('import glob') or stripped.startswith('from weasyprint'):
skip_imports = True
continue
if skip_imports:
if stripped == '' or stripped.startswith('#'):
continue
skip_imports = False
# Skip BASE/WEEK setup that we'll rebuild
new_lines.append(line)
content = '\n'.join(new_lines)
# 8. Remove old BASE/WEEK setup and PDF cleanup
content = re.sub(
r'^BASE\s*=\s*os\.path\.expanduser\(.*?\)\nWEEK\s*=\s*os\.path\.join\(BASE,\s*["\']Week_\d+["\']\)\nos\.makedirs\(WEEK.*?\)\nfor\s+f\s+in\s+glob\.glob\(os\.path\.join\(WEEK.*?\)\):\s*os\.remove\(f\)',
'', content, flags=re.MULTILINE | re.DOTALL
)
# 9. Remove old generate loop at the end
content = re.sub(
r'^#?\s*=.*generate all days.*?\n.*?for\s+day_name.*?print\(".*?Week \d+ complete',
'', content, flags=re.MULTILINE | re.DOTALL
)
# Clean up
content = re.sub(r'\n{3,}', '\n\n', content)
# 10. Build new header
import_block = build_import(week_num)
# Extract the docstring
doc_match = re.match(r'^#!?.*?\n"""(.*?)"""', content, re.DOTALL | re.MULTILINE)
docstring = doc_match.group(0) if doc_match else ''
# Remove shebang and docstring from content
content = re.sub(r'^#!?.*?"""(.*?)"""', '', content, flags=re.DOTALL | re.MULTILINE)
content = re.sub(r'^\n+', '', content)
# Build teacher guide stubs
tg_stubs = ''
for day_lower, topic in topics.items():
tg_stubs += build_tg_stub(day_lower.capitalize(), week_num, topics)
# Build the new file
new_content = f'''#!/usr/bin/env python3
"""
{docstring}
"""
{import_block}
BASE = os.path.expanduser("~/Home_School/2nd_Grade/Math")
WEEK = os.path.join(BASE, "Week_{week_num}")
{content}
# ═══════════════════════════════════════════════════
# TEACHER GUIDES
# ═══════════════════════════════════════════════════
{tg_stubs}
# ═══════════════════════════════════════════════════
# Generate
# ═══════════════════════════════════════════════════
days = [
'''
for day_lower, topic in topics.items():
day_cap = day_lower.capitalize()
new_content += f' ("{day_cap}", "{topic}", {day_lower}, {day_lower}_tg),\n'
new_content += ''']
generate_simple(WEEK, days)
print(f"\\nWeek {week_num} complete! PDFs + Teacher Guides in", WEEK)'''
if dry_run:
print(f" Week {week_num}: Would refactor {len(content)} → {len(new_content)} chars")
return True
write_file(path, new_content)
print(f" Week {week_num}: Refactored ({len(original)} → {len(new_content)} chars)")
return True
def main():
print("Refactoring math course generators...")
print(f"Base: {BASE}")
print()
for week in range(2, 33):
refactor_week(week)
print()
print("Done! Now test each week:")
print(" for i in {2..32}; do python3 generate_week${i}.py; done")
if __name__ == '__main__':
main()