Skip to main content

ppt_rs/cli/
parser.rs

1//! Command-line argument parser using clap
2
3use 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    /// Create a new presentation
39    #[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        /// Output file path (.pptx)
48        #[arg(value_name = "FILE", help = "Path to the output PPTX file")]
49        output: String,
50
51        /// Presentation title
52        #[arg(long, help = "Title of the presentation (stored in metadata)")]
53        title: Option<String>,
54
55        /// Number of slides to create
56        #[arg(long, default_value_t = 1, help = "Number of blank slides to create")]
57        slides: usize,
58
59        /// Template file to use
60        #[arg(long, help = "Template PPTX file to use as base (not yet implemented)")]
61        template: Option<String>,
62    },
63
64    /// Generate PPTX from Markdown file
65    #[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        /// Input markdown file
104        #[arg(value_name = "INPUT", help = "Path to the input Markdown file")]
105        input: String,
106
107        /// Output PPTX file (optional: auto-generated from input if not provided)
108        #[arg(
109            value_name = "OUTPUT",
110            help = "Path to the output PPTX file (default: INPUT.pptx)"
111        )]
112        output: Option<String>,
113
114        /// Presentation title
115        #[arg(long, help = "Title of the presentation (overrides Markdown content)")]
116        title: Option<String>,
117    },
118
119    /// Show presentation information
120    #[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        /// PPTX file to inspect
128        #[arg(value_name = "FILE", help = "Path to the PPTX file to inspect")]
129        file: String,
130    },
131
132    /// Validate a PPTX file
133    #[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        /// PPTX file to validate
142        #[arg(value_name = "FILE")]
143        file: String,
144    },
145
146    /// Export presentation to other formats
147    #[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        /// Input PPTX file
155        #[arg(value_name = "INPUT")]
156        input: String,
157
158        /// Output file path
159        #[arg(value_name = "OUTPUT")]
160        output: String,
161
162        /// Output format (overrides extension)
163        #[arg(long, value_enum)]
164        format: Option<ExportFormat>,
165    },
166
167    /// Merge multiple presentations
168    #[command(long_about = "Merge multiple PPTX files into one.
169        
170Slides from all input files will be appended in order.")]
171    Merge {
172        /// Output PPTX file
173        #[arg(short, long)]
174        output: String,
175
176        /// Input PPTX files
177        #[arg(value_name = "INPUTS", required = true, num_args = 1..)]
178        inputs: Vec<String>,
179    },
180
181    /// Convert PDF to PowerPoint
182    #[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        /// Input PDF file
191        #[arg(value_name = "INPUT")]
192        input: String,
193
194        /// Output PPTX file
195        #[arg(value_name = "OUTPUT")]
196        output: Option<String>,
197    },
198
199    /// Generate PPTX from HTML file
200    #[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        /// Input HTML file
224        #[arg(value_name = "INPUT", help = "Path to the input HTML file")]
225        input: String,
226
227        /// Output PPTX file (optional: auto-generated from input if not provided)
228        #[arg(
229            value_name = "OUTPUT",
230            help = "Path to the output PPTX file (default: INPUT.pptx)"
231        )]
232        output: Option<String>,
233
234        /// Presentation title
235        #[arg(long, help = "Title of the presentation (overrides HTML <title>)")]
236        title: Option<String>,
237
238        /// Maximum number of slides
239        #[arg(long, default_value_t = 50, help = "Maximum number of slides to generate")]
240        max_slides: usize,
241
242        /// Maximum bullets per slide
243        #[arg(long, default_value_t = 10, help = "Maximum bullet points per slide")]
244        max_bullets: usize,
245
246        /// Disable image placeholders
247        #[arg(long, help = "Disable image placeholder extraction")]
248        no_images: bool,
249
250        /// Disable table extraction
251        #[arg(long, help = "Disable table extraction")]
252        no_tables: bool,
253
254        /// Disable code block extraction
255        #[arg(long, help = "Disable code block extraction")]
256        no_code: bool,
257    },
258
259    /// Generate PPTX from webpage (requires web2ppt feature)
260    #[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        /// URL to convert
274        #[arg(value_name = "URL")]
275        url: String,
276
277        /// Output file path (.pptx)
278        #[arg(short, long, default_value = "output.pptx")]
279        output: String,
280
281        /// Presentation title (overrides page title)
282        #[arg(long)]
283        title: Option<String>,
284
285        /// Maximum number of slides to generate
286        #[arg(long, default_value_t = 20)]
287        max_slides: usize,
288
289        /// Maximum bullet points per slide
290        #[arg(long, default_value_t = 7)]
291        max_bullets: usize,
292
293        /// Disable image extraction
294        #[arg(long)]
295        no_images: bool,
296
297        /// Disable table extraction
298        #[arg(long)]
299        no_tables: bool,
300
301        /// Disable code block extraction
302        #[arg(long)]
303        no_code: bool,
304
305        /// Don't add source URL to last slide
306        #[arg(long)]
307        no_source_url: bool,
308
309        /// Request timeout in seconds
310        #[arg(long, default_value_t = 30)]
311        timeout: u64,
312
313        /// Enable verbose logging
314        #[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}