From 943f8fcd4c2e5b2744bd9d308d279e37e3f434a4 Mon Sep 17 00:00:00 2001 From: user622628252416 Date: Mon, 19 Aug 2024 00:29:31 +0200 Subject: [PATCH 1/8] add tree-style command parsing utilities, heavily inspired by Mojang's Brigadier and adjust existing commands to use them --- pumpkin/src/commands/arg_player.rs | 43 +++++++++ pumpkin/src/commands/cmd_gamemode.rs | 95 +++++++++++++++++++ pumpkin/src/commands/cmd_pumpkin.rs | 21 +++++ pumpkin/src/commands/cmd_stop.rs | 15 +++ pumpkin/src/commands/dispatcher.rs | 83 +++++++++++++++++ pumpkin/src/commands/gamemode.rs | 56 ----------- pumpkin/src/commands/mod.rs | 84 +++++++++-------- pumpkin/src/commands/pumpkin.rs | 20 ---- pumpkin/src/commands/stop.rs | 15 --- pumpkin/src/commands/tree.rs | 87 +++++++++++++++++ pumpkin/src/commands/tree_builder.rs | 134 +++++++++++++++++++++++++++ 11 files changed, 523 insertions(+), 130 deletions(-) create mode 100644 pumpkin/src/commands/arg_player.rs create mode 100644 pumpkin/src/commands/cmd_gamemode.rs create mode 100644 pumpkin/src/commands/cmd_pumpkin.rs create mode 100644 pumpkin/src/commands/cmd_stop.rs create mode 100644 pumpkin/src/commands/dispatcher.rs delete mode 100644 pumpkin/src/commands/gamemode.rs delete mode 100644 pumpkin/src/commands/pumpkin.rs delete mode 100644 pumpkin/src/commands/stop.rs create mode 100644 pumpkin/src/commands/tree.rs create mode 100644 pumpkin/src/commands/tree_builder.rs diff --git a/pumpkin/src/commands/arg_player.rs b/pumpkin/src/commands/arg_player.rs new file mode 100644 index 000000000..d7587ebf2 --- /dev/null +++ b/pumpkin/src/commands/arg_player.rs @@ -0,0 +1,43 @@ +use crate::client::Client; +use crate::commands::CommandSender; +use crate::commands::CommandSender::Player; +use crate::commands::dispatcher::InvalidTreeError; +use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError; +use crate::commands::tree::{ConsumedArgs, RawArgs}; + +/// todo: implement (so far only own name + @s is implemented) +pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option { + let s = args.pop()?; + + if let Player(client) = src { + if s == "@s" { + return Some(s.into()) + } + if let Some(profile) = &client.gameprofile { + if profile.name == s { + return Some(s.into()) + }; + }; + }; + + None +} + +/// todo: implement (so far only own name + @s is implemented) +pub fn parse_arg_player<'a>(src: &'a mut CommandSender, arg_name: &str, consumed_args: &ConsumedArgs) -> Result<&'a mut Client, InvalidTreeError> { + let s = consumed_args.get(arg_name) + .ok_or(InvalidConsumptionError(None))?; + + if let Player(client) = src { + if s == "@s" { + return Ok(client) + } + if let Some(profile) = &client.gameprofile { + if profile.name == s.as_ref() { + return Ok(client) + }; + }; + }; + + Err(InvalidConsumptionError(Some(s.into()))) +} \ No newline at end of file diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs new file mode 100644 index 000000000..1b80de38a --- /dev/null +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -0,0 +1,95 @@ +use std::str::FromStr; + +use num_traits::FromPrimitive; + +use crate::commands::arg_player::{consume_arg_player, parse_arg_player}; + +use crate::commands::CommandSender; +use crate::commands::CommandSender::Player; +use crate::commands::dispatcher::InvalidTreeError; +use crate::commands::dispatcher::InvalidTreeError::{InvalidConsumptionError, InvalidRequirementError}; +use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs}; +use crate::commands::tree_builder::{argument, require}; +use crate::entity::player::GameMode; + +pub(crate) const NAME: &str = "gamemode"; + +const DESCRIPTION: &str = "Changes the gamemode for a Player"; + +const ARG_GAMEMODE: &str = "gamemode"; +const ARG_TARGET: &str = "target"; + +pub fn consume_arg_gamemode(_src: &CommandSender, args: &mut RawArgs) -> Option { + let s = args.pop()?; + + if let Ok(id) = s.parse::() { + match GameMode::from_u8(id) { + None | Some(GameMode::Undefined) => {}, + Some(_) => return Some(s.into()) + }; + }; + + return match GameMode::from_str(s) { + Err(_) | Ok(GameMode::Undefined) => None, + Ok(_) => Some(s.into()) + } +} + +pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result { + let s = consumed_args.get(ARG_GAMEMODE) + .ok_or(InvalidConsumptionError(None))?; + + if let Ok(id) = s.parse::() { + match GameMode::from_u8(id) { + None | Some(GameMode::Undefined) => {}, + Some(gamemode) => return Ok(gamemode) + }; + }; + + return match GameMode::from_str(s) { + Err(_) | Ok(GameMode::Undefined) => Err(InvalidConsumptionError(Some(s.into()))), + Ok(gamemode) => Ok(gamemode) + }; +} + +pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(DESCRIPTION).with_child( + + require(&|sender| { + sender.permission_lvl() >= 2 + }).with_child( + + argument(ARG_GAMEMODE, consume_arg_gamemode).with_child( + + require(&|sender| sender.is_player()) + + .execute(&|sender, args| { + let gamemode = parse_arg_gamemode(args)?; + + return if let Player(target) = sender { + target.set_gamemode(gamemode); + target.send_system_message(format!("Game mode was set to {:?}", gamemode).into()); + + Ok(()) + } else { + Err(InvalidRequirementError) + } + }) + + ).with_child( + + argument(ARG_TARGET, consume_arg_player) + + .execute(&|sender, args| { + let gamemode = parse_arg_gamemode(args)?; + let target = parse_arg_player(sender, ARG_TARGET, args)?; + + target.set_gamemode(gamemode); + target.send_system_message(format!("Game mode was set to {:?}", gamemode).into()); + + Ok(()) + }) + ) + ) + ) +} diff --git a/pumpkin/src/commands/cmd_pumpkin.rs b/pumpkin/src/commands/cmd_pumpkin.rs new file mode 100644 index 000000000..1bb0b9cab --- /dev/null +++ b/pumpkin/src/commands/cmd_pumpkin.rs @@ -0,0 +1,21 @@ +use pumpkin_text::{color::NamedColor, TextComponent}; +use pumpkin_protocol::CURRENT_MC_PROTOCOL; +use crate::server::CURRENT_MC_VERSION; + +use crate::commands::tree::CommandTree; + +pub(crate) const NAME: &str = "pumpkin"; + +const DESCRIPTION: &str = "Displays information about Pumpkin"; + +pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(DESCRIPTION).execute(&|sender, _| { + let version = env!("CARGO_PKG_VERSION"); + let description = env!("CARGO_PKG_DESCRIPTION"); + sender.send_message(TextComponent::from( + format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")).color_named(NamedColor::Green) + ); + + Ok(()) + }) +} diff --git a/pumpkin/src/commands/cmd_stop.rs b/pumpkin/src/commands/cmd_stop.rs new file mode 100644 index 000000000..57f55fc78 --- /dev/null +++ b/pumpkin/src/commands/cmd_stop.rs @@ -0,0 +1,15 @@ +use crate::commands::tree::CommandTree; +use crate::commands::tree_builder::require; + +pub(crate) const NAME: &str = "stop"; + +const DESCRIPTION: &str = "Stops the server"; + +pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(DESCRIPTION).with_child( + require(&|sender| { sender.permission_lvl() >= 4 }) + .execute(&|_sender, _args| { + std::process::exit(0) + }) + ) +} \ No newline at end of file diff --git a/pumpkin/src/commands/dispatcher.rs b/pumpkin/src/commands/dispatcher.rs new file mode 100644 index 000000000..fe4520c53 --- /dev/null +++ b/pumpkin/src/commands/dispatcher.rs @@ -0,0 +1,83 @@ +use std::collections::HashMap; +use crate::commands::CommandSender; +use crate::commands::dispatcher::InvalidTreeError::{InvalidConsumptionError, InvalidRequirementError}; +use crate::commands::tree::{ConsumedArgs, NodeType, RawArgs, CommandTree}; + +#[derive(Debug)] +pub(crate) enum InvalidTreeError { + /// This error means that there was an error while parsing a previously consumed argument. + /// That only happens when consumption is wrongly implemented, as it should ensure parsing may + /// never fail. + InvalidConsumptionError(Option), + + /// Return this if a condition that a [Node::Require] should ensure is met is not met. + InvalidRequirementError, +} + +pub(crate) struct CommandDispatcher<'a> { + pub(crate) commands: HashMap<&'a str, CommandTree<'a>> +} + +impl <'a> CommandDispatcher<'a> { + pub(crate) fn dispatch(&'a self, src: &mut CommandSender, cmd: &str) -> Result<(), &str> { + + let mut parts = cmd.split_ascii_whitespace(); + let key = parts.next().ok_or("Empty Command")?; + let raw_args: Vec<&str> = parts.rev().collect(); + + let tree = self.commands.get(key).ok_or("Command not found")?; + + for path in tree.iter_paths() { + match Self::try_path(src, path, tree, raw_args.clone()) { + Err(InvalidConsumptionError(s)) => { + println!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed"); + }, + Err(InvalidRequirementError) => { + println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); + }, + Ok(fitting_path) => { + if fitting_path { return Ok(()) } + } + } + } + + Err("Invalid Syntax: ") + } + + fn try_path(src: &mut CommandSender, path: Vec, tree: &CommandTree, mut raw_args: RawArgs) -> Result { + + let mut parsed_args: ConsumedArgs = HashMap::new(); + + for node in path.iter().map(|&i| &tree.nodes[i]) { + match node.node_type { + NodeType::ExecuteLeaf { run } => { + return if raw_args.is_empty() { + run(src, &parsed_args)?; + Ok(true) + } else { + Ok(false) + }; + } + NodeType::Literal { string, .. } => { + if raw_args.pop() != Some(string) { + return Ok(false); + } + } + NodeType::Argument { consumer: consume, name, .. } => { + if let Some(consumed) = consume(src, &mut raw_args) { + parsed_args.insert(name, consumed); + } else { + return Ok(false); + } + } + NodeType::Require { predicate, .. } => { + if !predicate(src) { + return Ok(false); + } + } + } + }; + + Ok(false) + } +} \ No newline at end of file diff --git a/pumpkin/src/commands/gamemode.rs b/pumpkin/src/commands/gamemode.rs deleted file mode 100644 index fa00cbeb5..000000000 --- a/pumpkin/src/commands/gamemode.rs +++ /dev/null @@ -1,56 +0,0 @@ -use num_traits::FromPrimitive; -use pumpkin_text::TextComponent; - -use crate::entity::player::GameMode; - -use super::Command; - -pub struct GamemodeCommand {} - -impl<'a> Command<'a> for GamemodeCommand { - const NAME: &'a str = "gamemode"; - - const DESCRIPTION: &'a str = "Changes the gamemode for a Player"; - - fn on_execute(sender: &mut super::CommandSender<'a>, command: String) { - let player = sender.as_mut_player().unwrap(); - let args: Vec<&str> = command.split_whitespace().collect(); - - if args.len() != 2 { - player.send_system_message( - TextComponent::from("Usage: /gamemode ") - .color_named(pumpkin_text::color::NamedColor::Red), - ); - return; - } - - let mode_str = args[1].to_lowercase(); - match mode_str.parse() { - Ok(mode) => { - player.set_gamemode(mode); - player.send_system_message(format!("Set own game mode to {:?}", mode).into()); - } - Err(_) => { - // try to parse from number - if let Ok(i) = mode_str.parse::() { - if let Some(mode) = GameMode::from_u8(i) { - player.set_gamemode(mode); - player - .send_system_message(format!("Set own game mode to {:?}", mode).into()); - return; - } - } - - player.send_system_message( - TextComponent::from("Invalid gamemode") - .color_named(pumpkin_text::color::NamedColor::Red), - ); - } - } - } - - // TODO: support console, (name required) - fn player_required() -> bool { - true - } -} diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index dc687430f..fc36ea18f 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -1,28 +1,16 @@ -use gamemode::GamemodeCommand; -use pumpkin::PumpkinCommand; +use std::collections::HashMap; +use std::sync::OnceLock; use pumpkin_text::TextComponent; -use stop::StopCommand; use crate::client::Client; - -mod gamemode; -mod pumpkin; -mod stop; - -/// I think it would be great to split this up into a seperate crate, But idk how i should do that, Because we have to rely on Client and Server -pub trait Command<'a> { - // Name of the Plugin, Use lower case - const NAME: &'a str; - const DESCRIPTION: &'a str; - - fn on_execute(sender: &mut CommandSender<'a>, command: String); - - /// Specifies wether the Command Sender has to be a Player - /// TODO: implement - fn player_required() -> bool { - false - } -} +use crate::commands::dispatcher::CommandDispatcher; +mod cmd_gamemode; +mod cmd_pumpkin; +mod cmd_stop; +mod tree; +mod tree_builder; +mod dispatcher; +mod arg_player; pub enum CommandSender<'a> { Rcon(&'a mut Vec), @@ -40,7 +28,7 @@ impl<'a> CommandSender<'a> { } } - pub fn is_player(&mut self) -> bool { + pub fn is_player(&self) -> bool { match self { CommandSender::Console => false, CommandSender::Player(_) => true, @@ -48,7 +36,7 @@ impl<'a> CommandSender<'a> { } } - pub fn is_console(&mut self) -> bool { + pub fn is_console(&self) -> bool { match self { CommandSender::Console => true, CommandSender::Player(_) => false, @@ -62,22 +50,40 @@ impl<'a> CommandSender<'a> { CommandSender::Rcon(_) => None, } } -} -pub fn handle_command(sender: &mut CommandSender, command: &str) { - let command = command.to_lowercase(); - // an ugly mess i know - if command.starts_with(PumpkinCommand::NAME) { - PumpkinCommand::on_execute(sender, command); - return; + + /// todo: implement + pub fn permission_lvl(&self) -> i32 { + match self { + CommandSender::Rcon(_) => 4, + CommandSender::Console => 4, + CommandSender::Player(_) => 4, + } } - if command.starts_with(GamemodeCommand::NAME) { - GamemodeCommand::on_execute(sender, command); - return; +} + +// todo: reconsider using constant +const DISPATCHER: OnceLock = OnceLock::new(); + +fn dispatcher_init<'a>() -> CommandDispatcher<'a> { + let mut map = HashMap::new(); + + map.insert(cmd_pumpkin::NAME, cmd_pumpkin::init_command_tree()); + map.insert(cmd_gamemode::NAME, cmd_gamemode::init_command_tree()); + map.insert(cmd_stop::NAME, cmd_stop::init_command_tree()); + + CommandDispatcher { + commands: map, } - if command.starts_with(StopCommand::NAME) { - StopCommand::on_execute(sender, command); - return; +} + +pub fn handle_command(sender: &mut CommandSender, cmd: &str) { + let dispatcher = DISPATCHER; + let dispatcher = dispatcher.get_or_init(dispatcher_init); + + if let Err(err) = dispatcher.dispatch(sender, cmd) { + sender.send_message( + TextComponent::from(err) + .color_named(pumpkin_text::color::NamedColor::Red), + ) } - // TODO: red color - sender.send_message("Command not Found".into()); } diff --git a/pumpkin/src/commands/pumpkin.rs b/pumpkin/src/commands/pumpkin.rs deleted file mode 100644 index 8a648dd50..000000000 --- a/pumpkin/src/commands/pumpkin.rs +++ /dev/null @@ -1,20 +0,0 @@ -use pumpkin_protocol::CURRENT_MC_PROTOCOL; -use pumpkin_text::{color::NamedColor, TextComponent}; - -use crate::server::CURRENT_MC_VERSION; - -use super::Command; - -pub struct PumpkinCommand {} - -impl<'a> Command<'a> for PumpkinCommand { - const NAME: &'a str = "pumpkin"; - - const DESCRIPTION: &'a str = "Displays information about Pumpkin"; - - fn on_execute(sender: &mut super::CommandSender<'a>, _command: String) { - let version = env!("CARGO_PKG_VERSION"); - let description = env!("CARGO_PKG_DESCRIPTION"); - sender.send_message(TextComponent::from(format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")).color_named(NamedColor::Green)) - } -} diff --git a/pumpkin/src/commands/stop.rs b/pumpkin/src/commands/stop.rs deleted file mode 100644 index 8a2fcfc4f..000000000 --- a/pumpkin/src/commands/stop.rs +++ /dev/null @@ -1,15 +0,0 @@ -use super::Command; - -pub struct StopCommand {} - -impl<'a> Command<'a> for StopCommand { - const NAME: &'static str = "stop"; - const DESCRIPTION: &'static str = "Stops the server"; - - fn on_execute(sender: &mut super::CommandSender<'a>, command: String) { - std::process::exit(0); - } - fn player_required() -> bool { - true - } -} diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs new file mode 100644 index 000000000..70ac6e5bb --- /dev/null +++ b/pumpkin/src/commands/tree.rs @@ -0,0 +1,87 @@ +use std::collections::{HashMap, VecDeque}; + +use crate::commands::CommandSender; +use crate::commands::dispatcher::InvalidTreeError; + +pub(crate) type RawArgs<'a> = Vec<&'a str>; + +pub(crate) type ConsumedArgs<'a> = HashMap<&'a str, String>; + +pub(crate) type ArgumentConsumer<'a> = fn(&CommandSender, &mut RawArgs) -> Option; + +pub(crate) struct Node<'a> { + pub(crate) children: Vec, + pub(crate) node_type: NodeType<'a>, +} + +pub(crate) enum NodeType<'a> { + ExecuteLeaf { + run: &'a dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError>, + }, + Literal { + string: &'a str, + }, + Argument { + name: &'a str, + consumer: ArgumentConsumer<'a>, + }, + Require { + predicate: &'a dyn Fn(&CommandSender) -> bool, + } +} + +pub(crate) struct CommandTree<'a> { + pub(crate) nodes: Vec>, + pub(crate) children: Vec, + pub(crate) description: &'a str, +} + +impl <'a> CommandTree<'a> { + /// iterate over all possible paths that end in a [NodeType::ExecuteLeaf] + pub(crate) fn iter_paths(&'a self) -> impl Iterator> + 'a { + let mut todo = VecDeque::<(usize, usize)>::new(); + + // add root's children + todo.extend(self.children.iter().map(|&i| (0, i))); + + TraverseAllPathsIter::<'a> { + tree: self, + path: Vec::::new(), + todo, + } + } +} + +struct TraverseAllPathsIter<'a> { + tree: &'a CommandTree<'a>, + path: Vec, + /// (depth, i) + todo: VecDeque<(usize, usize)>, +} + +impl <'a>Iterator for TraverseAllPathsIter<'a> { + + type Item = Vec; + + fn next(&mut self) -> Option { + loop { + let (depth, i) = self.todo.pop_front()?; + let node = &self.tree.nodes[i]; + + // add new children to front + self.todo.reserve(node.children.len()); + node.children.iter().rev().for_each(|&c| self.todo.push_front((depth + 1, c))); + + // update path + while self.path.len() > depth { + self.path.pop(); + }; + self.path.push(i); + + if let NodeType::ExecuteLeaf { .. } = node.node_type { + return Some(self.path.clone()); + } + } + } +} + diff --git a/pumpkin/src/commands/tree_builder.rs b/pumpkin/src/commands/tree_builder.rs new file mode 100644 index 000000000..efd298a3a --- /dev/null +++ b/pumpkin/src/commands/tree_builder.rs @@ -0,0 +1,134 @@ +use crate::commands::CommandSender; +use crate::commands::dispatcher::InvalidTreeError; +use crate::commands::tree::{ArgumentConsumer, ConsumedArgs, Node, NodeType, CommandTree}; + +impl <'a> CommandTree<'a> { + pub fn with_child(mut self, child: impl NodeBuilder<'a>) -> Self { + let node = child.build(&mut self); + self.children.push(self.nodes.len()); + self.nodes.push(node); + self + } + + pub fn new(description: &'a str) -> Self { + Self { + nodes: Vec::new(), + children: Vec::new(), + description, + } + } + + /// Executes if a command terminates at this [Node], i.e. without any arguments. + pub fn execute(mut self, run: &'a dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError>) -> Self { + let node = Node { + node_type: NodeType::ExecuteLeaf { + run, + }, + children: Vec::new(), + }; + + self.children.push(self.nodes.len()); + self.nodes.push(node); + + self + } +} + +pub trait NodeBuilder<'a> { + fn build(self, tree: &mut CommandTree<'a>) -> Node<'a>; +} + +struct LeafNodeBuilder<'a> { + node_type: NodeType<'a>, +} + +impl <'a>NodeBuilder<'a> for LeafNodeBuilder<'a> { + fn build(self, _tree: &mut CommandTree<'a>) -> Node<'a> { + Node { + children: Vec::new(), + node_type: self.node_type, + } + } +} + +pub struct NonLeafNodeBuilder<'a> { + node_type: NodeType<'a>, + child_nodes: Vec>, + leaf_nodes: Vec> +} + +impl <'a>NodeBuilder<'a> for NonLeafNodeBuilder<'a> { + fn build(self, tree: &mut CommandTree<'a>) -> Node<'a> { + let mut child_indices = Vec::new(); + + for node_builder in self.child_nodes { + let node = node_builder.build(tree); + child_indices.push(tree.nodes.len()); + tree.nodes.push(node); + } + + for node_builder in self.leaf_nodes { + let node = node_builder.build(tree); + child_indices.push(tree.nodes.len()); + tree.nodes.push(node); + } + + Node { + children: child_indices, + node_type: self.node_type, + } + } +} + +impl <'a>NonLeafNodeBuilder<'a> { + pub fn with_child(mut self, child: NonLeafNodeBuilder<'a>) -> Self { + self.child_nodes.push(child); + self + } + + /// Executes if a command terminates at this [Node]. + pub fn execute(mut self, run: &'a dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError>) -> Self { + self.leaf_nodes.push(LeafNodeBuilder { + node_type: NodeType::ExecuteLeaf { + run + }, + }); + + self + } +} + +/// Matches a sting literal. +pub fn literal(string: &str) -> NonLeafNodeBuilder { + NonLeafNodeBuilder { + node_type: NodeType::Literal { + string + }, + child_nodes: Vec::new(), + leaf_nodes: Vec::new(), + } +} + +/// ```name``` identifies this argument. +/// ```consumer: [ArgumentConsumer]``` has the purpose of validating arguments. Conversion may start here, as long as the result remains a [String] (e.g. convert offset to absolute position actual coordinates). It must remove consumed arg(s) from [RawArgs] and return them. It must return None if [RawArgs] are invalid. [RawArgs] is reversed, so [Vec::pop] can be used to obtain args in ltr order. +pub fn argument<'a>(name: &'a str, consumer: ArgumentConsumer) -> NonLeafNodeBuilder<'a> { + NonLeafNodeBuilder { + node_type: NodeType::Argument { + name, + consumer, + }, + child_nodes: Vec::new(), + leaf_nodes: Vec::new(), + } +} + +/// ```predicate``` should return ```false``` if requirement for reaching following [Node]s is not met. +pub fn require(predicate: &dyn Fn(&CommandSender) -> bool) -> NonLeafNodeBuilder { + NonLeafNodeBuilder { + node_type: NodeType::Require { + predicate + }, + child_nodes: Vec::new(), + leaf_nodes: Vec::new(), + } +} From 40b2fdc45f9c6c727d62aa2eba19a5599268b2b1 Mon Sep 17 00:00:00 2001 From: user622628252416 Date: Mon, 19 Aug 2024 18:33:06 +0200 Subject: [PATCH 2/8] add command usage hint after syntax error --- pumpkin/src/commands/dispatcher.rs | 6 +++-- pumpkin/src/commands/mod.rs | 3 +++ pumpkin/src/commands/tree.rs | 43 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/pumpkin/src/commands/dispatcher.rs b/pumpkin/src/commands/dispatcher.rs index fe4520c53..d65483352 100644 --- a/pumpkin/src/commands/dispatcher.rs +++ b/pumpkin/src/commands/dispatcher.rs @@ -19,7 +19,7 @@ pub(crate) struct CommandDispatcher<'a> { } impl <'a> CommandDispatcher<'a> { - pub(crate) fn dispatch(&'a self, src: &mut CommandSender, cmd: &str) -> Result<(), &str> { + pub(crate) fn dispatch(&'a self, src: &mut CommandSender, cmd: &str) -> Result<(), String> { let mut parts = cmd.split_ascii_whitespace(); let key = parts.next().ok_or("Empty Command")?; @@ -31,9 +31,11 @@ impl <'a> CommandDispatcher<'a> { match Self::try_path(src, path, tree, raw_args.clone()) { Err(InvalidConsumptionError(s)) => { println!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed"); + return Err("Internal Error (See logs for details)".into()) }, Err(InvalidRequirementError) => { println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); + return Err("Internal Error (See logs for details)".into()) }, Ok(fitting_path) => { if fitting_path { return Ok(()) } @@ -41,7 +43,7 @@ impl <'a> CommandDispatcher<'a> { } } - Err("Invalid Syntax: ") + Err(format!("Invalid Syntax. Usage:{}", tree.paths_formatted(key))) } fn try_path(src: &mut CommandSender, path: Vec, tree: &CommandTree, mut raw_args: RawArgs) -> Result { diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index fc36ea18f..28e042f2d 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -4,6 +4,8 @@ use pumpkin_text::TextComponent; use crate::client::Client; use crate::commands::dispatcher::CommandDispatcher; +use crate::server::Server; + mod cmd_gamemode; mod cmd_pumpkin; mod cmd_stop; @@ -11,6 +13,7 @@ mod tree; mod tree_builder; mod dispatcher; mod arg_player; +mod cmd_teleport; pub enum CommandSender<'a> { Rcon(&'a mut Vec), diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index 70ac6e5bb..46b45dbba 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -50,6 +50,49 @@ impl <'a> CommandTree<'a> { todo, } } + + pub(crate) fn paths_formatted(&'a self, name: &str) -> String { + let paths: Vec> = self.iter_paths() + .map(|path| path.iter().map(|&i| &self.nodes[i].node_type).collect()) + .collect(); + + let len = paths.iter() + .map(|path| path.iter() + .map(|node| match node { + NodeType::ExecuteLeaf { .. } => 0, + NodeType::Literal { string } => string.len() + 1, + NodeType::Argument { name, .. } => name.len() + 3, + NodeType::Require { .. } => 0, + }) + .sum::() + name.len() + 2 + ) + .sum::(); + + let mut s = String::with_capacity(len); + + for path in paths { + s.push('\n'); + s.push('/'); + s.push_str(name); + for node in path { + match node { + NodeType::Literal { string } => { + s.push(' '); + s.push_str(string); + } + NodeType::Argument { name, .. } => { + s.push(' '); + s.push('<'); + s.push_str(name); + s.push('>'); + } + _ => {} + } + } + } + + s + } } struct TraverseAllPathsIter<'a> { From bfb3840b945f2a8d53864cd7468a24027658dd24 Mon Sep 17 00:00:00 2001 From: user622628252416 Date: Mon, 19 Aug 2024 19:26:02 +0200 Subject: [PATCH 3/8] add help command --- pumpkin/src/commands/cmd_gamemode.rs | 2 +- pumpkin/src/commands/cmd_help.rs | 61 ++++++++++++++++++++++++++++ pumpkin/src/commands/cmd_pumpkin.rs | 2 +- pumpkin/src/commands/cmd_stop.rs | 2 +- pumpkin/src/commands/mod.rs | 5 ++- pumpkin/src/commands/tree.rs | 4 +- 6 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 pumpkin/src/commands/cmd_help.rs diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index 1b80de38a..4373a7ff8 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -14,7 +14,7 @@ use crate::entity::player::GameMode; pub(crate) const NAME: &str = "gamemode"; -const DESCRIPTION: &str = "Changes the gamemode for a Player"; +const DESCRIPTION: &str = "Change a player's gamemode."; const ARG_GAMEMODE: &str = "gamemode"; const ARG_TARGET: &str = "target"; diff --git a/pumpkin/src/commands/cmd_help.rs b/pumpkin/src/commands/cmd_help.rs new file mode 100644 index 000000000..6c12182b5 --- /dev/null +++ b/pumpkin/src/commands/cmd_help.rs @@ -0,0 +1,61 @@ +use crate::commands::{CommandSender, DISPATCHER, dispatcher_init}; +use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError}; +use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError; +use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs}; +use crate::commands::tree_builder::argument; + +pub(crate) const NAME: &str = "help"; +pub(crate) const ALIAS: &str = "?"; + +const DESCRIPTION: &str = "Print a help message."; + +const ARG_COMMAND: &str = "command"; + +fn consume_arg_command(_src: &CommandSender, args: &mut RawArgs) -> Option { + let s = args.pop()?; + + let dispatcher = DISPATCHER; + let dispatcher = dispatcher.get_or_init(dispatcher_init); + + if dispatcher.commands.contains_key(s) { Some(s.into()) } + else { None } +} + +fn parse_arg_command<'a>(consumed_args: &'a ConsumedArgs, dispatcher: &'a CommandDispatcher) -> Result<(&'a str, &'a CommandTree<'a>), InvalidTreeError> { + let command_name = consumed_args.get(ARG_COMMAND) + .ok_or(InvalidConsumptionError(None))?; + + if let Some(tree) = dispatcher.commands.get::<&str>(&command_name.as_str()) { + Ok((command_name, tree)) + } else { + Err(InvalidConsumptionError(Some(command_name.into()))) + } +} + +pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(DESCRIPTION).with_child( + argument(ARG_COMMAND, consume_arg_command).execute(&|sender, args| { + let dispatcher = DISPATCHER; + let dispatcher = dispatcher.get_or_init(dispatcher_init); + + let (name, tree) = parse_arg_command(args, dispatcher)?; + + sender.send_message( + format!("{} - {} Usage:{}", name, tree.description, tree.paths_formatted(name)).into() + ); + + Ok(()) + }) + ).execute(&|sender, _args| { + let dispatcher = DISPATCHER; + let dispatcher = dispatcher.get_or_init(dispatcher_init); + + for (name, tree) in &dispatcher.commands { + sender.send_message( + format!("{} - {} Usage:{}", name, tree.description, tree.paths_formatted(name)).into() + ); + }; + + Ok(()) + }) +} \ No newline at end of file diff --git a/pumpkin/src/commands/cmd_pumpkin.rs b/pumpkin/src/commands/cmd_pumpkin.rs index 1bb0b9cab..40b9e7ff4 100644 --- a/pumpkin/src/commands/cmd_pumpkin.rs +++ b/pumpkin/src/commands/cmd_pumpkin.rs @@ -6,7 +6,7 @@ use crate::commands::tree::CommandTree; pub(crate) const NAME: &str = "pumpkin"; -const DESCRIPTION: &str = "Displays information about Pumpkin"; +const DESCRIPTION: &str = "Display information about Pumpkin."; pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(DESCRIPTION).execute(&|sender, _| { diff --git a/pumpkin/src/commands/cmd_stop.rs b/pumpkin/src/commands/cmd_stop.rs index 57f55fc78..55f694e6f 100644 --- a/pumpkin/src/commands/cmd_stop.rs +++ b/pumpkin/src/commands/cmd_stop.rs @@ -3,7 +3,7 @@ use crate::commands::tree_builder::require; pub(crate) const NAME: &str = "stop"; -const DESCRIPTION: &str = "Stops the server"; +const DESCRIPTION: &str = "Stop the server."; pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(DESCRIPTION).with_child( diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 28e042f2d..2774182ac 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -4,8 +4,6 @@ use pumpkin_text::TextComponent; use crate::client::Client; use crate::commands::dispatcher::CommandDispatcher; -use crate::server::Server; - mod cmd_gamemode; mod cmd_pumpkin; mod cmd_stop; @@ -14,6 +12,7 @@ mod tree_builder; mod dispatcher; mod arg_player; mod cmd_teleport; +mod cmd_help; pub enum CommandSender<'a> { Rcon(&'a mut Vec), @@ -73,6 +72,8 @@ fn dispatcher_init<'a>() -> CommandDispatcher<'a> { map.insert(cmd_pumpkin::NAME, cmd_pumpkin::init_command_tree()); map.insert(cmd_gamemode::NAME, cmd_gamemode::init_command_tree()); map.insert(cmd_stop::NAME, cmd_stop::init_command_tree()); + map.insert(cmd_help::NAME, cmd_help::init_command_tree()); + map.insert(cmd_help::ALIAS, cmd_help::init_command_tree()); CommandDispatcher { commands: map, diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index 46b45dbba..852ac21d9 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -70,8 +70,8 @@ impl <'a> CommandTree<'a> { let mut s = String::with_capacity(len); - for path in paths { - s.push('\n'); + for path in paths.iter() { + s.push(if paths.len() > 1 { '\n' } else { ' ' }); s.push('/'); s.push_str(name); for node in path { From d221f8900f9c4e7c74f303eb35c1ff93f04640ff Mon Sep 17 00:00:00 2001 From: user622628252416 Date: Tue, 20 Aug 2024 12:09:25 +0200 Subject: [PATCH 4/8] add documentation --- pumpkin/src/commands/dispatcher.rs | 12 ++++++++---- pumpkin/src/commands/mod.rs | 7 +++++-- pumpkin/src/commands/tree.rs | 6 ++++++ pumpkin/src/commands/tree_builder.rs | 29 +++++++++++++++++++++++++--- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/pumpkin/src/commands/dispatcher.rs b/pumpkin/src/commands/dispatcher.rs index d65483352..82af621e3 100644 --- a/pumpkin/src/commands/dispatcher.rs +++ b/pumpkin/src/commands/dispatcher.rs @@ -18,7 +18,10 @@ pub(crate) struct CommandDispatcher<'a> { pub(crate) commands: HashMap<&'a str, CommandTree<'a>> } +/// Stores registered [CommandTree]s and dispatches commands to them. impl <'a> CommandDispatcher<'a> { + + /// Execute a command using its corresponding [CommandTree]. pub(crate) fn dispatch(&'a self, src: &mut CommandSender, cmd: &str) -> Result<(), String> { let mut parts = cmd.split_ascii_whitespace(); @@ -27,8 +30,9 @@ impl <'a> CommandDispatcher<'a> { let tree = self.commands.get(key).ok_or("Command not found")?; + // try paths until fitting path is found for path in tree.iter_paths() { - match Self::try_path(src, path, tree, raw_args.clone()) { + match Self::try_is_fitting_path(src, path, tree, raw_args.clone()) { Err(InvalidConsumptionError(s)) => { println!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed"); return Err("Internal Error (See logs for details)".into()) @@ -37,8 +41,8 @@ impl <'a> CommandDispatcher<'a> { println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); return Err("Internal Error (See logs for details)".into()) }, - Ok(fitting_path) => { - if fitting_path { return Ok(()) } + Ok(is_fitting_path) => { + if is_fitting_path { return Ok(()) } } } } @@ -46,7 +50,7 @@ impl <'a> CommandDispatcher<'a> { Err(format!("Invalid Syntax. Usage:{}", tree.paths_formatted(key))) } - fn try_path(src: &mut CommandSender, path: Vec, tree: &CommandTree, mut raw_args: RawArgs) -> Result { + fn try_is_fitting_path(src: &mut CommandSender, path: Vec, tree: &CommandTree, mut raw_args: RawArgs) -> Result { let mut parsed_args: ConsumedArgs = HashMap::new(); diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 2774182ac..bf14bb491 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -11,7 +11,6 @@ mod tree; mod tree_builder; mod dispatcher; mod arg_player; -mod cmd_teleport; mod cmd_help; pub enum CommandSender<'a> { @@ -63,9 +62,13 @@ impl<'a> CommandSender<'a> { } } -// todo: reconsider using constant +/// todo: reconsider using constant +/// +/// Central point from which commands are dispatched. Should always be initialized using +/// [dispatcher_init]. const DISPATCHER: OnceLock = OnceLock::new(); +/// create [CommandDispatcher] instance for [DISPATCHER] fn dispatcher_init<'a>() -> CommandDispatcher<'a> { let mut map = HashMap::new(); diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index 852ac21d9..52c406231 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -2,11 +2,15 @@ use std::collections::{HashMap, VecDeque}; use crate::commands::CommandSender; use crate::commands::dispatcher::InvalidTreeError; +use crate::commands::tree_builder::{argument, NonLeafNodeBuilder}; +/// see [argument] pub(crate) type RawArgs<'a> = Vec<&'a str>; +/// see [argument] and [CommandTree::execute]/[NonLeafNodeBuilder::execute] pub(crate) type ConsumedArgs<'a> = HashMap<&'a str, String>; +/// see [argument] pub(crate) type ArgumentConsumer<'a> = fn(&CommandSender, &mut RawArgs) -> Option; pub(crate) struct Node<'a> { @@ -51,6 +55,8 @@ impl <'a> CommandTree<'a> { } } + + /// format possible paths as [String], using ```name``` as the command name pub(crate) fn paths_formatted(&'a self, name: &str) -> String { let paths: Vec> = self.iter_paths() .map(|path| path.iter().map(|&i| &self.nodes[i].node_type).collect()) diff --git a/pumpkin/src/commands/tree_builder.rs b/pumpkin/src/commands/tree_builder.rs index efd298a3a..c3dac899c 100644 --- a/pumpkin/src/commands/tree_builder.rs +++ b/pumpkin/src/commands/tree_builder.rs @@ -3,6 +3,8 @@ use crate::commands::dispatcher::InvalidTreeError; use crate::commands::tree::{ArgumentConsumer, ConsumedArgs, Node, NodeType, CommandTree}; impl <'a> CommandTree<'a> { + + /// Add a child [Node] to the root of this [CommandTree]. pub fn with_child(mut self, child: impl NodeBuilder<'a>) -> Self { let node = child.build(&mut self); self.children.push(self.nodes.len()); @@ -19,6 +21,12 @@ impl <'a> CommandTree<'a> { } /// Executes if a command terminates at this [Node], i.e. without any arguments. + /// + /// [ConsumedArgs] maps the names of all + /// arguments to the result of their consumption, i.e. a string that can be parsed to the + /// desired type. + /// + /// Also see [NonLeafNodeBuilder::execute]. pub fn execute(mut self, run: &'a dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError>) -> Self { let node = Node { node_type: NodeType::ExecuteLeaf { @@ -81,12 +89,20 @@ impl <'a>NodeBuilder<'a> for NonLeafNodeBuilder<'a> { } impl <'a>NonLeafNodeBuilder<'a> { + + /// Add a child [Node] to this one. pub fn with_child(mut self, child: NonLeafNodeBuilder<'a>) -> Self { self.child_nodes.push(child); self } /// Executes if a command terminates at this [Node]. + /// + /// [ConsumedArgs] maps the names of all + /// arguments to the result of their consumption, i.e. a string that can be parsed to the + /// desired type. + /// + /// Also see [CommandTree::execute]. pub fn execute(mut self, run: &'a dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError>) -> Self { self.leaf_nodes.push(LeafNodeBuilder { node_type: NodeType::ExecuteLeaf { @@ -109,8 +125,14 @@ pub fn literal(string: &str) -> NonLeafNodeBuilder { } } -/// ```name``` identifies this argument. -/// ```consumer: [ArgumentConsumer]``` has the purpose of validating arguments. Conversion may start here, as long as the result remains a [String] (e.g. convert offset to absolute position actual coordinates). It must remove consumed arg(s) from [RawArgs] and return them. It must return None if [RawArgs] are invalid. [RawArgs] is reversed, so [Vec::pop] can be used to obtain args in ltr order. +/// ```name``` identifies this argument in [ConsumedArgs]. +/// +/// ```consumer: ArgumentConsumer``` has the purpose of validating arguments. Conversion may start +/// here, as long as the result remains a [String] (e.g. convert offset to absolute position actual +/// coordinates), because the result of this function will be passed to following +/// [NonLeafNodeBuilder::execute] nodes in a [ConsumedArgs] instance. It must remove consumed arg(s) +/// from [RawArgs] and return them. It must return None if [RawArgs] are invalid. [RawArgs] is +/// reversed, so [Vec::pop] can be used to obtain args in ltr order. pub fn argument<'a>(name: &'a str, consumer: ArgumentConsumer) -> NonLeafNodeBuilder<'a> { NonLeafNodeBuilder { node_type: NodeType::Argument { @@ -122,7 +144,8 @@ pub fn argument<'a>(name: &'a str, consumer: ArgumentConsumer) -> NonLeafNodeBui } } -/// ```predicate``` should return ```false``` if requirement for reaching following [Node]s is not met. +/// ```predicate``` should return ```false``` if requirement for reaching following [Node]s is not +/// met. pub fn require(predicate: &dyn Fn(&CommandSender) -> bool) -> NonLeafNodeBuilder { NonLeafNodeBuilder { node_type: NodeType::Require { From 4bc1f7b0b2116e2f58eec152a7b84492e3e1dc16 Mon Sep 17 00:00:00 2001 From: user622628252416 Date: Tue, 20 Aug 2024 16:32:39 +0200 Subject: [PATCH 5/8] fix clippy lints --- pumpkin/src/commands/arg_player.rs | 63 ++++++++++++++++------------ pumpkin/src/commands/cmd_gamemode.rs | 6 +-- pumpkin/src/commands/cmd_help.rs | 9 ++-- pumpkin/src/commands/mod.rs | 5 +-- pumpkin/src/commands/tree.rs | 12 +++--- pumpkin/src/commands/tree_builder.rs | 6 +-- 6 files changed, 53 insertions(+), 48 deletions(-) diff --git a/pumpkin/src/commands/arg_player.rs b/pumpkin/src/commands/arg_player.rs index d7587ebf2..a804a6f13 100644 --- a/pumpkin/src/commands/arg_player.rs +++ b/pumpkin/src/commands/arg_player.rs @@ -5,39 +5,50 @@ use crate::commands::dispatcher::InvalidTreeError; use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError; use crate::commands::tree::{ConsumedArgs, RawArgs}; -/// todo: implement (so far only own name + @s is implemented) +/// todo: implement (so far only own name + @s/@p is implemented) pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option { let s = args.pop()?; - - if let Player(client) = src { - if s == "@s" { - return Some(s.into()) - } - if let Some(profile) = &client.gameprofile { - if profile.name == s { - return Some(s.into()) + + match s { + "@s" if src.is_player() => Some(s.into()), + "@p" if src.is_player() => Some(s.into()), + "@r" => None, // todo: implement random player target selector + "@a" | "@e" => None, // todo: implement all players target selector + _ => { + // todo: implement any other player than sender + if let Player(client) = src { + if let Some(profile) = &client.gameprofile { + if profile.name == s { + return Some(s.into()) + }; + }; }; - }; - }; - - None + None + } + } } -/// todo: implement (so far only own name + @s is implemented) +/// todo: implement (so far only own name + @s/@p is implemented) pub fn parse_arg_player<'a>(src: &'a mut CommandSender, arg_name: &str, consumed_args: &ConsumedArgs) -> Result<&'a mut Client, InvalidTreeError> { let s = consumed_args.get(arg_name) - .ok_or(InvalidConsumptionError(None))?; + .ok_or(InvalidConsumptionError(None))? + .as_str(); - if let Player(client) = src { - if s == "@s" { - return Ok(client) - } - if let Some(profile) = &client.gameprofile { - if profile.name == s.as_ref() { - return Ok(client) + match s { + "@s" if src.is_player() => Ok(src.as_mut_player().unwrap()), + "@p" if src.is_player() => Ok(src.as_mut_player().unwrap()), + "@r" => Err(InvalidConsumptionError(Some(s.into()))), // todo: implement random player target selector + "@a" | "@e" => Err(InvalidConsumptionError(Some(s.into()))), // todo: implement all players target selector + _ => { + // todo: implement any other player than sender + if let Player(client) = src { + if let Some(profile) = &client.gameprofile { + if profile.name == s { + return Ok(client) + }; + }; }; - }; - }; - - Err(InvalidConsumptionError(Some(s.into()))) + Err(InvalidConsumptionError(Some(s.into()))) + } + } } \ No newline at end of file diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index fec3bc70c..f4a558cb2 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -30,7 +30,7 @@ pub fn consume_arg_gamemode(_src: &CommandSender, args: &mut RawArgs) -> Option< }; }; - return match GameMode::from_str(s) { + match GameMode::from_str(s) { Err(_) | Ok(GameMode::Undefined) => None, Ok(_) => Some(s.into()) } @@ -47,10 +47,10 @@ pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result Err(InvalidConsumptionError(Some(s.into()))), Ok(gamemode) => Ok(gamemode) - }; + } } pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { diff --git a/pumpkin/src/commands/cmd_help.rs b/pumpkin/src/commands/cmd_help.rs index d6353a715..7255fc9f1 100644 --- a/pumpkin/src/commands/cmd_help.rs +++ b/pumpkin/src/commands/cmd_help.rs @@ -15,8 +15,7 @@ const ARG_COMMAND: &str = "command"; fn consume_arg_command(_src: &CommandSender, args: &mut RawArgs) -> Option { let s = args.pop()?; - let dispatcher = DISPATCHER; - let dispatcher = dispatcher.get_or_init(dispatcher_init); + let dispatcher = DISPATCHER.get_or_init(dispatcher_init); if dispatcher.commands.contains_key(s) { Some(s.into()) } else { None } @@ -36,8 +35,7 @@ fn parse_arg_command<'a>(consumed_args: &'a ConsumedArgs, dispatcher: &'a Comman pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(DESCRIPTION).with_child( argument(ARG_COMMAND, consume_arg_command).execute(&|sender, args| { - let dispatcher = DISPATCHER; - let dispatcher = dispatcher.get_or_init(dispatcher_init); + let dispatcher = DISPATCHER.get_or_init(dispatcher_init); let (name, tree) = parse_arg_command(args, dispatcher)?; @@ -50,8 +48,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { Ok(()) }) ).execute(&|sender, _args| { - let dispatcher = DISPATCHER; - let dispatcher = dispatcher.get_or_init(dispatcher_init); + let dispatcher = DISPATCHER.get_or_init(dispatcher_init); for (name, tree) in &dispatcher.commands { sender.send_message( diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index f84ce719a..7bec0e83a 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -66,7 +66,7 @@ impl<'a> CommandSender<'a> { /// /// Central point from which commands are dispatched. Should always be initialized using /// [dispatcher_init]. -const DISPATCHER: OnceLock = OnceLock::new(); +static DISPATCHER: OnceLock = OnceLock::new(); /// create [CommandDispatcher] instance for [DISPATCHER] fn dispatcher_init<'a>() -> CommandDispatcher<'a> { @@ -84,8 +84,7 @@ fn dispatcher_init<'a>() -> CommandDispatcher<'a> { } pub fn handle_command(sender: &mut CommandSender, cmd: &str) { - let dispatcher = DISPATCHER; - let dispatcher = dispatcher.get_or_init(dispatcher_init); + let dispatcher = DISPATCHER.get_or_init(dispatcher_init); if let Err(err) = dispatcher.dispatch(sender, cmd) { sender.send_message( diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index 52c406231..492214e69 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -2,15 +2,13 @@ use std::collections::{HashMap, VecDeque}; use crate::commands::CommandSender; use crate::commands::dispatcher::InvalidTreeError; -use crate::commands::tree_builder::{argument, NonLeafNodeBuilder}; - -/// see [argument] +/// see [crate::commands::tree_builder::argument] pub(crate) type RawArgs<'a> = Vec<&'a str>; -/// see [argument] and [CommandTree::execute]/[NonLeafNodeBuilder::execute] +/// see [crate::commands::tree_builder::argument] and [CommandTree::execute]/[crate::commands::tree_builder::NonLeafNodeBuilder::execute] pub(crate) type ConsumedArgs<'a> = HashMap<&'a str, String>; -/// see [argument] +/// see [crate::commands::tree_builder::argument] pub(crate) type ArgumentConsumer<'a> = fn(&CommandSender, &mut RawArgs) -> Option; pub(crate) struct Node<'a> { @@ -20,7 +18,7 @@ pub(crate) struct Node<'a> { pub(crate) enum NodeType<'a> { ExecuteLeaf { - run: &'a dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError>, + run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync), }, Literal { string: &'a str, @@ -30,7 +28,7 @@ pub(crate) enum NodeType<'a> { consumer: ArgumentConsumer<'a>, }, Require { - predicate: &'a dyn Fn(&CommandSender) -> bool, + predicate: &'a (dyn Fn(&CommandSender) -> bool + Sync), } } diff --git a/pumpkin/src/commands/tree_builder.rs b/pumpkin/src/commands/tree_builder.rs index c3dac899c..28f721217 100644 --- a/pumpkin/src/commands/tree_builder.rs +++ b/pumpkin/src/commands/tree_builder.rs @@ -27,7 +27,7 @@ impl <'a> CommandTree<'a> { /// desired type. /// /// Also see [NonLeafNodeBuilder::execute]. - pub fn execute(mut self, run: &'a dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError>) -> Self { + pub fn execute(mut self, run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync)) -> Self { let node = Node { node_type: NodeType::ExecuteLeaf { run, @@ -103,7 +103,7 @@ impl <'a>NonLeafNodeBuilder<'a> { /// desired type. /// /// Also see [CommandTree::execute]. - pub fn execute(mut self, run: &'a dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError>) -> Self { + pub fn execute(mut self, run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync)) -> Self { self.leaf_nodes.push(LeafNodeBuilder { node_type: NodeType::ExecuteLeaf { run @@ -146,7 +146,7 @@ pub fn argument<'a>(name: &'a str, consumer: ArgumentConsumer) -> NonLeafNodeBui /// ```predicate``` should return ```false``` if requirement for reaching following [Node]s is not /// met. -pub fn require(predicate: &dyn Fn(&CommandSender) -> bool) -> NonLeafNodeBuilder { +pub fn require(predicate: &(dyn Fn(&CommandSender) -> bool + Sync)) -> NonLeafNodeBuilder { NonLeafNodeBuilder { node_type: NodeType::Require { predicate From 413f88d14589757d28175bb36e3e141bab308181 Mon Sep 17 00:00:00 2001 From: user622628252416 Date: Tue, 20 Aug 2024 16:51:44 +0200 Subject: [PATCH 6/8] suppress dead_code warning on Literal command node --- pumpkin/src/commands/tree.rs | 1 + pumpkin/src/commands/tree_builder.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index 492214e69..68d9bd30b 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -20,6 +20,7 @@ pub(crate) enum NodeType<'a> { ExecuteLeaf { run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync), }, + #[allow(dead_code)] // todo: remove (so far no commands requiring this are implemented) Literal { string: &'a str, }, diff --git a/pumpkin/src/commands/tree_builder.rs b/pumpkin/src/commands/tree_builder.rs index 28f721217..c4ec99178 100644 --- a/pumpkin/src/commands/tree_builder.rs +++ b/pumpkin/src/commands/tree_builder.rs @@ -115,6 +115,7 @@ impl <'a>NonLeafNodeBuilder<'a> { } /// Matches a sting literal. +#[allow(dead_code)] // todo: remove (so far no commands requiring this are implemented) pub fn literal(string: &str) -> NonLeafNodeBuilder { NonLeafNodeBuilder { node_type: NodeType::Literal { From 92e6d264b4f38e8fc0667c351788b9889537ddc7 Mon Sep 17 00:00:00 2001 From: user622628252416 Date: Tue, 20 Aug 2024 17:01:25 +0200 Subject: [PATCH 7/8] format code --- pumpkin/src/commands/arg_player.rs | 25 ++++++---- pumpkin/src/commands/cmd_gamemode.rs | 70 ++++++++++++-------------- pumpkin/src/commands/cmd_help.rs | 75 ++++++++++++++++------------ pumpkin/src/commands/cmd_pumpkin.rs | 6 +-- pumpkin/src/commands/cmd_stop.rs | 8 ++- pumpkin/src/commands/dispatcher.rs | 51 ++++++++++++------- pumpkin/src/commands/mod.rs | 15 +++--- pumpkin/src/commands/tree.rs | 55 ++++++++++---------- pumpkin/src/commands/tree_builder.rs | 51 ++++++++----------- 9 files changed, 186 insertions(+), 170 deletions(-) diff --git a/pumpkin/src/commands/arg_player.rs b/pumpkin/src/commands/arg_player.rs index a804a6f13..a8ee8f460 100644 --- a/pumpkin/src/commands/arg_player.rs +++ b/pumpkin/src/commands/arg_player.rs @@ -1,25 +1,25 @@ use crate::client::Client; -use crate::commands::CommandSender; -use crate::commands::CommandSender::Player; use crate::commands::dispatcher::InvalidTreeError; use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError; use crate::commands::tree::{ConsumedArgs, RawArgs}; +use crate::commands::CommandSender; +use crate::commands::CommandSender::Player; /// todo: implement (so far only own name + @s/@p is implemented) pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option { let s = args.pop()?; - - match s { + + match s { "@s" if src.is_player() => Some(s.into()), "@p" if src.is_player() => Some(s.into()), - "@r" => None, // todo: implement random player target selector + "@r" => None, // todo: implement random player target selector "@a" | "@e" => None, // todo: implement all players target selector _ => { // todo: implement any other player than sender if let Player(client) = src { if let Some(profile) = &client.gameprofile { if profile.name == s { - return Some(s.into()) + return Some(s.into()); }; }; }; @@ -29,8 +29,13 @@ pub fn consume_arg_player(src: &CommandSender, args: &mut RawArgs) -> Option(src: &'a mut CommandSender, arg_name: &str, consumed_args: &ConsumedArgs) -> Result<&'a mut Client, InvalidTreeError> { - let s = consumed_args.get(arg_name) +pub fn parse_arg_player<'a>( + src: &'a mut CommandSender, + arg_name: &str, + consumed_args: &ConsumedArgs, +) -> Result<&'a mut Client, InvalidTreeError> { + let s = consumed_args + .get(arg_name) .ok_or(InvalidConsumptionError(None))? .as_str(); @@ -44,11 +49,11 @@ pub fn parse_arg_player<'a>(src: &'a mut CommandSender, arg_name: &str, consumed if let Player(client) = src { if let Some(profile) = &client.gameprofile { if profile.name == s { - return Ok(client) + return Ok(client); }; }; }; Err(InvalidConsumptionError(Some(s.into()))) } } -} \ No newline at end of file +} diff --git a/pumpkin/src/commands/cmd_gamemode.rs b/pumpkin/src/commands/cmd_gamemode.rs index f4a558cb2..b054fdc24 100644 --- a/pumpkin/src/commands/cmd_gamemode.rs +++ b/pumpkin/src/commands/cmd_gamemode.rs @@ -5,12 +5,14 @@ use pumpkin_text::TextComponent; use crate::commands::arg_player::{consume_arg_player, parse_arg_player}; -use crate::commands::CommandSender; -use crate::commands::CommandSender::Player; use crate::commands::dispatcher::InvalidTreeError; -use crate::commands::dispatcher::InvalidTreeError::{InvalidConsumptionError, InvalidRequirementError}; +use crate::commands::dispatcher::InvalidTreeError::{ + InvalidConsumptionError, InvalidRequirementError, +}; use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs}; use crate::commands::tree_builder::{argument, require}; +use crate::commands::CommandSender; +use crate::commands::CommandSender::Player; use crate::entity::player::GameMode; pub(crate) const NAME: &str = "gamemode"; @@ -25,76 +27,70 @@ pub fn consume_arg_gamemode(_src: &CommandSender, args: &mut RawArgs) -> Option< if let Ok(id) = s.parse::() { match GameMode::from_u8(id) { - None | Some(GameMode::Undefined) => {}, - Some(_) => return Some(s.into()) + None | Some(GameMode::Undefined) => {} + Some(_) => return Some(s.into()), }; }; match GameMode::from_str(s) { Err(_) | Ok(GameMode::Undefined) => None, - Ok(_) => Some(s.into()) + Ok(_) => Some(s.into()), } } pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result { - let s = consumed_args.get(ARG_GAMEMODE) + let s = consumed_args + .get(ARG_GAMEMODE) .ok_or(InvalidConsumptionError(None))?; if let Ok(id) = s.parse::() { match GameMode::from_u8(id) { - None | Some(GameMode::Undefined) => {}, - Some(gamemode) => return Ok(gamemode) + None | Some(GameMode::Undefined) => {} + Some(gamemode) => return Ok(gamemode), }; }; match GameMode::from_str(s) { Err(_) | Ok(GameMode::Undefined) => Err(InvalidConsumptionError(Some(s.into()))), - Ok(gamemode) => Ok(gamemode) + Ok(gamemode) => Ok(gamemode), } } pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(DESCRIPTION).with_child( - - require(&|sender| { - sender.permission_lvl() >= 2 - }).with_child( - - argument(ARG_GAMEMODE, consume_arg_gamemode).with_child( - - require(&|sender| sender.is_player()) - - .execute(&|sender, args| { + require(&|sender| sender.permission_lvl() >= 2).with_child( + argument(ARG_GAMEMODE, consume_arg_gamemode) + .with_child( + require(&|sender| sender.is_player()).execute(&|sender, args| { let gamemode = parse_arg_gamemode(args)?; return if let Player(target) = sender { target.set_gamemode(gamemode); - target.send_system_message(TextComponent::text( - &format!("Game mode was set to {:?}", gamemode) - )); + target.send_system_message(TextComponent::text(&format!( + "Game mode was set to {:?}", + gamemode + ))); Ok(()) } else { Err(InvalidRequirementError) - } - }) - - ).with_child( - - argument(ARG_TARGET, consume_arg_player) - - .execute(&|sender, args| { + }; + }), + ) + .with_child( + argument(ARG_TARGET, consume_arg_player).execute(&|sender, args| { let gamemode = parse_arg_gamemode(args)?; let target = parse_arg_player(sender, ARG_TARGET, args)?; target.set_gamemode(gamemode); - target.send_system_message(TextComponent::text( - &format!("Set own game mode to {:?}", gamemode) - )); + target.send_system_message(TextComponent::text(&format!( + "Set own game mode to {:?}", + gamemode + ))); Ok(()) - }) - ) - ) + }), + ), + ), ) } diff --git a/pumpkin/src/commands/cmd_help.rs b/pumpkin/src/commands/cmd_help.rs index 7255fc9f1..7ffa733c1 100644 --- a/pumpkin/src/commands/cmd_help.rs +++ b/pumpkin/src/commands/cmd_help.rs @@ -1,9 +1,9 @@ -use pumpkin_text::TextComponent; -use crate::commands::{CommandSender, DISPATCHER, dispatcher_init}; -use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError}; use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError; +use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError}; use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs}; use crate::commands::tree_builder::argument; +use crate::commands::{dispatcher_init, CommandSender, DISPATCHER}; +use pumpkin_text::TextComponent; pub(crate) const NAME: &str = "help"; pub(crate) const ALIAS: &str = "?"; @@ -17,12 +17,19 @@ fn consume_arg_command(_src: &CommandSender, args: &mut RawArgs) -> Option(consumed_args: &'a ConsumedArgs, dispatcher: &'a CommandDispatcher) -> Result<(&'a str, &'a CommandTree<'a>), InvalidTreeError> { - let command_name = consumed_args.get(ARG_COMMAND) +fn parse_arg_command<'a>( + consumed_args: &'a ConsumedArgs, + dispatcher: &'a CommandDispatcher, +) -> Result<(&'a str, &'a CommandTree<'a>), InvalidTreeError> { + let command_name = consumed_args + .get(ARG_COMMAND) .ok_or(InvalidConsumptionError(None))?; if let Some(tree) = dispatcher.commands.get::<&str>(&command_name.as_str()) { @@ -33,31 +40,35 @@ fn parse_arg_command<'a>(consumed_args: &'a ConsumedArgs, dispatcher: &'a Comman } pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(DESCRIPTION).with_child( - argument(ARG_COMMAND, consume_arg_command).execute(&|sender, args| { + CommandTree::new(DESCRIPTION) + .with_child( + argument(ARG_COMMAND, consume_arg_command).execute(&|sender, args| { + let dispatcher = DISPATCHER.get_or_init(dispatcher_init); + + let (name, tree) = parse_arg_command(args, dispatcher)?; + + sender.send_message(TextComponent::text(&format!( + "{} - {} Usage:{}", + name, + tree.description, + tree.paths_formatted(name) + ))); + + Ok(()) + }), + ) + .execute(&|sender, _args| { let dispatcher = DISPATCHER.get_or_init(dispatcher_init); - - let (name, tree) = parse_arg_command(args, dispatcher)?; - - sender.send_message( - TextComponent::text( - &format!("{} - {} Usage:{}", name, tree.description, tree.paths_formatted(name)) - ) - ); - + + for (name, tree) in &dispatcher.commands { + sender.send_message(TextComponent::text(&format!( + "{} - {} Usage:{}", + name, + tree.description, + tree.paths_formatted(name) + ))); + } + Ok(()) }) - ).execute(&|sender, _args| { - let dispatcher = DISPATCHER.get_or_init(dispatcher_init); - - for (name, tree) in &dispatcher.commands { - sender.send_message( - TextComponent::text( - &format!("{} - {} Usage:{}", name, tree.description, tree.paths_formatted(name)) - ) - ); - }; - - Ok(()) - }) -} \ No newline at end of file +} diff --git a/pumpkin/src/commands/cmd_pumpkin.rs b/pumpkin/src/commands/cmd_pumpkin.rs index debb70ae6..cbbac0680 100644 --- a/pumpkin/src/commands/cmd_pumpkin.rs +++ b/pumpkin/src/commands/cmd_pumpkin.rs @@ -1,6 +1,6 @@ -use pumpkin_text::{color::NamedColor, TextComponent}; -use pumpkin_protocol::CURRENT_MC_PROTOCOL; use crate::server::CURRENT_MC_VERSION; +use pumpkin_protocol::CURRENT_MC_PROTOCOL; +use pumpkin_text::{color::NamedColor, TextComponent}; use crate::commands::tree::CommandTree; @@ -12,7 +12,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(DESCRIPTION).execute(&|sender, _| { let version = env!("CARGO_PKG_VERSION"); let description = env!("CARGO_PKG_DESCRIPTION"); - + sender.send_message(TextComponent::text( &format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})") ).color_named(NamedColor::Green)); diff --git a/pumpkin/src/commands/cmd_stop.rs b/pumpkin/src/commands/cmd_stop.rs index 55f694e6f..b576c299d 100644 --- a/pumpkin/src/commands/cmd_stop.rs +++ b/pumpkin/src/commands/cmd_stop.rs @@ -7,9 +7,7 @@ const DESCRIPTION: &str = "Stop the server."; pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(DESCRIPTION).with_child( - require(&|sender| { sender.permission_lvl() >= 4 }) - .execute(&|_sender, _args| { - std::process::exit(0) - }) + require(&|sender| sender.permission_lvl() >= 4) + .execute(&|_sender, _args| std::process::exit(0)), ) -} \ No newline at end of file +} diff --git a/pumpkin/src/commands/dispatcher.rs b/pumpkin/src/commands/dispatcher.rs index 82af621e3..a01e20b81 100644 --- a/pumpkin/src/commands/dispatcher.rs +++ b/pumpkin/src/commands/dispatcher.rs @@ -1,12 +1,14 @@ -use std::collections::HashMap; +use crate::commands::dispatcher::InvalidTreeError::{ + InvalidConsumptionError, InvalidRequirementError, +}; +use crate::commands::tree::{CommandTree, ConsumedArgs, NodeType, RawArgs}; use crate::commands::CommandSender; -use crate::commands::dispatcher::InvalidTreeError::{InvalidConsumptionError, InvalidRequirementError}; -use crate::commands::tree::{ConsumedArgs, NodeType, RawArgs, CommandTree}; +use std::collections::HashMap; #[derive(Debug)] pub(crate) enum InvalidTreeError { /// This error means that there was an error while parsing a previously consumed argument. - /// That only happens when consumption is wrongly implemented, as it should ensure parsing may + /// That only happens when consumption is wrongly implemented, as it should ensure parsing may /// never fail. InvalidConsumptionError(Option), @@ -15,15 +17,13 @@ pub(crate) enum InvalidTreeError { } pub(crate) struct CommandDispatcher<'a> { - pub(crate) commands: HashMap<&'a str, CommandTree<'a>> + pub(crate) commands: HashMap<&'a str, CommandTree<'a>>, } /// Stores registered [CommandTree]s and dispatches commands to them. -impl <'a> CommandDispatcher<'a> { - +impl<'a> CommandDispatcher<'a> { /// Execute a command using its corresponding [CommandTree]. pub(crate) fn dispatch(&'a self, src: &mut CommandSender, cmd: &str) -> Result<(), String> { - let mut parts = cmd.split_ascii_whitespace(); let key = parts.next().ok_or("Empty Command")?; let raw_args: Vec<&str> = parts.rev().collect(); @@ -35,23 +35,32 @@ impl <'a> CommandDispatcher<'a> { match Self::try_is_fitting_path(src, path, tree, raw_args.clone()) { Err(InvalidConsumptionError(s)) => { println!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed"); - return Err("Internal Error (See logs for details)".into()) - }, + return Err("Internal Error (See logs for details)".into()); + } Err(InvalidRequirementError) => { println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); - return Err("Internal Error (See logs for details)".into()) - }, + return Err("Internal Error (See logs for details)".into()); + } Ok(is_fitting_path) => { - if is_fitting_path { return Ok(()) } + if is_fitting_path { + return Ok(()); + } } } } - Err(format!("Invalid Syntax. Usage:{}", tree.paths_formatted(key))) + Err(format!( + "Invalid Syntax. Usage:{}", + tree.paths_formatted(key) + )) } - fn try_is_fitting_path(src: &mut CommandSender, path: Vec, tree: &CommandTree, mut raw_args: RawArgs) -> Result { - + fn try_is_fitting_path( + src: &mut CommandSender, + path: Vec, + tree: &CommandTree, + mut raw_args: RawArgs, + ) -> Result { let mut parsed_args: ConsumedArgs = HashMap::new(); for node in path.iter().map(|&i| &tree.nodes[i]) { @@ -69,7 +78,11 @@ impl <'a> CommandDispatcher<'a> { return Ok(false); } } - NodeType::Argument { consumer: consume, name, .. } => { + NodeType::Argument { + consumer: consume, + name, + .. + } => { if let Some(consumed) = consume(src, &mut raw_args) { parsed_args.insert(name, consumed); } else { @@ -82,8 +95,8 @@ impl <'a> CommandDispatcher<'a> { } } } - }; + } Ok(false) } -} \ No newline at end of file +} diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 7bec0e83a..c622691d6 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -1,17 +1,17 @@ +use pumpkin_text::TextComponent; use std::collections::HashMap; use std::sync::OnceLock; -use pumpkin_text::TextComponent; use crate::client::Client; use crate::commands::dispatcher::CommandDispatcher; +mod arg_player; mod cmd_gamemode; +mod cmd_help; mod cmd_pumpkin; mod cmd_stop; +mod dispatcher; mod tree; mod tree_builder; -mod dispatcher; -mod arg_player; -mod cmd_help; pub enum CommandSender<'a> { Rcon(&'a mut Vec), @@ -78,9 +78,7 @@ fn dispatcher_init<'a>() -> CommandDispatcher<'a> { map.insert(cmd_help::NAME, cmd_help::init_command_tree()); map.insert(cmd_help::ALIAS, cmd_help::init_command_tree()); - CommandDispatcher { - commands: map, - } + CommandDispatcher { commands: map } } pub fn handle_command(sender: &mut CommandSender, cmd: &str) { @@ -88,8 +86,7 @@ pub fn handle_command(sender: &mut CommandSender, cmd: &str) { if let Err(err) = dispatcher.dispatch(sender, cmd) { sender.send_message( - TextComponent::text(&err) - .color_named(pumpkin_text::color::NamedColor::Red), + TextComponent::text(&err).color_named(pumpkin_text::color::NamedColor::Red), ) } } diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index 68d9bd30b..c286ead46 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, VecDeque}; -use crate::commands::CommandSender; use crate::commands::dispatcher::InvalidTreeError; +use crate::commands::CommandSender; /// see [crate::commands::tree_builder::argument] pub(crate) type RawArgs<'a> = Vec<&'a str>; @@ -21,16 +21,14 @@ pub(crate) enum NodeType<'a> { run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync), }, #[allow(dead_code)] // todo: remove (so far no commands requiring this are implemented) - Literal { - string: &'a str, - }, + Literal { string: &'a str }, Argument { name: &'a str, consumer: ArgumentConsumer<'a>, }, Require { predicate: &'a (dyn Fn(&CommandSender) -> bool + Sync), - } + }, } pub(crate) struct CommandTree<'a> { @@ -39,7 +37,7 @@ pub(crate) struct CommandTree<'a> { pub(crate) description: &'a str, } -impl <'a> CommandTree<'a> { +impl<'a> CommandTree<'a> { /// iterate over all possible paths that end in a [NodeType::ExecuteLeaf] pub(crate) fn iter_paths(&'a self) -> impl Iterator> + 'a { let mut todo = VecDeque::<(usize, usize)>::new(); @@ -54,25 +52,29 @@ impl <'a> CommandTree<'a> { } } - /// format possible paths as [String], using ```name``` as the command name pub(crate) fn paths_formatted(&'a self, name: &str) -> String { - let paths: Vec> = self.iter_paths() + let paths: Vec> = self + .iter_paths() .map(|path| path.iter().map(|&i| &self.nodes[i].node_type).collect()) .collect(); - - let len = paths.iter() - .map(|path| path.iter() - .map(|node| match node { - NodeType::ExecuteLeaf { .. } => 0, - NodeType::Literal { string } => string.len() + 1, - NodeType::Argument { name, .. } => name.len() + 3, - NodeType::Require { .. } => 0, - }) - .sum::() + name.len() + 2 - ) + + let len = paths + .iter() + .map(|path| { + path.iter() + .map(|node| match node { + NodeType::ExecuteLeaf { .. } => 0, + NodeType::Literal { string } => string.len() + 1, + NodeType::Argument { name, .. } => name.len() + 3, + NodeType::Require { .. } => 0, + }) + .sum::() + + name.len() + + 2 + }) .sum::(); - + let mut s = String::with_capacity(len); for path in paths.iter() { @@ -95,7 +97,7 @@ impl <'a> CommandTree<'a> { } } } - + s } } @@ -107,8 +109,7 @@ struct TraverseAllPathsIter<'a> { todo: VecDeque<(usize, usize)>, } -impl <'a>Iterator for TraverseAllPathsIter<'a> { - +impl<'a> Iterator for TraverseAllPathsIter<'a> { type Item = Vec; fn next(&mut self) -> Option { @@ -118,12 +119,15 @@ impl <'a>Iterator for TraverseAllPathsIter<'a> { // add new children to front self.todo.reserve(node.children.len()); - node.children.iter().rev().for_each(|&c| self.todo.push_front((depth + 1, c))); + node.children + .iter() + .rev() + .for_each(|&c| self.todo.push_front((depth + 1, c))); // update path while self.path.len() > depth { self.path.pop(); - }; + } self.path.push(i); if let NodeType::ExecuteLeaf { .. } = node.node_type { @@ -132,4 +136,3 @@ impl <'a>Iterator for TraverseAllPathsIter<'a> { } } } - diff --git a/pumpkin/src/commands/tree_builder.rs b/pumpkin/src/commands/tree_builder.rs index c4ec99178..63531909b 100644 --- a/pumpkin/src/commands/tree_builder.rs +++ b/pumpkin/src/commands/tree_builder.rs @@ -1,9 +1,8 @@ -use crate::commands::CommandSender; use crate::commands::dispatcher::InvalidTreeError; -use crate::commands::tree::{ArgumentConsumer, ConsumedArgs, Node, NodeType, CommandTree}; - -impl <'a> CommandTree<'a> { +use crate::commands::tree::{ArgumentConsumer, CommandTree, ConsumedArgs, Node, NodeType}; +use crate::commands::CommandSender; +impl<'a> CommandTree<'a> { /// Add a child [Node] to the root of this [CommandTree]. pub fn with_child(mut self, child: impl NodeBuilder<'a>) -> Self { let node = child.build(&mut self); @@ -11,7 +10,7 @@ impl <'a> CommandTree<'a> { self.nodes.push(node); self } - + pub fn new(description: &'a str) -> Self { Self { nodes: Vec::new(), @@ -27,11 +26,12 @@ impl <'a> CommandTree<'a> { /// desired type. /// /// Also see [NonLeafNodeBuilder::execute]. - pub fn execute(mut self, run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync)) -> Self { + pub fn execute( + mut self, + run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync), + ) -> Self { let node = Node { - node_type: NodeType::ExecuteLeaf { - run, - }, + node_type: NodeType::ExecuteLeaf { run }, children: Vec::new(), }; @@ -50,7 +50,7 @@ struct LeafNodeBuilder<'a> { node_type: NodeType<'a>, } -impl <'a>NodeBuilder<'a> for LeafNodeBuilder<'a> { +impl<'a> NodeBuilder<'a> for LeafNodeBuilder<'a> { fn build(self, _tree: &mut CommandTree<'a>) -> Node<'a> { Node { children: Vec::new(), @@ -62,10 +62,10 @@ impl <'a>NodeBuilder<'a> for LeafNodeBuilder<'a> { pub struct NonLeafNodeBuilder<'a> { node_type: NodeType<'a>, child_nodes: Vec>, - leaf_nodes: Vec> + leaf_nodes: Vec>, } -impl <'a>NodeBuilder<'a> for NonLeafNodeBuilder<'a> { +impl<'a> NodeBuilder<'a> for NonLeafNodeBuilder<'a> { fn build(self, tree: &mut CommandTree<'a>) -> Node<'a> { let mut child_indices = Vec::new(); @@ -88,8 +88,7 @@ impl <'a>NodeBuilder<'a> for NonLeafNodeBuilder<'a> { } } -impl <'a>NonLeafNodeBuilder<'a> { - +impl<'a> NonLeafNodeBuilder<'a> { /// Add a child [Node] to this one. pub fn with_child(mut self, child: NonLeafNodeBuilder<'a>) -> Self { self.child_nodes.push(child); @@ -103,13 +102,14 @@ impl <'a>NonLeafNodeBuilder<'a> { /// desired type. /// /// Also see [CommandTree::execute]. - pub fn execute(mut self, run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync)) -> Self { + pub fn execute( + mut self, + run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync), + ) -> Self { self.leaf_nodes.push(LeafNodeBuilder { - node_type: NodeType::ExecuteLeaf { - run - }, + node_type: NodeType::ExecuteLeaf { run }, }); - + self } } @@ -118,9 +118,7 @@ impl <'a>NonLeafNodeBuilder<'a> { #[allow(dead_code)] // todo: remove (so far no commands requiring this are implemented) pub fn literal(string: &str) -> NonLeafNodeBuilder { NonLeafNodeBuilder { - node_type: NodeType::Literal { - string - }, + node_type: NodeType::Literal { string }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), } @@ -136,10 +134,7 @@ pub fn literal(string: &str) -> NonLeafNodeBuilder { /// reversed, so [Vec::pop] can be used to obtain args in ltr order. pub fn argument<'a>(name: &'a str, consumer: ArgumentConsumer) -> NonLeafNodeBuilder<'a> { NonLeafNodeBuilder { - node_type: NodeType::Argument { - name, - consumer, - }, + node_type: NodeType::Argument { name, consumer }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), } @@ -149,9 +144,7 @@ pub fn argument<'a>(name: &'a str, consumer: ArgumentConsumer) -> NonLeafNodeBui /// met. pub fn require(predicate: &(dyn Fn(&CommandSender) -> bool + Sync)) -> NonLeafNodeBuilder { NonLeafNodeBuilder { - node_type: NodeType::Require { - predicate - }, + node_type: NodeType::Require { predicate }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), } From fd544b52ed506580a17d6330440ccf8984f93be7 Mon Sep 17 00:00:00 2001 From: user622628252416 Date: Tue, 20 Aug 2024 17:03:30 +0200 Subject: [PATCH 8/8] add todo to CommandTree formatting --- pumpkin/src/commands/tree.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pumpkin/src/commands/tree.rs b/pumpkin/src/commands/tree.rs index c286ead46..b4132b18e 100644 --- a/pumpkin/src/commands/tree.rs +++ b/pumpkin/src/commands/tree.rs @@ -53,6 +53,8 @@ impl<'a> CommandTree<'a> { } /// format possible paths as [String], using ```name``` as the command name + /// + /// todo: merge into single line pub(crate) fn paths_formatted(&'a self, name: &str) -> String { let paths: Vec> = self .iter_paths()