use clap::{Parser as ClapParser, Subcommand, ValueEnum};
#[derive(ClapParser, Debug)]
#[command(name = "pptcli")]
#[command(about = "PowerPoint Generator - Create, read, and update PowerPoint 2007+ (.pptx) files")]
#[command(
long_about = "pptcli - A command-line tool for generating PowerPoint presentations from Markdown, webpages, or programmatically.
Examples:
# Create a simple presentation
pptcli create output.pptx --title \"My Presentation\" --slides 5
# Convert Markdown to PowerPoint
pptcli md2ppt slides.md presentation.pptx
# Auto-generate output filename from Markdown
pptcli md2ppt slides.md
# Convert webpage to PowerPoint (requires --features web2ppt)
pptcli web2ppt https://example.com -o output.pptx
# Validate a PPTX file
pptcli validate presentation.pptx
# Show presentation information
pptcli info presentation.pptx"
)]
#[command(version)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(
long_about = "Create a new PowerPoint presentation with the specified number of slides.
Examples:
pptcli create output.pptx --title \"My Presentation\" --slides 5
pptcli create report.pptx --slides 10"
)]
Create {
#[arg(value_name = "FILE", help = "Path to the output PPTX file")]
output: String,
#[arg(long, help = "Title of the presentation (stored in metadata)")]
title: Option<String>,
#[arg(long, default_value_t = 1, help = "Number of blank slides to create")]
slides: usize,
#[arg(long, help = "Template PPTX file to use as base (not yet implemented)")]
template: Option<String>,
},
#[command(
name = "md2ppt",
alias = "from-md",
alias = "from-markdown",
long_about = "Convert a Markdown file to a PowerPoint presentation.
Supported Markdown Features:
# Heading → New slide with title
## Subheading → Bold bullet point
- Bullet → Bullet points (also *, +)
1. Numbered → Numbered list items
**bold** → Bold text
*italic* → Italic text
`code` → Inline code
> Blockquote → Speaker notes
| Table | → Tables (GFM style)
```code``` → Code blocks (as shapes)
```mermaid → Mermaid diagrams (12 types)
--- → Slide break (continuation)
Example Markdown:
# Introduction
- Welcome to the presentation
- **Key point** with emphasis
# Data
| Name | Value |
|------|-------|
| A | 100 |
> Speaker notes go here
Examples:
pptcli md2ppt slides.md presentation.pptx
pptcli md2ppt slides.md --title \"My Presentation\"
pptcli md2ppt slides.md # Auto-generates slides.pptx"
)]
Md2Ppt {
#[arg(value_name = "INPUT", help = "Path to the input Markdown file")]
input: String,
#[arg(
value_name = "OUTPUT",
help = "Path to the output PPTX file (default: INPUT.pptx)"
)]
output: Option<String>,
#[arg(long, help = "Title of the presentation (overrides Markdown content)")]
title: Option<String>,
},
#[command(long_about = "Display information about a PPTX file.
Shows file size, modification date, and basic metadata.
Example:
pptcli info presentation.pptx")]
Info {
#[arg(value_name = "FILE", help = "Path to the PPTX file to inspect")]
file: String,
},
#[command(long_about = "Validate a PPTX file structure and content.
Checks for:
- Valid ZIP structure
- Required parts (presentation.xml, slide masters, etc.)
- Content types
- Relationships")]
Validate {
#[arg(value_name = "FILE")]
file: String,
},
#[command(long_about = "Export PPTX to PDF, HTML, or images.
Formats:
- pdf: Requires LibreOffice installed
- html: Self-contained HTML slideshow
- png: Requires LibreOffice and pdftoppm")]
Export {
#[arg(value_name = "INPUT")]
input: String,
#[arg(value_name = "OUTPUT")]
output: String,
#[arg(long, value_enum)]
format: Option<ExportFormat>,
},
#[command(long_about = "Merge multiple PPTX files into one.
Slides from all input files will be appended in order.")]
Merge {
#[arg(short, long)]
output: String,
#[arg(value_name = "INPUTS", required = true, num_args = 1..)]
inputs: Vec<String>,
},
#[command(
name = "pdf2ppt",
long_about = "Convert PDF pages to PowerPoint slides.
Requires `pdftoppm` (poppler) installed.
Each page becomes a slide with the page image."
)]
Pdf2Ppt {
#[arg(value_name = "INPUT")]
input: String,
#[arg(value_name = "OUTPUT")]
output: Option<String>,
},
#[cfg(feature = "web2ppt")]
#[command(
name = "web2ppt",
long_about = "Convert a webpage to a PowerPoint presentation.
Extracts:
- Title and headings
- Text content
- Images
- Tables
- Code blocks"
)]
Web2Ppt {
#[arg(value_name = "URL")]
url: String,
#[arg(short, long, default_value = "output.pptx")]
output: String,
#[arg(long)]
title: Option<String>,
#[arg(long, default_value_t = 20)]
max_slides: usize,
#[arg(long, default_value_t = 7)]
max_bullets: usize,
#[arg(long)]
no_images: bool,
#[arg(long)]
no_tables: bool,
#[arg(long)]
no_code: bool,
#[arg(long)]
no_source_url: bool,
#[arg(long, default_value_t = 30)]
timeout: u64,
#[arg(short, long)]
verbose: bool,
},
}
#[derive(ValueEnum, Clone, Debug)]
pub enum ExportFormat {
Pdf,
Html,
Png,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_create() {
let args = vec![
"pptcli".to_string(),
"create".to_string(),
"test.pptx".to_string(),
"--title".to_string(),
"My Presentation".to_string(),
];
let cli = Cli::parse_from(args.iter());
match cli.command {
Commands::Create { output, title, .. } => {
assert_eq!(output, "test.pptx");
assert_eq!(title, Some("My Presentation".to_string()));
}
_ => panic!("Expected Create command"),
}
}
#[test]
fn test_parse_md2ppt_with_output() {
let args = vec![
"pptcli".to_string(),
"md2ppt".to_string(),
"input.md".to_string(),
"output.pptx".to_string(),
"--title".to_string(),
"From Markdown".to_string(),
];
let cli = Cli::parse_from(args.iter());
match cli.command {
Commands::Md2Ppt {
input,
output,
title,
} => {
assert_eq!(input, "input.md");
assert_eq!(output, Some("output.pptx".to_string()));
assert_eq!(title, Some("From Markdown".to_string()));
}
_ => panic!("Expected Md2Ppt command"),
}
}
#[test]
fn test_parse_md2ppt_auto_output() {
let args = vec![
"pptcli".to_string(),
"md2ppt".to_string(),
"input.md".to_string(),
"--title".to_string(),
"From Markdown".to_string(),
];
let cli = Cli::parse_from(args.iter());
match cli.command {
Commands::Md2Ppt {
input,
output,
title,
} => {
assert_eq!(input, "input.md");
assert_eq!(output, None);
assert_eq!(title, Some("From Markdown".to_string()));
}
_ => panic!("Expected Md2Ppt command"),
}
}
#[test]
fn test_parse_from_md_alias() {
let args = vec![
"pptcli".to_string(),
"from-md".to_string(),
"input.md".to_string(),
"output.pptx".to_string(),
];
let cli = Cli::parse_from(args.iter());
match cli.command {
Commands::Md2Ppt { input, output, .. } => {
assert_eq!(input, "input.md");
assert_eq!(output, Some("output.pptx".to_string()));
}
_ => panic!("Expected Md2Ppt command via from-md alias"),
}
}
#[test]
fn test_parse_info() {
let args = vec![
"pptcli".to_string(),
"info".to_string(),
"test.pptx".to_string(),
];
let cli = Cli::parse_from(args.iter());
match cli.command {
Commands::Info { file } => {
assert_eq!(file, "test.pptx");
}
_ => panic!("Expected Info command"),
}
}
}