1use clap::{Parser as ClapParser, Subcommand, ValueEnum};
4
5#[derive(ClapParser, Debug)]
6#[command(name = "pptcli")]
7#[command(about = "PowerPoint Generator - Create, read, and update PowerPoint 2007+ (.pptx) files")]
8#[command(
9 long_about = "pptcli - A command-line tool for generating PowerPoint presentations from Markdown, webpages, or programmatically.
10
11Examples:
12 # Create a simple presentation
13 pptcli create output.pptx --title \"My Presentation\" --slides 5
14
15 # Convert Markdown to PowerPoint
16 pptcli md2ppt slides.md presentation.pptx
17
18 # Auto-generate output filename from Markdown
19 pptcli md2ppt slides.md
20
21 # Convert webpage to PowerPoint (requires --features web2ppt)
22 pptcli web2ppt https://example.com -o output.pptx
23
24 # Validate a PPTX file
25 pptcli validate presentation.pptx
26
27 # Show presentation information
28 pptcli info presentation.pptx"
29)]
30#[command(version)]
31pub struct Cli {
32 #[command(subcommand)]
33 pub command: Commands,
34}
35
36#[derive(Subcommand, Debug)]
37pub enum Commands {
38 #[command(
40 long_about = "Create a new PowerPoint presentation with the specified number of slides.
41
42Examples:
43 pptcli create output.pptx --title \"My Presentation\" --slides 5
44 pptcli create report.pptx --slides 10"
45 )]
46 Create {
47 #[arg(value_name = "FILE", help = "Path to the output PPTX file")]
49 output: String,
50
51 #[arg(long, help = "Title of the presentation (stored in metadata)")]
53 title: Option<String>,
54
55 #[arg(long, default_value_t = 1, help = "Number of blank slides to create")]
57 slides: usize,
58
59 #[arg(long, help = "Template PPTX file to use as base (not yet implemented)")]
61 template: Option<String>,
62 },
63
64 #[command(
66 name = "md2ppt",
67 alias = "from-md",
68 alias = "from-markdown",
69 long_about = "Convert a Markdown file to a PowerPoint presentation.
70
71Supported Markdown Features:
72 # Heading → New slide with title
73 ## Subheading → Bold bullet point
74 - Bullet → Bullet points (also *, +)
75 1. Numbered → Numbered list items
76 **bold** → Bold text
77 *italic* → Italic text
78 `code` → Inline code
79 > Blockquote → Speaker notes
80 | Table | → Tables (GFM style)
81 ```code``` → Code blocks (as shapes)
82 ```mermaid → Mermaid diagrams (12 types)
83 --- → Slide break (continuation)
84
85Example Markdown:
86 # Introduction
87 - Welcome to the presentation
88 - **Key point** with emphasis
89
90 # Data
91 | Name | Value |
92 |------|-------|
93 | A | 100 |
94
95 > Speaker notes go here
96
97Examples:
98 pptcli md2ppt slides.md presentation.pptx
99 pptcli md2ppt slides.md --title \"My Presentation\"
100 pptcli md2ppt slides.md # Auto-generates slides.pptx"
101 )]
102 Md2Ppt {
103 #[arg(value_name = "INPUT", help = "Path to the input Markdown file")]
105 input: String,
106
107 #[arg(
109 value_name = "OUTPUT",
110 help = "Path to the output PPTX file (default: INPUT.pptx)"
111 )]
112 output: Option<String>,
113
114 #[arg(long, help = "Title of the presentation (overrides Markdown content)")]
116 title: Option<String>,
117 },
118
119 #[command(long_about = "Display information about a PPTX file.
121
122Shows file size, modification date, and basic metadata.
123
124Example:
125 pptcli info presentation.pptx")]
126 Info {
127 #[arg(value_name = "FILE", help = "Path to the PPTX file to inspect")]
129 file: String,
130 },
131
132 #[command(long_about = "Validate a PPTX file structure and content.
134
135Checks for:
136- Valid ZIP structure
137- Required parts (presentation.xml, slide masters, etc.)
138- Content types
139- Relationships")]
140 Validate {
141 #[arg(value_name = "FILE")]
143 file: String,
144 },
145
146 #[command(long_about = "Export PPTX to PDF, HTML, or images.
148
149Formats:
150- pdf: Requires LibreOffice installed
151- html: Self-contained HTML slideshow
152- png: Requires LibreOffice and pdftoppm")]
153 Export {
154 #[arg(value_name = "INPUT")]
156 input: String,
157
158 #[arg(value_name = "OUTPUT")]
160 output: String,
161
162 #[arg(long, value_enum)]
164 format: Option<ExportFormat>,
165 },
166
167 #[command(long_about = "Merge multiple PPTX files into one.
169
170Slides from all input files will be appended in order.")]
171 Merge {
172 #[arg(short, long)]
174 output: String,
175
176 #[arg(value_name = "INPUTS", required = true, num_args = 1..)]
178 inputs: Vec<String>,
179 },
180
181 #[command(
183 name = "pdf2ppt",
184 long_about = "Convert PDF pages to PowerPoint slides.
185
186Requires `pdftoppm` (poppler) installed.
187Each page becomes a slide with the page image."
188 )]
189 Pdf2Ppt {
190 #[arg(value_name = "INPUT")]
192 input: String,
193
194 #[arg(value_name = "OUTPUT")]
196 output: Option<String>,
197 },
198
199 #[command(
201 name = "html2ppt",
202 alias = "from-html",
203 alias = "from-html-file",
204 long_about = "Convert an HTML file to a PowerPoint presentation.
205
206Converts HTML content into PowerPoint slides:
207 <h1> → New slide with title
208 <h2>-<h6> → Bold section headers
209 <p> → Bullet points
210 <ul>/<ol> → List items
211 <table> → Tables with styled headers
212 <pre>/<code> → Code blocks
213 <img> → Image placeholders
214 <blockquote> → Speaker notes
215 <hr> → Slide break
216
217Examples:
218 pptcli html2ppt slides.html presentation.pptx
219 pptcli html2ppt slides.html --title \"My Presentation\"
220 pptcli html2ppt slides.html # Auto-generates slides.pptx"
221 )]
222 Html2Ppt {
223 #[arg(value_name = "INPUT", help = "Path to the input HTML file")]
225 input: String,
226
227 #[arg(
229 value_name = "OUTPUT",
230 help = "Path to the output PPTX file (default: INPUT.pptx)"
231 )]
232 output: Option<String>,
233
234 #[arg(long, help = "Title of the presentation (overrides HTML <title>)")]
236 title: Option<String>,
237
238 #[arg(long, default_value_t = 50, help = "Maximum number of slides to generate")]
240 max_slides: usize,
241
242 #[arg(long, default_value_t = 10, help = "Maximum bullet points per slide")]
244 max_bullets: usize,
245
246 #[arg(long, help = "Disable image placeholder extraction")]
248 no_images: bool,
249
250 #[arg(long, help = "Disable table extraction")]
252 no_tables: bool,
253
254 #[arg(long, help = "Disable code block extraction")]
256 no_code: bool,
257 },
258
259 #[cfg(feature = "web2ppt")]
261 #[command(
262 name = "web2ppt",
263 long_about = "Convert a webpage to a PowerPoint presentation.
264
265Extracts:
266- Title and headings
267- Text content
268- Images
269- Tables
270- Code blocks"
271 )]
272 Web2Ppt {
273 #[arg(value_name = "URL")]
275 url: String,
276
277 #[arg(short, long, default_value = "output.pptx")]
279 output: String,
280
281 #[arg(long)]
283 title: Option<String>,
284
285 #[arg(long, default_value_t = 20)]
287 max_slides: usize,
288
289 #[arg(long, default_value_t = 7)]
291 max_bullets: usize,
292
293 #[arg(long)]
295 no_images: bool,
296
297 #[arg(long)]
299 no_tables: bool,
300
301 #[arg(long)]
303 no_code: bool,
304
305 #[arg(long)]
307 no_source_url: bool,
308
309 #[arg(long, default_value_t = 30)]
311 timeout: u64,
312
313 #[arg(short, long)]
315 verbose: bool,
316 },
317}
318
319#[derive(ValueEnum, Clone, Debug)]
320pub enum ExportFormat {
321 Pdf,
322 Html,
323 Png,
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_parse_create() {
332 let args = vec![
333 "pptcli".to_string(),
334 "create".to_string(),
335 "test.pptx".to_string(),
336 "--title".to_string(),
337 "My Presentation".to_string(),
338 ];
339 let cli = Cli::parse_from(args.iter());
340 match cli.command {
341 Commands::Create { output, title, .. } => {
342 assert_eq!(output, "test.pptx");
343 assert_eq!(title, Some("My Presentation".to_string()));
344 }
345 _ => panic!("Expected Create command"),
346 }
347 }
348
349 #[test]
350 fn test_parse_md2ppt_with_output() {
351 let args = vec![
352 "pptcli".to_string(),
353 "md2ppt".to_string(),
354 "input.md".to_string(),
355 "output.pptx".to_string(),
356 "--title".to_string(),
357 "From Markdown".to_string(),
358 ];
359 let cli = Cli::parse_from(args.iter());
360 match cli.command {
361 Commands::Md2Ppt {
362 input,
363 output,
364 title,
365 } => {
366 assert_eq!(input, "input.md");
367 assert_eq!(output, Some("output.pptx".to_string()));
368 assert_eq!(title, Some("From Markdown".to_string()));
369 }
370 _ => panic!("Expected Md2Ppt command"),
371 }
372 }
373
374 #[test]
375 fn test_parse_md2ppt_auto_output() {
376 let args = vec![
377 "pptcli".to_string(),
378 "md2ppt".to_string(),
379 "input.md".to_string(),
380 "--title".to_string(),
381 "From Markdown".to_string(),
382 ];
383 let cli = Cli::parse_from(args.iter());
384 match cli.command {
385 Commands::Md2Ppt {
386 input,
387 output,
388 title,
389 } => {
390 assert_eq!(input, "input.md");
391 assert_eq!(output, None);
392 assert_eq!(title, Some("From Markdown".to_string()));
393 }
394 _ => panic!("Expected Md2Ppt command"),
395 }
396 }
397
398 #[test]
399 fn test_parse_from_md_alias() {
400 let args = vec![
401 "pptcli".to_string(),
402 "from-md".to_string(),
403 "input.md".to_string(),
404 "output.pptx".to_string(),
405 ];
406 let cli = Cli::parse_from(args.iter());
407 match cli.command {
408 Commands::Md2Ppt { input, output, .. } => {
409 assert_eq!(input, "input.md");
410 assert_eq!(output, Some("output.pptx".to_string()));
411 }
412 _ => panic!("Expected Md2Ppt command via from-md alias"),
413 }
414 }
415
416 #[test]
417 fn test_parse_info() {
418 let args = vec![
419 "pptcli".to_string(),
420 "info".to_string(),
421 "test.pptx".to_string(),
422 ];
423 let cli = Cli::parse_from(args.iter());
424 match cli.command {
425 Commands::Info { file } => {
426 assert_eq!(file, "test.pptx");
427 }
428 _ => panic!("Expected Info command"),
429 }
430 }
431}