add tree-style command parsing utilities, heavily inspired by Mojang's Brigadier and adjust existing commands to use them
This commit is contained in:
parent
77b205ada0
commit
943f8fcd4c
43
pumpkin/src/commands/arg_player.rs
Normal file
43
pumpkin/src/commands/arg_player.rs
Normal file
@ -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<String> {
|
||||
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())))
|
||||
}
|
||||
95
pumpkin/src/commands/cmd_gamemode.rs
Normal file
95
pumpkin/src/commands/cmd_gamemode.rs
Normal file
@ -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<String> {
|
||||
let s = args.pop()?;
|
||||
|
||||
if let Ok(id) = s.parse::<u8>() {
|
||||
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<GameMode, InvalidTreeError> {
|
||||
let s = consumed_args.get(ARG_GAMEMODE)
|
||||
.ok_or(InvalidConsumptionError(None))?;
|
||||
|
||||
if let Ok(id) = s.parse::<u8>() {
|
||||
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(())
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
21
pumpkin/src/commands/cmd_pumpkin.rs
Normal file
21
pumpkin/src/commands/cmd_pumpkin.rs
Normal file
@ -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(())
|
||||
})
|
||||
}
|
||||
15
pumpkin/src/commands/cmd_stop.rs
Normal file
15
pumpkin/src/commands/cmd_stop.rs
Normal file
@ -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)
|
||||
})
|
||||
)
|
||||
}
|
||||
83
pumpkin/src/commands/dispatcher.rs
Normal file
83
pumpkin/src/commands/dispatcher.rs
Normal file
@ -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<String>),
|
||||
|
||||
/// 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<usize>, tree: &CommandTree, mut raw_args: RawArgs) -> Result<bool, InvalidTreeError> {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -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 <mode>")
|
||||
.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::<u8>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -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<String>),
|
||||
@ -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<CommandDispatcher> = 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());
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
87
pumpkin/src/commands/tree.rs
Normal file
87
pumpkin/src/commands/tree.rs
Normal file
@ -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<String>;
|
||||
|
||||
pub(crate) struct Node<'a> {
|
||||
pub(crate) children: Vec<usize>,
|
||||
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<Node<'a>>,
|
||||
pub(crate) children: Vec<usize>,
|
||||
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<Item = Vec<usize>> + '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::<usize>::new(),
|
||||
todo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TraverseAllPathsIter<'a> {
|
||||
tree: &'a CommandTree<'a>,
|
||||
path: Vec<usize>,
|
||||
/// (depth, i)
|
||||
todo: VecDeque<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl <'a>Iterator for TraverseAllPathsIter<'a> {
|
||||
|
||||
type Item = Vec<usize>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
134
pumpkin/src/commands/tree_builder.rs
Normal file
134
pumpkin/src/commands/tree_builder.rs
Normal file
@ -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<NonLeafNodeBuilder<'a>>,
|
||||
leaf_nodes: Vec<LeafNodeBuilder<'a>>
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user