#!/usr/bin/env python3
"""
Lint math week generators for common bugs.
Checks:
1. Missing f-string prefix on lines with += and {var} patterns
2. Uninterpolated variables in HTML output
3. Activity count violations (should be ≤5 per day)
"""
import sys
import re
import os
def check_fstring_issues(filepath):
"""Find lines with {var} that lack f-string prefix."""
issues = []
with open(filepath, 'r') as f:
lines = f.readlines()
for i, line in enumerate(lines, 1):
# Skip comments and docstrings
stripped = line.strip()
if stripped.startswith('#') or stripped.startswith('"""'):
continue
# Check for += with {var} but no f prefix
if '+=' not in line and not line.strip().startswith('s'):
continue
# Check for {var} patterns without f prefix
if not re.search(r'\{[a-zA-Z_][a-zA-Z0-9_]*\}', line):
continue
# Skip if it already has f prefix on the part with {var}
# Pattern: += f'...' or += f"..." or ... + f'...' or ... + f"..."
if '+= f"' in line or "+= f'" in line or re.search(r'\+ f["\'].*\{[a-zA-Z_]', line):
continue
# Skip if it's using .format()
if '.format(' in line:
continue
# Skip if it's a function call like {b(...)}
if re.search(r'\{[a-zA-Z_]+\(', line):
continue
issues.append({
'line': i,
'content': line.rstrip(),
'hint': 'Missing f-string prefix? Add f before the quote.'
})
return issues
def check_html_output(filepath):
"""Check generated HTML for uninterpolated {var} patterns."""
issues = []
with open(filepath, 'r') as f:
content = f.read()
# Find {var} patterns that look like uninterpolated variables
# Exclude things like {b("5em")} which are function calls
pattern = r'\{[a-zA-Z_][a-zA-Z0-9_]*\}'
matches = re.findall(pattern, content)
# Filter out function call patterns like {b(...)}
real_vars = [m for m in matches if '(' not in m]
if real_vars:
issues.append({
'vars': real_vars[:10], # Show first 10
'hint': 'These variables were not interpolated. Check for missing f-string prefix.'
})
return issues
def check_activity_count(filepath):
"""Check that each day has ≤5 activities (new standard)."""
issues = []
with open(filepath, 'r') as f:
content = f.read()
# Find page() calls and count sec() calls in their arguments
# This is a rough check — looks for sec( patterns
return issues
def main():
if len(sys.argv) < 2:
print("Usage: lint_week.py <week_generator.py> [generated_html_files...]")
print("Example: lint_week.py generate_week8.py Week_8/*.html")
sys.exit(1)
generator = sys.argv[1]
html_files = sys.argv[2:] if len(sys.argv) > 2 else []
print(f"Linting: {generator}")
print("=" * 60)
errors = 0
# Check generator for f-string issues
print("\n1. Checking f-string interpolation...")
issues = check_fstring_issues(generator)
if issues:
print(f" ⚠ Found {len(issues)} potential issue(s):")
for issue in issues:
print(f" Line {issue['line']}: {issue['content'][:80]}...")
print(f" → {issue['hint']}")
errors += len(issues)
else:
print(" ✓ No f-string issues found")
# Check HTML output if files provided
if html_files:
print("\n2. Checking HTML output...")
for html_file in html_files:
if not os.path.exists(html_file):
print(f" ⚠ File not found: {html_file}")
continue
issues = check_html_output(html_file)
if issues:
print(f" ⚠ {html_file}:")
for issue in issues:
print(f" Uninterpolated vars: {issue['vars']}")
print(f" → {issue['hint']}")
errors += 1
else:
print(f" ✓ {html_file}")
print("\n" + "=" * 60)
if errors == 0:
print("✓ All checks passed!")
sys.exit(0)
else:
print(f"✗ Found {errors} issue(s)")
sys.exit(1)
if __name__ == "__main__":
main()