mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-31 08:22:33 +00:00
Merge branch 'master' into master
This commit is contained in:
@@ -15,7 +15,7 @@ use crate::commands::CommandSender;
|
||||
use crate::commands::CommandSender::Player;
|
||||
use crate::entity::player::GameMode;
|
||||
|
||||
pub(crate) const NAME: &str = "gamemode";
|
||||
const NAMES: [&str; 1] = ["gamemode"];
|
||||
|
||||
const DESCRIPTION: &str = "Change a player's gamemode.";
|
||||
|
||||
@@ -57,7 +57,7 @@ pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result<GameMode, Inva
|
||||
}
|
||||
|
||||
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
|
||||
CommandTree::new(DESCRIPTION).with_child(
|
||||
CommandTree::new(NAMES, DESCRIPTION).with_child(
|
||||
require(&|sender| sender.permission_lvl() >= 2).with_child(
|
||||
argument(ARG_GAMEMODE, consume_arg_gamemode)
|
||||
.with_child(
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError;
|
||||
use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError};
|
||||
use crate::commands::tree::{CommandTree, ConsumedArgs, RawArgs};
|
||||
use crate::commands::tree::{Command, CommandTree, ConsumedArgs, RawArgs};
|
||||
use crate::commands::tree_builder::argument;
|
||||
use crate::commands::{dispatcher_init, CommandSender, DISPATCHER};
|
||||
use pumpkin_core::text::TextComponent;
|
||||
|
||||
pub(crate) const NAME: &str = "help";
|
||||
pub(crate) const ALIAS: &str = "?";
|
||||
const NAMES: [&str; 3] = ["help", "h", "?"];
|
||||
|
||||
const DESCRIPTION: &str = "Print a help message.";
|
||||
|
||||
@@ -17,41 +16,35 @@ fn consume_arg_command(_src: &CommandSender, args: &mut RawArgs) -> Option<Strin
|
||||
|
||||
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
|
||||
|
||||
if dispatcher.commands.contains_key(s) {
|
||||
Some(s.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
dispatcher.get_tree(s).ok().map(|tree| tree.names[0].into())
|
||||
}
|
||||
|
||||
fn parse_arg_command<'a>(
|
||||
consumed_args: &'a ConsumedArgs,
|
||||
dispatcher: &'a CommandDispatcher,
|
||||
) -> Result<(&'a str, &'a CommandTree<'a>), InvalidTreeError> {
|
||||
) -> Result<&'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())))
|
||||
}
|
||||
dispatcher
|
||||
.get_tree(command_name)
|
||||
.map_err(|_| InvalidConsumptionError(Some(command_name.into())))
|
||||
}
|
||||
|
||||
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
|
||||
CommandTree::new(DESCRIPTION)
|
||||
CommandTree::new(NAMES, 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)?;
|
||||
let tree = parse_arg_command(args, dispatcher)?;
|
||||
|
||||
sender.send_message(TextComponent::text(&format!(
|
||||
"{} - {} Usage:{}",
|
||||
name,
|
||||
"{} - {} Usage: {}",
|
||||
tree.names.join("/"),
|
||||
tree.description,
|
||||
tree.paths_formatted(name)
|
||||
tree
|
||||
)));
|
||||
|
||||
Ok(())
|
||||
@@ -60,12 +53,19 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
|
||||
.execute(&|sender, _args| {
|
||||
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
|
||||
|
||||
for (name, tree) in &dispatcher.commands {
|
||||
let mut keys: Vec<&str> = dispatcher.commands.keys().copied().collect();
|
||||
keys.sort();
|
||||
|
||||
for key in keys {
|
||||
let Command::Tree(tree) = &dispatcher.commands[key] else {
|
||||
continue;
|
||||
};
|
||||
|
||||
sender.send_message(TextComponent::text(&format!(
|
||||
"{} - {} Usage:{}",
|
||||
name,
|
||||
"{} - {} Usage: {}",
|
||||
tree.names.join("/"),
|
||||
tree.description,
|
||||
tree.paths_formatted(name)
|
||||
tree
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ use pumpkin_protocol::CURRENT_MC_PROTOCOL;
|
||||
|
||||
use crate::commands::tree::CommandTree;
|
||||
|
||||
pub(crate) const NAME: &str = "pumpkin";
|
||||
const NAMES: [&str; 1] = ["pumpkin"];
|
||||
|
||||
const DESCRIPTION: &str = "Display information about Pumpkin.";
|
||||
|
||||
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
|
||||
CommandTree::new(DESCRIPTION).execute(&|sender, _| {
|
||||
CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, _| {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let description = env!("CARGO_PKG_DESCRIPTION");
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::commands::tree::CommandTree;
|
||||
use crate::commands::tree_builder::require;
|
||||
|
||||
pub(crate) const NAME: &str = "stop";
|
||||
const NAMES: [&str; 1] = ["stop"];
|
||||
|
||||
const DESCRIPTION: &str = "Stop the server.";
|
||||
|
||||
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
|
||||
CommandTree::new(DESCRIPTION).with_child(
|
||||
CommandTree::new(NAMES, DESCRIPTION).with_child(
|
||||
require(&|sender| sender.permission_lvl() >= 4)
|
||||
.execute(&|_sender, _args| std::process::exit(0)),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::commands::dispatcher::InvalidTreeError::{
|
||||
InvalidConsumptionError, InvalidRequirementError,
|
||||
};
|
||||
use crate::commands::tree::{CommandTree, ConsumedArgs, NodeType, RawArgs};
|
||||
use crate::commands::tree::{Command, CommandTree, ConsumedArgs, NodeType, RawArgs};
|
||||
use crate::commands::CommandSender;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -17,7 +17,7 @@ pub(crate) enum InvalidTreeError {
|
||||
}
|
||||
|
||||
pub(crate) struct CommandDispatcher<'a> {
|
||||
pub(crate) commands: HashMap<&'a str, CommandTree<'a>>,
|
||||
pub(crate) commands: HashMap<&'a str, Command<'a>>,
|
||||
}
|
||||
|
||||
/// Stores registered [CommandTree]s and dispatches commands to them.
|
||||
@@ -28,7 +28,7 @@ impl<'a> CommandDispatcher<'a> {
|
||||
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")?;
|
||||
let tree = self.get_tree(key)?;
|
||||
|
||||
// try paths until fitting path is found
|
||||
for path in tree.iter_paths() {
|
||||
@@ -49,10 +49,22 @@ impl<'a> CommandDispatcher<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"Invalid Syntax. Usage:{}",
|
||||
tree.paths_formatted(key)
|
||||
))
|
||||
Err(format!("Invalid Syntax. Usage: {}", tree))
|
||||
}
|
||||
|
||||
pub(crate) fn get_tree(&'a self, key: &str) -> Result<&'a CommandTree<'a>, String> {
|
||||
let command = self.commands.get(key).ok_or("Command not found")?;
|
||||
|
||||
match command {
|
||||
Command::Tree(tree) => Ok(tree),
|
||||
Command::Alias(target) => {
|
||||
let Some(Command::Tree(tree)) = &self.commands.get(target) else {
|
||||
println!("Error while parsing command alias \"{key}\": pointing to \"{target}\" which is not a valid tree");
|
||||
return Err("Internal Error (See logs for details)".into());
|
||||
};
|
||||
Ok(tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_is_fitting_path(
|
||||
@@ -99,4 +111,17 @@ impl<'a> CommandDispatcher<'a> {
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Register a command with the dispatcher.
|
||||
pub(crate) fn register(&mut self, tree: CommandTree<'a>) {
|
||||
let mut names = tree.names.iter();
|
||||
|
||||
let primary_name = names.next().expect("at least one name must be provided");
|
||||
|
||||
for &name in names {
|
||||
self.commands.insert(name, Command::Alias(primary_name));
|
||||
}
|
||||
|
||||
self.commands.insert(primary_name, Command::Tree(tree));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ mod cmd_stop;
|
||||
mod dispatcher;
|
||||
mod tree;
|
||||
mod tree_builder;
|
||||
mod tree_format;
|
||||
|
||||
pub enum CommandSender<'a> {
|
||||
Rcon(&'a mut Vec<String>),
|
||||
@@ -70,14 +71,14 @@ static DISPATCHER: OnceLock<CommandDispatcher> = OnceLock::new();
|
||||
|
||||
/// create [CommandDispatcher] instance for [DISPATCHER]
|
||||
fn dispatcher_init<'a>() -> CommandDispatcher<'a> {
|
||||
let mut map = HashMap::new();
|
||||
let mut dispatcher = CommandDispatcher {
|
||||
commands: 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());
|
||||
map.insert(cmd_help::NAME, cmd_help::init_command_tree());
|
||||
map.insert(cmd_help::ALIAS, cmd_help::init_command_tree());
|
||||
CommandDispatcher { commands: map }
|
||||
dispatcher.register(cmd_pumpkin::init_command_tree());
|
||||
dispatcher.register(cmd_gamemode::init_command_tree());
|
||||
dispatcher.register(cmd_stop::init_command_tree());
|
||||
dispatcher.register(cmd_help::init_command_tree());
|
||||
}
|
||||
|
||||
pub fn handle_command(sender: &mut CommandSender, cmd: &str) {
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use crate::commands::dispatcher::InvalidTreeError;
|
||||
use crate::commands::CommandSender;
|
||||
|
||||
/// see [crate::commands::tree_builder::argument]
|
||||
pub(crate) type RawArgs<'a> = Vec<&'a str>;
|
||||
|
||||
@@ -31,9 +32,15 @@ pub(crate) enum NodeType<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) enum Command<'a> {
|
||||
Tree(CommandTree<'a>),
|
||||
Alias(&'a str),
|
||||
}
|
||||
|
||||
pub(crate) struct CommandTree<'a> {
|
||||
pub(crate) nodes: Vec<Node<'a>>,
|
||||
pub(crate) children: Vec<usize>,
|
||||
pub(crate) names: Vec<&'a str>,
|
||||
pub(crate) description: &'a str,
|
||||
}
|
||||
|
||||
@@ -51,57 +58,6 @@ impl<'a> CommandTree<'a> {
|
||||
todo,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Vec<&NodeType>> = 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::<usize>()
|
||||
+ name.len()
|
||||
+ 2
|
||||
})
|
||||
.sum::<usize>();
|
||||
|
||||
let mut s = String::with_capacity(len);
|
||||
|
||||
for path in paths.iter() {
|
||||
s.push(if paths.len() > 1 { '\n' } else { ' ' });
|
||||
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> {
|
||||
|
||||
@@ -11,10 +11,23 @@ impl<'a> CommandTree<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn new(description: &'a str) -> Self {
|
||||
/// provide at least one name
|
||||
pub fn new<const NAME_COUNT: usize>(
|
||||
names: [&'a str; NAME_COUNT],
|
||||
description: &'a str,
|
||||
) -> Self {
|
||||
assert!(NAME_COUNT > 0);
|
||||
|
||||
let mut names_vec = Vec::with_capacity(NAME_COUNT);
|
||||
|
||||
for name in names {
|
||||
names_vec.push(name);
|
||||
}
|
||||
|
||||
Self {
|
||||
nodes: Vec::new(),
|
||||
children: Vec::new(),
|
||||
names: names_vec,
|
||||
description,
|
||||
}
|
||||
}
|
||||
|
||||
125
pumpkin/src/commands/tree_format.rs
Normal file
125
pumpkin/src/commands/tree_format.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use crate::commands::tree::{CommandTree, Node, NodeType};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::{Display, Formatter, Write};
|
||||
|
||||
trait IsVisible {
|
||||
/// whether node should be printed in help command/usage hint
|
||||
fn is_visible(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<'a> IsVisible for Node<'a> {
|
||||
fn is_visible(&self) -> bool {
|
||||
match self.node_type {
|
||||
NodeType::ExecuteLeaf { .. } => false,
|
||||
NodeType::Literal { .. } => true,
|
||||
NodeType::Argument { .. } => true,
|
||||
NodeType::Require { .. } => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Node<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self.node_type {
|
||||
NodeType::Literal { string } => {
|
||||
f.write_str(string)?;
|
||||
}
|
||||
NodeType::Argument { name, .. } => {
|
||||
f.write_char('<')?;
|
||||
f.write_str(name)?;
|
||||
f.write_char('>')?;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_require_nodes(nodes: &[Node], children: &[usize]) -> Vec<usize> {
|
||||
let mut new_children = Vec::with_capacity(children.len());
|
||||
|
||||
for &i in children {
|
||||
let node = &nodes[i];
|
||||
match &node.node_type {
|
||||
NodeType::Require { .. } => {
|
||||
new_children.extend(flatten_require_nodes(nodes, node.children.as_slice()))
|
||||
}
|
||||
_ => new_children.push(i),
|
||||
}
|
||||
}
|
||||
|
||||
new_children
|
||||
}
|
||||
|
||||
impl<'a> Display for CommandTree<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_char('/')?;
|
||||
f.write_str(self.names[0])?;
|
||||
|
||||
let mut todo = VecDeque::<&[usize]>::with_capacity(self.children.len());
|
||||
todo.push_back(&self.children);
|
||||
|
||||
loop {
|
||||
let Some(children) = todo.pop_front() else {
|
||||
break;
|
||||
};
|
||||
|
||||
let flattened_children = flatten_require_nodes(&self.nodes, children);
|
||||
let visible_children = flattened_children
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&i| self.nodes[i].is_visible())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if visible_children.is_empty() {
|
||||
break;
|
||||
};
|
||||
|
||||
f.write_char(' ')?;
|
||||
|
||||
let is_optional = flattened_children
|
||||
.iter()
|
||||
.map(|&i| &self.nodes[i].node_type)
|
||||
.any(|node| matches!(node, NodeType::ExecuteLeaf { .. }));
|
||||
|
||||
if is_optional {
|
||||
f.write_char('[')?;
|
||||
}
|
||||
|
||||
match visible_children.as_slice() {
|
||||
[] => unreachable!(),
|
||||
[i] => {
|
||||
let node = &self.nodes[*i];
|
||||
|
||||
node.fmt(f)?;
|
||||
|
||||
todo.push_back(&node.children);
|
||||
}
|
||||
_ => {
|
||||
// todo: handle cases where one of these nodes has visible children
|
||||
f.write_char('(')?;
|
||||
|
||||
let mut iter = visible_children.iter().map(|&i| &self.nodes[i]);
|
||||
|
||||
if let Some(node) = iter.next() {
|
||||
node.fmt(f)?;
|
||||
}
|
||||
|
||||
for node in iter {
|
||||
f.write_char('|')?;
|
||||
node.fmt(f)?;
|
||||
}
|
||||
|
||||
f.write_char(')')?;
|
||||
}
|
||||
}
|
||||
|
||||
if is_optional {
|
||||
f.write_char(']')?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user