pptx-cli 1.3.3__tar.gz → 1.3.5__tar.gz

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.
Files changed (78) hide show
  1. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/skills/pptx-deck-builder/SKILL.md +28 -0
  2. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/PKG-INFO +1 -1
  3. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/__init__.py +1 -1
  4. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/cli.py +32 -1
  5. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/commands/schema.py +127 -63
  6. pptx_cli-1.3.5/tests/test_schema.py +134 -0
  7. pptx_cli-1.3.3/tests/test_schema.py +0 -80
  8. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.claude/settings.local.json +0 -0
  9. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.editorconfig +0 -0
  10. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  12. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  13. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  14. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/copilot-instructions.md +0 -0
  15. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/instructions/backend.instructions.md +0 -0
  16. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/instructions/testing.instructions.md +0 -0
  17. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/skills/pptx/SKILL.md +0 -0
  18. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/skills/pptx/references/deck-spec.md +0 -0
  19. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/skills/pptx-deck-builder/references/excal-diagrams.md +0 -0
  20. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/skills/pptx-deck-builder/references/mckinsey-style.md +0 -0
  21. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/skills/pptx-deck-builder/references/pptx-workflow.md +0 -0
  22. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/workflows/ci.yml +0 -0
  23. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.github/workflows/publish.yml +0 -0
  24. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.gitignore +0 -0
  25. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/.python-version +0 -0
  26. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/AGENTS.md +0 -0
  27. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/ARCHITECTURE.md +0 -0
  28. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/CHANGELOG.md +0 -0
  29. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/CLI-MANIFEST.md +0 -0
  30. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/CONTRIBUTING.md +0 -0
  31. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/DECISIONS/ADR-0001-initial-architecture.md +0 -0
  32. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/LICENSE +0 -0
  33. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/PRD.md +0 -0
  34. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/PREVIEW-FUTURE.md +0 -0
  35. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/PROJECT.md +0 -0
  36. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/README.md +0 -0
  37. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/SCAFFOLD.md +0 -0
  38. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/SECURITY.md +0 -0
  39. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/TESTING.md +0 -0
  40. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/docs/DOMAIN.md +0 -0
  41. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/docs/GLOSSARY.md +0 -0
  42. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/docs/ROADMAP.md +0 -0
  43. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/docs/SCAFFOLDING-NOTES.md +0 -0
  44. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/guardrails/.gitignore +0 -0
  45. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/guardrails/guardrails.jsonl +0 -0
  46. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/guardrails/links.jsonl +0 -0
  47. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/guardrails/references.jsonl +0 -0
  48. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/guardrails/taxonomy.json +0 -0
  49. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/guardrails-explorer.html +0 -0
  50. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/pyproject.toml +0 -0
  51. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/pyrightconfig.json +0 -0
  52. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/scripts/bump_version.py +0 -0
  53. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/__main__.py +0 -0
  54. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/commands/__init__.py +0 -0
  55. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/commands/compose.py +0 -0
  56. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/commands/guide.py +0 -0
  57. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/commands/init.py +0 -0
  58. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/commands/inspect.py +0 -0
  59. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/commands/manifest_ops.py +0 -0
  60. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/commands/validate.py +0 -0
  61. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/commands/wrapper.py +0 -0
  62. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/__init__.py +0 -0
  63. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/composition.py +0 -0
  64. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/ids.py +0 -0
  65. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/io.py +0 -0
  66. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/manifest_store.py +0 -0
  67. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/markdown.py +0 -0
  68. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/runtime.py +0 -0
  69. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/template.py +0 -0
  70. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/validation.py +0 -0
  71. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/core/versioning.py +0 -0
  72. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/models/__init__.py +0 -0
  73. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/models/envelope.py +0 -0
  74. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/src/pptx_cli/models/manifest.py +0 -0
  75. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/tests/conftest.py +0 -0
  76. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/tests/test_cli.py +0 -0
  77. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/tests/test_versioning.py +0 -0
  78. {pptx_cli-1.3.3 → pptx_cli-1.3.5}/uv.lock +0 -0
