Skip to main content

yog_command/
lib.rs

1//! Command domain — the context passed to a command handler.
2//!
3//! A mod registers a command by name on the registry; when a player runs
4//! `/<name> <args>`, its handler receives a [`CommandContext`] and may return a
5//! reply string sent back to the source.
6
7/// Details of a command invocation.
8#[derive(Debug, Clone)]
9pub struct CommandContext {
10    /// Command name that was run, without the leading slash.
11    pub name: String,
12    /// Raw argument string after the command name (empty if none).
13    pub args: String,
14    /// Name of the player (or console) that ran the command.
15    pub source: String,
16    /// UUID of the executing entity (empty if run from the console).
17    pub uuid: String,
18    /// Dimension the source is in, empty for console-run commands (no world).
19    pub dimension: String,
20}
21
22impl CommandContext {
23    /// Arguments split on whitespace — for plain (untyped) commands.
24    pub fn arg_list(&self) -> Vec<&str> {
25        self.args.split_whitespace().collect()
26    }
27
28    // ── typed-command helpers ─────────────────────────────────────────────────
29    //
30    // Typed commands (registered with `Registry::on_typed_command`) receive
31    // tab-separated argument values.  Use these helpers to extract them.
32
33    /// Arguments split on `\t` — for typed commands registered with a schema.
34    pub fn typed_args(&self) -> Vec<&str> {
35        if self.args.is_empty() {
36            vec![]
37        } else {
38            self.args.split('\t').collect()
39        }
40    }
41
42    /// The raw string of typed argument at `idx`, or `None` if out of range.
43    pub fn arg_str(&self, idx: usize) -> Option<&str> {
44        if self.args.is_empty() {
45            return None;
46        }
47        self.args.split('\t').nth(idx)
48    }
49
50    /// Typed argument `idx` parsed as `i32`, or `None`.
51    pub fn arg_int(&self, idx: usize) -> Option<i32> {
52        self.arg_str(idx)?.parse().ok()
53    }
54
55    /// Typed argument `idx` parsed as `f32`, or `None`.
56    pub fn arg_float(&self, idx: usize) -> Option<f32> {
57        self.arg_str(idx)?.parse().ok()
58    }
59
60    /// Typed argument `idx` as a player name, or `None`.
61    pub fn arg_player(&self, idx: usize) -> Option<&str> {
62        self.arg_str(idx)
63    }
64
65    /// Typed argument `idx` as a block position (`"x,y,z"`), or `None`.
66    pub fn arg_blockpos(&self, idx: usize) -> Option<(i32, i32, i32)> {
67        let s = self.arg_str(idx)?;
68        let mut it = s.split(',');
69        let x = it.next()?.parse().ok()?;
70        let y = it.next()?.parse().ok()?;
71        let z = it.next()?.parse().ok()?;
72        Some((x, y, z))
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    fn ctx(args: &str) -> CommandContext {
81        CommandContext { name: "hex".into(), args: args.into(), source: "Dev".into(), uuid: String::new(), dimension: String::new() }
82    }
83
84    #[test]
85    fn plain_and_typed_arguments() {
86        assert_eq!(ctx("  a  b ").arg_list(), ["a", "b"]);
87        let t = ctx("5\t2.5\tSteve\t1,-2,3");
88        assert_eq!(t.typed_args().len(), 4);
89        assert_eq!(t.arg_int(0), Some(5));
90        assert_eq!(t.arg_float(1), Some(2.5));
91        assert_eq!(t.arg_player(2), Some("Steve"));
92        assert_eq!(t.arg_blockpos(3), Some((1, -2, 3)));
93        assert_eq!(t.arg_int(2), None, "not a number");
94        assert_eq!(t.arg_str(9), None);
95        assert!(ctx("").typed_args().is_empty());
96        assert_eq!(ctx("").arg_str(0), None);
97        assert_eq!(ctx("1,2").arg_blockpos(0), None);
98    }
99}