feat: add dms, hieracthical arguments, permission and rank system
This commit is contained in:
parent
544bbf73cb
commit
257bc55b75
25 changed files with 503 additions and 209 deletions
|
|
@ -29,6 +29,7 @@ pub struct ArgumentSpec {
|
|||
pub arg_type: ArgumentType,
|
||||
pub required: bool,
|
||||
pub default: Option<&'static str>,
|
||||
pub children: &'static [ArgumentSpec],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -60,16 +61,16 @@ pub fn parse_argument(arg: &str, spec: &ArgumentSpec) -> Result<ParsedArgument,
|
|||
ArgumentType::Integer => arg
|
||||
.parse::<i64>()
|
||||
.map(ParsedArgument::Integer)
|
||||
.map_err(|_| format!("Expected integer for '{}', got '{}'", spec.name, arg)),
|
||||
.map_err(|_| format!("Argument '{}' must be an integer, got '{}'", spec.name, arg)),
|
||||
ArgumentType::Float => arg
|
||||
.parse::<f64>()
|
||||
.map(ParsedArgument::Float)
|
||||
.map_err(|_| format!("Expected float for '{}', got '{}'", spec.name, arg)),
|
||||
.map_err(|_| format!("Argument '{}' must be a float, got '{}'", spec.name, arg)),
|
||||
ArgumentType::Boolean => match arg.to_lowercase().as_str() {
|
||||
"true" | "yes" | "1" => Ok(ParsedArgument::Boolean(true)),
|
||||
"false" | "no" | "0" => Ok(ParsedArgument::Boolean(false)),
|
||||
_ => Err(format!(
|
||||
"Expected boolean for '{}', got '{}'",
|
||||
"Argument '{}' must be a boolean, got '{}'",
|
||||
spec.name, arg
|
||||
)),
|
||||
},
|
||||
|
|
@ -99,90 +100,102 @@ impl ParsedArguments {
|
|||
}
|
||||
}
|
||||
|
||||
fn token_matches_spec(token: &str, spec: &ArgumentSpec) -> bool {
|
||||
match &spec.arg_type {
|
||||
ArgumentType::String => true,
|
||||
ArgumentType::Integer => token.parse::<i64>().is_ok(),
|
||||
ArgumentType::Float => token.parse::<f64>().is_ok(),
|
||||
ArgumentType::Boolean => {
|
||||
matches!(
|
||||
token.to_lowercase().as_str(),
|
||||
"true" | "yes" | "1" | "false" | "no" | "0"
|
||||
)
|
||||
}
|
||||
ArgumentType::Enum(variants) => variants.contains(&token),
|
||||
ArgumentType::GreedyString => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_spec_chain(
|
||||
specs: &[ArgumentSpec],
|
||||
raw_args: &[String],
|
||||
parsed: &mut HashMap<&'static str, ParsedArgument>,
|
||||
depth: usize,
|
||||
) -> Result<usize, String> {
|
||||
if depth > 10 {
|
||||
return Err("Too many nested arguments (depth > 10)".into());
|
||||
}
|
||||
|
||||
let mut consumed: usize = 0;
|
||||
|
||||
for spec in specs {
|
||||
if let ArgumentType::GreedyString = spec.arg_type {
|
||||
if consumed >= raw_args.len() {
|
||||
if let Some(def) = spec.default {
|
||||
let parsed_arg = parse_argument(def, spec)?;
|
||||
parsed.insert(spec.name, parsed_arg);
|
||||
} else if spec.required {
|
||||
return Err(format!("Missing required argument '{}'", spec.name));
|
||||
}
|
||||
} else {
|
||||
let rest = raw_args[consumed..].join(" ");
|
||||
let parsed_arg = parse_argument(rest.as_str(), spec)?;
|
||||
parsed.insert(spec.name, parsed_arg);
|
||||
consumed = raw_args.len();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if consumed < raw_args.len() {
|
||||
let next = &raw_args[consumed];
|
||||
if token_matches_spec(next, spec) {
|
||||
let parsed_arg = parse_argument(next.as_str(), spec)?;
|
||||
parsed.insert(spec.name, parsed_arg);
|
||||
consumed += 1;
|
||||
|
||||
if !spec.children.is_empty() {
|
||||
let used =
|
||||
parse_spec_chain(spec.children, &raw_args[consumed..], parsed, depth + 1)?;
|
||||
consumed += used;
|
||||
}
|
||||
} else if let Some(def) = spec.default {
|
||||
let parsed_arg = parse_argument(def, spec)?;
|
||||
parsed.insert(spec.name, parsed_arg);
|
||||
|
||||
if !spec.children.is_empty() {
|
||||
let used =
|
||||
parse_spec_chain(spec.children, &raw_args[consumed..], parsed, depth + 1)?;
|
||||
consumed += used;
|
||||
}
|
||||
} else if spec.required {
|
||||
return Err(format!("Missing required argument '{}'", spec.name));
|
||||
}
|
||||
} else if let Some(def) = spec.default {
|
||||
let parsed_arg = parse_argument(def, spec)?;
|
||||
parsed.insert(spec.name, parsed_arg);
|
||||
|
||||
if !spec.children.is_empty() {
|
||||
let used =
|
||||
parse_spec_chain(spec.children, &raw_args[consumed..], parsed, depth + 1)?;
|
||||
consumed += used;
|
||||
}
|
||||
} else if spec.required {
|
||||
return Err(format!("Missing required argument '{}'", spec.name));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(consumed)
|
||||
}
|
||||
|
||||
pub fn parse_arguments(
|
||||
specs: &[ArgumentSpec],
|
||||
raw_args: &[String],
|
||||
) -> Result<ParsedArguments, String> {
|
||||
let mut parsed = HashMap::new();
|
||||
let raw_vec = raw_args.to_vec();
|
||||
|
||||
let mut i = 0;
|
||||
while i < specs.len() {
|
||||
let spec = &specs[i];
|
||||
|
||||
let raw_value = match &spec.arg_type {
|
||||
ArgumentType::GreedyString => {
|
||||
if i >= raw_args.len() {
|
||||
spec.default.map(|s| s.to_string())
|
||||
} else {
|
||||
Some(raw_args[i..].join(" "))
|
||||
}
|
||||
}
|
||||
_ => raw_args
|
||||
.get(i)
|
||||
.cloned()
|
||||
.or_else(|| spec.default.map(|s| s.to_string())),
|
||||
};
|
||||
|
||||
match raw_value {
|
||||
Some(ref value) => {
|
||||
let parsed_arg = match &spec.arg_type {
|
||||
ArgumentType::String => ParsedArgument::String(value.clone()),
|
||||
ArgumentType::Integer => value
|
||||
.parse::<i64>()
|
||||
.map(ParsedArgument::Integer)
|
||||
.map_err(|_| {
|
||||
format!(
|
||||
"Argument '{}' must be an integer, got '{}'",
|
||||
spec.name, value
|
||||
)
|
||||
})?,
|
||||
ArgumentType::Float => value
|
||||
.parse::<f64>()
|
||||
.map(ParsedArgument::Float)
|
||||
.map_err(|_| {
|
||||
format!("Argument '{}' must be a float, got '{}'", spec.name, value)
|
||||
})?,
|
||||
ArgumentType::Boolean => match value.to_lowercase().as_str() {
|
||||
"true" | "yes" | "1" => ParsedArgument::Boolean(true),
|
||||
"false" | "no" | "0" => ParsedArgument::Boolean(false),
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Argument '{}' must be a boolean, got '{}'",
|
||||
spec.name, value
|
||||
));
|
||||
}
|
||||
},
|
||||
ArgumentType::Enum(variants) => {
|
||||
if variants.contains(&value.as_str()) {
|
||||
ParsedArgument::Enum(value.clone())
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Argument '{}' must be one of {:?}, got '{}'",
|
||||
spec.name, variants, value
|
||||
));
|
||||
}
|
||||
}
|
||||
ArgumentType::GreedyString => ParsedArgument::GreedyString(value.clone()),
|
||||
};
|
||||
|
||||
parsed.insert(spec.name, parsed_arg);
|
||||
|
||||
if let ArgumentType::GreedyString = spec.arg_type {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None if spec.required => {
|
||||
return Err(format!("Missing required argument '{}'", spec.name));
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
let _ = parse_spec_chain(specs, raw_args, &mut parsed, 0)?;
|
||||
|
||||
Ok(ParsedArguments {
|
||||
raw: raw_vec,
|
||||
raw: raw_args.to_vec(),
|
||||
parsed,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue