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 webpage (requires web2ppt feature)
200    #[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        /// URL to convert
214        #[arg(value_name = "URL")]
215        url: String,
216
217        /// Output file path (.pptx)
218        #[arg(short, long, default_value = "output.pptx")]
219        output: String,
220
221        /// Presentation title (overrides page title)
222        #[arg(long)]
223        title: Option<String>,
224
225        /// Maximum number of slides to generate
226        #[arg(long, default_value_t = 20)]
227        max_slides: usize,
228
229        /// Maximum bullet points per slide
230        #[arg(long, default_value_t = 7)]
231        max_bullets: usize,
232
233        /// Disable image extraction
234        #[arg(long)]
235        no_images: bool,
236
237        /// Disable table extraction
238        #[arg(long)]
239        no_tables: bool,
240
241        /// Disable code block extraction
242        #[arg(long)]
243        no_code: bool,
244
245        /// Don't add source URL to last slide
246        #[arg(long)]
247        no_source_url: bool,
248
249        /// Request timeout in seconds
250        #[arg(long, default_value_t = 30)]
251        timeout: u64,
252
253        /// Enable verbose logging
254        #[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}