Release standalone PPT template workflow 0.5.0
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
PPT Master - Error Message Helper
|
||||
|
||||
Provides user-friendly error messages and specific fix suggestions.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from console_encoding import configure_utf8_stdio
|
||||
|
||||
configure_utf8_stdio()
|
||||
|
||||
|
||||
class ErrorHelper:
|
||||
"""Error message helper."""
|
||||
|
||||
# Error types and their corresponding fix suggestions
|
||||
ERROR_SOLUTIONS = {
|
||||
'missing_readme': {
|
||||
'message': 'Missing README.md file',
|
||||
'solutions': [
|
||||
'Create a README.md file with project description, usage instructions, etc.',
|
||||
'Document the project goal, source material, canvas, generated artifacts, and export path',
|
||||
'Keep project-specific instructions local instead of copying a repository example'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'missing_spec': {
|
||||
'message': 'Missing design specification file',
|
||||
'solutions': [
|
||||
'Create a design_spec.md file',
|
||||
'Include: canvas specs, color scheme, font specs, layout specs, content outline',
|
||||
'Refer to the design specification generated by the Strategist role'
|
||||
],
|
||||
'severity': 'warning'
|
||||
},
|
||||
'missing_svg_output': {
|
||||
'message': 'Missing svg_output directory',
|
||||
'solutions': [
|
||||
'Create the svg_output directory: mkdir svg_output',
|
||||
'Place generated SVG files in this directory',
|
||||
'Ensure SVG files follow naming convention: slide_XX_name.svg'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'empty_svg_output': {
|
||||
'message': 'svg_output directory is empty',
|
||||
'solutions': [
|
||||
'Use the AI role (Executor) to generate SVG files',
|
||||
'Save SVG files to the svg_output directory',
|
||||
'Ensure file naming format: slide_01_cover.svg, slide_02_content.svg, etc.'
|
||||
],
|
||||
'severity': 'warning'
|
||||
},
|
||||
'invalid_svg_naming': {
|
||||
'message': 'Non-standard SVG file naming',
|
||||
'solutions': [
|
||||
'Rename SVG files using format: slide_XX_name.svg',
|
||||
'XX should be a two-digit number (01, 02, ...)',
|
||||
'name should use English or pinyin, separated by underscores',
|
||||
'Example: slide_01_cover.svg, slide_02_overview.svg'
|
||||
],
|
||||
'severity': 'warning'
|
||||
},
|
||||
'missing_project_date': {
|
||||
'message': 'Project directory missing date suffix',
|
||||
'solutions': [
|
||||
'Rename the project directory to add a date suffix: _YYYYMMDD',
|
||||
'Format: {project_name}_{format}_{YYYYMMDD}',
|
||||
'Example: my_project_ppt169_20251116',
|
||||
'Command: mv old_name new_name_ppt169_20251116'
|
||||
],
|
||||
'severity': 'warning'
|
||||
},
|
||||
'viewbox_mismatch': {
|
||||
'message': 'SVG viewBox differs from the recorded canvas format',
|
||||
'solutions': [
|
||||
'Check the viewBox attribute of SVG files',
|
||||
'Treat the root viewBox as the actual canvas size',
|
||||
'If the project metadata is stale, export will use the SVG viewBox',
|
||||
'PPT 16:9 should be: viewBox="0 0 1280 720"',
|
||||
'PPT 4:3 should be: viewBox="0 0 1024 768"',
|
||||
'Reference: references/canvas-formats.md'
|
||||
],
|
||||
'severity': 'warning'
|
||||
},
|
||||
'multiple_viewboxes': {
|
||||
'message': 'Multiple different viewBox settings detected',
|
||||
'solutions': [
|
||||
'Unify the viewBox across all SVG files',
|
||||
'All pages in the same project should use the same canvas size',
|
||||
'Use find-and-replace tools for batch correction',
|
||||
'Reference the viewBox setting of the first page'
|
||||
],
|
||||
'severity': 'warning'
|
||||
},
|
||||
'no_viewbox': {
|
||||
'message': 'SVG file missing viewBox attribute',
|
||||
'solutions': [
|
||||
'Add the viewBox attribute to the SVG root element',
|
||||
'Format: <svg viewBox="0 0 1280 720" ...>',
|
||||
'Root width/height are optional compatibility attributes',
|
||||
'The root viewBox is mandatory for SVG generation'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'foreignobject_detected': {
|
||||
'message': 'Forbidden <foreignObject> element detected',
|
||||
'solutions': [
|
||||
'Remove <foreignObject> elements',
|
||||
'Use <text> + <tspan> for manual line wrapping',
|
||||
'This is a project technical specification requirement',
|
||||
'Reference: references/shared-standards-core.md'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'clippath_on_non_image': {
|
||||
'message': 'clip-path is only allowed on <image> elements',
|
||||
'solutions': [
|
||||
'Remove clip-path from shapes / groups / text',
|
||||
'Draw the target geometry directly with the matching native element: <circle> / <ellipse> / <rect rx="..."> / <polygon> / <path>. A rect clipped to a circle is just a <circle>.',
|
||||
'clip-path on <image> is conditionally allowed — see references/shared-standards-core.md §1.2'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'clippath_def_missing': {
|
||||
'message': 'clip-path references a <clipPath> id that does not exist in <defs>',
|
||||
'solutions': [
|
||||
'Define the referenced <clipPath id="..."> inside <defs>',
|
||||
'The clipPath must contain exactly one shape child (circle / ellipse / rect with rx,ry / path / polygon)',
|
||||
'Reference: references/shared-standards-core.md §1.2'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'mask_detected': {
|
||||
'message': 'Forbidden <mask> element detected',
|
||||
'solutions': [
|
||||
'Remove <mask> elements',
|
||||
'PPT does not support SVG masks',
|
||||
'Use opacity (opacity/fill-opacity) as an alternative'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'style_element_detected': {
|
||||
'message': 'Forbidden <style> element detected',
|
||||
'solutions': [
|
||||
'Remove <style> elements',
|
||||
'Convert CSS styles to inline attributes',
|
||||
'Example: fill="#000" instead of class="text-black"'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'class_attribute_detected': {
|
||||
'message': 'Forbidden class attribute detected',
|
||||
'solutions': [
|
||||
'Remove all class attributes',
|
||||
'Use inline styles instead',
|
||||
'Example: fill="#000" stroke="#333" directly on the element'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'id_attribute_detected': {
|
||||
'message': 'Forbidden CSS selector usage with id detected',
|
||||
'solutions': [
|
||||
'Keep IDs for local references or documented semantic/animation groups',
|
||||
'Remove <style> rules and CSS selectors',
|
||||
'Use inline presentation attributes instead'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'external_css_detected': {
|
||||
'message': 'Forbidden external CSS reference detected',
|
||||
'solutions': [
|
||||
'Remove <?xml-stylesheet?> declarations',
|
||||
'Remove <link rel="stylesheet"> references',
|
||||
'Remove @import external styles',
|
||||
'Convert styles to inline attributes'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
# Note: <marker> and marker-end are NO LONGER forbidden — they are
|
||||
# conditionally allowed (see references/shared-standards-core.md §1.1).
|
||||
# The converter maps qualifying markers to native DrawingML arrow heads.
|
||||
'marker_orphan_ref': {
|
||||
'message': 'marker-start/marker-end references a marker id, but no <marker> element is defined',
|
||||
'solutions': [
|
||||
'Define the <marker> inside <defs>',
|
||||
'Or remove the marker-start/marker-end attribute',
|
||||
'See shared-standards-core.md §1.1 for marker constraints',
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'event_attribute_detected': {
|
||||
'message': 'Forbidden event attribute detected',
|
||||
'solutions': [
|
||||
'Remove onclick/onload and other event attributes',
|
||||
'Scripts and event handling are forbidden in SVG',
|
||||
'Implement interactivity in PPT instead'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'set_detected': {
|
||||
'message': 'Forbidden <set> element detected',
|
||||
'solutions': [
|
||||
'Remove <set> elements',
|
||||
'SVG animations will not be exported to PPT',
|
||||
'Use PPT native animations for animation effects'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'iframe_detected': {
|
||||
'message': 'Forbidden <iframe> element detected',
|
||||
'solutions': [
|
||||
'Remove <iframe> elements',
|
||||
'External pages should not be embedded in SVG'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'textpath_detected': {
|
||||
'message': 'Forbidden <textPath> element detected',
|
||||
'solutions': [
|
||||
'Remove <textPath> elements',
|
||||
'Text on path is not compatible with PPT',
|
||||
'Use regular <text> elements and adjust position manually'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'webfont_detected': {
|
||||
'message': 'Forbidden web font (@font-face) detected',
|
||||
'solutions': [
|
||||
'Remove @font-face declarations',
|
||||
'Use font-family stacks that export PPT-safe pre-installed typefaces',
|
||||
'Example: font-family: "Microsoft YaHei", Arial, sans-serif'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'animation_detected': {
|
||||
'message': 'Forbidden SMIL animation element detected',
|
||||
'solutions': [
|
||||
'Remove all <animate>, <animateMotion>, <animateTransform> and similar elements',
|
||||
'SVG animations will not be exported to PPT',
|
||||
'Use PPT native animations for animation effects'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'script_detected': {
|
||||
'message': 'Forbidden <script> element detected',
|
||||
'solutions': [
|
||||
'Remove <script> elements',
|
||||
'Scripts and event handling are forbidden',
|
||||
'JavaScript in SVG will not execute in PPT'
|
||||
],
|
||||
'severity': 'error'
|
||||
},
|
||||
'invalid_font': {
|
||||
'message': 'Font stack exports non-PPT-safe typefaces to PPTX',
|
||||
'solutions': [
|
||||
'Use stacks whose exported Latin / EA typefaces are pre-installed',
|
||||
'CJK: "Microsoft YaHei", sans-serif | SimSun, serif',
|
||||
'Latin: Arial, sans-serif | "Times New Roman", serif',
|
||||
'Mono: Consolas, "Courier New", monospace',
|
||||
'See strategist.md §g for the full PPT-safe discipline'
|
||||
],
|
||||
'severity': 'warning'
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_solution(cls, error_type: str, context: Optional[Dict] = None) -> Dict:
|
||||
"""
|
||||
Get the solution for an error.
|
||||
|
||||
Args:
|
||||
error_type: Error type
|
||||
context: Context information (optional)
|
||||
|
||||
Returns:
|
||||
Dictionary containing message, solutions, severity
|
||||
"""
|
||||
if error_type in cls.ERROR_SOLUTIONS:
|
||||
solution = cls.ERROR_SOLUTIONS[error_type].copy()
|
||||
|
||||
# Customize message based on context
|
||||
if context:
|
||||
solution = cls._customize_solution(solution, context)
|
||||
|
||||
return solution
|
||||
|
||||
# Unknown error type
|
||||
return {
|
||||
'message': 'Unknown error',
|
||||
'solutions': ['Please check the documentation or contact the maintainer'],
|
||||
'severity': 'error'
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _customize_solution(cls, solution: Dict, context: Dict) -> Dict:
|
||||
"""
|
||||
Customize solution based on context.
|
||||
|
||||
Args:
|
||||
solution: Original solution
|
||||
context: Context information
|
||||
|
||||
Returns:
|
||||
Customized solution
|
||||
"""
|
||||
customized = solution.copy()
|
||||
|
||||
# Customize based on project path
|
||||
if 'project_path' in context:
|
||||
project_path = context['project_path']
|
||||
customized['solutions'] = [
|
||||
s.replace('<project_path>', project_path).replace(
|
||||
'<your_project>', project_path)
|
||||
for s in customized['solutions']
|
||||
]
|
||||
|
||||
# Customize based on filename
|
||||
if 'file_name' in context:
|
||||
file_name = context['file_name']
|
||||
customized['message'] = f"{customized['message']}: {file_name}"
|
||||
|
||||
# Customize based on expected/actual values
|
||||
if 'expected' in context and 'actual' in context:
|
||||
customized['message'] += f" (expected: {context['expected']}, actual: {context['actual']})"
|
||||
|
||||
return customized
|
||||
|
||||
@classmethod
|
||||
def format_error_message(cls, error_type: str, context: Optional[Dict] = None) -> str:
|
||||
"""
|
||||
Format error message (for terminal output).
|
||||
|
||||
Args:
|
||||
error_type: Error type
|
||||
context: Context information
|
||||
|
||||
Returns:
|
||||
Formatted error message string
|
||||
"""
|
||||
solution = cls.get_solution(error_type, context)
|
||||
|
||||
lines = []
|
||||
|
||||
# Error message
|
||||
severity_icon = "[ERROR]" if solution['severity'] == 'error' else "[WARN]"
|
||||
lines.append(f"{severity_icon} {solution['message']}")
|
||||
|
||||
# Solutions
|
||||
if solution['solutions']:
|
||||
lines.append("\nSuggested fixes:")
|
||||
for i, sol in enumerate(solution['solutions'], 1):
|
||||
lines.append(f" {i}. {sol}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@classmethod
|
||||
def print_error(cls, error_type: str, context: Optional[Dict] = None):
|
||||
"""
|
||||
Print formatted error message.
|
||||
|
||||
Args:
|
||||
error_type: Error type
|
||||
context: Context information
|
||||
"""
|
||||
print(cls.format_error_message(error_type, context))
|
||||
|
||||
@classmethod
|
||||
def get_all_error_types(cls) -> List[str]:
|
||||
"""Get all supported error types."""
|
||||
return list(cls.ERROR_SOLUTIONS.keys())
|
||||
|
||||
@classmethod
|
||||
def print_help(cls):
|
||||
"""Print all error types and solutions."""
|
||||
print("PPT Master - Error Types and Solutions\n")
|
||||
print("=" * 80)
|
||||
|
||||
for error_type, info in cls.ERROR_SOLUTIONS.items():
|
||||
print(f"\n[{error_type}]")
|
||||
print(f"Message: {info['message']}")
|
||||
print(f"Severity: {info['severity']}")
|
||||
print("Solutions:")
|
||||
for i, sol in enumerate(info['solutions'], 1):
|
||||
print(f" {i}. {sol}")
|
||||
print("-" * 80)
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
"""Build the command-line parser."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Look up PPT Master error messages and suggested fixes.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"error_type",
|
||||
nargs="?",
|
||||
choices=sorted(ErrorHelper.ERROR_SOLUTIONS),
|
||||
help="Error type to explain",
|
||||
)
|
||||
parser.add_argument(
|
||||
"context",
|
||||
nargs="*",
|
||||
metavar="key=value",
|
||||
help="Optional context values used by templates",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
"""Run the CLI entry point for error lookup."""
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if not args.error_type:
|
||||
ErrorHelper.print_help()
|
||||
return 0
|
||||
|
||||
context = {}
|
||||
for item in args.context:
|
||||
if '=' not in item:
|
||||
parser.error(f"context values must use key=value syntax: {item}")
|
||||
key, value = item.split('=', 1)
|
||||
context[key] = value
|
||||
|
||||
print(ErrorHelper.format_error_message(args.error_type, context))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user