feat(cli): add Clap subcommand hierarchy and global argument flags (GH-20) #49
No files matched your search
@@ -15,4 +15,5 @@ tokio.workspace = true
|
||||
clap.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,122 @@
|
||||
//! Command-line argument hierarchy and Clap subcommand parsing.
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Nuncio CLI — Developer-first cross-platform mail & calendar automation.
|
||||
#[derive(Parser, Debug, Clone, PartialEq, Eq)]
|
||||
#[command(name = "nuncio", author, version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
/// Output machine-readable JSON payloads to stdout.
|
||||
#[arg(long, global = true)]
|
||||
pub json: bool,
|
||||
|
||||
/// Target specific account ID.
|
||||
#[arg(long, global = true)]
|
||||
pub account: Option<String>,
|
||||
|
||||
/// Enable verbose log output to stderr.
|
||||
#[arg(short, long, global = true)]
|
||||
pub verbose: bool,
|
||||
|
||||
/// Subcommand action to perform.
|
||||
#[command(subcommand)]
|
||||
pub command: Commands,
|
||||
}
|
||||
|
||||
/// Available subcommands for `nuncio-cli`.
|
||||
#[derive(Subcommand, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Commands {
|
||||
/// Trigger mail & calendar synchronization.
|
||||
Sync,
|
||||
/// List messages in a folder.
|
||||
List {
|
||||
/// Mailbox folder name (default: "inbox").
|
||||
#[arg(short, long, default_value = "inbox")]
|
||||
folder: String,
|
||||
},
|
||||
/// Send an email message.
|
||||
Send {
|
||||
/// Recipient email address.
|
||||
#[arg(short, long)]
|
||||
to: String,
|
||||
/// Message subject line.
|
||||
#[arg(short, long)]
|
||||
subject: String,
|
||||
/// Message body content.
|
||||
#[arg(short, long)]
|
||||
body: String,
|
||||
},
|
||||
/// Perform full-text search across messages and calendar events.
|
||||
Search {
|
||||
/// Query string.
|
||||
#[arg(short, long)]
|
||||
query: String,
|
||||
},
|
||||
/// Display or validate CLI account configuration.
|
||||
Config,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_sync_command_flags() {
|
||||
let cli = Cli::parse_from(["nuncio", "--json", "sync"]);
|
||||
assert!(cli.json);
|
||||
assert_eq!(cli.command, Commands::Sync);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_list_command_with_folder() {
|
||||
let cli = Cli::parse_from(["nuncio", "--account", "acct-1", "list", "--folder", "Sent"]);
|
||||
assert_eq!(cli.account, Some("acct-1".to_string()));
|
||||
assert_eq!(
|
||||
cli.command,
|
||||
Commands::List {
|
||||
folder: "Sent".to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_send_command() {
|
||||
let cli = Cli::parse_from([
|
||||
"nuncio",
|
||||
"send",
|
||||
"--to",
|
||||
"bob@nuncio.mx",
|
||||
"--subject",
|
||||
"Meeting",
|
||||
"--body",
|
||||
"Hello Bob",
|
||||
]);
|
||||
assert_eq!(
|
||||
cli.command,
|
||||
Commands::Send {
|
||||
to: "bob@nuncio.mx".to_string(),
|
||||
subject: "Meeting".to_string(),
|
||||
body: "Hello Bob".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_search_command() {
|
||||
let cli = Cli::parse_from(["nuncio", "search", "--query", "Architecture"]);
|
||||
assert_eq!(
|
||||
cli.command,
|
||||
Commands::Search {
|
||||
query: "Architecture".to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_config_command() {
|
||||
let cli = Cli::parse_from(["nuncio", "config"]);
|
||||
assert_eq!(cli.command, Commands::Config);
|
||||
}
|
||||
}
|
||||
+36
-101
@@ -1,114 +1,49 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use nuncio_core::NuncioEngine;
|
||||
//! Nuncio CLI main application entry point.
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "nuncio")]
|
||||
#[command(author = "James Maes <james@kof22.com>")]
|
||||
#[command(version = "0.1.0")]
|
||||
#[command(about = "Cross-platform scriptable mail and calendar CLI", long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
mod args;
|
||||
|
||||
/// Output JSON formatted response for Unix pipelines
|
||||
#[arg(long, global = true)]
|
||||
json: bool,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Mail management subcommands
|
||||
Mail {
|
||||
#[command(subcommand)]
|
||||
action: MailCommands,
|
||||
},
|
||||
/// Calendar management subcommands
|
||||
Cal {
|
||||
#[command(subcommand)]
|
||||
action: CalCommands,
|
||||
},
|
||||
/// Trigger background sync across mail and calendar providers
|
||||
Sync,
|
||||
/// Display system and account status
|
||||
Status,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum MailCommands {
|
||||
/// List messages in a folder
|
||||
List {
|
||||
#[arg(short, long, default_value = "inbox")]
|
||||
folder: String,
|
||||
#[arg(short, long, default_value_t = 20)]
|
||||
limit: usize,
|
||||
},
|
||||
/// Read a specific message by ID
|
||||
Read {
|
||||
id: String,
|
||||
},
|
||||
/// Send an outgoing email
|
||||
Send {
|
||||
#[arg(long)]
|
||||
to: String,
|
||||
#[arg(long)]
|
||||
subject: String,
|
||||
#[arg(long)]
|
||||
body: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum CalCommands {
|
||||
/// List upcoming calendar events
|
||||
List {
|
||||
#[arg(long)]
|
||||
today: bool,
|
||||
},
|
||||
/// Create a new calendar event
|
||||
Add {
|
||||
#[arg(long)]
|
||||
summary: String,
|
||||
#[arg(long)]
|
||||
start: String,
|
||||
#[arg(long)]
|
||||
end: String,
|
||||
},
|
||||
}
|
||||
use args::{Cli, Commands};
|
||||
use clap::Parser;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
let engine = NuncioEngine::new();
|
||||
|
||||
match cli.command {
|
||||
Commands::Mail { action } => match action {
|
||||
MailCommands::List { folder, limit } => {
|
||||
if cli.json {
|
||||
println!("{{\"engine\":\"{}\",\"action\":\"mail_list\",\"folder\":\"{}\",\"limit\":{}}}", engine.name, folder, limit);
|
||||
} else {
|
||||
println!("Listing {} messages in folder '{}' (limit: {})", engine.name, folder, limit);
|
||||
}
|
||||
}
|
||||
MailCommands::Read { id } => {
|
||||
println!("Reading message {}", id);
|
||||
}
|
||||
MailCommands::Send { to, subject, body: _ } => {
|
||||
println!("Sending message to {} with subject '{}'", to, subject);
|
||||
}
|
||||
},
|
||||
Commands::Cal { action } => match action {
|
||||
CalCommands::List { today } => {
|
||||
println!("Listing calendar events (today: {})", today);
|
||||
}
|
||||
CalCommands::Add { summary, start, end } => {
|
||||
println!("Adding event '{}' from {} to {}", summary, start, end);
|
||||
}
|
||||
},
|
||||
Commands::Sync => {
|
||||
println!("Triggering account sync...");
|
||||
if cli.json {
|
||||
println!(r#"{{"status":"ok","action":"sync"}}"#);
|
||||
} else {
|
||||
println!("Triggering account synchronization...");
|
||||
}
|
||||
}
|
||||
Commands::Status => {
|
||||
println!("{} Engine ({}): Operational", engine.name, engine.domain);
|
||||
Commands::List { folder } => {
|
||||
if cli.json {
|
||||
println!(r#"{{"status":"ok","action":"list","folder":"{}"}}"#, folder);
|
||||
} else {
|
||||
println!("Listing messages in folder '{}'", folder);
|
||||
}
|
||||
}
|
||||
Commands::Send { to, subject, body: _ } => {
|
||||
if cli.json {
|
||||
println!(r#"{{"status":"ok","action":"send","to":"{}"}}"#, to);
|
||||
} else {
|
||||
println!("Sending message to {} ('{}')", to, subject);
|
||||
}
|
||||
}
|
||||
Commands::Search { query } => {
|
||||
if cli.json {
|
||||
println!(r#"{{"status":"ok","action":"search","query":"{}"}}"#, query);
|
||||
} else {
|
||||
println!("Searching for '{}'", query);
|
||||
}
|
||||
}
|
||||
Commands::Config => {
|
||||
if cli.json {
|
||||
println!(r#"{{"status":"ok","action":"config"}}"#);
|
||||
} else {
|
||||
println!("Nuncio CLI Configuration Operational");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user