@@ -51,6 +51,34 @@ For consulting-style decks, write a **ghost deck** first — a title-only
51
51
  outline where each slide title states the takeaway (not the topic). The titles
52
52
  alone should tell a coherent story. See `references/mckinsey-style.md`.
53
53
 
54
+ ### Working from a YAML input file
55
+
56
+ When the user provides a YAML (or similar structured) file that already
57
+ contains slide content, treat it as the authoritative source. The file
58
+ typically defines `slides:` with per-slide fields such as `title`, `body`,
59
+ `notes`, `table`, `chart`, and `image`.
60
+
61
+ **Carry over every field.** Walk through each slide in the input and map
62
+ every field into the deck spec. The most common mistake is silently dropping
63
+ fields that do not map to a placeholder — especially `notes`. Always include:
64
+
65
+ - `title` and `body` → map to the layout's `title` and `content_1`
66
+ - `notes` → map to the slide-level `notes:` key in the deck spec
67
+ - `table` → map to a `{ kind: table, ... }` content block
68
+ - `chart` → map to a `{ kind: chart, ... }` content block
69
+ - `image` → map to a `{ kind: image, ... }` content block
70
+
71
+ **Do not summarize, rewrite, or omit content** unless the user explicitly
72
+ asks for editing. The goal is faithful conversion from the input format to
73
+ a template-bound deck spec, not reinterpretation.
74
+
75
+ **Checklist before building:** after writing the deck spec, verify that:
76
+
77
+ 1. The number of slides matches the input.
78
+ 2. Every `notes:` block from the input appears in the spec.
79
+ 3. Every table, chart, and image from the input is present.
80
+ 4. Source attributions are preserved.
81
+
54
82
  ### Step 1 — Initialize the manifest (one-time setup)
55
83
 
56
84
  Check if a manifest package already exists. If not, initialize from the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pptx-cli
3
- Version: 1.3.3
3
+ Version: 1.3.5
4
4
  Summary: Template-bound PowerPoint generation for enterprise decks
5
5
  Author: Thomas Rohde
6
6
  License: MIT License
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "1.3.3"
5
+ __version__ = "1.3.5"
@@ -245,13 +245,44 @@ def schema_command(
245
245
  Path | None,
246
246
  typer.Option("--template", help="Path to a manifest package directory."),
247
247
  ] = None,
248
+ no_template: Annotated[
249
+ bool,
250
+ typer.Option("--no-template", help="Emit generic schema without template layouts."),
251
+ ] = False,
248
252
  no_copy: Annotated[
249
253
  bool,
250
254
  typer.Option("--no-copy", help="Skip copying output to the clipboard."),
251
255
  ] = False,
252
256
  ) -> None:
253
257
  """Print the deck-spec YAML reference (for pasting into LLM prompts)."""
254
- text = build_schema_document(template)
258
+ if template is not None and no_template:
259
+ typer.echo("Error: --template and --no-template are mutually exclusive.", err=True)
260
+ raise typer.Exit(code=1)
261
+
262
+ effective_template = template
263
+ if not no_template and effective_template is None:
264
+ # Auto-discover manifest in cwd
265
+ for candidate in [Path("manifest.yaml"), Path("manifest/manifest.yaml")]:
266
+ if candidate.exists():
267
+ effective_template = candidate.parent
268
+ break
269
+ if effective_template is None:
270
+ # Fall back to generic schema with a hint
271
+ typer.echo(
272
+ "Hint: use --template <dir> for a layout-aware schema, "
273
+ "or --no-template for the generic version.",
274
+ err=True,
275
+ )
276
+
277
+ if effective_template is not None and not (effective_template / "manifest.yaml").exists():
278
+ typer.echo(
279
+ f"Error: manifest not found at '{effective_template / 'manifest.yaml'}'.\n"
280
+ f"Run 'pptx init <template.pptx> --out {effective_template}' first.",
281
+ err=True,
282
+ )
283
+ raise typer.Exit(code=1)
284
+
285
+ text = build_schema_document(effective_template)
255
286
  typer.echo(text)
