Files
hugohe3--ppt-master/skills/ppt-master/scripts/batch_validate.py
T
wehub-resource-sync 8ef9db2513
Deploy to GitHub Pages / deploy (push) Has been cancelled
Deploy to GitHub Pages / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:56:49 +08:00

325 lines
10 KiB
Python

#!/usr/bin/env python3
"""
PPT Master - Batch Project Validation Tool
Checks the structural integrity and compliance of multiple projects at once.
Usage:
python3 scripts/batch_validate.py examples
python3 scripts/batch_validate.py projects
python3 scripts/batch_validate.py --all
python3 scripts/batch_validate.py examples projects
"""
import argparse
import sys
from collections import defaultdict
from pathlib import Path
from console_encoding import configure_utf8_stdio
configure_utf8_stdio()
try:
from project_utils import (
find_all_projects,
get_project_info,
validate_project_structure,
validate_svg_viewbox,
CANVAS_FORMATS
)
except ImportError:
print("Error: Unable to import project_utils module")
print("Please ensure project_utils.py is in the same directory")
sys.exit(1)
class BatchValidator:
"""Batch validator"""
def __init__(self):
self.results: list[dict[str, object]] = []
self.summary = {
'total': 0,
'valid': 0,
'has_errors': 0,
'has_warnings': 0,
'missing_readme': 0,
'missing_spec': 0,
'svg_issues': 0
}
def validate_directory(self, directory: str, recursive: bool = False) -> list[dict[str, object]]:
"""
Validate all projects in a directory
Args:
directory: Directory path
recursive: Whether to recursively search subdirectories
Returns:
List of validation results
"""
dir_path = Path(directory)
if not dir_path.exists():
print(f"[ERROR] Directory does not exist: {directory}")
return []
print(f"\n[SCAN] Scanning directory: {directory}")
print("=" * 80)
projects = find_all_projects(directory)
if not projects:
print(f"[WARN] No projects found")
return []
print(f"Found {len(projects)} project(s)\n")
for project_path in projects:
self.validate_project(str(project_path))
return self.results
def validate_project(self, project_path: str) -> dict[str, object]:
"""
Validate a single project
Args:
project_path: Project path
Returns:
Validation result dictionary
"""
self.summary['total'] += 1
# Get project info
info = get_project_info(project_path)
# Validate project structure
is_valid, errors, warnings = validate_project_structure(project_path)
# Validate SVG viewBox
svg_warnings = []
if info['svg_files']:
project_path_obj = Path(project_path)
svg_files = [project_path_obj / 'svg_output' /
f for f in info['svg_files']]
svg_warnings = validate_svg_viewbox(svg_files, info['format'])
# Aggregate results
result = {
'path': project_path,
'name': info['name'],
'format': info['format_name'],
'date': info['date_formatted'],
'svg_count': info['svg_count'],
'is_valid': is_valid,
'errors': errors,
'warnings': warnings + svg_warnings,
'has_readme': info['has_readme'],
'has_spec': info['has_spec']
}
self.results.append(result)
# Update statistics
if is_valid and not warnings and not svg_warnings:
self.summary['valid'] += 1
status = "[OK]"
elif errors:
self.summary['has_errors'] += 1
status = "[ERROR]"
else:
self.summary['has_warnings'] += 1
status = "[WARN]"
if not info['has_readme']:
self.summary['missing_readme'] += 1
if not info['has_spec']:
self.summary['missing_spec'] += 1
if svg_warnings:
self.summary['svg_issues'] += 1
# Print result
print(f"{status} {info['name']}")
print(f" Path: {project_path}")
print(
f" Format: {info['format_name']} | SVG: {info['svg_count']} file(s) | Date: {info['date_formatted']}")
if errors:
print(f" [ERROR] Errors ({len(errors)}):")
for error in errors:
print(f" - {error}")
if warnings or svg_warnings:
all_warnings = warnings + svg_warnings
print(f" [WARN] Warnings ({len(all_warnings)}):")
for warning in all_warnings[:3]: # Only show first 3 warnings
print(f" - {warning}")
if len(all_warnings) > 3:
print(f" ... and {len(all_warnings) - 3} more warning(s)")
print()
return result
def print_summary(self) -> None:
"""Print a summary of validation results."""
print("\n" + "=" * 80)
print("[Summary] Validation Summary")
print("=" * 80)
print(f"\nTotal projects: {self.summary['total']}")
print(
f" [OK] Fully valid: {self.summary['valid']} ({self._percentage(self.summary['valid'])}%)")
print(
f" [WARN] With warnings: {self.summary['has_warnings']} ({self._percentage(self.summary['has_warnings'])}%)")
print(
f" [ERROR] With errors: {self.summary['has_errors']} ({self._percentage(self.summary['has_errors'])}%)")
print(f"\nCommon issues:")
print(f" Missing README.md: {self.summary['missing_readme']} project(s)")
print(f" Missing design spec: {self.summary['missing_spec']} project(s)")
print(f" SVG format issues: {self.summary['svg_issues']} project(s)")
# Group statistics by format
format_stats = defaultdict(int)
for result in self.results:
format_stats[result['format']] += 1
if format_stats:
print(f"\nCanvas format distribution:")
for fmt, count in sorted(format_stats.items(), key=lambda x: x[1], reverse=True):
print(f" {fmt}: {count} project(s)")
# Provide fix suggestions
if self.summary['has_errors'] > 0 or self.summary['has_warnings'] > 0:
print(f"\n[TIP] Fix suggestions:")
if self.summary['missing_readme'] > 0:
print(f" 1. Create documentation for projects missing README")
print(
f" Reference: examples/google_annual_report_ppt169_20251116/README.md")
if self.summary['svg_issues'] > 0:
print(f" 2. Check SVG root viewBox settings")
print(f" The SVG root viewBox is the export canvas authority")
if self.summary['missing_spec'] > 0:
print(f" 3. Add design specification files")
def _percentage(self, count: int) -> int:
"""Calculate percentage"""
if self.summary['total'] == 0:
return 0
return int(count / self.summary['total'] * 100)
def export_report(self, output_file: str = 'validation_report.txt') -> None:
"""
Export validation report to file
Args:
output_file: Output file path
"""
with open(output_file, 'w', encoding='utf-8') as f:
f.write("PPT Master Project Validation Report\n")
f.write("=" * 80 + "\n\n")
for result in self.results:
status = "[OK] Valid" if result['is_valid'] and not result['warnings'] else \
"[ERROR] Error" if result['errors'] else "[WARN] Warning"
f.write(f"{status} - {result['name']}\n")
f.write(f"Path: {result['path']}\n")
f.write(
f"Format: {result['format']} | SVG: {result['svg_count']} file(s)\n")
if result['errors']:
f.write(f"\nErrors:\n")
for error in result['errors']:
f.write(f" - {error}\n")
if result['warnings']:
f.write(f"\nWarnings:\n")
for warning in result['warnings']:
f.write(f" - {warning}\n")
f.write("\n" + "-" * 80 + "\n\n")
# Write summary
f.write("\n" + "=" * 80 + "\n")
f.write("Validation Summary\n")
f.write("=" * 80 + "\n\n")
f.write(f"Total projects: {self.summary['total']}\n")
f.write(f"Fully valid: {self.summary['valid']}\n")
f.write(f"With warnings: {self.summary['has_warnings']}\n")
f.write(f"With errors: {self.summary['has_errors']}\n")
print(f"\n[REPORT] Validation report exported: {output_file}")
def build_parser() -> argparse.ArgumentParser:
"""Build the command-line parser."""
parser = argparse.ArgumentParser(
description="Validate one or more PPT Master project directories.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""Examples:
python3 scripts/batch_validate.py examples
python3 scripts/batch_validate.py projects
python3 scripts/batch_validate.py examples projects
python3 scripts/batch_validate.py --all
""",
)
parser.add_argument("directories", nargs="*", help="Directories to scan")
parser.add_argument("--all", action="store_true", help="Validate examples and projects")
parser.add_argument("--export", action="store_true", help="Write a validation report")
parser.add_argument(
"--output",
default="validation_report.txt",
help="Report path when --export is used",
)
return parser
def main(argv: list[str] | None = None) -> int:
"""Run the CLI entry point."""
parser = build_parser()
args = parser.parse_args(argv)
validator = BatchValidator()
if args.all:
directories = ['examples', 'projects']
else:
directories = args.directories
if not directories:
parser.print_help()
return 0
# Validate each directory
for directory in directories:
if Path(directory).exists():
validator.validate_directory(directory)
else:
print(f"[WARN] Skipping non-existent directory: {directory}\n")
# Print summary
validator.print_summary()
# Export report (if specified)
if args.export:
validator.export_report(args.output)
# Return exit code
if validator.summary['has_errors'] > 0:
return 1
elif validator.summary['has_warnings'] > 0:
return 2
return 0
if __name__ == '__main__':
raise SystemExit(main())