返回 ppt-master
error_helper.py
根目录 / skills / ppt-master / scripts / error_helper.py
1 #!/usr/bin/env python3
2 """
3 PPT Master - Error Message Helper
4
5 Provides user-friendly error messages and specific fix suggestions.
6 """
7
8 import argparse
9 from typing import Dict, List, Optional
10
11 from console_encoding import configure_utf8_stdio
12
13 configure_utf8_stdio()
14
15
16 class ErrorHelper:
17 """Error message helper."""
18
19 # Error types and their corresponding fix suggestions
20 ERROR_SOLUTIONS = {
21 'missing_readme': {
22 'message': 'Missing README.md file',
23 'solutions': [
24 'Create a README.md file with project description, usage instructions, etc.',
25 'Document the project goal, source material, canvas, generated artifacts, and export path',
26 'Keep project-specific instructions local instead of copying a repository example'
27 ],
28 'severity': 'error'
29 },
30 'missing_spec': {
31 'message': 'Missing design specification file',
32 'solutions': [
33 'Create a design_spec.md file',
34 'Include: canvas specs, color scheme, font specs, layout specs, content outline',
35 'Refer to the design specification generated by the Strategist role'
36 ],
37 'severity': 'warning'
38 },
39 'missing_svg_output': {
40 'message': 'Missing svg_output directory',
41 'solutions': [
42 'Create the svg_output directory: mkdir svg_output',
43 'Place generated SVG files in this directory',
44 'Ensure SVG files follow naming convention: slide_XX_name.svg'
45 ],
46 'severity': 'error'
47 },
48 'empty_svg_output': {
49 'message': 'svg_output directory is empty',
50 'solutions': [
51 'Use the AI role (Executor) to generate SVG files',
52 'Save SVG files to the svg_output directory',
53 'Ensure file naming format: slide_01_cover.svg, slide_02_content.svg, etc.'
54 ],
55 'severity': 'warning'
56 },
57 'invalid_svg_naming': {
58 'message': 'Non-standard SVG file naming',
59 'solutions': [
60 'Rename SVG files using format: slide_XX_name.svg',
61 'XX should be a two-digit number (01, 02, ...)',
62 'name should use English or pinyin, separated by underscores',
63 'Example: slide_01_cover.svg, slide_02_overview.svg'
64 ],
65 'severity': 'warning'
66 },
67 'missing_project_date': {
68 'message': 'Project directory missing date suffix',
69 'solutions': [
70 'Rename the project directory to add a date suffix: _YYYYMMDD',
71 'Format: {project_name}_{format}_{YYYYMMDD}',
72 'Example: my_project_ppt169_20251116',
73 'Command: mv old_name new_name_ppt169_20251116'
74 ],
75 'severity': 'warning'
76 },
77 'viewbox_mismatch': {
78 'message': 'SVG viewBox differs from the recorded canvas format',
79 'solutions': [
80 'Check the viewBox attribute of SVG files',
81 'Treat the root viewBox as the actual canvas size',
82 'If the project metadata is stale, export will use the SVG viewBox',
83 'PPT 16:9 should be: viewBox="0 0 1280 720"',
84 'PPT 4:3 should be: viewBox="0 0 1024 768"',
85 'Reference: references/canvas-formats.md'
86 ],
87 'severity': 'warning'
88 },
89 'multiple_viewboxes': {
90 'message': 'Multiple different viewBox settings detected',
91 'solutions': [
92 'Unify the viewBox across all SVG files',
93 'All pages in the same project should use the same canvas size',
94 'Use find-and-replace tools for batch correction',
95 'Reference the viewBox setting of the first page'
96 ],
97 'severity': 'warning'
98 },
99 'no_viewbox': {
100 'message': 'SVG file missing viewBox attribute',
101 'solutions': [
102 'Add the viewBox attribute to the SVG root element',
103 'Format: <svg viewBox="0 0 1280 720" ...>',
104 'Root width/height are optional compatibility attributes',
105 'The root viewBox is mandatory for SVG generation'
106 ],
107 'severity': 'error'
108 },
109 'foreignobject_detected': {
110 'message': 'Forbidden <foreignObject> element detected',
111 'solutions': [
112 'Remove <foreignObject> elements',
113 'Use <text> + <tspan> for manual line wrapping',
114 'This is a project technical specification requirement',
115 'Reference: references/shared-standards-core.md'
116 ],
117 'severity': 'error'
118 },
119 'clippath_on_non_image': {
120 'message': 'clip-path is only allowed on <image> elements',
121 'solutions': [
122 'Remove clip-path from shapes / groups / text',
123 '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>.',
124 'clip-path on <image> is conditionally allowed — see references/shared-standards-core.md §1.2'
125 ],
126 'severity': 'error'
127 },
128 'clippath_def_missing': {
129 'message': 'clip-path references a <clipPath> id that does not exist in <defs>',
130 'solutions': [
131 'Define the referenced <clipPath id="..."> inside <defs>',
132 'The clipPath must contain exactly one shape child (circle / ellipse / rect with rx,ry / path / polygon)',
133 'Reference: references/shared-standards-core.md §1.2'
134 ],
135 'severity': 'error'
136 },
137 'mask_detected': {
138 'message': 'Forbidden <mask> element detected',
139 'solutions': [
140 'Remove <mask> elements',
141 'PPT does not support SVG masks',
142 'Use opacity (opacity/fill-opacity) as an alternative'
143 ],
144 'severity': 'error'
145 },
146 'style_element_detected': {
147 'message': 'Forbidden <style> element detected',
148 'solutions': [
149 'Remove <style> elements',
150 'Convert CSS styles to inline attributes',
151 'Example: fill="#000" instead of class="text-black"'
152 ],
153 'severity': 'error'
154 },
155 'class_attribute_detected': {
156 'message': 'Forbidden class attribute detected',
157 'solutions': [
158 'Remove all class attributes',
159 'Use inline styles instead',
160 'Example: fill="#000" stroke="#333" directly on the element'
161 ],
162 'severity': 'error'
163 },
164 'id_attribute_detected': {
165 'message': 'Forbidden CSS selector usage with id detected',
166 'solutions': [
167 'Keep IDs for local references or documented semantic/animation groups',
168 'Remove <style> rules and CSS selectors',
169 'Use inline presentation attributes instead'
170 ],
171 'severity': 'error'
172 },
173 'external_css_detected': {
174 'message': 'Forbidden external CSS reference detected',
175 'solutions': [
176 'Remove <?xml-stylesheet?> declarations',
177 'Remove <link rel="stylesheet"> references',
178 'Remove @import external styles',
179 'Convert styles to inline attributes'
180 ],
181 'severity': 'error'
182 },
183 # Note: <marker> and marker-end are NO LONGER forbidden — they are
184 # conditionally allowed (see references/shared-standards-core.md §1.1).
185 # The converter maps qualifying markers to native DrawingML arrow heads.
186 'marker_orphan_ref': {
187 'message': 'marker-start/marker-end references a marker id, but no <marker> element is defined',
188 'solutions': [
189 'Define the <marker> inside <defs>',
190 'Or remove the marker-start/marker-end attribute',
191 'See shared-standards-core.md §1.1 for marker constraints',
192 ],
193 'severity': 'error'
194 },
195 'event_attribute_detected': {
196 'message': 'Forbidden event attribute detected',
197 'solutions': [
198 'Remove onclick/onload and other event attributes',
199 'Scripts and event handling are forbidden in SVG',
200 'Implement interactivity in PPT instead'
201 ],
202 'severity': 'error'
203 },
204 'set_detected': {
205 'message': 'Forbidden <set> element detected',
206 'solutions': [
207 'Remove <set> elements',
208 'SVG animations will not be exported to PPT',
209 'Use PPT native animations for animation effects'
210 ],
211 'severity': 'error'
212 },
213 'iframe_detected': {
214 'message': 'Forbidden <iframe> element detected',
215 'solutions': [
216 'Remove <iframe> elements',
217 'External pages should not be embedded in SVG'
218 ],
219 'severity': 'error'
220 },
221 'textpath_detected': {
222 'message': 'Forbidden <textPath> element detected',
223 'solutions': [
224 'Remove <textPath> elements',
225 'Text on path is not compatible with PPT',
226 'Use regular <text> elements and adjust position manually'
227 ],
228 'severity': 'error'
229 },
230 'webfont_detected': {
231 'message': 'Forbidden web font (@font-face) detected',
232 'solutions': [
233 'Remove @font-face declarations',
234 'Use font-family stacks that export target-installed/approved PPT-safe typefaces',
235 'Example: font-family: "Microsoft YaHei", Arial, sans-serif'
236 ],
237 'severity': 'error'
238 },
239 'animation_detected': {
240 'message': 'Forbidden SMIL animation element detected',
241 'solutions': [
242 'Remove all <animate>, <animateMotion>, <animateTransform> and similar elements',
243 'SVG animations will not be exported to PPT',
244 'Use PPT native animations for animation effects'
245 ],
246 'severity': 'error'
247 },
248 'script_detected': {
249 'message': 'Forbidden <script> element detected',
250 'solutions': [
251 'Remove <script> elements',
252 'Scripts and event handling are forbidden',
253 'JavaScript in SVG will not execute in PPT'
254 ],
255 'severity': 'error'
256 },
257 'invalid_font': {
258 'message': 'Font stack exports non-PPT-safe typefaces to PPTX',
259 'solutions': [
260 'Use stacks whose exported Latin / EA faces are installed or approved on the delivery target',
261 'Do not choose PPTX faces from the authoring host font inventory',
262 'CJK: "Microsoft YaHei", "Noto Sans CJK SC", sans-serif | SimSun, "Noto Serif CJK SC", serif',
263 'Latin: Arial, sans-serif | "Times New Roman", serif',
264 'Mono: Consolas, "Courier New", monospace',
265 'See strategist.md §g for the full PPT-safe discipline'
266 ],
267 'severity': 'warning'
268 }
269 }
270
271 @classmethod
272 def get_solution(cls, error_type: str, context: Optional[Dict] = None) -> Dict:
273 """
274 Get the solution for an error.
275
276 Args:
277 error_type: Error type
278 context: Context information (optional)
279
280 Returns:
281 Dictionary containing message, solutions, severity
282 """
283 if error_type in cls.ERROR_SOLUTIONS:
284 solution = cls.ERROR_SOLUTIONS[error_type].copy()
285
286 # Customize message based on context
287 if context:
288 solution = cls._customize_solution(solution, context)
289
290 return solution
291
292 # Unknown error type
293 return {
294 'message': 'Unknown error',
295 'solutions': ['Please check the documentation or contact the maintainer'],
296 'severity': 'error'
297 }
298
299 @classmethod
300 def _customize_solution(cls, solution: Dict, context: Dict) -> Dict:
301 """
302 Customize solution based on context.
303
304 Args:
305 solution: Original solution
306 context: Context information
307
308 Returns:
309 Customized solution
310 """
311 customized = solution.copy()
312
313 # Customize based on project path
314 if 'project_path' in context:
315 project_path = context['project_path']
316 customized['solutions'] = [
317 s.replace('<project_path>', project_path).replace(
318 '<your_project>', project_path)
319 for s in customized['solutions']
320 ]
321
322 # Customize based on filename
323 if 'file_name' in context:
324 file_name = context['file_name']
325 customized['message'] = f"{customized['message']}: {file_name}"
326
327 # Customize based on expected/actual values
328 if 'expected' in context and 'actual' in context:
329 customized['message'] += f" (expected: {context['expected']}, actual: {context['actual']})"
330
331 return customized
332
333 @classmethod
334 def format_error_message(cls, error_type: str, context: Optional[Dict] = None) -> str:
335 """
336 Format error message (for terminal output).
337
338 Args:
339 error_type: Error type
340 context: Context information
341
342 Returns:
343 Formatted error message string
344 """
345 solution = cls.get_solution(error_type, context)
346
347 lines = []
348
349 # Error message
350 severity_icon = "[ERROR]" if solution['severity'] == 'error' else "[WARN]"
351 lines.append(f"{severity_icon} {solution['message']}")
352
353 # Solutions
354 if solution['solutions']:
355 lines.append("\nSuggested fixes:")
356 for i, sol in enumerate(solution['solutions'], 1):
357 lines.append(f" {i}. {sol}")
358
359 return "\n".join(lines)
360
361 @classmethod
362 def print_error(cls, error_type: str, context: Optional[Dict] = None):
363 """
364 Print formatted error message.
365
366 Args:
367 error_type: Error type
368 context: Context information
369 """
370 print(cls.format_error_message(error_type, context))
371
372 @classmethod
373 def get_all_error_types(cls) -> List[str]:
374 """Get all supported error types."""
375 return list(cls.ERROR_SOLUTIONS.keys())
376
377 @classmethod
378 def print_help(cls):
379 """Print all error types and solutions."""
380 print("PPT Master - Error Types and Solutions\n")
381 print("=" * 80)
382
383 for error_type, info in cls.ERROR_SOLUTIONS.items():
384 print(f"\n[{error_type}]")
385 print(f"Message: {info['message']}")
386 print(f"Severity: {info['severity']}")
387 print("Solutions:")
388 for i, sol in enumerate(info['solutions'], 1):
389 print(f" {i}. {sol}")
390 print("-" * 80)
391
392
393 def build_parser() -> argparse.ArgumentParser:
394 """Build the command-line parser."""
395 parser = argparse.ArgumentParser(
396 description="Look up PPT Master error messages and suggested fixes.",
397 )
398 parser.add_argument(
399 "error_type",
400 nargs="?",
401 choices=sorted(ErrorHelper.ERROR_SOLUTIONS),
402 help="Error type to explain",
403 )
404 parser.add_argument(
405 "context",
406 nargs="*",
407 metavar="key=value",
408 help="Optional context values used by templates",
409 )
410 return parser
411
412
413 def main(argv: list[str] | None = None) -> int:
414 """Run the CLI entry point for error lookup."""
415 parser = build_parser()
416 args = parser.parse_args(argv)
417
418 if not args.error_type:
419 ErrorHelper.print_help()
420 return 0
421
422 context = {}
423 for item in args.context:
424 if '=' not in item:
425 parser.error(f"context values must use key=value syntax: {item}")
426 key, value = item.split('=', 1)
427 context[key] = value
428
429 print(ErrorHelper.format_error_message(args.error_type, context))
430 return 0
431
432
433 if __name__ == '__main__':
434 raise SystemExit(main())
435
435 lines PYTHON