deckbuilder 1.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,739 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Deckbuilder Command Line Tools
4
+
5
+ Standalone utilities for template analysis, documentation generation, and validation.
6
+ These tools are designed to be run independently for template management tasks.
7
+ """
8
+
9
+ import argparse
10
+ import json
11
+ import os
12
+ import sys
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+
16
+ # Add parent directory to path for imports
17
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
18
+
19
+ # Import after path modification - this is intentional to find the mcp_server module
20
+ from mcp_server.tools import TemplateAnalyzer # noqa: E402
21
+
22
+ try:
23
+ from .naming_conventions import NamingConvention, PlaceholderContext # noqa: E402
24
+ except ImportError:
25
+ # Handle direct script execution
26
+ from naming_conventions import NamingConvention, PlaceholderContext # noqa: E402
27
+
28
+
29
+ class TemplateManager:
30
+ """Command-line template management utilities"""
31
+
32
+ def __init__(self, template_folder=None, output_folder=None):
33
+ # Use command-line arguments or sensible defaults
34
+ if template_folder:
35
+ self.template_folder = template_folder
36
+ else:
37
+ # Default: look for assets/templates relative to current directory
38
+ current_dir = Path.cwd()
39
+ if (current_dir / "assets" / "templates").exists():
40
+ self.template_folder = str(current_dir / "assets" / "templates")
41
+ else:
42
+ # Try from project root
43
+ project_root = Path(__file__).parent.parent.parent
44
+ self.template_folder = str(project_root / "assets" / "templates")
45
+
46
+ if output_folder:
47
+ self.output_folder = output_folder
48
+ else:
49
+ # Default: create output folder in current directory
50
+ self.output_folder = str(Path.cwd() / "template_output")
51
+
52
+ # Ensure folders exist
53
+ os.makedirs(self.output_folder, exist_ok=True)
54
+
55
+ # Don't create analyzer yet - wait until we need it
56
+
57
+ def analyze_template(self, template_name: str, verbose: bool = False) -> dict:
58
+ """
59
+ Analyze a PowerPoint template and generate JSON mapping.
60
+
61
+ Args:
62
+ template_name: Name of template (e.g., 'default')
63
+ verbose: Print detailed analysis information
64
+
65
+ Returns:
66
+ Template analysis results
67
+ """
68
+ print(f"🔍 Analyzing template: {template_name}")
69
+ print(f"📁 Template folder: {self.template_folder}")
70
+ print(f"📂 Output folder: {self.output_folder}")
71
+
72
+ try:
73
+ # Set environment variables for the analyzer
74
+ os.environ["DECK_TEMPLATE_FOLDER"] = self.template_folder
75
+ os.environ["DECK_OUTPUT_FOLDER"] = self.output_folder
76
+
77
+ # Create analyzer instance with current environment
78
+ analyzer = TemplateAnalyzer()
79
+
80
+ # Run analysis
81
+ result = analyzer.analyze_pptx_template(template_name)
82
+
83
+ # Print summary
84
+ layouts_count = len(result.get("layouts", {}))
85
+ print("✅ Analysis complete!")
86
+ print(f" 📊 Found {layouts_count} layouts")
87
+
88
+ # Print layout summary
89
+ if verbose and "layouts" in result:
90
+ print("\n📋 Layout Summary:")
91
+ for layout_name, layout_info in result["layouts"].items():
92
+ placeholder_count = len(layout_info.get("placeholders", {}))
93
+ index = layout_info.get("index", "?")
94
+ print(f" {index:2d}: {layout_name} ({placeholder_count} placeholders)")
95
+
96
+ # Check for generated file
97
+ base_name = template_name.replace(".pptx", "")
98
+ output_file = os.path.join(self.output_folder, f"{base_name}.g.json")
99
+ if os.path.exists(output_file):
100
+ print(f"📄 Generated: {output_file}")
101
+ print(" ✏️ Edit this file with semantic placeholder names")
102
+ print(f" 📝 Rename to '{base_name}.json' when ready")
103
+
104
+ return result
105
+
106
+ except Exception as e:
107
+ print(f"❌ Error analyzing template: {str(e)}")
108
+ return {}
109
+
110
+ def document_template(self, template_name: str, output_path: str = None) -> str:
111
+ """
112
+ Generate comprehensive documentation for a template.
113
+
114
+ Args:
115
+ template_name: Name of template to document
116
+ output_path: Custom output path (optional)
117
+
118
+ Returns:
119
+ Path to generated documentation
120
+ """
121
+ print(f"📝 Generating documentation for: {template_name}")
122
+
123
+ # Analyze template first
124
+ analysis = self.analyze_template(template_name, verbose=False)
125
+ if not analysis:
126
+ return ""
127
+
128
+ # Load JSON mapping if available
129
+ base_name = template_name.replace(".pptx", "")
130
+ mapping_file = os.path.join(self.template_folder, f"{base_name}.json")
131
+ mapping = {}
132
+
133
+ if os.path.exists(mapping_file):
134
+ try:
135
+ with open(mapping_file, "r", encoding="utf-8") as f:
136
+ mapping = json.load(f)
137
+ print(f"📄 Using mapping: {mapping_file}")
138
+ except Exception as e:
139
+ print(f"⚠️ Could not load mapping file: {e}")
140
+
141
+ # Generate documentation
142
+ doc_content = self._generate_template_documentation(template_name, analysis, mapping)
143
+
144
+ # Save documentation
145
+ if not output_path:
146
+ project_root = Path(__file__).parent.parent.parent
147
+ docs_folder = project_root / "docs" / "Features"
148
+ docs_folder.mkdir(parents=True, exist_ok=True)
149
+ output_path = str(docs_folder / f"{base_name.title()}Template.md")
150
+
151
+ with open(output_path, "w", encoding="utf-8") as f:
152
+ f.write(doc_content)
153
+
154
+ print(f"✅ Documentation generated: {output_path}")
155
+ return output_path
156
+
157
+ def _generate_template_documentation(
158
+ self, template_name: str, analysis: dict, mapping: dict
159
+ ) -> str:
160
+ """Generate markdown documentation for template"""
161
+ base_name = template_name.replace(".pptx", "")
162
+ layouts = analysis.get("layouts", {})
163
+
164
+ # Generate layout summary table
165
+ table_rows = []
166
+ for layout_name, layout_info in layouts.items():
167
+ index = layout_info.get("index", "?")
168
+ placeholders = layout_info.get("placeholders", {})
169
+ placeholder_count = len(placeholders)
170
+
171
+ # Check if mapping exists
172
+ mapping_status = "✅" if mapping.get("layouts", {}).get(layout_name) else "❌"
173
+
174
+ # Check structured frontmatter support (placeholder for now)
175
+ structured_status = "⏳" # Would check structured_frontmatter.py
176
+
177
+ table_rows.append(
178
+ f"| {layout_name} | {index} | {placeholder_count} | {structured_status} | {mapping_status} |"
179
+ )
180
+
181
+ # Generate detailed layout specifications
182
+ detailed_layouts = []
183
+ for layout_name, layout_info in layouts.items():
184
+ index = layout_info.get("index", "?")
185
+ placeholders = layout_info.get("placeholders", {})
186
+
187
+ layout_section = f"### {layout_name} (Index: {index})\\n\\n"
188
+ layout_section += "**PowerPoint Placeholders**:\\n"
189
+
190
+ for idx, placeholder_name in placeholders.items():
191
+ # Handle both string and object formats
192
+ if isinstance(placeholder_name, dict):
193
+ name = placeholder_name.get("name", "Unknown")
194
+ actual_idx = placeholder_name.get("idx", idx)
195
+ else:
196
+ name = placeholder_name
197
+ actual_idx = idx
198
+ layout_section += f'- `idx={actual_idx}`: "{name}"\\n'
199
+
200
+ # Add mapping info if available
201
+ layout_mapping = mapping.get("layouts", {}).get(layout_name)
202
+ if layout_mapping:
203
+ layout_section += "\\n**JSON Mapping**: ✅ Configured\\n"
204
+ else:
205
+ layout_section += "\\n**JSON Mapping**: ❌ Not configured\\n"
206
+
207
+ layout_section += "**Structured Frontmatter**: ⏳ To be implemented\\n"
208
+
209
+ detailed_layouts.append(layout_section)
210
+
211
+ # Generate full documentation
212
+ doc_content = f"""# {base_name.title()} Template Documentation
213
+
214
+ ## Template Overview
215
+ - **Template Name**: {template_name}
216
+ - **Analysis Date**: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
217
+ - **Total Layouts**: {len(layouts)}
218
+ - **Template Location**: `{self.template_folder}/{template_name}`
219
+
220
+ ## Layout Summary
221
+
222
+ | Layout Name | Index | Placeholders | Structured Support | JSON Mapping |
223
+ |-------------|-------|--------------|-------------------|--------------|
224
+ {chr(10).join(table_rows)}
225
+
226
+ ## Detailed Layout Specifications
227
+
228
+ {chr(10).join(detailed_layouts)}
229
+
230
+ ## Template Management
231
+
232
+ ### Adding JSON Mapping
233
+ 1. **Analyze template**: Run `python -m deckbuilder.cli_tools analyze {base_name}`
234
+ 2. **Edit generated file**: Customize `{base_name}.g.json` with semantic names
235
+ 3. **Activate mapping**: Rename to `{base_name}.json` in templates folder
236
+
237
+ ### Example JSON Mapping Structure
238
+ ```json
239
+ {{
240
+ "template_info": {{
241
+ "name": "{base_name.title()}",
242
+ "version": "1.0"
243
+ }},
244
+ "layouts": {{
245
+ "Title Slide": {{
246
+ "index": 0,
247
+ "placeholders": {{
248
+ "0": "Title 1",
249
+ "1": "Subtitle 2"
250
+ }}
251
+ }}
252
+ }},
253
+ "aliases": {{
254
+ "title": "Title Slide",
255
+ "content": "Title and Content"
256
+ }}
257
+ }}
258
+ ```
259
+
260
+ ### Usage Examples
261
+
262
+ **JSON Format**:
263
+ ```json
264
+ {{
265
+ "presentation": {{
266
+ "slides": [
267
+ {{
268
+ "type": "Title Slide",
269
+ "layout": "Title Slide",
270
+ "title": "My Presentation",
271
+ "subtitle": "Subtitle text"
272
+ }}
273
+ ]
274
+ }}
275
+ }}
276
+ ```
277
+
278
+ **Markdown with Frontmatter**:
279
+ ```yaml
280
+ ---
281
+ layout: Title Slide
282
+ ---
283
+ # My Presentation
284
+ ## Subtitle text
285
+ ```
286
+
287
+ ---
288
+ *Generated automatically by Deckbuilder Template Manager on {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}*
289
+ """
290
+
291
+ return doc_content
292
+
293
+ def validate_template(self, template_name: str) -> dict:
294
+ """
295
+ Validate template structure and mappings.
296
+
297
+ Args:
298
+ template_name: Name of template to validate
299
+
300
+ Returns:
301
+ Validation results
302
+ """
303
+ print(f"🔍 Validating template: {template_name}")
304
+
305
+ validation_results = {
306
+ "template_file": self._validate_template_file(template_name),
307
+ "json_mapping": self._validate_json_mapping(template_name),
308
+ "placeholder_naming": self._validate_placeholder_naming(template_name),
309
+ }
310
+
311
+ # Print results
312
+ for category, results in validation_results.items():
313
+ status = results.get("status", "unknown")
314
+ if status == "valid":
315
+ print(f"✅ {category}: Valid")
316
+ elif status == "issues_found":
317
+ print(f"⚠️ {category}: Issues found")
318
+ for issue in results.get("issues", []):
319
+ print(f" - {issue}")
320
+ else:
321
+ print(f"❌ {category}: {results.get('error', 'Unknown error')}")
322
+
323
+ return validation_results
324
+
325
+ def _validate_template_file(self, template_name: str) -> dict:
326
+ """Validate template file exists and is accessible"""
327
+ try:
328
+ if not template_name.endswith(".pptx"):
329
+ template_name += ".pptx"
330
+
331
+ template_path = os.path.join(self.template_folder, template_name)
332
+
333
+ if not os.path.exists(template_path):
334
+ return {"status": "error", "error": f"Template file not found: {template_path}"}
335
+
336
+ # Try to load with python-pptx
337
+ from pptx import Presentation
338
+
339
+ prs = Presentation(template_path)
340
+
341
+ return {
342
+ "status": "valid",
343
+ "layout_count": len(prs.slide_layouts),
344
+ "file_size": os.path.getsize(template_path),
345
+ }
346
+
347
+ except Exception as e:
348
+ return {"status": "error", "error": str(e)}
349
+
350
+ def _validate_json_mapping(self, template_name: str) -> dict:
351
+ """Validate JSON mapping file"""
352
+ base_name = template_name.replace(".pptx", "")
353
+ mapping_file = os.path.join(self.template_folder, f"{base_name}.json")
354
+
355
+ if not os.path.exists(mapping_file):
356
+ return {"status": "missing", "message": "JSON mapping file not found"}
357
+
358
+ try:
359
+ with open(mapping_file, "r", encoding="utf-8") as f:
360
+ mapping = json.load(f)
361
+
362
+ # Basic structure validation
363
+ required_keys = ["template_info", "layouts"]
364
+ missing_keys = [key for key in required_keys if key not in mapping]
365
+
366
+ if missing_keys:
367
+ return {
368
+ "status": "issues_found",
369
+ "issues": [f"Missing required keys: {missing_keys}"],
370
+ }
371
+
372
+ return {"status": "valid", "layouts_count": len(mapping.get("layouts", {}))}
373
+
374
+ except json.JSONDecodeError as e:
375
+ return {"status": "error", "error": f"Invalid JSON: {str(e)}"}
376
+ except Exception as e:
377
+ return {"status": "error", "error": str(e)}
378
+
379
+ def _validate_placeholder_naming(self, template_name: str) -> dict:
380
+ """Validate placeholder naming conventions"""
381
+ # Placeholder for naming convention validation
382
+ return {"status": "valid", "message": "Naming validation not yet implemented"}
383
+
384
+ def enhance_template(
385
+ self,
386
+ template_name: str,
387
+ mapping_file: str = None,
388
+ create_backup: bool = True,
389
+ use_conventions: bool = False,
390
+ ) -> dict:
391
+ """
392
+ Enhance template by updating master slide placeholder names using semantic mapping.
393
+
394
+ Args:
395
+ template_name: Name of template to enhance
396
+ mapping_file: Custom JSON mapping file (optional)
397
+ create_backup: Create backup before modification (default: True)
398
+ use_conventions: Use convention-based naming system (default: False)
399
+
400
+ Returns:
401
+ Enhancement results with success/failure information
402
+ """
403
+ print(f"🔧 Enhancing template: {template_name}")
404
+
405
+ try:
406
+ # Ensure template name has .pptx extension
407
+ if not template_name.endswith(".pptx"):
408
+ template_name += ".pptx"
409
+
410
+ template_path = os.path.join(self.template_folder, template_name)
411
+
412
+ if not os.path.exists(template_path):
413
+ return {"status": "error", "error": f"Template file not found: {template_path}"}
414
+
415
+ # Determine mapping file path (only if not using conventions)
416
+ if not use_conventions:
417
+ base_name = template_name.replace(".pptx", "")
418
+ if not mapping_file:
419
+ mapping_file = os.path.join(self.template_folder, f"{base_name}.json")
420
+
421
+ if not os.path.exists(mapping_file):
422
+ return {
423
+ "status": "error",
424
+ "error": f"Mapping file not found: {mapping_file}. Run 'analyze' first to generate mapping.",
425
+ }
426
+
427
+ # Create backup if requested
428
+ if create_backup:
429
+ backup_path = self._create_template_backup(template_path)
430
+ print(f"📄 Backup created: {backup_path}")
431
+
432
+ # Load or generate mapping
433
+ if use_conventions:
434
+ print("🎯 Using convention-based naming system...")
435
+ # Generate convention-based mapping
436
+ mapping = self._generate_convention_mapping(template_path)
437
+ else:
438
+ # Load existing mapping
439
+ with open(mapping_file, "r", encoding="utf-8") as f:
440
+ mapping = json.load(f)
441
+
442
+ # Enhance template
443
+ modifications = self._modify_master_slide_placeholders(template_path, mapping)
444
+
445
+ if modifications["modified_count"] > 0:
446
+ print("✅ Enhancement complete!")
447
+ print(
448
+ f" 📊 Modified {modifications['modified_count']} placeholders across {modifications['layout_count']} layouts"
449
+ )
450
+
451
+ if "enhanced_template_path" in modifications:
452
+ print(
453
+ f" 📄 Enhanced template saved: {modifications['enhanced_template_path']}"
454
+ )
455
+
456
+ if modifications["issues"]:
457
+ print("⚠️ Issues encountered:")
458
+ for issue in modifications["issues"]:
459
+ print(f" - {issue}")
460
+
461
+ return {
462
+ "status": "success",
463
+ "modifications": modifications,
464
+ "backup_path": backup_path if create_backup else None,
465
+ }
466
+ else:
467
+ print("ℹ️ No modifications needed - all placeholders already have correct names")
468
+ return {
469
+ "status": "no_changes",
470
+ "message": "Template already has correct placeholder names",
471
+ }
472
+
473
+ except Exception as e:
474
+ return {"status": "error", "error": str(e)}
475
+
476
+ def _create_template_backup(self, template_path: str) -> str:
477
+ """Create backup copy of template file in organized backups folder"""
478
+ import shutil
479
+ from datetime import datetime
480
+
481
+ # Generate backup filename with timestamp
482
+ path_obj = Path(template_path)
483
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
484
+ backup_name = f"{path_obj.stem}_backup_{timestamp}{path_obj.suffix}"
485
+
486
+ # Create backups folder within templates directory
487
+ backups_folder = path_obj.parent / "backups"
488
+ backups_folder.mkdir(exist_ok=True)
489
+
490
+ backup_path = backups_folder / backup_name
491
+
492
+ # Copy file
493
+ shutil.copy2(template_path, backup_path)
494
+ return str(backup_path)
495
+
496
+ def _modify_master_slide_placeholders(self, template_path: str, mapping: dict) -> dict:
497
+ """
498
+ Modify master slide placeholder names using python-pptx.
499
+
500
+ Args:
501
+ template_path: Path to PowerPoint template
502
+ mapping: JSON mapping with placeholder names
503
+
504
+ Returns:
505
+ Dictionary with modification results
506
+ """
507
+ from pptx import Presentation
508
+
509
+ modifications = {"modified_count": 0, "layout_count": 0, "issues": [], "changes": []}
510
+
511
+ # Load presentation
512
+ prs = Presentation(template_path)
513
+ layouts_mapping = mapping.get("layouts", {})
514
+
515
+ # Access the slide master (this is the key change!)
516
+ slide_master = prs.slide_master
517
+
518
+ # First, try to modify placeholders on the master slide itself
519
+ try:
520
+ for placeholder in slide_master.placeholders:
521
+ placeholder_idx = str(placeholder.placeholder_format.idx)
522
+ # Try to find this placeholder in any layout mapping
523
+ for layout_name, layout_info in layouts_mapping.items():
524
+ placeholder_mapping = layout_info.get("placeholders", {})
525
+ if placeholder_idx in placeholder_mapping:
526
+ new_name = placeholder_mapping[placeholder_idx]
527
+ old_name = placeholder.name
528
+ try:
529
+ placeholder.element.nvSpPr.cNvPr.name = new_name
530
+ modifications["modified_count"] += 1
531
+ modifications["changes"].append(
532
+ {
533
+ "location": "master_slide",
534
+ "placeholder_idx": placeholder_idx,
535
+ "old_name": old_name,
536
+ "new_name": new_name,
537
+ }
538
+ )
539
+ break
540
+ except Exception as e:
541
+ modifications["issues"].append(
542
+ f"Failed to modify master placeholder {placeholder_idx}: {str(e)}"
543
+ )
544
+ except Exception as e:
545
+ modifications["issues"].append(f"No direct master placeholders or error: {e}")
546
+
547
+ # Process each slide layout in the master
548
+ for layout in slide_master.slide_layouts:
549
+ layout_name = layout.name
550
+ modifications["layout_count"] += 1
551
+
552
+ if layout_name not in layouts_mapping:
553
+ modifications["issues"].append(f"Layout '{layout_name}' not found in mapping file")
554
+ continue
555
+
556
+ layout_mapping = layouts_mapping[layout_name]
557
+ placeholder_mapping = layout_mapping.get("placeholders", {})
558
+
559
+ # Modify placeholders in this master slide layout
560
+ for placeholder in layout.placeholders:
561
+ placeholder_idx = str(placeholder.placeholder_format.idx)
562
+
563
+ if placeholder_idx in placeholder_mapping:
564
+ new_name = placeholder_mapping[placeholder_idx]
565
+ old_name = placeholder.name
566
+
567
+ try:
568
+ # Update placeholder name on the master slide layout
569
+ if hasattr(placeholder, "element") and hasattr(
570
+ placeholder.element, "nvSpPr"
571
+ ):
572
+ placeholder.element.nvSpPr.cNvPr.name = new_name
573
+ modifications["modified_count"] += 1
574
+ modifications["changes"].append(
575
+ {
576
+ "layout": layout_name,
577
+ "placeholder_idx": placeholder_idx,
578
+ "old_name": old_name,
579
+ "new_name": new_name,
580
+ }
581
+ )
582
+ else:
583
+ modifications["issues"].append(
584
+ f"Cannot modify placeholder {placeholder_idx} in {layout_name} - unsupported structure"
585
+ )
586
+ except Exception as e:
587
+ modifications["issues"].append(
588
+ f"Failed to modify placeholder {placeholder_idx} in {layout_name}: {str(e)}"
589
+ )
590
+
591
+ # Save modified presentation with .g.pptx extension
592
+ try:
593
+ # Generate enhanced template filename with .g.pptx convention
594
+ path_obj = Path(template_path)
595
+ enhanced_name = f"{path_obj.stem}.g{path_obj.suffix}"
596
+ enhanced_path = path_obj.parent / enhanced_name
597
+
598
+ prs.save(str(enhanced_path))
599
+ modifications["enhanced_template_path"] = str(enhanced_path)
600
+ except Exception as e:
601
+ modifications["issues"].append(f"Failed to save enhanced template: {str(e)}")
602
+
603
+ return modifications
604
+
605
+ def _generate_convention_mapping(self, template_path: str) -> dict:
606
+ """
607
+ Generate convention-based mapping for template placeholders.
608
+
609
+ Args:
610
+ template_path: Path to PowerPoint template
611
+
612
+ Returns:
613
+ Convention-based mapping dictionary
614
+ """
615
+ from pptx import Presentation
616
+
617
+ # Load presentation to analyze structure
618
+ prs = Presentation(template_path)
619
+ convention = NamingConvention()
620
+
621
+ # Build mapping using convention system
622
+ mapping = {"template_info": {"name": "Convention-Based", "version": "1.0"}, "layouts": {}}
623
+
624
+ # Process each slide layout
625
+ layout_index = 0
626
+ for layout in prs.slide_layouts:
627
+ layout_name = layout.name
628
+ layout_placeholders = {}
629
+
630
+ for placeholder in layout.placeholders:
631
+ placeholder_idx = str(placeholder.placeholder_format.idx)
632
+
633
+ # Create context for convention naming
634
+ context = PlaceholderContext(
635
+ layout_name=layout_name,
636
+ placeholder_idx=placeholder_idx,
637
+ total_placeholders=len(layout.placeholders),
638
+ )
639
+
640
+ # Generate convention-based name
641
+ convention_name = convention.generate_placeholder_name(context)
642
+ layout_placeholders[placeholder_idx] = convention_name
643
+
644
+ mapping["layouts"][layout_name] = {
645
+ "index": layout_index,
646
+ "placeholders": layout_placeholders,
647
+ }
648
+
649
+ layout_index += 1
650
+
651
+ return mapping
652
+
653
+
654
+ def main():
655
+ """Command-line interface for template management tools"""
656
+ parser = argparse.ArgumentParser(
657
+ description="Deckbuilder Template Management Tools",
658
+ formatter_class=argparse.RawDescriptionHelpFormatter,
659
+ epilog="""
660
+ Examples:
661
+ python cli_tools.py analyze default
662
+ python cli_tools.py analyze default --verbose
663
+ python cli_tools.py document default
664
+ python cli_tools.py validate default
665
+ python cli_tools.py enhance default
666
+ python cli_tools.py enhance default --no-backup
667
+ python cli_tools.py enhance default --use-conventions
668
+ python cli_tools.py analyze default --template-folder ./templates --output-folder ./output
669
+ """,
670
+ )
671
+
672
+ # Global arguments
673
+ parser.add_argument(
674
+ "--template-folder", "-t", help="Path to templates folder (default: auto-detect)"
675
+ )
676
+ parser.add_argument(
677
+ "--output-folder", "-o", help="Path to output folder (default: ./template_output)"
678
+ )
679
+
680
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
681
+
682
+ # Analyze command
683
+ analyze_parser = subparsers.add_parser("analyze", help="Analyze PowerPoint template structure")
684
+ analyze_parser.add_argument("template", help="Template name (e.g., default)")
685
+ analyze_parser.add_argument(
686
+ "--verbose", "-v", action="store_true", help="Show detailed analysis"
687
+ )
688
+
689
+ # Document command
690
+ doc_parser = subparsers.add_parser("document", help="Generate template documentation")
691
+ doc_parser.add_argument("template", help="Template name to document")
692
+ doc_parser.add_argument("--doc-output", help="Documentation output file path")
693
+
694
+ # Validate command
695
+ validate_parser = subparsers.add_parser("validate", help="Validate template and mappings")
696
+ validate_parser.add_argument("template", help="Template name to validate")
697
+
698
+ # Enhance command
699
+ enhance_parser = subparsers.add_parser(
700
+ "enhance", help="Enhance template with corrected placeholder names (saves as .g.pptx)"
701
+ )
702
+ enhance_parser.add_argument("template", help="Template name to enhance")
703
+ enhance_parser.add_argument("--mapping-file", help="Custom JSON mapping file path")
704
+ enhance_parser.add_argument(
705
+ "--no-backup", action="store_true", help="Skip creating backup before modification"
706
+ )
707
+ enhance_parser.add_argument(
708
+ "--use-conventions",
709
+ action="store_true",
710
+ help="Use convention-based naming system instead of JSON mapping",
711
+ )
712
+
713
+ args = parser.parse_args()
714
+
715
+ if not args.command:
716
+ parser.print_help()
717
+ return
718
+
719
+ # Initialize template manager with command-line arguments
720
+ manager = TemplateManager(
721
+ template_folder=args.template_folder, output_folder=args.output_folder
722
+ )
723
+
724
+ # Execute command
725
+ if args.command == "analyze":
726
+ manager.analyze_template(args.template, verbose=args.verbose)
727
+ elif args.command == "document":
728
+ manager.document_template(args.template, getattr(args, "doc_output", None))
729
+ elif args.command == "validate":
730
+ manager.validate_template(args.template)
731
+ elif args.command == "enhance":
732
+ create_backup = not args.no_backup
733
+ manager.enhance_template(
734
+ args.template, args.mapping_file, create_backup, args.use_conventions
735
+ )
736
+
737
+
738
+ if __name__ == "__main__":
739
+ main()