1use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
14pub enum Arg {
15 Boolean,
18 Double,
20 Integer,
22 String,
24
25 Entity,
28 GameProfile,
30 BlockPos,
32 ColumnPos,
34 Vec3,
36 Vec2,
38 BlockState,
40 ItemStack,
42 Message,
44 Component,
46 ResourceLocation,
48 Uuid,
50 Rotation,
52
53 Options(Vec<std::string::String>),
57 Player,
60}
61
62#[derive(Debug, Clone)]
67pub enum Validation {
68 Auto,
70 Custom(std::string::String),
72 Disabled,
75}
76
77#[derive(Debug, Clone)]
79pub struct CommandArg {
80 pub name: std::string::String,
82 pub arg_type: Arg,
84 pub validation: Validation,
86 pub required: bool,
88}
89
90#[derive(Debug, Clone, PartialEq)]
92pub enum ArgValue {
93 String(std::string::String),
95 Integer(i64),
97 Double(f64),
99 Boolean(bool),
101 Vec3(f64, f64, f64),
103 BlockPos(i32, i32, i32),
105}
106
107#[derive(Debug)]
112pub struct CommandArgs {
113 values: HashMap<std::string::String, ArgValue>,
114 raw: std::string::String,
115}
116
117impl CommandArgs {
118 pub fn new(raw: std::string::String) -> Self {
120 Self {
121 values: HashMap::new(),
122 raw,
123 }
124 }
125
126 pub fn insert(&mut self, name: std::string::String, value: ArgValue) {
128 self.values.insert(name, value);
129 }
130
131 pub fn get_string(&self, name: &str) -> Option<&str> {
133 match self.values.get(name) {
134 Some(ArgValue::String(s)) => Some(s),
135 _ => None,
136 }
137 }
138
139 pub fn get_integer(&self, name: &str) -> Option<i64> {
141 match self.values.get(name) {
142 Some(ArgValue::Integer(v)) => Some(*v),
143 _ => None,
144 }
145 }
146
147 pub fn get_double(&self, name: &str) -> Option<f64> {
149 match self.values.get(name) {
150 Some(ArgValue::Double(v)) => Some(*v),
151 _ => None,
152 }
153 }
154
155 pub fn get_bool(&self, name: &str) -> Option<bool> {
157 match self.values.get(name) {
158 Some(ArgValue::Boolean(v)) => Some(*v),
159 _ => None,
160 }
161 }
162
163 pub fn get_vec3(&self, name: &str) -> Option<(f64, f64, f64)> {
165 match self.values.get(name) {
166 Some(ArgValue::Vec3(x, y, z)) => Some((*x, *y, *z)),
167 _ => None,
168 }
169 }
170
171 pub fn get_block_pos(&self, name: &str) -> Option<(i32, i32, i32)> {
173 match self.values.get(name) {
174 Some(ArgValue::BlockPos(x, y, z)) => Some((*x, *y, *z)),
175 _ => None,
176 }
177 }
178
179 pub fn raw(&self) -> &str {
181 &self.raw
182 }
183}
184
185impl Arg {
186 pub fn token_count(&self) -> usize {
191 match self {
192 Arg::Vec3 | Arg::BlockPos => 3,
193 Arg::Vec2 | Arg::ColumnPos | Arg::Rotation => 2,
194 Arg::Message => 0, _ => 1,
196 }
197 }
198}
199
200pub fn parse_command_args(
206 raw: &str,
207 schema: &[CommandArg],
208 variants: &[Vec<CommandArg>],
209) -> Result<CommandArgs, std::string::String> {
210 if variants.is_empty() {
211 return parse_args(raw, schema);
212 }
213
214 let mut sorted: Vec<&Vec<CommandArg>> = variants.iter().collect();
218 sorted.sort_by(|a, b| {
219 let count_a: usize = a.iter().map(|arg| arg.arg_type.token_count()).sum();
220 let count_b: usize = b.iter().map(|arg| arg.arg_type.token_count()).sum();
221 count_b.cmp(&count_a)
222 });
223
224 let mut last_err = String::new();
225 for variant in sorted {
226 match parse_args(raw, variant) {
227 Ok(args) => return Ok(args),
228 Err(e) => last_err = e,
229 }
230 }
231 Err(last_err)
232}
233
234pub fn parse_args(raw: &str, schema: &[CommandArg]) -> Result<CommandArgs, std::string::String> {
236 let tokens: Vec<&str> = raw.split_whitespace().collect();
237 let mut args = CommandArgs::new(raw.to_string());
238
239 let required_count = schema.iter().filter(|a| a.required).count();
240 if tokens.len() < required_count {
241 let names: Vec<&str> = schema.iter().map(|a| a.name.as_str()).collect();
242 let usage = names
243 .iter()
244 .map(|n| format!("<{n}>"))
245 .collect::<Vec<_>>()
246 .join(" ");
247 return Err(format!("Usage: {usage}"));
248 }
249
250 let mut tok = 0; for arg_def in schema {
253 if matches!(arg_def.arg_type, Arg::Message) {
255 let remainder: String = tokens[tok..].join(" ");
256 if remainder.is_empty() && arg_def.required {
257 return Err(format!("Missing required argument: {}", arg_def.name));
258 }
259 if !remainder.is_empty() {
260 args.insert(arg_def.name.clone(), ArgValue::String(remainder));
261 }
262 break;
263 }
264
265 let count = arg_def.arg_type.token_count();
266
267 if tok >= tokens.len() {
268 if arg_def.required {
269 return Err(format!("Missing required argument: {}", arg_def.name));
270 }
271 continue;
272 }
273
274 if count > 1 {
276 if tok + count > tokens.len() {
277 if arg_def.required {
278 return Err(format!(
279 "Not enough values for '{}' (expected {count})",
280 arg_def.name
281 ));
282 }
283 continue;
284 }
285 let value = match &arg_def.arg_type {
286 Arg::Vec3 => {
287 let x = tokens[tok]
288 .parse::<f64>()
289 .map_err(|_| format!("Invalid coordinate for '{}'", arg_def.name))?;
290 let y = tokens[tok + 1]
291 .parse::<f64>()
292 .map_err(|_| format!("Invalid coordinate for '{}'", arg_def.name))?;
293 let z = tokens[tok + 2]
294 .parse::<f64>()
295 .map_err(|_| format!("Invalid coordinate for '{}'", arg_def.name))?;
296 ArgValue::Vec3(x, y, z)
297 }
298 Arg::BlockPos => {
299 let x = tokens[tok]
300 .parse::<i32>()
301 .map_err(|_| format!("Invalid block coordinate for '{}'", arg_def.name))?;
302 let y = tokens[tok + 1]
303 .parse::<i32>()
304 .map_err(|_| format!("Invalid block coordinate for '{}'", arg_def.name))?;
305 let z = tokens[tok + 2]
306 .parse::<i32>()
307 .map_err(|_| format!("Invalid block coordinate for '{}'", arg_def.name))?;
308 ArgValue::BlockPos(x, y, z)
309 }
310 _ => {
311 ArgValue::String(tokens[tok..tok + count].join(" "))
313 }
314 };
315 args.insert(arg_def.name.clone(), value);
316 tok += count;
317 continue;
318 }
319
320 let token = tokens[tok];
321 tok += 1;
322
323 if matches!(arg_def.validation, Validation::Disabled) {
324 args.insert(arg_def.name.clone(), ArgValue::String(token.to_string()));
325 continue;
326 }
327
328 match &arg_def.arg_type {
329 Arg::String
330 | Arg::Player
331 | Arg::Entity
332 | Arg::GameProfile
333 | Arg::BlockState
334 | Arg::ItemStack
335 | Arg::Component
336 | Arg::ResourceLocation
337 | Arg::Uuid => {
338 args.insert(arg_def.name.clone(), ArgValue::String(token.to_string()));
339 }
340 Arg::Integer => match token.parse::<i64>() {
341 Ok(v) => {
342 args.insert(arg_def.name.clone(), ArgValue::Integer(v));
343 }
344 Err(_) => {
345 return Err(match &arg_def.validation {
346 Validation::Custom(msg) => msg.clone(),
347 _ => format!("Expected an integer for '{}'", arg_def.name),
348 });
349 }
350 },
351 Arg::Double => match token.parse::<f64>() {
352 Ok(v) => {
353 args.insert(arg_def.name.clone(), ArgValue::Double(v));
354 }
355 Err(_) => {
356 return Err(match &arg_def.validation {
357 Validation::Custom(msg) => msg.clone(),
358 _ => format!("Expected a number for '{}'", arg_def.name),
359 });
360 }
361 },
362 Arg::Options(choices) => {
363 if choices.iter().any(|c| c == token) {
364 args.insert(arg_def.name.clone(), ArgValue::String(token.to_string()));
365 } else {
366 return Err(match &arg_def.validation {
367 Validation::Custom(msg) => msg.clone(),
368 _ => {
369 let opts = choices.join(", ");
370 format!("Invalid '{}'. Options: {opts}", arg_def.name)
371 }
372 });
373 }
374 }
375 Arg::Boolean => match token {
376 "true" => {
377 args.insert(arg_def.name.clone(), ArgValue::Boolean(true));
378 }
379 "false" => {
380 args.insert(arg_def.name.clone(), ArgValue::Boolean(false));
381 }
382 _ => {
383 return Err(match &arg_def.validation {
384 Validation::Custom(msg) => msg.clone(),
385 _ => format!("Expected true/false for '{}'", arg_def.name),
386 });
387 }
388 },
389 Arg::Vec3
391 | Arg::Vec2
392 | Arg::BlockPos
393 | Arg::ColumnPos
394 | Arg::Rotation
395 | Arg::Message => {
396 unreachable!()
397 }
398 }
399 }
400
401 Ok(args)
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 fn arg(name: &str, arg_type: Arg) -> CommandArg {
409 CommandArg {
410 name: name.to_string(),
411 arg_type,
412 validation: Validation::Auto,
413 required: true,
414 }
415 }
416
417 #[test]
418 fn parse_double_args() {
419 let schema = vec![
420 arg("x", Arg::Double),
421 arg("y", Arg::Double),
422 arg("z", Arg::Double),
423 ];
424 let result = parse_args("10.5 64.0 -5.0", &schema).unwrap();
425 assert_eq!(result.get_double("x"), Some(10.5));
426 assert_eq!(result.get_double("y"), Some(64.0));
427 assert_eq!(result.get_double("z"), Some(-5.0));
428 }
429
430 #[test]
431 fn parse_integer_args() {
432 let schema = vec![arg("count", Arg::Integer)];
433 let result = parse_args("42", &schema).unwrap();
434 assert_eq!(result.get_integer("count"), Some(42));
435 }
436
437 #[test]
438 fn parse_string_arg() {
439 let schema = vec![arg("name", Arg::String)];
440 let result = parse_args("Steve", &schema).unwrap();
441 assert_eq!(result.get_string("name"), Some("Steve"));
442 }
443
444 #[test]
445 fn parse_options_valid() {
446 let schema = vec![arg(
447 "mode",
448 Arg::Options(vec!["survival".into(), "creative".into()]),
449 )];
450 let result = parse_args("creative", &schema).unwrap();
451 assert_eq!(result.get_string("mode"), Some("creative"));
452 }
453
454 #[test]
455 fn parse_options_invalid() {
456 let schema = vec![arg(
457 "mode",
458 Arg::Options(vec!["survival".into(), "creative".into()]),
459 )];
460 let err = parse_args("hardcore", &schema).unwrap_err();
461 assert!(err.contains("Invalid 'mode'"));
462 }
463
464 #[test]
465 fn parse_options_custom_error() {
466 let schema = vec![CommandArg {
467 name: "mode".into(),
468 arg_type: Arg::Options(vec!["survival".into(), "creative".into()]),
469 validation: Validation::Custom("Nope, bad mode".into()),
470 required: true,
471 }];
472 let err = parse_args("hardcore", &schema).unwrap_err();
473 assert_eq!(err, "Nope, bad mode");
474 }
475
476 #[test]
477 fn parse_double_invalid() {
478 let schema = vec![arg("x", Arg::Double)];
479 let err = parse_args("abc", &schema).unwrap_err();
480 assert!(err.contains("Expected a number"));
481 }
482
483 #[test]
484 fn parse_too_few_args() {
485 let schema = vec![
486 arg("x", Arg::Double),
487 arg("y", Arg::Double),
488 arg("z", Arg::Double),
489 ];
490 let err = parse_args("10.5", &schema).unwrap_err();
491 assert!(err.contains("Usage:"));
492 }
493
494 #[test]
495 fn parse_validation_disabled() {
496 let schema = vec![CommandArg {
497 name: "value".into(),
498 arg_type: Arg::Double,
499 validation: Validation::Disabled,
500 required: true,
501 }];
502 let result = parse_args("abc", &schema).unwrap();
503 assert_eq!(result.get_string("value"), Some("abc"));
504 }
505
506 #[test]
507 fn parse_optional_arg_missing() {
508 let schema = vec![CommandArg {
509 name: "target".into(),
510 arg_type: Arg::String,
511 validation: Validation::Auto,
512 required: false,
513 }];
514 let result = parse_args("", &schema).unwrap();
515 assert_eq!(result.get_string("target"), None);
516 }
517
518 #[test]
519 fn parse_greedy_string() {
520 let schema = vec![arg("msg", Arg::Message)];
521 let result = parse_args("hello world foo", &schema).unwrap();
522 assert_eq!(result.get_string("msg"), Some("hello world foo"));
523 }
524
525 #[test]
526 fn parse_boolean_valid() {
527 let schema = vec![arg("flag", Arg::Boolean)];
528 let result = parse_args("true", &schema).unwrap();
529 assert_eq!(result.get_bool("flag"), Some(true));
530
531 let result = parse_args("false", &schema).unwrap();
532 assert_eq!(result.get_bool("flag"), Some(false));
533 }
534
535 #[test]
536 fn parse_boolean_invalid() {
537 let schema = vec![arg("flag", Arg::Boolean)];
538 let err = parse_args("maybe", &schema).unwrap_err();
539 assert!(err.contains("Expected true/false"));
540 }
541
542 #[test]
543 fn parse_player_arg() {
544 let schema = vec![arg("target", Arg::Player)];
545 let result = parse_args("Steve", &schema).unwrap();
546 assert_eq!(result.get_string("target"), Some("Steve"));
547 }
548
549 #[test]
550 fn parse_variants_first_match() {
551 let v1 = vec![arg("x", Arg::Double), arg("y", Arg::Double)];
552 let v2 = vec![arg("name", Arg::String)];
553 let result = parse_command_args("10.5 20.0", &[], &[v1, v2]).unwrap();
554 assert_eq!(result.get_double("x"), Some(10.5));
555 }
556
557 #[test]
558 fn parse_variants_second_match() {
559 let v1 = vec![arg("x", Arg::Double), arg("y", Arg::Double)];
560 let v2 = vec![arg("name", Arg::Player)];
561 let result = parse_command_args("Steve", &[], &[v1, v2]).unwrap();
562 assert_eq!(result.get_string("name"), Some("Steve"));
563 }
564
565 #[test]
566 fn raw_preserved() {
567 let schema = vec![arg("msg", Arg::String)];
568 let result = parse_args("hello world", &schema).unwrap();
569 assert_eq!(result.raw(), "hello world");
570 }
571
572 #[test]
573 fn parse_vec3_typed() {
574 let schema = vec![arg("pos", Arg::Vec3)];
575 let result = parse_args("10.5 64.0 -5.0", &schema).unwrap();
576 assert_eq!(result.get_vec3("pos"), Some((10.5, 64.0, -5.0)));
577 assert_eq!(result.get_string("pos"), None); }
579
580 #[test]
581 fn parse_vec3_invalid() {
582 let schema = vec![arg("pos", Arg::Vec3)];
583 let err = parse_args("10.5 abc -5.0", &schema).unwrap_err();
584 assert!(err.contains("Invalid coordinate"));
585 }
586
587 #[test]
588 fn parse_block_pos_typed() {
589 let schema = vec![arg("pos", Arg::BlockPos)];
590 let result = parse_args("10 64 -5", &schema).unwrap();
591 assert_eq!(result.get_block_pos("pos"), Some((10, 64, -5)));
592 }
593
594 #[test]
595 fn token_count_method() {
596 assert_eq!(Arg::Vec3.token_count(), 3);
597 assert_eq!(Arg::BlockPos.token_count(), 3);
598 assert_eq!(Arg::Vec2.token_count(), 2);
599 assert_eq!(Arg::Rotation.token_count(), 2);
600 assert_eq!(Arg::Message.token_count(), 0);
601 assert_eq!(Arg::String.token_count(), 1);
602 assert_eq!(Arg::Boolean.token_count(), 1);
603 }
604}