Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prettier CommandTree formatting for help command and usage hint #48

Merged
merged 2 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_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_text::{color::NamedColor, TextComponent};

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