From 943f8fcd4c2e5b2744bd9d308d279e37e3f434a4 Mon Sep 17 00:00:00 2001 From: user622628252416 Date: Mon, 19 Aug 2024 00:29:31 +0200 Subject: [PATCH] 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 | 86 +++++++++-------- 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, 524 insertions(+), 131 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, } } + + /// todo: implement + pub fn permission_lvl(&self) -> i32 { + match self { + CommandSender::Rcon(_) => 4, + CommandSender::Console => 4, + CommandSender::Player(_) => 4, + } + } } -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: 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, + } +} + +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), + ) } - if command.starts_with(GamemodeCommand::NAME) { - GamemodeCommand::on_execute(sender, command); - return; - } - if command.starts_with(StopCommand::NAME) { - StopCommand::on_execute(sender, command); - return; - } - // 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(), + } +}