pub mod bootstrap;
pub mod commands;
pub mod complete;
pub mod error;
pub mod help;
pub mod router;
pub mod schema;
pub mod token;
pub mod types;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OutputFormat {
#[default]
Human,
Json,
Yaml,
}
impl OutputFormat {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"human" | "h" | "text" => Some(OutputFormat::Human),
"json" | "j" => Some(OutputFormat::Json),
"yaml" | "yml" | "y" => Some(OutputFormat::Yaml),
_ => None,
}
}
pub fn as_str(&self) -> &str {
match self {
OutputFormat::Human => "human",
OutputFormat::Json => "json",
OutputFormat::Yaml => "yaml",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CliContext {
pub raw: Vec<String>,
pub domain: Option<String>,
pub resource: Option<String>,
pub verb: Option<String>,
pub target: Option<String>,
pub args: Vec<String>,
pub flags: HashMap<String, String>,
}
impl CliContext {
pub fn new() -> Self {
Self::default()
}
pub fn get_flag(&self, key: &str) -> Option<String> {
self.flags.get(key).cloned()
}
pub fn has_flag(&self, key: &str) -> bool {
self.flags.contains_key(key)
}
pub fn get_flag_or(&self, key: &str, default: &str) -> String {
self.get_flag(key).unwrap_or_else(|| default.to_string())
}
pub fn domain_only(&self) -> Option<&str> {
self.domain.as_deref()
}
pub fn wants_json(&self) -> bool {
self.has_flag("json") || self.has_flag("j")
}
pub fn wants_machine_output(&self) -> bool {
self.get_output_format() != OutputFormat::Human
}
pub fn get_output_format(&self) -> OutputFormat {
if self.wants_json() {
return OutputFormat::Json;
}
let format_str = self
.get_flag("output")
.or_else(|| self.get_flag("o"))
.or_else(|| self.get_flag("format"));
if let Some(format_str) = format_str {
OutputFormat::from_str(&format_str).unwrap_or_default()
} else {
OutputFormat::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_context_default() {
let ctx = CliContext::default();
assert!(ctx.raw.is_empty());
assert!(ctx.domain.is_none());
assert!(ctx.resource.is_none());
assert!(ctx.verb.is_none());
assert!(ctx.target.is_none());
assert!(ctx.args.is_empty());
assert!(ctx.flags.is_empty());
}
#[test]
fn test_cli_context_new() {
let ctx = CliContext::new();
assert!(ctx.raw.is_empty());
assert!(ctx.domain.is_none());
}
#[test]
fn test_get_flag_from_cli() {
let mut ctx = CliContext::new();
ctx.flags.insert("path".to_string(), "/data".to_string());
ctx.flags
.insert("bind".to_string(), "0.0.0.0:6380".to_string());
assert_eq!(ctx.get_flag("path"), Some("/data".to_string()));
assert_eq!(ctx.get_flag("bind"), Some("0.0.0.0:6380".to_string()));
assert_eq!(ctx.get_flag("nonexistent"), None);
}
#[test]
fn test_has_flag() {
let mut ctx = CliContext::new();
ctx.flags.insert("verbose".to_string(), "true".to_string());
ctx.flags.insert("quiet".to_string(), "".to_string());
assert!(ctx.has_flag("verbose"));
assert!(ctx.has_flag("quiet"));
assert!(!ctx.has_flag("nonexistent"));
}
#[test]
fn test_get_flag_or() {
let mut ctx = CliContext::new();
ctx.flags.insert("path".to_string(), "/data".to_string());
assert_eq!(ctx.get_flag_or("path", "/default"), "/data");
assert_eq!(ctx.get_flag_or("bind", "0.0.0.0:6380"), "0.0.0.0:6380");
}
#[test]
fn test_domain_only() {
let mut ctx = CliContext::new();
assert_eq!(ctx.domain_only(), None);
ctx.domain = Some("server".to_string());
assert_eq!(ctx.domain_only(), Some("server"));
}
#[test]
fn test_get_output_format_default() {
let ctx = CliContext::new();
let format = ctx.get_output_format();
assert_eq!(format, OutputFormat::default());
}
#[test]
fn test_get_output_format_from_output_flag() {
let mut ctx = CliContext::new();
ctx.flags.insert("output".to_string(), "json".to_string());
let format = ctx.get_output_format();
assert_eq!(format, OutputFormat::Json);
}
#[test]
fn test_get_output_format_from_o_flag() {
let mut ctx = CliContext::new();
ctx.flags.insert("o".to_string(), "json".to_string());
let format = ctx.get_output_format();
assert_eq!(format, OutputFormat::Json);
}
#[test]
fn test_get_output_format_from_format_flag() {
let mut ctx = CliContext::new();
ctx.flags.insert("format".to_string(), "json".to_string());
let format = ctx.get_output_format();
assert_eq!(format, OutputFormat::Json);
}
#[test]
fn test_get_output_format_from_json_flag() {
let mut ctx = CliContext::new();
ctx.flags.insert("json".to_string(), "true".to_string());
let format = ctx.get_output_format();
assert_eq!(format, OutputFormat::Json);
}
#[test]
fn test_wants_json_from_short_flag() {
let mut ctx = CliContext::new();
ctx.flags.insert("j".to_string(), "true".to_string());
assert!(ctx.wants_json());
assert_eq!(ctx.get_output_format(), OutputFormat::Json);
}
#[test]
fn test_wants_machine_output_from_yaml_flag() {
let mut ctx = CliContext::new();
ctx.flags.insert("output".to_string(), "yaml".to_string());
assert!(ctx.wants_machine_output());
}
#[test]
fn test_get_output_format_priority() {
let mut ctx = CliContext::new();
ctx.flags.insert("output".to_string(), "json".to_string());
ctx.flags.insert("o".to_string(), "text".to_string());
ctx.flags.insert("format".to_string(), "csv".to_string());
let format = ctx.get_output_format();
assert_eq!(format, OutputFormat::Json);
}
#[test]
fn test_cli_context_with_full_command() {
let mut ctx = CliContext::new();
ctx.raw = vec![
"server".to_string(),
"--path".to_string(),
"/data".to_string(),
"--bind".to_string(),
"0.0.0.0:6380".to_string(),
];
ctx.domain = Some("server".to_string());
ctx.flags.insert("path".to_string(), "/data".to_string());
ctx.flags
.insert("bind".to_string(), "0.0.0.0:6380".to_string());
assert_eq!(ctx.domain_only(), Some("server"));
assert_eq!(ctx.get_flag("path"), Some("/data".to_string()));
assert_eq!(ctx.get_flag_or("bind", "localhost:6380"), "0.0.0.0:6380");
assert!(ctx.has_flag("path"));
assert!(!ctx.has_flag("verbose"));
}
#[test]
fn test_cli_context_with_args() {
let mut ctx = CliContext::new();
ctx.args = vec!["arg1".to_string(), "arg2".to_string(), "arg3".to_string()];
assert_eq!(ctx.args.len(), 3);
assert_eq!(ctx.args[0], "arg1");
assert_eq!(ctx.args[1], "arg2");
assert_eq!(ctx.args[2], "arg3");
}
#[test]
fn test_cli_context_clone() {
let mut ctx = CliContext::new();
ctx.domain = Some("server".to_string());
ctx.flags
.insert("bind".to_string(), "0.0.0.0:6380".to_string());
let ctx2 = ctx.clone();
assert_eq!(ctx2.domain, ctx.domain);
assert_eq!(ctx2.get_flag("bind"), ctx.get_flag("bind"));
}
#[test]
fn test_cli_context_debug() {
let ctx = CliContext::new();
let debug_str = format!("{:?}", ctx);
assert!(debug_str.contains("CliContext"));
assert!(debug_str.contains("raw"));
assert!(debug_str.contains("domain"));
}
#[test]
fn test_output_format_from_str() {
assert_eq!(OutputFormat::from_str("json"), Some(OutputFormat::Json));
assert_eq!(OutputFormat::from_str("JSON"), Some(OutputFormat::Json));
assert_eq!(OutputFormat::from_str("yaml"), Some(OutputFormat::Yaml));
assert_eq!(OutputFormat::from_str("yml"), Some(OutputFormat::Yaml));
assert_eq!(OutputFormat::from_str("human"), Some(OutputFormat::Human));
assert_eq!(OutputFormat::from_str("text"), Some(OutputFormat::Human));
assert_eq!(OutputFormat::from_str("xml"), None);
}
#[test]
fn test_output_format_as_str() {
assert_eq!(OutputFormat::Human.as_str(), "human");
assert_eq!(OutputFormat::Json.as_str(), "json");
assert_eq!(OutputFormat::Yaml.as_str(), "yaml");
}
}