Skip to content

Commit

Permalink
Merge pull request #48 from user622628252416/master
Browse files Browse the repository at this point in the history
Add prettier CommandTree formatting for help command and usage hint
  • Loading branch information
Snowiiii authored Aug 21, 2024
2 parents c02200c + ab8956a commit bda7a1a
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 95 deletions.
4 changes: 2 additions & 2 deletions pumpkin/src/commands/cmd_gamemode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.";

Expand Down Expand Up @@ -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(
Expand Down
46 changes: 23 additions & 23 deletions pumpkin/src/commands/cmd_help.rs
Original file line number Diff line number Diff line change
@@ -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.";

Expand All @@ -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(())
Expand All @@ -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
)));
}

Expand Down
4 changes: 2 additions & 2 deletions pumpkin/src/commands/cmd_pumpkin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
4 changes: 2 additions & 2 deletions pumpkin/src/commands/cmd_stop.rs
Original file line number Diff line number Diff line change
@@ -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)),
)
Expand Down
39 changes: 32 additions & 7 deletions pumpkin/src/commands/dispatcher.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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.
Expand All @@ -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() {
Expand All @@ -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(
Expand Down Expand Up @@ -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));
}
}
16 changes: 9 additions & 7 deletions pumpkin/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>),
Expand Down Expand Up @@ -70,15 +71,16 @@ 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());
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());

CommandDispatcher { commands: map }
dispatcher
}

pub fn handle_command(sender: &mut CommandSender, cmd: &str) {
Expand Down
58 changes: 7 additions & 51 deletions pumpkin/src/commands/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;

Expand Down Expand Up @@ -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,
}

Expand All @@ -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> {
Expand Down
15 changes: 14 additions & 1 deletion pumpkin/src/commands/tree_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
Loading

0 comments on commit bda7a1a

Please sign in to comment.