use unilang_parser::cli_parser::{parse_cli_args, parse_cli_str_args, CliParams, CliParseResult};
#[derive(Default, Debug, Clone, PartialEq)]
struct TestParams
{
timeout: u64,
verbose: bool,
dry_run: bool,
tags: Vec<String>,
error_on_unknown: bool,
}
impl CliParams for TestParams
{
fn process_param(&mut self, key: &str, value: &str) -> Result<bool, String>
{
match key
{
"timeout" =>
{
self.timeout = value.parse()
.map_err(|e| format!("Invalid timeout: {e}"))?;
}
"verbose" =>
{
self.verbose = value == "true" || value == "1";
}
"dry" =>
{
self.dry_run = value != "0";
}
"tag" =>
{
self.tags.push(value.to_string());
}
"error_on_unknown" =>
{
self.error_on_unknown = value == "true" || value == "1";
}
_ =>
{
if self.error_on_unknown
{
return Err(format!("Unknown parameter: {key}"));
}
return Ok(false);
}
}
Ok(true)
}
fn validate(&self) -> Result<(), String>
{
Ok(())
}
}
#[derive(Default, Debug)]
struct ValidatingParams
{
timeout: u64,
}
impl CliParams for ValidatingParams
{
fn process_param(&mut self, key: &str, value: &str) -> Result<bool, String>
{
match key
{
"timeout" =>
{
self.timeout = value.parse()
.map_err(|e| format!("Invalid timeout: {e}"))?;
}
_ => return Ok(false),
}
Ok(true)
}
fn validate(&self) -> Result<(), String>
{
if self.timeout == 0
{
return Err("timeout must be > 0".into());
}
Ok(())
}
}
#[derive(Default, Debug)]
struct StrictParams
{
timeout: u64,
verbose: bool,
}
impl CliParams for StrictParams
{
fn process_param(&mut self, key: &str, value: &str) -> Result<bool, String>
{
match key
{
"timeout" =>
{
self.timeout = value.parse()
.map_err(|e| format!("Invalid timeout: {e}"))?;
}
"verbose" =>
{
self.verbose = value == "true" || value == "1";
}
_ => return Err(format!("Unknown parameter: {key}")),
}
Ok(true)
}
}
#[test]
fn basic_parsing()
{
let args = vec![
"timeout::5000".to_string(),
"hello".to_string(),
"world".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 5000);
assert_eq!(result.message, "hello world");
}
#[test]
fn no_message()
{
let args = vec!["timeout::5000".to_string()];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 5000);
assert_eq!(result.message, "");
}
#[test]
fn no_params()
{
let args = vec![
"hello".to_string(),
"world".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 0); assert!(!result.params.verbose); assert_eq!(result.message, "hello world");
}
#[test]
fn empty_input()
{
let args: Vec<String> = vec![];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 0);
assert_eq!(result.message, "");
}
#[test]
fn multiple_different_params()
{
let args = vec![
"timeout::1000".to_string(),
"verbose::true".to_string(),
"dry::1".to_string(),
"run".to_string(),
"tests".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 1000);
assert!(result.params.verbose);
assert!(result.params.dry_run);
assert_eq!(result.message, "run tests");
}
#[test]
fn multiple_same_param()
{
let args = vec![
"tag::a".to_string(),
"tag::b".to_string(),
"tag::c".to_string(),
"message".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.tags, vec!["a", "b", "c"]);
assert_eq!(result.message, "message");
}
#[test]
fn params_in_any_order()
{
let args = vec![
"dry::1".to_string(),
"timeout::2000".to_string(),
"verbose::true".to_string(),
"msg".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 2000);
assert!(result.params.verbose);
assert!(result.params.dry_run);
assert_eq!(result.message, "msg");
}
#[test]
fn boolean_true_variations()
{
let args1 = vec!["verbose::true".to_string(), "msg".to_string()];
let result1: CliParseResult<TestParams> = parse_cli_args(&args1).unwrap();
assert!(result1.params.verbose);
let args2 = vec!["verbose::1".to_string(), "msg".to_string()];
let result2: CliParseResult<TestParams> = parse_cli_args(&args2).unwrap();
assert!(result2.params.verbose);
}
#[test]
fn boolean_false_variations()
{
let args1 = vec!["verbose::false".to_string(), "msg".to_string()];
let result1: CliParseResult<TestParams> = parse_cli_args(&args1).unwrap();
assert!(!result1.params.verbose);
let args2 = vec!["verbose::0".to_string(), "msg".to_string()];
let result2: CliParseResult<TestParams> = parse_cli_args(&args2).unwrap();
assert!(!result2.params.verbose);
}
#[test]
fn dry_run_modes()
{
let args1 = vec!["dry::0".to_string(), "msg".to_string()];
let result1: CliParseResult<TestParams> = parse_cli_args(&args1).unwrap();
assert!(!result1.params.dry_run);
let args2 = vec!["dry::1".to_string(), "msg".to_string()];
let result2: CliParseResult<TestParams> = parse_cli_args(&args2).unwrap();
assert!(result2.params.dry_run);
}
#[test]
fn unknown_param_as_message()
{
let args = vec![
"timeout::100".to_string(),
"unknown::value".to_string(),
"text".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 100);
assert!(result.message.contains("unknown::value"));
assert!(result.message.contains("text"));
}
#[test]
fn unknown_param_error()
{
let args = vec![
"unknown::value".to_string(),
"message".to_string(),
];
let result: Result<CliParseResult<StrictParams>, String> = parse_cli_args(&args);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("Unknown parameter: unknown"));
}
#[test]
fn validation_error()
{
let args = vec!["timeout::0".to_string()];
let result: Result<CliParseResult<ValidatingParams>, String> = parse_cli_args(&args);
assert!(result.is_err());
assert!(result.unwrap_err().contains("timeout must be > 0"));
}
#[test]
fn validation_passes()
{
let args = vec!["timeout::100".to_string()];
let result: CliParseResult<ValidatingParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 100);
}
#[test]
fn validation_on_empty_input()
{
let args: Vec<String> = vec![];
let result: Result<CliParseResult<ValidatingParams>, String> = parse_cli_args(&args);
assert!(result.is_err());
assert!(result.unwrap_err().contains("timeout must be > 0"));
}
#[test]
fn invalid_timeout_value()
{
let args = vec![
"timeout::not_a_number".to_string(),
"message".to_string(),
];
let result: Result<CliParseResult<TestParams>, String> = parse_cli_args(&args);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid timeout"));
}
#[test]
fn invalid_numeric_format()
{
let args = vec![
"timeout::12.5".to_string(), "message".to_string(),
];
let result: Result<CliParseResult<TestParams>, String> = parse_cli_args(&args);
assert!(result.is_err());
}
#[test]
fn message_with_colons()
{
let args = vec![
"timeout::100".to_string(),
"check".to_string(),
"std::vector".to_string(),
"in".to_string(),
"C++".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 100);
assert_eq!(result.message, "check std::vector in C++");
}
#[test]
fn message_with_special_chars()
{
let args = vec![
"dry::1".to_string(),
"what".to_string(),
"is".to_string(),
"$PATH?".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert!(result.params.dry_run);
assert_eq!(result.message, "what is $PATH?");
}
#[test]
fn param_syntax_in_message()
{
let args = vec![
"msg".to_string(),
"timeout::100".to_string(),
"after".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 0); assert_eq!(result.message, "msg timeout::100 after");
}
#[test]
fn parse_str_args_basic()
{
let args = &["timeout::5000", "hello", "world"];
let result: CliParseResult<TestParams> = parse_cli_str_args(args).unwrap();
assert_eq!(result.params.timeout, 5000);
assert_eq!(result.message, "hello world");
}
#[test]
fn parse_str_args_empty()
{
let args: &[&str] = &[];
let result: CliParseResult<TestParams> = parse_cli_str_args(args).unwrap();
assert_eq!(result.params.timeout, 0);
assert_eq!(result.message, "");
}
#[test]
fn parse_str_args_with_multiple_params()
{
let args = &["timeout::1000", "verbose::true", "dry::1", "run", "tests"];
let result: CliParseResult<TestParams> = parse_cli_str_args(args).unwrap();
assert_eq!(result.params.timeout, 1000);
assert!(result.params.verbose);
assert!(result.params.dry_run);
assert_eq!(result.message, "run tests");
}
#[test]
fn single_word_message()
{
let args = vec!["hello".to_string()];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.message, "hello");
}
#[test]
fn only_params_no_message()
{
let args = vec![
"timeout::1000".to_string(),
"verbose::true".to_string(),
"dry::1".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 1000);
assert!(result.params.verbose);
assert!(result.params.dry_run);
assert_eq!(result.message, "");
}
#[test]
fn long_message()
{
let args = vec![
"timeout::100".to_string(),
"this".to_string(),
"is".to_string(),
"a".to_string(),
"very".to_string(),
"long".to_string(),
"message".to_string(),
"with".to_string(),
"many".to_string(),
"words".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 100);
assert_eq!(result.message, "this is a very long message with many words");
}
#[test]
fn numeric_string_in_message()
{
let args = vec![
"timeout::100".to_string(),
"error".to_string(),
"code".to_string(),
"42".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.message, "error code 42");
}
#[test]
fn wplan_agent_pattern()
{
let args = vec![
"timeout::7200000".to_string(),
"verbose::true".to_string(),
"dry::0".to_string(),
"fix".to_string(),
"the".to_string(),
"bug".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 7_200_000);
assert!(result.params.verbose);
assert!(!result.params.dry_run);
assert_eq!(result.message, "fix the bug");
}
#[test]
fn all_defaults()
{
let args = vec![
"analyze".to_string(),
"this".to_string(),
"code".to_string(),
];
let result: CliParseResult<TestParams> = parse_cli_args(&args).unwrap();
assert_eq!(result.params.timeout, 0);
assert!(!result.params.verbose);
assert!(!result.params.dry_run);
assert!(result.params.tags.is_empty());
assert_eq!(result.message, "analyze this code");
}
use unilang_parser::cli_parser::{CliParser, CliParamsAdvanced, CliParseResultAdvanced};
use std::collections::{BTreeSet, HashMap};
type TestConfig = HashMap<String, u64>;
#[derive(Default, Debug)]
struct ConfigParams
{
timeout: u64,
verbosity: u8,
dry_run: bool,
interactive: bool,
}
impl CliParamsAdvanced<TestConfig> for ConfigParams
{
fn process_param(&mut self, key: &str, value: &str) -> Result<Option<&'static str>, String>
{
match key
{
"timeout" =>
{
self.timeout = value.parse()
.map_err(|e| format!("Invalid timeout: {e}"))?;
Ok(Some("timeout"))
}
"v" | "verbosity" =>
{
self.verbosity = value.parse()
.map_err(|e| format!("Invalid verbosity: {e}"))?;
Ok(Some("verbosity"))
}
"dry" =>
{
self.dry_run = value != "0";
Ok(Some("dry_run"))
}
"interactive" =>
{
self.interactive = value == "1";
Ok(Some("interactive"))
}
_ => Ok(None),
}
}
fn apply_defaults(&mut self, config: &TestConfig, explicit: &BTreeSet<String>)
{
if !explicit.contains("timeout")
{
self.timeout = *config.get("timeout").unwrap_or(&30000);
}
if !explicit.contains("verbosity")
{
self.verbosity = (*config.get("verbosity").unwrap_or(&2)).min(255) as u8;
}
}
fn finalize(&mut self, explicit: &BTreeSet<String>, message: &str)
{
if message.is_empty() && !explicit.contains("interactive")
{
self.interactive = true;
}
}
fn validate(&self) -> Result<(), String>
{
if self.verbosity > 5
{
return Err("verbosity must be 0-5".to_string());
}
Ok(())
}
}
#[test]
fn advanced_basic_parsing()
{
let config: TestConfig = HashMap::new();
let args = vec![
"timeout::5000".to_string(),
"hello".to_string(),
"world".to_string(),
];
let result: CliParseResultAdvanced<ConfigParams> = CliParser::new()
.with_config(&config)
.parse(&args)
.unwrap();
assert_eq!(result.params.timeout, 5000);
assert_eq!(result.message, "hello world");
assert!(result.explicit_params.contains("timeout"));
assert!(!result.explicit_params.contains("verbosity"));
}
#[test]
fn advanced_config_defaults_applied()
{
let mut config: TestConfig = HashMap::new();
config.insert("timeout".to_string(), 60000);
config.insert("verbosity".to_string(), 3);
let args = vec![
"run".to_string(),
"tests".to_string(),
];
let result: CliParseResultAdvanced<ConfigParams> = CliParser::new()
.with_config(&config)
.parse(&args)
.unwrap();
assert_eq!(result.params.timeout, 60000);
assert_eq!(result.params.verbosity, 3);
assert_eq!(result.message, "run tests");
assert!(result.explicit_params.is_empty());
}
#[test]
fn advanced_explicit_overrides_config()
{
let mut config: TestConfig = HashMap::new();
config.insert("timeout".to_string(), 60000);
config.insert("verbosity".to_string(), 3);
let args = vec![
"timeout::1000".to_string(),
"do".to_string(),
"something".to_string(),
];
let result: CliParseResultAdvanced<ConfigParams> = CliParser::new()
.with_config(&config)
.parse(&args)
.unwrap();
assert_eq!(result.params.timeout, 1000);
assert_eq!(result.params.verbosity, 3);
assert!(result.explicit_params.contains("timeout"));
assert!(!result.explicit_params.contains("verbosity"));
}
#[test]
fn advanced_alias_tracking()
{
let config: TestConfig = HashMap::new();
let args = vec![
"v::4".to_string(), "message".to_string(),
];
let result: CliParseResultAdvanced<ConfigParams> = CliParser::new()
.with_config(&config)
.parse(&args)
.unwrap();
assert_eq!(result.params.verbosity, 4);
assert!(result.explicit_params.contains("verbosity"));
assert!(!result.explicit_params.contains("v"));
}
#[test]
fn advanced_finalize_smart_defaults()
{
let config: TestConfig = HashMap::new();
let args: Vec<String> = vec![];
let result: CliParseResultAdvanced<ConfigParams> = CliParser::new()
.with_config(&config)
.parse(&args)
.unwrap();
assert!(result.params.interactive);
assert!(result.message.is_empty());
}
#[test]
fn advanced_finalize_explicit_interactive()
{
let config: TestConfig = HashMap::new();
let args = vec![
"interactive::0".to_string(),
];
let result: CliParseResultAdvanced<ConfigParams> = CliParser::new()
.with_config(&config)
.parse(&args)
.unwrap();
assert!(!result.params.interactive);
assert!(result.explicit_params.contains("interactive"));
}
#[test]
fn advanced_validation_error()
{
let config: TestConfig = HashMap::new();
let args = vec![
"v::10".to_string(), "message".to_string(),
];
let result = CliParser::new()
.with_config(&config)
.parse::<ConfigParams>(&args);
assert!(result.is_err());
assert!(result.unwrap_err().contains("verbosity must be 0-5"));
}
#[test]
fn advanced_multiple_params()
{
let config: TestConfig = HashMap::new();
let args = vec![
"timeout::5000".to_string(),
"v::3".to_string(),
"dry::1".to_string(),
"run".to_string(),
"tests".to_string(),
];
let result: CliParseResultAdvanced<ConfigParams> = CliParser::new()
.with_config(&config)
.parse(&args)
.unwrap();
assert_eq!(result.params.timeout, 5000);
assert_eq!(result.params.verbosity, 3);
assert!(result.params.dry_run);
assert_eq!(result.message, "run tests");
assert!(result.explicit_params.contains("timeout"));
assert!(result.explicit_params.contains("verbosity"));
assert!(result.explicit_params.contains("dry_run"));
}
#[test]
fn advanced_no_config_error()
{
let args = vec!["message".to_string()];
let result = CliParser::<TestConfig>::new()
.parse::<ConfigParams>(&args);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Configuration required"));
}
#[test]
fn advanced_parse_str_convenience()
{
let config: TestConfig = HashMap::new();
let args = &["timeout::100", "hello"];
let result: CliParseResultAdvanced<ConfigParams> = CliParser::new()
.with_config(&config)
.parse_str(args)
.unwrap();
assert_eq!(result.params.timeout, 100);
assert_eq!(result.message, "hello");
}
#[test]
fn advanced_unknown_param_starts_message()
{
let config: TestConfig = HashMap::new();
let args = vec![
"timeout::100".to_string(),
"unknown::value".to_string(),
"rest".to_string(),
];
let result: CliParseResultAdvanced<ConfigParams> = CliParser::new()
.with_config(&config)
.parse(&args)
.unwrap();
assert_eq!(result.params.timeout, 100);
assert_eq!(result.message, "unknown::value rest");
}
#[test]
fn advanced_debug_impl()
{
let config: TestConfig = HashMap::new();
let parser = CliParser::new().with_config(&config);
let debug_str = format!("{parser:?}");
assert!(debug_str.contains("CliParser"));
assert!(debug_str.contains("config"));
}