mod logging;
use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::{
generate,
shells::{Bash, Elvish, Fish, PowerShell, Zsh},
};
use cardinal_core::parse_command;
use cardinal_tui::{run_tui, validate_runtime_config};
use crate::logging::StructuredLogger;
#[derive(Debug, Parser)]
#[command(name = "cardinal")]
#[command(about = "Command-first terminal mail/calendar workspace")]
struct Cli {
#[command(subcommand)]
command: Option<CliCommand>,
}
#[derive(Debug, Subcommand)]
enum CliCommand {
Tui,
Parse { input: String },
Doctor,
#[command(name = "commands")]
CommandList,
Config {
#[command(subcommand)]
command: ConfigSubcommand,
},
Completions { shell: CompletionShell },
Man,
}
#[derive(Debug, Subcommand)]
enum ConfigSubcommand {
Validate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
enum CompletionShell {
Bash,
Elvish,
Fish,
PowerShell,
Zsh,
}
fn main() {
let cli = Cli::parse();
let mut logger = match StructuredLogger::from_env() {
Ok(logger) => logger,
Err(error) => {
eprintln!("error: failed to initialize structured logger: {error}");
std::process::exit(1);
}
};
logger.event("info", "startup", &[("command", describe_command(&cli))]);
let exit_code = run(cli, &mut logger);
logger.event("info", "shutdown", &[("exit_code", exit_code.to_string())]);
if exit_code != 0 {
std::process::exit(exit_code);
}
}
fn run(cli: Cli, logger: &mut StructuredLogger) -> i32 {
match cli.command {
Some(CliCommand::Tui) => run_tui_with_logging(logger),
Some(CliCommand::Parse { input }) => {
logger.event("info", "parse_command", &[("input", input.clone())]);
match parse_command(&input) {
Ok(command) => {
println!("{command:#?}");
0
}
Err(error) => {
eprintln!("error: {error}");
logger.event(
"error",
"parse_command_failed",
&[("error", error.to_string())],
);
2
}
}
}
Some(CliCommand::Doctor) => {
let report = validate_runtime_config();
println!("cardinal: pre-alpha scaffold");
println!("core: command parser available");
println!("tui: milestone 1-10 scaffold available");
println!(
"config: {}",
if report.has_errors() {
"validation errors present"
} else {
"validation clean or warnings only"
}
);
println!("sync: external command integration available");
println!("packaging: metadata and generation commands present");
0
}
Some(CliCommand::CommandList) => {
for command in COMMAND_EXAMPLES {
println!("{command}");
}
0
}
Some(CliCommand::Config {
command: ConfigSubcommand::Validate,
}) => {
let report = validate_runtime_config();
println!("{}", report.summary());
for issue in &report.issues {
println!("- {:?}: {}", issue.severity, issue.message);
}
if report.has_errors() {
1
} else {
0
}
}
Some(CliCommand::Completions { shell }) => {
logger.event(
"info",
"generate_completions",
&[("shell", format!("{shell:?}"))],
);
print_completions(shell);
0
}
Some(CliCommand::Man) => match print_man_page() {
Ok(()) => 0,
Err(error) => {
eprintln!("error: failed to render man page: {error}");
logger.event(
"error",
"render_man_page_failed",
&[("error", error.to_string())],
);
1
}
},
None => run_tui_with_logging(logger),
}
}
fn run_tui_with_logging(logger: &mut StructuredLogger) -> i32 {
logger.event("info", "tui_start", &[]);
match run_tui() {
Ok(()) => 0,
Err(error) => {
eprintln!("error: failed to launch TUI: {error}");
logger.event("error", "tui_failed", &[("error", error.to_string())]);
1
}
}
}
fn print_completions(shell: CompletionShell) {
let mut command = Cli::command();
let binary_name = command.get_name().to_owned();
match shell {
CompletionShell::Bash => generate(Bash, &mut command, &binary_name, &mut std::io::stdout()),
CompletionShell::Elvish => {
generate(Elvish, &mut command, &binary_name, &mut std::io::stdout())
}
CompletionShell::Fish => generate(Fish, &mut command, &binary_name, &mut std::io::stdout()),
CompletionShell::PowerShell => generate(
PowerShell,
&mut command,
&binary_name,
&mut std::io::stdout(),
),
CompletionShell::Zsh => generate(Zsh, &mut command, &binary_name, &mut std::io::stdout()),
}
}
fn print_man_page() -> Result<(), std::io::Error> {
let command = Cli::command();
let man = clap_mangen::Man::new(command);
man.render(&mut std::io::stdout())
}
fn describe_command(cli: &Cli) -> String {
match cli.command {
Some(CliCommand::Tui) => "tui".to_owned(),
Some(CliCommand::Parse { .. }) => "parse".to_owned(),
Some(CliCommand::Doctor) => "doctor".to_owned(),
Some(CliCommand::CommandList) => "commands".to_owned(),
Some(CliCommand::Config { .. }) => "config".to_owned(),
Some(CliCommand::Completions { .. }) => "completions".to_owned(),
Some(CliCommand::Man) => "man".to_owned(),
None => "default-tui".to_owned(),
}
}
const COMMAND_EXAMPLES: &[&str] = &[
":list inboxes",
":list mail",
":open personal",
":open 4",
":reply",
":reply all",
":send",
":send confirm",
":archive",
":delete",
":spam",
":undo",
":calendar today",
":agenda week",
":event new",
":event edit",
":event delete",
":invite accept",
":sync",
":search calendar:work interview",
];