返回 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 PPT-safe pre-installed 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 typefaces are pre-installed',
261 'CJK: "Microsoft YaHei", sans-serif | SimSun, serif',
262 'Latin: Arial, sans-serif | "Times New Roman", serif',
263 'Mono: Consolas, "Courier New", monospace',
264 'See strategist.md §g for the full PPT-safe discipline'
265 ],
266 'severity': 'warning'
267 }
268 }
269
270 @classmethod
271 def get_solution(cls, error_type: str, context: Optional[Dict] = None) -> Dict:
272 """
273 Get the solution for an error.
274
275 Args:
276 error_type: Error type
277 context: Context information (optional)
278
279 Returns:
280 Dictionary containing message, solutions, severity
281 """
282 if error_type in cls.ERROR_SOLUTIONS:
283 solution = cls.ERROR_SOLUTIONS[error_type].copy()
284
285 # Customize message based on context
286 if context:
287 solution = cls._customize_solution(solution, context)
288
289 return solution
290
291 # Unknown error type
292 return {
293 'message': 'Unknown error',
294 'solutions': ['Please check the documentation or contact the maintainer'],
295 'severity': 'error'
296 }
297
298 @classmethod
299 def _customize_solution(cls, solution: Dict, context: Dict) -> Dict:
300 """
301 Customize solution based on context.
302
303 Args:
304 solution: Original solution
305 context: Context information
306
307 Returns:
308 Customized solution
309 """
310 customized = solution.copy()
311
312 # Customize based on project path
313 if 'project_path' in context:
314 project_path = context['project_path']
315 customized['solutions'] = [
316 s.replace('<project_path>', project_path).replace(
317 '<your_project>', project_path)
318 for s in customized['solutions']
319 ]
320
321 # Customize based on filename
322 if 'file_name' in context:
323 file_name = context['file_name']
324 customized['message'] = f"{customized['message']}: {file_name}"
325
326 # Customize based on expected/actual values
327 if 'expected' in context and 'actual' in context:
328 customized['message'] += f" (expected: {context['expected']}, actual: {context['actual']})"
329
330 return customized
331
332 @classmethod
333 def format_error_message(cls, error_type: str, context: Optional[Dict] = None) -> str:
334 """
335 Format error message (for terminal output).
336
337 Args:
338 error_type: Error type
339 context: Context information
340
341 Returns:
342 Formatted error message string
343 """
344 solution = cls.get_solution(error_type, context)
345
346 lines = []
347
348 # Error message
349 severity_icon = "[ERROR]" if solution['severity'] == 'error' else "[WARN]"
350 lines.append(f"{severity_icon} {solution['message']}")
351
352 # Solutions
353 if solution['solutions']:
354 lines.append("\nSuggested fixes:")
355 for i, sol in enumerate(solution['solutions'], 1):
356 lines.append(f" {i}. {sol}")
357
358 return "\n".join(lines)
359
360 @classmethod
361 def print_error(cls, error_type: str, context: Optional[Dict] = None):
362 """
363 Print formatted error message.
364
365 Args:
366 error_type: Error type
367 context: Context information
368 """
369 print(cls.format_error_message(error_type, context))
370
371 @classmethod
372 def get_all_error_types(cls) -> List[str]:
373 """Get all supported error types."""
374 return list(cls.ERROR_SOLUTIONS.keys())
375
376 @classmethod
377 def print_help(cls):
378 """Print all error types and solutions."""
379 print("PPT Master - Error Types and Solutions\n")
380 print("=" * 80)
381
382 for error_type, info in cls.ERROR_SOLUTIONS.items():
383 print(f"\n[{error_type}]")
384 print(f"Message: {info['message']}")
385 print(f"Severity: {info['severity']}")
386 print("Solutions:")
387 for i, sol in enumerate(info['solutions'], 1):
388 print(f" {i}. {sol}")
389 print("-" * 80)
390
391
392 def build_parser() -> argparse.ArgumentParser:
393 """Build the command-line parser."""
394 parser = argparse.ArgumentParser(
395 description="Look up PPT Master error messages and suggested fixes.",
396 )
397 parser.add_argument(
398 "error_type",
399 nargs="?",
400 choices=sorted(ErrorHelper.ERROR_SOLUTIONS),
401 help="Error type to explain",
402 )
403 parser.add_argument(
404 "context",
405 nargs="*",
406 metavar="key=value",
407 help="Optional context values used by templates",
408 )
409 return parser
410
411
412 def main(argv: list[str] | None = None) -> int:
413 """Run the CLI entry point for error lookup."""
414 parser = build_parser()
415 args = parser.parse_args(argv)
416
417 if not args.error_type:
418 ErrorHelper.print_help()
419 return 0
420
421 context = {}
422 for item in args.context:
423 if '=' not in item:
424 parser.error(f"context values must use key=value syntax: {item}")
425 key, value = item.split('=', 1)
426 context[key] = value
427
428 print(ErrorHelper.format_error_message(args.error_type, context))
429 return 0
430
431
432 if __name__ == '__main__':
433 raise SystemExit(main())
434
434 lines PYTHON