#!/usr/bin/env python3
"""
Refactor generate_weekN.py to use math_helpers.py.
Strategy: Line-by-line parsing that identifies sections:
- KEEP: docstring, day functions (monday..friday), days list, generate loop
- REPLACE: imports, BASE/WEEK setup, CSS, color constants, helpers, SVG funcs
- ADD: teacher guide stubs
"""
import os
import re
BASE = os.path.expanduser("~/Home_School/2nd_Grade/Math")
# Names of functions we want to KEEP as day content
DAY_FUNCS = {'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'}
# Names of functions to REMOVE (now in math_helpers)
REMOVE_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',
'hdr', 'sec', 'b', 'bw', 'bl', 'page', 'page_multi',
}
def parse_week_file(path):
"""Parse a week file into sections."""
with open(path, 'r') as f:
lines = f.readlines()
# Extract docstring
docstring = ''
doc_end = 0
in_doc = False
for i, line in enumerate(lines):
if '"""' in line:
if not in_doc:
in_doc = True
doc_start = i
else:
doc_end = i + 1
break
if in_doc:
docstring = ''.join(lines[doc_start:doc_end])
# Find function boundaries
func_starts = {} # name -> line index
func_ends = {} # name -> line index
current_func = None
current_func_indent = 0
for i, line in enumerate(lines):
# Check for top-level def
stripped = line.strip()
if stripped.startswith('def ') and not line[0].isspace():
# End previous function
if current_func:
func_ends[current_func] = i
match = re.match(r'def\s+(\w+)\s*\(', stripped)
if match:
current_func = match.group(1)
func_starts[current_func] = i
current_func_indent = 0
# End last function
if current_func:
func_ends[current_func] = len(lines)
# Find days list
days_start = None
days_end = None
in_days = False
for i, line in enumerate(lines):
stripped = line.strip()
if re.match(r'^days\s*=\s*\[', stripped) and not line[0].isspace():
days_start = i
in_days = True
elif in_days:
if stripped == ']':
days_end = i + 1
break
if stripped.startswith(']') and not line[0].isspace():
days_end = i + 1
break
# Find generate loop (after days list)
gen_start = days_end if days_end else None
gen_end = len(lines)
# Find CSS block
css_start = None
css_end = None
in_css = False
for i, line in enumerate(lines):
stripped = line.strip()
if re.match(r'^CSS\s*=\s*r"""', stripped) or re.match(r'^CSS\s*=\s*"""', stripped):
css_start = i
in_css = True
elif in_css:
if stripped == '"""' or stripped.endswith('"""'):
css_end = i + 1
break
# Find color constants block
color_start = None
color_end = None
for i, line in enumerate(lines):
stripped = line.strip()
if stripped == '# Color palette':
color_start = i
elif color_start is not None:
if stripped.startswith('#') or any(stripped.startswith(c) and '=' in stripped for c in ['RED', 'BLUE', 'GREEN', 'YELLOW', 'ORANGE', 'PURPLE', 'PINK', 'TEAL', 'GOLD']):
color_end = i + 1
elif stripped == '':
continue
else:
color_end = i
break
# Find shebang line
shebang = ''
if lines and lines[0].startswith('#!'):
shebang = lines[0].rstrip('\n')
return {
'shebang': shebang,
'docstring': docstring,
'func_starts': func_starts,
'func_ends': func_ends,
'days_start': days_start,
'days_end': days_end,
'gen_start': gen_start,
'gen_end': gen_end,
'css_start': css_start,
'css_end': css_end,
'color_start': color_start,
'color_end': color_end,
'lines': lines,
}
def extract_day_topics(days_text):
"""Extract day name and topic from days list text."""
topics = {}
pattern = r'\("([^"]+)"\s*,\s*"([^"]+)"\s*,\s*(\w+)'
for match in re.finditer(pattern, days_text):
day = match.group(1).lower()
topic = match.group(2)
func = match.group(3)
topics[day] = {'topic': topic, 'func': func}
return topics
def build_refactored(path, week_num):
"""Build refactored version of a week file."""
info = parse_week_file(path)
lines = info['lines']
# Extract day function content
day_content_lines = []
for day in DAY_FUNCS:
if day in info['func_starts']:
start = info['func_starts'][day]
end = info['func_ends'].get(day, len(lines))
# Add comment header
day_content_lines.append(f'\n# ═══════════════════════════════════════════════════')
day_content_lines.append(f'# {day.upper()}')
day_content_lines.append(f'# ═══════════════════════════════════════════════════')
day_content_lines.append('')
for i in range(start, end):
day_content_lines.append(lines[i])
day_content_lines.append('')
# Extract and update days list + generate loop
days_list_lines = []
gen_lines = []
if info['days_start'] is not None:
for i in range(info['days_start'], info['gen_end']):
days_list_lines.append(lines[i])
days_text = ''.join(days_list_lines)
topics = extract_day_topics(days_text)
# Build teacher guide stubs
tg_lines = []
tg_lines.append('')
tg_lines.append('# ═══════════════════════════════════════════════════')
tg_lines.append('# TEACHER GUIDES')
tg_lines.append('# ═══════════════════════════════════════════════════')
for day, info_dict in topics.items():
topic = info_dict['topic']
tg_name = f'{day}_tg'
tg_lines.append(f'''
def {tg_name}():
"""Teacher guide for {day.capitalize()} - {topic}."""
return tg_page("{day.capitalize()}", "{topic}",
tg_note("Answers for {day.capitalize()} - {topic}")
)''')
# Build new days list
new_days_lines = []
new_days_lines.append('')
new_days_lines.append('# ═══════════════════════════════════════════════════')
new_days_lines.append('# Generate')
new_days_lines.append('# ═══════════════════════════════════════════════════')
new_days_lines.append('days = [')
for day, info_dict in topics.items():
day_cap = day.capitalize()
func = info_dict['func']
tg_name = f'{day}_tg'
new_days_lines.append(f' ("{day_cap}", "{info_dict["topic"]}", {func}, {tg_name}),')
new_days_lines.append(']')
new_days_lines.append('')
new_days_lines.append('generate_simple(WEEK, days)')
new_days_lines.append(f'print(f"\\\\nWeek {week_num} complete! PDFs + Teacher Guides in", WEEK)')
# Assemble
result = []
if info['shebang']:
result.append(info['shebang'])
result.append('"""')
# Clean up docstring
doc_lines = info['docstring'].strip().split('\n')
for dl in doc_lines:
result.append(dl)
result.append('"""')
result.append('')
result.append('import os')
result.append('import glob')
result.append('from weasyprint import HTML')
result.append('')
result.append('from math_helpers import (')
result.append(' CSS, TEACHER_CSS,')
result.append(' page, page_multi, hdr, sec, b, bw, bl,')
result.append(' generate_simple,')
result.append(' tg_page, tg_page_multi, tg_sec, tg_grid_answers, tg_note, tg_section_title,')
result.append(' BLUE, GREEN, RED, YELLOW, ORANGE, PURPLE, PINK, TEAL, GOLD,')
result.append(' circle_svg, eq_group_svg, array_svg, shape_svg,')
result.append(' clock_svg, fraction_svg, coin_svg, grid_svg,')
result.append(' bar_graph_svg, star_svg, trophy_svg, medal_svg, check_svg,')
result.append(' times_table_html, skip_count_html, mini_chart_html,')
result.append(' ruler_inches, ruler_cm, measure_line, measure_line_cm,')
result.append(' fraction_bar_svg,')
result.append(')')
result.append('')
result.append('BASE = os.path.expanduser("~/Home_School/2nd_Grade/Math")')
result.append(f'WEEK = os.path.join(BASE, "Week_{week_num}")')
# Day functions
result.extend(day_content_lines)
# Teacher guides
result.extend(tg_lines)
# Generate
result.extend(new_days_lines)
result.append('')
return '\n'.join(result)
def main():
print("Refactoring math course generators (v2)...")
print()
for week in range(2, 33):
path = os.path.join(BASE, f'generate_week{week}.py')
if not os.path.exists(path):
print(f" SKIP: Week {week} not found")
continue
try:
new_content = build_refactored(path, week)
with open(path, 'w') as f:
f.write(new_content)
size = len(new_content)
print(f" Week {week}: OK ({size:,} bytes)")
except Exception as e:
print(f" Week {week}: ERROR - {e}")
print()
print("Done!")
if __name__ == '__main__':
main()