From 270507736af57aae6801dc9eb3c3132139d0d07b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 12 Jan 2022 00:40:43 +0000 Subject: [PATCH] a --- Cargo.lock | 28 ++++++ azalea-brigadier/Cargo.toml | 1 + azalea-brigadier/README.md | 1 + .../src/arguments/argument_type.rs | 28 ++++-- .../src/arguments/bool_argument_type.rs | 1 + .../src/builder/argument_builder.rs | 8 +- .../src/builder/literal_argument_builder.rs | 15 +++- .../src/builder/required_argument_builder.rs | 11 ++- azalea-brigadier/src/command.rs | 4 +- azalea-brigadier/src/command_dispatcher.rs | 17 +++- .../src/context/command_context_builder.rs | 16 +++- .../src/context/parsed_command_node.rs | 9 ++ azalea-brigadier/src/redirect_modifier.rs | 5 +- .../src/suggestion/integer_suggestion.rs | 1 + .../src/suggestion/suggestions.rs | 86 ++++++++++++++++++- .../src/suggestion/suggestions_builder.rs | 4 +- .../src/tree/argument_command_node.rs | 17 +++- azalea-brigadier/src/tree/command_node.rs | 59 +++++++++++-- .../src/tree/literal_command_node.rs | 20 ++++- .../src/tree/root_command_node.rs | 19 +++- 20 files changed, 307 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0eeb6520..a0ca81fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,7 @@ dependencies = [ name = "azalea-brigadier" version = "0.1.0" dependencies = [ + "dyn-clonable", "lazy_static", ] @@ -336,6 +337,33 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + [[package]] name = "either" version = "1.6.1" diff --git a/azalea-brigadier/Cargo.toml b/azalea-brigadier/Cargo.toml index 3694a4b7..4e8968d7 100644 --- a/azalea-brigadier/Cargo.toml +++ b/azalea-brigadier/Cargo.toml @@ -7,3 +7,4 @@ version = "0.1.0" [dependencies] lazy_static = "^1.4" +dyn-clonable = "^0.9" diff --git a/azalea-brigadier/README.md b/azalea-brigadier/README.md index 92c0d27e..df69b5c0 100644 --- a/azalea-brigadier/README.md +++ b/azalea-brigadier/README.md @@ -1,3 +1,4 @@ # Azalea Brigadier A Rustier port of Mojang's [Brigadier](https://github.com/Mojang/brigadier) command parsing and dispatching library. + diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs index 46026735..890cdea0 100644 --- a/azalea-brigadier/src/arguments/argument_type.rs +++ b/azalea-brigadier/src/arguments/argument_type.rs @@ -5,11 +5,19 @@ use crate::{ string_reader::StringReader, suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder}, }; +use dyn_clonable::*; -pub trait Types { +#[clonable] +// This should be applied to an Enum +pub trait Types: Clone { fn bool(value: bool) -> Self where Self: Sized; + + /// Get the less specific ArgumentType from this enum + fn inner(&self) -> Box> + where + Self: Sized; } /* @@ -25,12 +33,21 @@ enum BrigadierTypes { Entity(EntityArgumentType) } + +impl Types for BrigadierTypes { + fn inner(&self) -> dyn ArgumentType { + match self { + Bool(t) => t, + Entity(t) => t + } + } +} */ -pub trait ArgumentType +#[clonable] +pub trait ArgumentType: Clone where - Self: Sized, - T: Types + ?Sized, + T: Types, { // T parse(StringReader reader) throws CommandSyntaxException; @@ -42,7 +59,7 @@ where // return Collections.emptyList(); // } - fn parse(&self, reader: &mut StringReader) -> Result; + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException>; fn list_suggestions( &self, @@ -50,6 +67,7 @@ where builder: &mut SuggestionsBuilder, ) -> Result where + Self: Sized, S: Sized, T: Sized; diff --git a/azalea-brigadier/src/arguments/bool_argument_type.rs b/azalea-brigadier/src/arguments/bool_argument_type.rs index 1237caa0..b04488c1 100644 --- a/azalea-brigadier/src/arguments/bool_argument_type.rs +++ b/azalea-brigadier/src/arguments/bool_argument_type.rs @@ -7,6 +7,7 @@ use crate::{ use super::argument_type::{ArgumentType, Types}; +#[derive(Clone)] pub struct BoolArgumentType {} impl ArgumentType for BoolArgumentType diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs index bd2a2c15..0360b05a 100644 --- a/azalea-brigadier/src/builder/argument_builder.rs +++ b/azalea-brigadier/src/builder/argument_builder.rs @@ -1,4 +1,5 @@ use crate::{ + arguments::argument_type::{ArgumentType, Types}, command::Command, redirect_modifier::RedirectModifier, single_redirect_modifier::SingleRedirectModifier, @@ -8,7 +9,7 @@ use crate::{ pub struct BaseArgumentBuilder<'a, S, T> where S: Sized, - T: Sized, + T: Sized + ArgumentType, { arguments: RootCommandNode<'a, S, T>, command: Option<&'a dyn Command>, @@ -22,7 +23,10 @@ pub trait ArgumentBuilder { fn build(self) -> dyn CommandNode; } -impl BaseArgumentBuilder<'_, S, T> { +impl BaseArgumentBuilder<'_, S, T> +where + T: ArgumentType, +{ pub fn then(&mut self, command: dyn CommandNode) -> Result<&mut T, String> { if self.target.is_some() { return Err("Cannot add children to a redirected node".to_string()); diff --git a/azalea-brigadier/src/builder/literal_argument_builder.rs b/azalea-brigadier/src/builder/literal_argument_builder.rs index cf9f1ee9..a4cb3f84 100644 --- a/azalea-brigadier/src/builder/literal_argument_builder.rs +++ b/azalea-brigadier/src/builder/literal_argument_builder.rs @@ -1,14 +1,23 @@ -use crate::tree::literal_command_node::LiteralCommandNode; +use crate::{ + arguments::argument_type::{ArgumentType, Types}, + tree::literal_command_node::LiteralCommandNode, +}; use super::argument_builder::BaseArgumentBuilder; -pub struct LiteralArgumentBuilder<'a, S, T> { +pub struct LiteralArgumentBuilder<'a, S, T> +where + T: ArgumentType, +{ literal: String, pub base: BaseArgumentBuilder<'a, S, T>, } -impl<'a, S, T> LiteralArgumentBuilder<'a, S, T> { +impl<'a, S, T> LiteralArgumentBuilder<'a, S, T> +where + T: ArgumentType, +{ pub fn new(literal: String) -> Self { Self { literal, diff --git a/azalea-brigadier/src/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs index 6f6fa8eb..b5f99828 100644 --- a/azalea-brigadier/src/builder/required_argument_builder.rs +++ b/azalea-brigadier/src/builder/required_argument_builder.rs @@ -1,11 +1,15 @@ use crate::{ + arguments::argument_type::{ArgumentType, Types}, suggestion::suggestion_provider::SuggestionProvider, tree::{argument_command_node::ArgumentCommandNode, command_node::BaseCommandNode}, }; use super::argument_builder::BaseArgumentBuilder; -pub struct RequiredArgumentBuilder<'a, S, T> { +pub struct RequiredArgumentBuilder<'a, S, T> +where + T: ArgumentType, +{ // private final String name; // private final ArgumentType type; // private SuggestionProvider suggestionsProvider = null; @@ -16,7 +20,10 @@ pub struct RequiredArgumentBuilder<'a, S, T> { pub base: BaseArgumentBuilder<'a, S, T>, } -impl<'a, S, T> RequiredArgumentBuilder<'a, S, T> { +impl<'a, S, T> RequiredArgumentBuilder<'a, S, T> +where + T: ArgumentType, +{ pub fn new(name: String, type_: T) -> Self { Self { name, diff --git a/azalea-brigadier/src/command.rs b/azalea-brigadier/src/command.rs index 520c8a52..dcbf3ffd 100644 --- a/azalea-brigadier/src/command.rs +++ b/azalea-brigadier/src/command.rs @@ -2,9 +2,11 @@ use crate::{ context::command_context::CommandContext, exceptions::command_syntax_exception::CommandSyntaxException, }; +use dyn_clonable::*; pub const SINGLE_SUCCESS: i32 = 1; -pub trait Command { +#[clonable] +pub trait Command: Clone { fn run(&self, context: &mut CommandContext) -> Result; } diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs index d0351547..f2fc7528 100644 --- a/azalea-brigadier/src/command_dispatcher.rs +++ b/azalea-brigadier/src/command_dispatcher.rs @@ -1,13 +1,22 @@ -use crate::tree::root_command_node::RootCommandNode; +use crate::{ + arguments::argument_type::{ArgumentType, Types}, + tree::root_command_node::RootCommandNode, +}; /// The core command dispatcher, for registering, parsing, and executing commands. /// The `S` generic is a custom "source" type, such as a user or originator of a command -#[derive(Default)] -pub struct CommandDispatcher<'a, S, T> { +#[derive(Default, Clone)] +pub struct CommandDispatcher<'a, S, T> +where + T: ArgumentType, +{ root: RootCommandNode<'a, S, T>, } -impl CommandDispatcher<'_, S, T> { +impl CommandDispatcher<'_, S, T> +where + T: ArgumentType, +{ /// The string required to separate individual arguments in an input string /// /// See: [`ARGUMENT_SEPARATOR_CHAR`] diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs index 88e26343..639a97ee 100644 --- a/azalea-brigadier/src/context/command_context_builder.rs +++ b/azalea-brigadier/src/context/command_context_builder.rs @@ -1,8 +1,10 @@ use std::collections::HashMap; use crate::{ - arguments::argument_type::ArgumentType, command::Command, - command_dispatcher::CommandDispatcher, redirect_modifier::RedirectModifier, + arguments::argument_type::{ArgumentType, Types}, + command::Command, + command_dispatcher::CommandDispatcher, + redirect_modifier::RedirectModifier, tree::command_node::CommandNode, }; @@ -25,7 +27,10 @@ use super::{ // private boolean forks; #[derive(Clone)] -pub struct CommandContextBuilder<'a, S, T> { +pub struct CommandContextBuilder<'a, S, T> +where + T: ArgumentType, +{ arguments: HashMap>, root_node: &'a dyn CommandNode, nodes: Vec>, @@ -45,7 +50,10 @@ pub struct CommandContextBuilder<'a, S, T> { // this.range = StringRange.at(start); // } -impl CommandContextBuilder<'_, S, T> { +impl CommandContextBuilder<'_, S, T> +where + T: ArgumentType, +{ pub fn new( dispatcher: CommandDispatcher, source: S, diff --git a/azalea-brigadier/src/context/parsed_command_node.rs b/azalea-brigadier/src/context/parsed_command_node.rs index 14168a06..c0be355c 100644 --- a/azalea-brigadier/src/context/parsed_command_node.rs +++ b/azalea-brigadier/src/context/parsed_command_node.rs @@ -19,3 +19,12 @@ impl ParsedCommandNode { &self.range } } + +impl Clone for ParsedCommandNode { + fn clone_from(&mut self, source: &Self) { + Self { + node: self.node.clone(), + range: self.range.clone(), + } + } +} diff --git a/azalea-brigadier/src/redirect_modifier.rs b/azalea-brigadier/src/redirect_modifier.rs index fd2e1bf7..fdb0a080 100644 --- a/azalea-brigadier/src/redirect_modifier.rs +++ b/azalea-brigadier/src/redirect_modifier.rs @@ -1,8 +1,11 @@ +use dyn_clonable::*; + use crate::{ context::command_context::CommandContext, exceptions::command_syntax_exception::CommandSyntaxException, }; -pub trait RedirectModifier { +#[clonable] +pub trait RedirectModifier: Clone { fn apply(&self, context: CommandContext) -> Result, CommandSyntaxException>; } diff --git a/azalea-brigadier/src/suggestion/integer_suggestion.rs b/azalea-brigadier/src/suggestion/integer_suggestion.rs index e69de29b..acee2329 100644 --- a/azalea-brigadier/src/suggestion/integer_suggestion.rs +++ b/azalea-brigadier/src/suggestion/integer_suggestion.rs @@ -0,0 +1 @@ +pub struct IntegerSuggestion {} diff --git a/azalea-brigadier/src/suggestion/suggestions.rs b/azalea-brigadier/src/suggestion/suggestions.rs index 18572d20..778c5de8 100644 --- a/azalea-brigadier/src/suggestion/suggestions.rs +++ b/azalea-brigadier/src/suggestion/suggestions.rs @@ -1,8 +1,90 @@ -use std::cmp; +use std::{cmp, collections::HashSet}; use crate::{context::string_range::StringRange, message::Message}; -pub struct Suggestions {} +use super::suggestion::Suggestion; + +#[derive(PartialEq, Eq, Hash, Default)] +pub struct Suggestions { + range: StringRange, + suggestions: Vec, +} + +impl Suggestions { + fn range(&self) -> &StringRange { + &self.range + } + + fn list(&self) -> &Vec { + &self.suggestions + } + + fn is_empty(&self) -> bool { + self.suggestions.is_empty() + } + + fn merge(command: &str, input: &Vec) { + if input.is_empty() { + return Self::default(); + } else if input.len() == 1 { + return input.iter().next(); + } + let texts = HashSet::new(); + for suggestions in input { + texts.extend(suggestions.list()) + } + Self::new(command, texts) + } + + // public static Suggestions create(final String command, final Collection suggestions) { + // if (suggestions.isEmpty()) { + // return EMPTY; + // } + // int start = Integer.MAX_VALUE; + // int end = Integer.MIN_VALUE; + // for (final Suggestion suggestion : suggestions) { + // start = Math.min(suggestion.getRange().getStart(), start); + // end = Math.max(suggestion.getRange().getEnd(), end); + // } + // final StringRange range = new StringRange(start, end); + // final Set texts = new HashSet<>(); + // for (final Suggestion suggestion : suggestions) { + // texts.add(suggestion.expand(command, range)); + // } + // final List sorted = new ArrayList<>(texts); + // sorted.sort((a, b) -> a.compareToIgnoreCase(b)); + // return new Suggestions(range, sorted); + pub fn new(command: String, suggestions: Vec) -> Self { + if suggestions.is_empty() { + return Self::default(); + } + let mut start = usize::MAX; + let mut end = usize::MIN; + for suggestion in suggestions { + let start = cmp::min(suggestion.range().start(), start); + let end = cmp::max(suggestion.range().end(), end); + } + let range = StringRange::new(start, end); + let texts = HashSet::new(); + for suggestion in suggestions { + texts.insert(suggestion.expand(command, range)); + } + let sorted = texts.sort_by(|a, b| a.compare_ignore_case(b)); + Suggestions { + range, + suggestions: sorted, + } + } +} + +impl Default for Suggestions { + fn default() -> Self { + Self { + range: StringRange::at(0), + suggestions: vec![], + } + } +} // #[cfg(test)] // mod tests { diff --git a/azalea-brigadier/src/suggestion/suggestions_builder.rs b/azalea-brigadier/src/suggestion/suggestions_builder.rs index 7853a3a2..bc8f6f5d 100644 --- a/azalea-brigadier/src/suggestion/suggestions_builder.rs +++ b/azalea-brigadier/src/suggestion/suggestions_builder.rs @@ -1,6 +1,8 @@ use crate::context::string_range::StringRange; -use super::{suggestion::Suggestion, suggestions::Suggestions}; +use super::{ + integer_suggestion::IntegerSuggestion, suggestion::Suggestion, suggestions::Suggestions, +}; pub struct SuggestionsBuilder { input: String, diff --git a/azalea-brigadier/src/tree/argument_command_node.rs b/azalea-brigadier/src/tree/argument_command_node.rs index 647b6c35..3fc1bb50 100644 --- a/azalea-brigadier/src/tree/argument_command_node.rs +++ b/azalea-brigadier/src/tree/argument_command_node.rs @@ -21,6 +21,7 @@ use super::command_node::{BaseCommandNode, CommandNode}; const USAGE_ARGUMENT_OPEN: &str = "<"; const USAGE_ARGUMENT_CLOSE: &str = ">"; +#[derive(Clone)] pub struct ArgumentCommandNode<'a, S, T> where // each argument command node has its own different type @@ -34,7 +35,10 @@ where pub base: BaseCommandNode<'a, S, T>, } -impl ArgumentCommandNode<'_, S, T> { +impl ArgumentCommandNode<'_, S, T> +where + T: ArgumentType, +{ fn get_type(&self) -> &T { &self.type_ } @@ -44,7 +48,11 @@ impl ArgumentCommandNode<'_, S, T> { } } -impl<'a, S, T> CommandNode for ArgumentCommandNode<'a, S, T> { +impl<'a, S, T> CommandNode for ArgumentCommandNode<'a, S, T> +where + T: ArgumentType + Clone, + S: Clone, +{ fn name(&self) -> &str { &self.name } @@ -117,7 +125,10 @@ impl<'a, S, T> CommandNode for ArgumentCommandNode<'a, S, T> { } } -impl Display for ArgumentCommandNode<'_, (), (), ()> { +impl Display for ArgumentCommandNode<'_, S, T> +where + T: ArgumentType, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "", self.name, self.type_) } diff --git a/azalea-brigadier/src/tree/command_node.rs b/azalea-brigadier/src/tree/command_node.rs index 8e262f0b..f3be1597 100644 --- a/azalea-brigadier/src/tree/command_node.rs +++ b/azalea-brigadier/src/tree/command_node.rs @@ -1,7 +1,6 @@ -use std::collections::HashMap; - +use super::{argument_command_node::ArgumentCommandNode, literal_command_node::LiteralCommandNode}; use crate::{ - arguments::argument_type::ArgumentType, + arguments::argument_type::{ArgumentType, Types}, builder::argument_builder::ArgumentBuilder, command::Command, context::{command_context::CommandContext, command_context_builder::CommandContextBuilder}, @@ -10,10 +9,14 @@ use crate::{ string_reader::StringReader, suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder}, }; +use dyn_clonable::*; +use std::{collections::HashMap, fmt::Debug}; -use super::{argument_command_node::ArgumentCommandNode, literal_command_node::LiteralCommandNode}; - -pub struct BaseCommandNode<'a, S, T> { +#[derive(Default)] +pub struct BaseCommandNode<'a, S, T> +where + T: ArgumentType, +{ children: HashMap>, literals: HashMap>, arguments: HashMap>, @@ -24,9 +27,49 @@ pub struct BaseCommandNode<'a, S, T> { command: Option<&'a dyn Command>, } -impl BaseCommandNode<'_, S, T> {} +impl BaseCommandNode<'_, S, T> where T: ArgumentType {} -pub trait CommandNode { +impl Clone for BaseCommandNode<'_, S, T> +where + T: ArgumentType, +{ + fn clone(&self) -> Self { + Self { + children: self.children.clone(), + literals: self.literals.clone(), + arguments: self.arguments.clone(), + requirement: self.requirement.clone(), + redirect: self.redirect.clone(), + modifier: self.modifier.clone(), + forks: self.forks.clone(), + command: self.command.clone(), + } + } +} + +impl Debug for BaseCommandNode<'_, S, T> +where + T: ArgumentType, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BaseCommandNode") + .field("children", &self.children) + .field("literals", &self.literals) + .field("arguments", &self.arguments) + .field("requirement", &self.requirement) + .field("redirect", &self.redirect) + .field("modifier", &self.modifier) + .field("forks", &self.forks) + .field("command", &self.command) + .finish() + } +} + +#[clonable] +pub trait CommandNode: Clone +where + T: ArgumentType, +{ fn name(&self) -> &str; fn usage_text(&self) -> &str; fn parse( diff --git a/azalea-brigadier/src/tree/literal_command_node.rs b/azalea-brigadier/src/tree/literal_command_node.rs index fe933669..021a3ea6 100644 --- a/azalea-brigadier/src/tree/literal_command_node.rs +++ b/azalea-brigadier/src/tree/literal_command_node.rs @@ -1,4 +1,5 @@ use crate::{ + arguments::argument_type::{ArgumentType, Types}, builder::literal_argument_builder::LiteralArgumentBuilder, command::Command, context::{command_context::CommandContext, command_context_builder::CommandContextBuilder}, @@ -12,15 +13,22 @@ use crate::{ use super::command_node::{BaseCommandNode, CommandNode}; -#[derive(Hash, PartialEq, Eq, Debug, Clone)] -pub struct LiteralCommandNode<'a, S, T> { +#[derive(Debug, Clone)] +pub struct LiteralCommandNode<'a, S, T> +where + // each argument command node has its own different type + T: ArgumentType, +{ literal: String, literal_lowercase: String, // Since Rust doesn't have extending, we put the struct this is extending as the "base" field pub base: BaseCommandNode<'a, S, T>, } -impl<'a, S, T> LiteralCommandNode<'a, S, T> { +impl<'a, S, T> LiteralCommandNode<'a, S, T> +where + T: ArgumentType, +{ pub fn new(literal: String, base: BaseCommandNode) -> Self { let literal_lowercase = literal.to_lowercase(); Self { @@ -51,7 +59,11 @@ impl<'a, S, T> LiteralCommandNode<'a, S, T> { } } -impl CommandNode for LiteralCommandNode<'_, S, T> { +impl CommandNode for LiteralCommandNode<'_, S, T> +where + T: ArgumentType + Clone, + S: Clone, +{ fn name(&self) -> &str { &self.literal } diff --git a/azalea-brigadier/src/tree/root_command_node.rs b/azalea-brigadier/src/tree/root_command_node.rs index 36787340..ded5fa77 100644 --- a/azalea-brigadier/src/tree/root_command_node.rs +++ b/azalea-brigadier/src/tree/root_command_node.rs @@ -1,6 +1,7 @@ use std::fmt::{Display, Formatter}; use crate::{ + arguments::argument_type::{ArgumentType, Types}, context::{command_context::CommandContext, command_context_builder::CommandContextBuilder}, exceptions::{ builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException, @@ -11,12 +12,21 @@ use crate::{ use super::command_node::{BaseCommandNode, CommandNode}; -pub struct RootCommandNode<'a, S, T> { +#[derive(Clone, Default)] +pub struct RootCommandNode<'a, S, T> +where + // each argument command node has its own different type + T: ArgumentType, +{ // Since Rust doesn't have extending, we put the struct this is extending as the "base" field pub base: BaseCommandNode<'a, S, T>, } -impl CommandNode for RootCommandNode<'_, S, T> { +impl CommandNode for RootCommandNode<'_, S, T> +where + T: ArgumentType + Clone, + S: Clone, +{ fn name(&self) -> &str { "" } @@ -53,7 +63,10 @@ impl CommandNode for RootCommandNode<'_, S, T> { } } -impl Display for RootCommandNode<'_, S, T> { +impl Display for RootCommandNode<'_, S, T> +where + T: ArgumentType, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "") }