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 #[cfg(feature = "web2ppt")]
201 #[command(
202 name = "web2ppt",
203 long_about = "Convert a webpage to a PowerPoint presentation.
204
205Extracts:
206- Title and headings
207- Text content
208- Images
209- Tables
210- Code blocks"
211 )]
212 Web2Ppt {
213 #[arg(value_name = "URL")]
215 url: String,
216
217 #[arg(short, long, default_value = "output.pptx")]
219 output: String,
220
221 #[arg(long)]
223 title: Option<String>,
224
225 #[arg(long, default_value_t = 20)]
227 max_slides: usize,
228
229 #[arg(long, default_value_t = 7)]
231 max_bullets: usize,
232
233 #[arg(long)]
235 no_images: bool,
236
237 #[arg(long)]
239 no_tables: bool,
240
241 #[arg(long)]
243 no_code: bool,
244
245 #[arg(long)]
247 no_source_url: bool,
248
249 #[arg(long, default_value_t = 30)]
251 timeout: u64,
252
253 #[arg(short, long)]
255 verbose: bool,
256 },
257}
258
259#[derive(ValueEnum, Clone, Debug)]
260pub enum ExportFormat {
261 Pdf,
262 Html,
263 Png,
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn test_parse_create() {
272 let args = vec![
273 "pptcli".to_string(),
274 "create".to_string(),
275 "test.pptx".to_string(),
276 "--title".to_string(),
277 "My Presentation".to_string(),
278 ];
279 let cli = Cli::parse_from(args.iter());
280 match cli.command {
281 Commands::Create { output, title, .. } => {
282 assert_eq!(output, "test.pptx");
283 assert_eq!(title, Some("My Presentation".to_string()));
284 }
285 _ => panic!("Expected Create command"),
286 }
287 }
288
289 #[test]
290 fn test_parse_md2ppt_with_output() {
291 let args = vec![
292 "pptcli".to_string(),
293 "md2ppt".to_string(),
294 "input.md".to_string(),
295 "output.pptx".to_string(),
296 "--title".to_string(),
297 "From Markdown".to_string(),
298 ];
299 let cli = Cli::parse_from(args.iter());
300 match cli.command {
301 Commands::Md2Ppt {
302 input,
303 output,
304 title,
305 } => {
306 assert_eq!(input, "input.md");
307 assert_eq!(output, Some("output.pptx".to_string()));
308 assert_eq!(title, Some("From Markdown".to_string()));
309 }
310 _ => panic!("Expected Md2Ppt command"),
311 }
312 }
313
314 #[test]
315 fn test_parse_md2ppt_auto_output() {
316 let args = vec![
317 "pptcli".to_string(),
318 "md2ppt".to_string(),
319 "input.md".to_string(),
320 "--title".to_string(),
321 "From Markdown".to_string(),
322 ];
323 let cli = Cli::parse_from(args.iter());
324 match cli.command {
325 Commands::Md2Ppt {
326 input,
327 output,
328 title,
329 } => {
330 assert_eq!(input, "input.md");
331 assert_eq!(output, None);
332 assert_eq!(title, Some("From Markdown".to_string()));
333 }
334 _ => panic!("Expected Md2Ppt command"),
335 }
336 }
337
338 #[test]
339 fn test_parse_from_md_alias() {
340 let args = vec![
341 "pptcli".to_string(),
342 "from-md".to_string(),
343 "input.md".to_string(),
344 "output.pptx".to_string(),
345 ];
346 let cli = Cli::parse_from(args.iter());
347 match cli.command {
348 Commands::Md2Ppt { input, output, .. } => {
349 assert_eq!(input, "input.md");
350 assert_eq!(output, Some("output.pptx".to_string()));
351 }
352 _ => panic!("Expected Md2Ppt command via from-md alias"),
353 }
354 }
355
356 #[test]
357 fn test_parse_info() {
358 let args = vec![
359 "pptcli".to_string(),
360 "info".to_string(),
361 "test.pptx".to_string(),
362 ];
363 let cli = Cli::parse_from(args.iter());
364 match cli.command {
365 Commands::Info { file } => {
366 assert_eq!(file, "test.pptx");
367 }
368 _ => panic!("Expected Info command"),
369 }
370 }
371}