559 lines
18 KiB
Rust
559 lines
18 KiB
Rust
use crate::command::argument_builder::private::Sealed;
|
|
use crate::command::argument_types::argument_type::AnyArgumentType;
|
|
use crate::command::node::detached::{
|
|
ArgumentDetachedNode, CommandDetachedNode, DetachedNode, GlobalNodeId, LiteralDetachedNode,
|
|
};
|
|
use crate::command::node::{
|
|
Command, CommandExecutor, RedirectModifier, Redirection, Requirement, Requirements,
|
|
};
|
|
use crate::command::suggestion::provider::SuggestionProvider;
|
|
use rustc_hash::FxHashMap;
|
|
use std::borrow::Cow;
|
|
use std::sync::Arc;
|
|
|
|
/// Represents an intermediate struct for
|
|
/// building arguments for commands.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// This is an implementation detail.
|
|
struct CommonArgumentBuilder {
|
|
pub global_id: GlobalNodeId,
|
|
pub arguments: FxHashMap<String, DetachedNode>,
|
|
pub command: Option<Command>,
|
|
pub requirements: Requirements,
|
|
pub target: Option<Redirection>,
|
|
pub modifier: RedirectModifier,
|
|
pub forks: bool,
|
|
}
|
|
|
|
impl CommonArgumentBuilder {
|
|
fn new() -> Self {
|
|
Self {
|
|
global_id: GlobalNodeId::new(),
|
|
arguments: FxHashMap::default(),
|
|
command: None,
|
|
requirements: Requirements::new(),
|
|
target: None,
|
|
modifier: RedirectModifier::KeepSource,
|
|
forks: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for CommonArgumentBuilder {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// A short-form way to create a new [`CommandArgumentBuilder`]
|
|
/// from a literal and a command description.
|
|
///
|
|
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
|
|
/// ```
|
|
/// use pumpkin::command::argument_builder::command;
|
|
///
|
|
/// let builder = command("foo", "A test command");
|
|
/// ```
|
|
///
|
|
/// The builder returned will eventually construct a [`CommandDetachedNode`].
|
|
/// This node can then be registered into a dispatcher.
|
|
pub fn command(
|
|
literal: impl Into<Cow<'static, str>>,
|
|
description: impl Into<Cow<'static, str>>,
|
|
) -> CommandArgumentBuilder {
|
|
CommandArgumentBuilder::new(literal, description)
|
|
}
|
|
|
|
/// A short-form way to create a new [`LiteralArgumentBuilder`]
|
|
/// from a literal.
|
|
///
|
|
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
|
|
/// ```
|
|
/// use pumpkin::command::argument_builder::literal;
|
|
///
|
|
/// let builder = literal("bar");
|
|
/// ```
|
|
///
|
|
/// The builder returned will eventually construct a [`LiteralDetachedNode`].
|
|
pub fn literal(literal: impl Into<Cow<'static, str>>) -> LiteralArgumentBuilder {
|
|
LiteralArgumentBuilder::new(literal)
|
|
}
|
|
|
|
/// A short-form way to create a new [`RequiredArgumentBuilder`]
|
|
/// from an argument type and name.
|
|
///
|
|
/// This can be imported directly without a prefix, or imported with the `argument_builder::` prefix. Here's an example of usage:
|
|
/// ```
|
|
/// use pumpkin::command::{
|
|
/// argument_builder::argument,
|
|
/// argument_types::core::integer::IntegerArgumentType
|
|
/// };
|
|
///
|
|
/// let argument_builder = argument("bar", IntegerArgumentType::new(1, 10));
|
|
/// ```
|
|
///
|
|
/// The builder returned will eventually construct a [`ArgumentDetachedNode`].
|
|
pub fn argument(
|
|
name: impl Into<Cow<'static, str>>,
|
|
arg_type: impl AnyArgumentType + 'static,
|
|
) -> RequiredArgumentBuilder {
|
|
RequiredArgumentBuilder::new(name, arg_type)
|
|
}
|
|
|
|
/// A builder that builds a literal, non-command [`DetachedNode`].
|
|
pub struct LiteralArgumentBuilder {
|
|
common: CommonArgumentBuilder,
|
|
literal: Cow<'static, str>,
|
|
}
|
|
|
|
/// A builder that builds a command [`DetachedNode`].
|
|
pub struct CommandArgumentBuilder {
|
|
common: CommonArgumentBuilder,
|
|
literal: Cow<'static, str>,
|
|
description: Cow<'static, str>,
|
|
source: Option<String>,
|
|
}
|
|
|
|
/// A builder that builds an argument [`DetachedNode`].
|
|
pub struct RequiredArgumentBuilder {
|
|
common: CommonArgumentBuilder,
|
|
name: Cow<'static, str>,
|
|
argument_type: Arc<dyn AnyArgumentType>,
|
|
suggestion_provider: Option<Arc<dyn SuggestionProvider>>,
|
|
}
|
|
|
|
mod private {
|
|
// We want to make this trait private so that
|
|
// we can implement it for only our
|
|
// argument builders defined here.
|
|
pub trait Sealed {}
|
|
}
|
|
|
|
pub trait ArgumentBuilder<N: Into<DetachedNode>>: Sized + Sealed {
|
|
/// Puts an argument to be specified, right after this one is specified.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if this node is redirected to another node, or the child
|
|
/// provided is of the type [`CommandDetachedNode`].
|
|
#[must_use]
|
|
fn then(self, child: impl Into<DetachedNode>) -> Self;
|
|
|
|
/// Gets the command to execute for the node being built.
|
|
#[must_use]
|
|
fn command(&self) -> Option<Command>;
|
|
|
|
/// Sets the command to execute for the node being built.
|
|
#[must_use]
|
|
fn executes(self, command: impl CommandExecutor + 'static) -> Self {
|
|
self.executes_arc(Arc::new(command))
|
|
}
|
|
|
|
/// Sets the command to execute for the node being built.
|
|
#[must_use]
|
|
fn executes_arc(self, command: Arc<dyn CommandExecutor + 'static>) -> Self;
|
|
|
|
/// Sets the redirect target of the node being built to another, without a modifier.
|
|
#[must_use]
|
|
fn redirect(self, redirection: impl Into<Redirection>) -> Self;
|
|
|
|
/// Sets the redirect target of the node being built to another, with a given modifier.
|
|
#[must_use]
|
|
fn redirect_with_modifier(
|
|
self,
|
|
redirection: impl Into<Redirection>,
|
|
redirect_modifier: RedirectModifier,
|
|
) -> Self;
|
|
|
|
/// Forks the given context, using multiple for later.
|
|
#[must_use]
|
|
fn fork(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier)
|
|
-> Self;
|
|
|
|
/// Forwards the given context, with the given `fork` flag.
|
|
#[must_use]
|
|
fn forward(
|
|
self,
|
|
redirection: impl Into<Redirection>,
|
|
redirect_modifier: RedirectModifier,
|
|
fork: bool,
|
|
) -> Self;
|
|
|
|
/// Gets a reference to the arguments of the node to be built.
|
|
#[must_use]
|
|
fn arguments(&self) -> &FxHashMap<String, DetachedNode>;
|
|
|
|
/// Gets the node to which the node being built by this [`ArgumentBuilder`] redirects.
|
|
#[must_use]
|
|
fn target(&self) -> Option<Redirection>;
|
|
|
|
/// Adds a given predicate to this list of requirements of the node being built.
|
|
///
|
|
/// This means that it is possible to chain multiple predicates together, which must all
|
|
/// be satisfied.
|
|
///
|
|
/// Permissions can also be inserted directly into this method as a `requirement`.
|
|
#[must_use]
|
|
fn requires(self, requirement: impl Into<Requirement>) -> Self;
|
|
|
|
/// Overwrites the current requirements of this node to a new value.
|
|
#[must_use]
|
|
fn overwrite_requirements(self, requirements: Requirements) -> Self;
|
|
|
|
/// Gets the redirect modifier of the node this [`ArgumentBuilder`] is building.
|
|
#[must_use]
|
|
fn redirect_modifier(&self) -> RedirectModifier;
|
|
|
|
/// Whether this builder forks.
|
|
#[must_use]
|
|
fn forks(&self) -> bool;
|
|
|
|
/// Returns the 'future [`GlobalNodeId`]' of the node that will be produced by this Builder.
|
|
/// Very useful for redirects.
|
|
#[must_use]
|
|
fn id(&self) -> GlobalNodeId;
|
|
|
|
/// Builds the node represented by this builder, consuming itself in the process.
|
|
#[must_use]
|
|
fn build(self) -> N;
|
|
}
|
|
|
|
// Implement the private trait for our builders!
|
|
impl Sealed for LiteralArgumentBuilder {}
|
|
impl Sealed for CommandArgumentBuilder {}
|
|
impl Sealed for RequiredArgumentBuilder {}
|
|
|
|
/// Helper macro to implement repeated code of `ArgumentBuilder` for our types.
|
|
macro_rules! impl_boilerplate_argument_builder {
|
|
() => {
|
|
fn then(mut self, argument: impl Into<DetachedNode>) -> Self {
|
|
assert!(
|
|
self.target().is_none(),
|
|
"Cannot add children to a redirected node"
|
|
);
|
|
let node = argument.into();
|
|
assert!(
|
|
!matches!(node, DetachedNode::Command(_)),
|
|
"Cannot add a CommandDetachedNode as a child of a builder"
|
|
);
|
|
|
|
self.common.arguments.insert(node.name(), node);
|
|
self
|
|
}
|
|
|
|
fn command(&self) -> Option<Command> {
|
|
self.common.command.clone()
|
|
}
|
|
|
|
fn executes_arc(mut self, command: Arc<dyn CommandExecutor + 'static>) -> Self {
|
|
self.common.command = Some(command);
|
|
self
|
|
}
|
|
|
|
fn requires(mut self, requirement: impl Into<Requirement>) -> Self {
|
|
self.common.requirements.0.push(requirement.into());
|
|
self
|
|
}
|
|
|
|
fn overwrite_requirements(mut self, requirements: Requirements) -> Self {
|
|
self.common.requirements = requirements;
|
|
self
|
|
}
|
|
|
|
fn redirect(self, redirection: impl Into<Redirection>) -> Self {
|
|
self.forward(redirection.into(), RedirectModifier::KeepSource, false)
|
|
}
|
|
|
|
fn redirect_with_modifier(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier) -> Self {
|
|
self.forward(redirection.into(), redirect_modifier, false)
|
|
}
|
|
|
|
fn fork(self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier) -> Self {
|
|
self.forward(redirection.into(), redirect_modifier, true)
|
|
}
|
|
|
|
fn forward(mut self, redirection: impl Into<Redirection>, redirect_modifier: RedirectModifier, fork: bool) -> Self {
|
|
assert!(self.common.arguments.is_empty(), "Cannot forward a node with children. The node must have no children to redirect somewhere else");
|
|
self.common.target = Some(redirection.into());
|
|
self.common.modifier = redirect_modifier;
|
|
self.common.forks = fork;
|
|
self
|
|
}
|
|
|
|
fn arguments(&self) -> &FxHashMap<String, DetachedNode> {
|
|
&self.common.arguments
|
|
}
|
|
|
|
fn target(&self) -> Option<Redirection> {
|
|
self.common.target.clone()
|
|
}
|
|
|
|
fn redirect_modifier(&self) -> RedirectModifier {
|
|
self.common.modifier.clone()
|
|
}
|
|
|
|
fn forks(&self) -> bool {
|
|
self.common.forks
|
|
}
|
|
|
|
fn id(&self) -> GlobalNodeId {
|
|
self.common.global_id
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Helper macro to generate `From` impl blocks for each builder.
|
|
macro_rules! impl_builder_from_impls {
|
|
($builder: ty => $detached_node: ty) => {
|
|
impl From<$builder> for $detached_node {
|
|
fn from(value: $builder) -> Self {
|
|
value.build()
|
|
}
|
|
}
|
|
|
|
impl From<$builder> for DetachedNode {
|
|
fn from(value: $builder) -> Self {
|
|
value.build().into()
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
impl_builder_from_impls!(LiteralArgumentBuilder => LiteralDetachedNode);
|
|
impl_builder_from_impls!(CommandArgumentBuilder => CommandDetachedNode);
|
|
impl_builder_from_impls!(RequiredArgumentBuilder => ArgumentDetachedNode);
|
|
|
|
impl LiteralArgumentBuilder {
|
|
/// Creates a new [`LiteralArgumentBuilder`] from a literal.
|
|
pub fn new(literal: impl Into<Cow<'static, str>>) -> Self {
|
|
Self {
|
|
common: CommonArgumentBuilder::new(),
|
|
literal: literal.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CommandArgumentBuilder {
|
|
/// Creates a new [`CommandArgumentBuilder`] from a literal and a command description.
|
|
pub fn new(
|
|
literal: impl Into<Cow<'static, str>>,
|
|
description: impl Into<Cow<'static, str>>,
|
|
) -> Self {
|
|
Self {
|
|
common: CommonArgumentBuilder::new(),
|
|
literal: literal.into(),
|
|
description: description.into(),
|
|
source: None,
|
|
}
|
|
}
|
|
|
|
/// Sets the source (e.g. plugin name) that registered this command.
|
|
#[must_use]
|
|
pub fn with_source(mut self, source: impl Into<String>) -> Self {
|
|
self.source = Some(source.into());
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RequiredArgumentBuilder {
|
|
/// Creates a new [`RequiredArgumentBuilder`] from a name and an argument type.
|
|
pub fn new(
|
|
name: impl Into<Cow<'static, str>>,
|
|
arg_type: impl AnyArgumentType + 'static,
|
|
) -> Self {
|
|
Self {
|
|
common: CommonArgumentBuilder::new(),
|
|
name: name.into(),
|
|
argument_type: Arc::new(arg_type),
|
|
suggestion_provider: None,
|
|
}
|
|
}
|
|
|
|
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
|
|
#[must_use]
|
|
pub fn suggests(self, provider: impl SuggestionProvider + 'static) -> Self {
|
|
self.suggests_arc(Arc::new(provider))
|
|
}
|
|
|
|
/// Sets the [`SuggestionProvider`] of this builder for the `ArgumentDetachedNode`.
|
|
#[must_use]
|
|
pub fn suggests_arc(mut self, provider: Arc<dyn SuggestionProvider>) -> Self {
|
|
self.suggestion_provider = Some(provider);
|
|
self
|
|
}
|
|
}
|
|
|
|
impl ArgumentBuilder<LiteralDetachedNode> for LiteralArgumentBuilder {
|
|
impl_boilerplate_argument_builder!();
|
|
|
|
fn build(self) -> LiteralDetachedNode {
|
|
let mut node = LiteralDetachedNode::new(
|
|
self.common.global_id,
|
|
self.literal,
|
|
self.common.command,
|
|
self.common.requirements,
|
|
self.common.target,
|
|
self.common.modifier,
|
|
self.common.forks,
|
|
);
|
|
node.children = self.common.arguments;
|
|
node
|
|
}
|
|
}
|
|
|
|
impl ArgumentBuilder<CommandDetachedNode> for CommandArgumentBuilder {
|
|
impl_boilerplate_argument_builder!();
|
|
|
|
fn build(self) -> CommandDetachedNode {
|
|
let mut node = CommandDetachedNode::new(
|
|
self.common.global_id,
|
|
self.literal,
|
|
self.description,
|
|
self.common.command,
|
|
self.common.requirements,
|
|
self.common.target,
|
|
self.common.modifier,
|
|
self.common.forks,
|
|
);
|
|
node.meta.source = self.source;
|
|
node.children = self.common.arguments;
|
|
node
|
|
}
|
|
}
|
|
|
|
impl ArgumentBuilder<ArgumentDetachedNode> for RequiredArgumentBuilder {
|
|
impl_boilerplate_argument_builder!();
|
|
|
|
fn build(self) -> ArgumentDetachedNode {
|
|
let mut node = ArgumentDetachedNode::new(
|
|
self.common.global_id,
|
|
self.name,
|
|
self.argument_type,
|
|
self.common.command,
|
|
self.common.requirements,
|
|
self.common.target,
|
|
self.common.modifier,
|
|
self.common.forks,
|
|
self.suggestion_provider,
|
|
);
|
|
node.children = self.common.arguments;
|
|
node
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
|
|
use crate::command::argument_types::core::double::DoubleArgumentType;
|
|
use crate::command::argument_types::core::integer::IntegerArgumentType;
|
|
use crate::command::argument_types::core::string::StringArgumentType;
|
|
use crate::command::errors::error_types;
|
|
use crate::command::node::Redirection;
|
|
use crate::command::node::attached::AttachedNode;
|
|
use crate::command::node::tree::Tree;
|
|
use crate::command::string_reader::StringReader;
|
|
|
|
#[test]
|
|
fn literal_one() {
|
|
let builder = literal("test");
|
|
let node = builder.build();
|
|
|
|
assert_eq!(node.meta.literal, "test");
|
|
}
|
|
|
|
#[test]
|
|
fn required_one() {
|
|
let builder = argument("test", IntegerArgumentType::new(1, 10));
|
|
let node = builder.build();
|
|
|
|
assert_eq!(node.meta.name, "test");
|
|
|
|
let mut reader1 = StringReader::new("5");
|
|
let mut reader2 = StringReader::new("11");
|
|
|
|
let boxed_result = node
|
|
.meta
|
|
.argument_type
|
|
.parse(&mut reader1)
|
|
.expect("The parsing should not have errored");
|
|
let result = boxed_result
|
|
.downcast::<i32>()
|
|
.expect("Downcasting shouldn't have failed");
|
|
assert_eq!(result, Box::new(5));
|
|
|
|
let error = node
|
|
.meta
|
|
.argument_type
|
|
.parse(&mut reader2)
|
|
.expect_err("The parsing should have errored as 11 is outside the range");
|
|
assert!(error.is(&error_types::INTEGER_TOO_HIGH));
|
|
}
|
|
|
|
#[test]
|
|
fn literal_multiple() {
|
|
let mut builder = command("letter", "A test command");
|
|
for letter in 'a'..='z' {
|
|
// Add a node per letter for the argument.
|
|
let letter_string = letter.to_string();
|
|
builder = builder.then(literal(letter_string));
|
|
}
|
|
|
|
let node = builder.build();
|
|
assert_eq!(node.children.len(), 26);
|
|
}
|
|
|
|
#[test]
|
|
fn required_multiple() {
|
|
let builder = command("test", "A test command")
|
|
.then(argument("number", DoubleArgumentType::any()))
|
|
.then(argument("word", StringArgumentType::SingleWord));
|
|
|
|
let node = builder.build();
|
|
assert_eq!(node.children.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn redirect() {
|
|
let builder = command("test", "A test command").redirect(Redirection::Root);
|
|
|
|
let mut tree = Tree::new();
|
|
let node_id = tree.add_child_to_root(builder);
|
|
|
|
let node = &tree[node_id];
|
|
let redirect = node
|
|
.redirect
|
|
.expect("Redirection should exist as it was added before");
|
|
|
|
let target_id = tree
|
|
.resolve(redirect)
|
|
.expect("Target should have been resolved properly");
|
|
let target = &tree[target_id];
|
|
|
|
assert!(matches!(target, AttachedNode::Root(_)));
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic = "Cannot forward a node with children. The node must have no children to redirect somewhere else"]
|
|
fn redirect_after_child() {
|
|
let _ = command("test", "A test command")
|
|
.then(literal("child"))
|
|
.redirect(Redirection::Root);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic = "Cannot add children to a redirected node"]
|
|
fn redirect_before_child() {
|
|
let _ = command("test", "A test command")
|
|
.redirect(Redirection::Root)
|
|
.then(literal("child"));
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic = "Cannot add a CommandDetachedNode as a child of a builder"]
|
|
fn add_command_as_child() {
|
|
let _ = command("foo", "A test command").then(command("bar", "Another test command"));
|
|
}
|
|
}
|