256
287
  if not no_copy:
257
288
  if copy_to_clipboard(text):
@@ -42,10 +42,10 @@ def copy_to_clipboard(text: str) -> bool:
42
42
 
43
43
 
44
44
  # ---------------------------------------------------------------------------
45
- # Generic (template-free) schema
45
+ # Shared constants
46
46
  # ---------------------------------------------------------------------------
47
47
 
48
- _GENERIC_SCHEMA = """\
48
+ _OUTPUT_FORMAT = """\
49
49
  <output-format>
50
50
  IMPORTANT: Your output MUST be a YAML deck spec, not a binary .pptx file.
51
51
  Do NOT attempt to generate PowerPoint XML, base64 data, or any format
@@ -56,8 +56,54 @@ Preferred: offer the YAML as a downloadable file (e.g. a download link or
56
56
  file attachment named "deck.yaml") so the user can save it directly.
57
57
  Fallback: emit a single ```yaml code block the user can copy-paste into
58
58
  a .yaml file and then run `pptx deck build -f deck.yaml`.
59
- </output-format>
59
+ </output-format>"""
60
60
 
61
+ _STYLE_GUIDE = """\
62
+ <style-guide>
63
+ Follow these principles to produce decision-grade slides.
64
+
65
+ Structure
66
+ - Lead with the answer. Do not build up to the conclusion.
67
+ - Pyramid Principle: governing thought -> supporting arguments -> evidence.
68
+ - Arguments must be MECE (mutually exclusive, collectively exhaustive).
69
+ - One slide = one message. Two insights -> two slides.
70
+
71
+ Titles
72
+ - Use action titles, not topic labels. The title states the takeaway.
73
+ Bad: "Market overview"
74
+ Good: "Nordic retail banking margins will remain under pressure through 2027"
75
+ - Title storyline reads on its own - skim only titles and understand the
76
+ full argument.
77
+ - Formulas:
78
+ Insight: "[What happened] because [driver]"
79
+ Comparison: "[A] outperforms [B] on [criterion]"
80
+ Implication: "[Fact] puts [objective] at risk"
81
+ Recommendation: "[Org] should [action] to achieve [outcome]"
82
+
83
+ Executive summary slide
84
+ - Situation: what context everyone agrees on.
85
+ - Complication: what changed or created urgency.
86
+ - Answer: the recommended response.
87
+ - Support: 2-3 reasons the answer is correct.
88
+
89
+ Deck sequence (typical)
90
+ 1. Title page
91
+ 2. Executive summary (situation -> complication -> resolution)
92
+ 3. Context / problem framing (only enough to orient)
93
+ 4. Analysis body (current state -> root causes -> options -> recommendation)
94
+ 5. Recommendation (explicit, decision-ready)
95
+ 6. Implementation / roadmap
96
+ 7. Risks and mitigations
97
+ 8. Appendix (source data, benchmarks, methodology)
98
+
99
+ Body content
100
+ - Every claim backed by evidence. The body proves the headline.
101
+ - Quantify. Replace vague adjectives with measured claims.
102
+ - Keep text tight: verbs, short bullets, no filler.
103
+ - Move backup detail to appendix - main story stays focused.
104
+ </style-guide>"""
105
+
106
+ _GENERIC_SLIDE_SCHEMA = """\
61
107
  <slide-schema>
62
108
  Use this schema to draft presentation content. Layout assignment and
63
109
  template binding happen later, so focus on the slides themselves.
@@ -101,8 +147,9 @@ slides:
101
147
  kind: image
102
148
  path: path/to/image.png
103
149
  image_fit: fit
104
- </slide-schema>
150
+ </slide-schema>"""
105
151
 
152
+ _GENERIC_CONTENT_RULES = """\
106
153
  <content-rules>
107
154
  - Each slide MUST have a "title".
108
155
  - Use "body" for the main text area. Markdown is auto-detected
@@ -113,56 +160,42 @@ slides:
113
160
  - "notes" is optional speaker-notes text (markdown ok).
114
161
  - Do NOT include a "layout" key - layout is assigned later when
115
162
  binding to a corporate template.
116
- </content-rules>
117
-
118
- <style-guide>
119
- Follow these principles to produce decision-grade slides.
163
+ </content-rules>"""
120
164
 
121
- Structure
122
- - Lead with the answer. Do not build up to the conclusion.
123
- - Pyramid Principle: governing thought -> supporting arguments -> evidence.
124
- - Arguments must be MECE (mutually exclusive, collectively exhaustive).
125
- - One slide = one message. Two insights -> two slides.
126
-
127
- Titles
128
- - Use action titles, not topic labels. The title states the takeaway.
129
- Bad: "Market overview"
130
- Good: "Nordic retail banking margins will remain under pressure through 2027"
131
- - Title storyline reads on its own - skim only titles and understand the
132
- full argument.
133
- - Formulas:
134
- Insight: "[What happened] because [driver]"
135
- Comparison: "[A] outperforms [B] on [criterion]"
136
- Implication: "[Fact] puts [objective] at risk"
137
- Recommendation: "[Org] should [action] to achieve [outcome]"
138
-
139
- Executive summary slide
140
- - Situation: what context everyone agrees on.
141
- - Complication: what changed or created urgency.
142
- - Answer: the recommended response.
143
- - Support: 2-3 reasons the answer is correct.
144
-
145
- Deck sequence (typical)
146
- 1. Title page
147
- 2. Executive summary (situation -> complication -> resolution)
148
- 3. Context / problem framing (only enough to orient)
149
- 4. Analysis body (current state -> root causes -> options -> recommendation)
150
- 5. Recommendation (explicit, decision-ready)
151
- 6. Implementation / roadmap
152
- 7. Risks and mitigations
153
- 8. Appendix (source data, benchmarks, methodology)
154
-
155
- Body content
156
- - Every claim backed by evidence. The body proves the headline.
157
- - Quantify. Replace vague adjectives with measured claims.
158
- - Keep text tight: verbs, short bullets, no filler.
159
- - Move backup detail to appendix - main story stays focused.
160
- </style-guide>
161
- """
165
+ _TEMPLATE_CONTENT_RULES = """\
166
+ <content-rules>
167
+ - Each slide MUST have a "layout" key matching one of the layout IDs
168
+ listed in the <layouts> section.
169
+ - Each slide MUST have a "content" dict that maps placeholder names
170
+ (from the layout's placeholders) to values.
171
+ - Content type dispatch:
172
+ - Plain text or markdown: provide a string value directly.
173
+ Markdown is auto-detected (headings, bullets, **bold**, *italic*).
174
+ - Image: { kind: image, path: path/to/image.png, image_fit: fit }
175
+ image_fit accepts: "fit" (default) or "cover".
176
+ - Table: { kind: table, columns: [...], rows: [[...], ...] }
177
+ - Chart: { kind: chart, chart_type: column_clustered, categories: [...],
178
+ series: [{ name: ..., values: [...] }] }
179
+ chart_type accepts: column_clustered, bar_clustered, line, pie.
180
+ - Respect "required" and "max_lines" guidance from the layout definitions.
181
+ - "notes" is optional speaker-notes text (markdown ok) at the slide level.
182
+ </content-rules>"""
183
+
184
+ # Recompose generic schema from shared pieces
185
+ _GENERIC_SCHEMA = (
186
+ _OUTPUT_FORMAT
187
+ + "\n\n"
188
+ + _GENERIC_SLIDE_SCHEMA
189
+ + "\n\n"
190
+ + _GENERIC_CONTENT_RULES
191
+ + "\n\n"
192
+ + _STYLE_GUIDE
193
+ + "\n"
194
+ )
162
195
 
163
196
 
164
197
  # ---------------------------------------------------------------------------
165
- # Template-enriched schema
198
+ # Template-enriched schema helpers
166
199
  # ---------------------------------------------------------------------------
167
200
 
168
201
 
@@ -207,21 +240,54 @@ def _example_slide(layout: LayoutContract) -> dict[str, Any]:
207
240
  return slide
208
241
 
209
242
 
210
- def _build_template_section(manifest: ManifestDocument) -> str:
211
- """Return the template-specific portion of the reference doc."""
243
+ def _build_layouts_section(manifest: ManifestDocument) -> str:
244
+ """Return the <layouts> XML section with all layout definitions."""
212
245
  layouts_ref: dict[str, Any] = {}
213
- examples: list[dict[str, Any]] = []
214
-
215
246
  for layout in manifest.layouts:
216
247
  layouts_ref[layout.id] = _layout_section(layout)
248
+ layouts_yaml = yaml.safe_dump(layouts_ref, sort_keys=False, allow_unicode=True, width=120)
249
+ return f"<layouts>\n{layouts_yaml}</layouts>"
250
+
251
+
252
+ def _build_deck_schema_section(manifest: ManifestDocument) -> str:
253
+ """Return the <deck-schema> XML section with DeckSpec example."""
254
+ examples: list[dict[str, Any]] = []
255
+ # Use up to 4 representative layouts for examples
256
+ for layout in manifest.layouts[:4]:
217
257
  examples.append(_example_slide(layout))
218
258
 
219
- doc: dict[str, Any] = {
220
- "template": manifest.template.name,
221
- "layouts": layouts_ref,
222
- "example_slides": examples,
259
+ example_spec: dict[str, Any] = {
260
+ "metadata": {
261
+ "title": "<deck title>",
262
+ "author": "<author name>",
263
+ },
264
+ "slides": examples,
223
265
  }
224
- return yaml.safe_dump(doc, sort_keys=False, allow_unicode=True, width=120)
266
+ example_yaml = yaml.safe_dump(example_spec, sort_keys=False, allow_unicode=True, width=120)
267
+
268
+ prose = (
269
+ f"Use this schema to draft presentation content for the "
270
+ f'"{manifest.template.name}" template.\n'
271
+ f"Each slide must reference a layout from the <layouts> section and "
272
+ f"provide content for its placeholders.\n\n"
273
+ )
274
+ return f"<deck-schema>\n{prose}{example_yaml}</deck-schema>"
275
+
276
+
277
+ def _build_template_schema(manifest: ManifestDocument) -> str:
278
+ """Compose the unified template-bound schema document."""
279
+ parts = [
280
+ _OUTPUT_FORMAT,
281
+ "",
282
+ _build_deck_schema_section(manifest),
283
+ "",
284
+ _build_layouts_section(manifest),
285
+ "",
286
+ _TEMPLATE_CONTENT_RULES,
287
+ "",
288
+ _STYLE_GUIDE,
289
+ ]
290
+ return "\n".join(parts) + "\n"
225
291
 
226
292
 
227
293
  # ---------------------------------------------------------------------------
@@ -231,9 +297,7 @@ def _build_template_section(manifest: ManifestDocument) -> str:
231
297
 
232
298
  def build_schema_document(template_dir: Path | None = None) -> str:
233
299
  """Return the full reference document as a string."""
234
- parts = [_GENERIC_SCHEMA]
235
300
  if template_dir is not None:
236
301
  manifest = load_effective_manifest(template_dir)
237
- parts.append("\n# ── Template-specific layouts ──────────────────────────────────\n")
238
- parts.append(_build_template_section(manifest))
239
- return "\n".join(parts)
302
+ return _build_template_schema(manifest)
303
+ return _GENERIC_SCHEMA
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import patch
4
+
5
+ from typer.testing import CliRunner
6
+
7
+ from pptx_cli.cli import app
8
+ from pptx_cli.commands.schema import build_schema_document
9
+
10
+ runner = CliRunner()
11
+
12
+
13
+ def test_generic_schema_contains_deck_structure() -> None:
14
+ text = build_schema_document()
15
+ assert "<slide-schema>" in text
16
+ assert "metadata:" in text
17
+ assert "slides:" in text
18
+ assert "title:" in text
19
+ assert "body:" in text
20
+ assert "notes:" in text
21
+
22
+
23
+ def test_generic_schema_documents_content_types() -> None:
24
+ text = build_schema_document()
25
+ assert "kind: image" in text
26
+ assert "kind: table" in text
27
+ assert "kind: chart" in text
28
+
29
+
30
+ def test_generic_schema_uses_xml_sections() -> None:
31
+ text = build_schema_document()
32
+ assert "<slide-schema>" in text
33
+ assert "</slide-schema>" in text
34
+ assert "<content-rules>" in text
35
+ assert "</content-rules>" in text
36
+ assert "<style-guide>" in text
37
+ assert "</style-guide>" in text
38
+
39
+
40
+ def test_generic_schema_omits_layout() -> None:
41
+ text = build_schema_document()
42
+ assert "Do NOT include" in text
43
+ assert "layout is assigned later" in text
44
+
45
+
46
+ def test_schema_command_outputs_text(tmp_path: str) -> None:
47
+ result = runner.invoke(app, ["schema", "--no-template", "--no-copy"])
48
+ assert result.exit_code == 0
49
+ assert "metadata:" in result.stdout
50
+ assert "slides:" in result.stdout
51
+
52
+
53
+ def test_schema_command_copies_to_clipboard() -> None:
54
+ with patch("pptx_cli.cli.copy_to_clipboard", return_value=True) as mock_copy:
55
+ result = runner.invoke(app, ["schema", "--no-template"])
56
+ assert result.exit_code == 0
57
+ mock_copy.assert_called_once()
58
+ assert "copied to clipboard" in result.stderr
59
+
60
+
61
+ def test_schema_command_no_copy_skips_clipboard() -> None:
62
+ with patch("pptx_cli.cli.copy_to_clipboard") as mock_copy:
63
+ result = runner.invoke(app, ["schema", "--no-template", "--no-copy"])
64
+ assert result.exit_code == 0
65
+ mock_copy.assert_not_called()
66
+
67
+
68
+ def test_schema_with_template(template_path: str, manifest_dir: str) -> None:
69
+ # First init a manifest so we can point --template at it
70
+ init_result = runner.invoke(
71
+ app,
72
+ ["init", str(template_path), "--out", str(manifest_dir), "--format", "json"],
73
+ )
74
+ assert init_result.exit_code == 0
75
+
76
+ result = runner.invoke(app, ["schema", "--template", str(manifest_dir), "--no-copy"])
77
+ assert result.exit_code == 0
78
+ assert "<layouts>" in result.stdout
79
+ assert "<deck-schema>" in result.stdout
80
+ assert "layout:" in result.stdout
81
+ assert "content:" in result.stdout
82
+
83
+
84
+ def test_template_schema_unified_sections(template_path: str, manifest_dir: str) -> None:
85
+ init_result = runner.invoke(
86
+ app,
87
+ ["init", str(template_path), "--out", str(manifest_dir), "--format", "json"],
88
+ )
89
+ assert init_result.exit_code == 0
90
+
91
+ result = runner.invoke(app, ["schema", "--template", str(manifest_dir), "--no-copy"])
92
+ assert result.exit_code == 0
93
+ assert "<deck-schema>" in result.stdout
94
+ assert "<layouts>" in result.stdout
95
+ assert "<content-rules>" in result.stdout
96
+ assert "<style-guide>" in result.stdout
97
+
98
+
99
+ def test_template_schema_has_layout_in_examples(template_path: str, manifest_dir: str) -> None:
100
+ init_result = runner.invoke(
101
+ app,
102
+ ["init", str(template_path), "--out", str(manifest_dir), "--format", "json"],
103
+ )
104
+ assert init_result.exit_code == 0
105
+
106
+ result = runner.invoke(app, ["schema", "--template", str(manifest_dir), "--no-copy"])
107
+ assert result.exit_code == 0
108
+ assert "layout:" in result.stdout
109
+ assert "content:" in result.stdout
110
+
111
+
112
+ def test_template_schema_no_generic_layout_warning(template_path: str, manifest_dir: str) -> None:
113
+ init_result = runner.invoke(
114
+ app,
115
+ ["init", str(template_path), "--out", str(manifest_dir), "--format", "json"],
116
+ )
117
+ assert init_result.exit_code == 0
118
+
119
+ result = runner.invoke(app, ["schema", "--template", str(manifest_dir), "--no-copy"])
120
+ assert result.exit_code == 0
121
+ assert "Do NOT include" not in result.stdout
122
+
123
+
124
+ def test_no_template_flag_emits_generic() -> None:
125
+ result = runner.invoke(app, ["schema", "--no-template", "--no-copy"])
126
+ assert result.exit_code == 0
127
+ assert "<slide-schema>" in result.stdout
128
+ assert "Do NOT include" in result.stdout
129
+
130
+
131
+ def test_template_and_no_template_conflict() -> None:
132
+ result = runner.invoke(app, ["schema", "--template", "some/dir", "--no-template", "--no-copy"])
133
+ assert result.exit_code != 0
134
+ assert "mutually exclusive" in result.stderr
@@ -1,80 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from unittest.mock import patch
4
-
5
- from typer.testing import CliRunner
6
-
7
- from pptx_cli.cli import app
8
- from pptx_cli.commands.schema import build_schema_document
9
-
10
- runner = CliRunner()
11
-
12
-
13
- def test_generic_schema_contains_deck_structure() -> None:
14
- text = build_schema_document()
15
- assert "<slide-schema>" in text
16
- assert "metadata:" in text
17
- assert "slides:" in text
18
- assert "title:" in text
19
- assert "body:" in text
20
- assert "notes:" in text
21
-
22
-
23
- def test_generic_schema_documents_content_types() -> None:
24
- text = build_schema_document()
25
- assert "kind: image" in text
26
- assert "kind: table" in text
27
- assert "kind: chart" in text
28
-
29
-
30
- def test_generic_schema_uses_xml_sections() -> None:
31
- text = build_schema_document()
32
- assert "<slide-schema>" in text
33
- assert "</slide-schema>" in text
34
- assert "<content-rules>" in text
35
- assert "</content-rules>" in text
36
- assert "<style-guide>" in text
37
- assert "</style-guide>" in text
38
-
39
-
40
- def test_generic_schema_omits_layout() -> None:
41
- text = build_schema_document()
42
- assert "Do NOT include" in text
43
- assert "layout is assigned later" in text
44
-
45
-
46
- def test_schema_command_outputs_text(tmp_path: str) -> None:
47
- result = runner.invoke(app, ["schema", "--no-copy"])
48
- assert result.exit_code == 0
49
- assert "metadata:" in result.stdout
50
- assert "slides:" in result.stdout
51
-
52
-
53
- def test_schema_command_copies_to_clipboard() -> None:
54
- with patch("pptx_cli.cli.copy_to_clipboard", return_value=True) as mock_copy:
55
- result = runner.invoke(app, ["schema"])
56
- assert result.exit_code == 0
57
- mock_copy.assert_called_once()
58
- assert "copied to clipboard" in result.stderr
59
-
60
-
61
- def test_schema_command_no_copy_skips_clipboard() -> None:
62
- with patch("pptx_cli.cli.copy_to_clipboard") as mock_copy:
63
- result = runner.invoke(app, ["schema", "--no-copy"])
64
- assert result.exit_code == 0
65
- mock_copy.assert_not_called()
66
-
67
-
68
- def test_schema_with_template(template_path: str, manifest_dir: str) -> None:
69
- # First init a manifest so we can point --template at it
70
- init_result = runner.invoke(
71
- app,
72
- ["init", str(template_path), "--out", str(manifest_dir), "--format", "json"],
73
- )
74
- assert init_result.exit_code == 0
75
-
76
- result = runner.invoke(app, ["schema", "--template", str(manifest_dir), "--no-copy"])
77
- assert result.exit_code == 0
78
- assert "template:" in result.stdout
79
- assert "layouts:" in result.stdout
80
- assert "placeholders:" in result.stdout
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes