mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 14:42:32 +00:00
add some more stuff from brigadier
This commit is contained in:
parent
d56f60c05f
commit
315f225819
25 changed files with 902 additions and 10 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
/target
|
||||
/doc
|
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -70,6 +70,9 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "azalea-brigadier"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azalea-chat"
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "azalea-brigadier"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "^1.4"
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder},
|
||||
};
|
||||
|
||||
pub trait ArgumentType<T> {
|
||||
pub trait ArgumentType {
|
||||
// T parse(StringReader reader) throws CommandSyntaxException;
|
||||
|
||||
// default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
|
||||
|
@ -16,7 +16,7 @@ pub trait ArgumentType<T> {
|
|||
// return Collections.emptyList();
|
||||
// }
|
||||
|
||||
fn parse(reader: &mut StringReader) -> Result<T, CommandSyntaxException>;
|
||||
fn parse<T>(reader: &mut StringReader) -> Result<T, CommandSyntaxException>;
|
||||
|
||||
fn list_suggestions<S>(
|
||||
context: &CommandContext<S>,
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
struct BoolArgumentType {
|
||||
// private static final Collection<String> EXAMPLES = Arrays.asList("true", "false");
|
||||
const EXAMPLES: &'static [&'static str] = &["true", "false"];
|
||||
}
|
||||
use crate::context::command_context::CommandContext;
|
||||
|
||||
impl ArgumentType for BoolArgumentType {
|
||||
|
||||
}
|
||||
use super::argument_type::ArgumentType;
|
||||
|
||||
struct BoolArgumentType {}
|
||||
|
||||
impl ArgumentType for BoolArgumentType {}
|
||||
|
||||
impl BoolArgumentType {
|
||||
const EXAMPLES: &'static [&'static str] = &["true", "false"];
|
||||
|
||||
fn bool() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn get_bool<S>(context: CommandContext<S>, name: String) {
|
||||
context.get_argument::<bool>(name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,7 @@
|
|||
pub mod argument_type;
|
||||
pub mod bool_argument_type;
|
||||
pub mod double_argument_type;
|
||||
pub mod float_argument_type;
|
||||
pub mod integer_argument_type;
|
||||
pub mod long_argument_type;
|
||||
pub mod string_argument_type;
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
use crate::{
|
||||
command::Command,
|
||||
redirect_modifier::RedirectModifier,
|
||||
single_redirect_modifier::SingleRedirectModifier,
|
||||
tree::{command_node::CommandNode, root_command_node::RootCommandNode},
|
||||
};
|
||||
|
||||
pub struct BaseArgumentBuilder<S, T>
|
||||
where
|
||||
T: ArgumentBuilder<S, T>,
|
||||
{
|
||||
arguments: RootCommandNode<S>,
|
||||
command: dyn Command<S>,
|
||||
requirement: dyn Fn(&S) -> bool,
|
||||
target: Option<dyn CommandNode<S>>,
|
||||
modifier: Option<dyn RedirectModifier<S>>,
|
||||
forks: bool,
|
||||
}
|
||||
|
||||
pub trait ArgumentBuilder<S, T> {
|
||||
fn this() -> T;
|
||||
fn build(self) -> dyn CommandNode<S>;
|
||||
}
|
||||
|
||||
impl<S, T> BaseArgumentBuilder<S, T>
|
||||
where
|
||||
T: ArgumentBuilder<S, T>,
|
||||
{
|
||||
pub fn then(&mut self, command: dyn CommandNode<S>) -> Result<&mut T, String> {
|
||||
if self.target.is_some() {
|
||||
return Err("Cannot add children to a redirected node".to_string());
|
||||
}
|
||||
self.command = command;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn arguments(&self) -> &Vec<dyn CommandNode<S>> {
|
||||
&self.arguments.get_children()
|
||||
}
|
||||
|
||||
pub fn executes(&mut self, command: dyn Command<S>) -> &mut T {
|
||||
self.command = command;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn command(&self) -> dyn Command<S> {
|
||||
self.command
|
||||
}
|
||||
|
||||
pub fn requires(&mut self, requirement: dyn Fn(&S) -> bool) -> &mut T {
|
||||
self.requirement = requirement;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn requirement(&self) -> dyn Fn(&S) -> bool {
|
||||
self.requirement
|
||||
}
|
||||
|
||||
pub fn redirect(&mut self, target: dyn CommandNode<S>) -> &mut T {
|
||||
self.forward(target, None, false)
|
||||
}
|
||||
|
||||
pub fn redirect_modifier(
|
||||
&mut self,
|
||||
target: dyn CommandNode<S>,
|
||||
modifier: dyn SingleRedirectModifier<S>,
|
||||
) -> &mut T {
|
||||
// forward(target, modifier == null ? null : o -> Collections.singleton(modifier.apply(o)), false);
|
||||
self.forward(target, modifier.map(|m| |o| vec![m.apply(o)]), false)
|
||||
}
|
||||
|
||||
pub fn fork(
|
||||
&mut self,
|
||||
target: dyn CommandNode<S>,
|
||||
modifier: dyn RedirectModifier<S>,
|
||||
) -> &mut T {
|
||||
self.forward(target, Some(modifier), true)
|
||||
}
|
||||
|
||||
pub fn forward(
|
||||
&mut self,
|
||||
target: dyn CommandNode<S>,
|
||||
modifier: Option<dyn RedirectModifier<S>>,
|
||||
fork: bool,
|
||||
) -> Result<&mut T, String> {
|
||||
if !self.arguments.get_children().is_empty() {
|
||||
return Err("Cannot forward a node with children".to_string());
|
||||
}
|
||||
self.target = Some(target);
|
||||
self.modifier = modifier;
|
||||
self.forks = fork;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn redirect(&self) -> Option<&dyn CommandNode<S>> {
|
||||
self.target.as_ref()
|
||||
}
|
||||
|
||||
pub fn redirect_modifier(&self) -> Option<&dyn RedirectModifier<S>> {
|
||||
self.modifier.as_ref()
|
||||
}
|
||||
|
||||
pub fn is_fork(&self) -> bool {
|
||||
self.forks
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod argument_builder;
|
||||
pub mod literal_argument_builder;
|
||||
pub mod required_argument_builder;
|
|
@ -0,0 +1,10 @@
|
|||
use crate::{
|
||||
context::command_context::CommandContext,
|
||||
exceptions::command_syntax_exception::CommandSyntaxException,
|
||||
};
|
||||
|
||||
pub const SINGLE_SUCCESS: i32 = 1;
|
||||
|
||||
pub trait Command<S> {
|
||||
fn run(&self, context: &mut CommandContext<S>) -> Result<i32, CommandSyntaxException>;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/// 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
|
||||
pub struct CommandDispatcher<S> {
|
||||
root: RootCommandNode<S>,
|
||||
}
|
||||
|
||||
impl<S> CommandDispatcher<S> {
|
||||
/// The string required to separate individual arguments in an input string
|
||||
///
|
||||
/// See: [`ARGUMENT_SEPARATOR_CHAR`]
|
||||
const ARGUMENT_SEPARATOR: &'static str = " ";
|
||||
|
||||
/// The char required to separate individual arguments in an input string
|
||||
///
|
||||
/// See: [`ARGUMENT_SEPARATOR`]
|
||||
const ARGUMENT_SEPARATOR_CHAR: char = ' ';
|
||||
|
||||
const USAGE_OPTIONAL_OPEN: &'static str = "[";
|
||||
const USAGE_OPTIONAL_CLOSE: &'static str = "]";
|
||||
const USAGE_REQUIRED_OPEN: &'static str = "(";
|
||||
const USAGE_REQUIRED_CLOSE: &'static str = ")";
|
||||
const USAGE_OR: &'static str = "|";
|
||||
}
|
|
@ -1,3 +1,90 @@
|
|||
use super::{
|
||||
parsed_argument::ParsedArgument, parsed_command_node::ParsedCommandNode,
|
||||
string_range::StringRange,
|
||||
};
|
||||
use crate::{
|
||||
arguments::argument_type::ArgumentType, command::Command, redirect_modifier::RedirectModifier,
|
||||
tree::command_node::CommandNode,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct CommandContext<S> {
|
||||
source: S,
|
||||
input: String,
|
||||
command: dyn Command<S>,
|
||||
arguments: HashMap<String, ParsedArgument<S, dyn ArgumentType>>,
|
||||
root_node: dyn CommandNode<S>,
|
||||
nodes: Vec<ParsedCommandNode<S>>,
|
||||
range: StringRange,
|
||||
child: Option<CommandContext<S>>,
|
||||
modifier: Option<dyn RedirectModifier<S>>,
|
||||
forks: bool,
|
||||
}
|
||||
|
||||
impl<S> CommandContext<S> {
|
||||
pub fn clone_for(&self, source: S) -> Self {
|
||||
if self.source == source {
|
||||
return self.clone();
|
||||
}
|
||||
Self {
|
||||
source,
|
||||
input: self.input.clone(),
|
||||
command: self.command.clone(),
|
||||
arguments: self.arguments.clone(),
|
||||
root_node: self.root_node.clone(),
|
||||
nodes: self.nodes.clone(),
|
||||
range: self.range.clone(),
|
||||
child: self.child.clone(),
|
||||
modifier: self.modifier.clone(),
|
||||
forks: self.forks,
|
||||
}
|
||||
}
|
||||
|
||||
fn child(&self) -> &Option<CommandContext<S>> {
|
||||
&self.child
|
||||
}
|
||||
|
||||
fn last_child(&self) -> &CommandContext<S> {
|
||||
let mut result = self;
|
||||
while result.child.is_some() {
|
||||
result = result.child.as_ref().unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn command(&self) -> &dyn Command<S> {
|
||||
&self.command
|
||||
}
|
||||
|
||||
fn source(&self) -> &S {
|
||||
&self.source
|
||||
}
|
||||
|
||||
// public <V> V getArgument(final String name, final Class<V> clazz) {
|
||||
// final ParsedArgument<S, ?> argument = arguments.get(name);
|
||||
|
||||
// if (argument == null) {
|
||||
// throw new IllegalArgumentException("No such argument '" + name + "' exists on this command");
|
||||
// }
|
||||
|
||||
// final Object result = argument.getResult();
|
||||
// if (PRIMITIVE_TO_WRAPPER.getOrDefault(clazz, clazz).isAssignableFrom(result.getClass())) {
|
||||
// return (V) result;
|
||||
// } else {
|
||||
// throw new IllegalArgumentException("Argument '" + name + "' is defined as " + result.getClass().getSimpleName() + ", not " + clazz);
|
||||
// }
|
||||
// }
|
||||
fn get_argument<V>(&self, name: &str) -> Result<V, String> {
|
||||
let argument = self.arguments.get(name);
|
||||
|
||||
if argument.is_none() {
|
||||
return Err(format!(
|
||||
"No such argument '{}' exists on this command",
|
||||
name
|
||||
));
|
||||
}
|
||||
|
||||
let result = argument.unwrap().result();
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
arguments::argument_type::ArgumentType, command::Command,
|
||||
command_dispatcher::CommandDispatcher, redirect_modifier::RedirectModifier,
|
||||
tree::command_node::CommandNode,
|
||||
};
|
||||
|
||||
use super::{
|
||||
command_context::CommandContext, parsed_argument::ParsedArgument,
|
||||
parsed_command_node::ParsedCommandNode, string_range::StringRange,
|
||||
suggestion_context::SuggestionContext,
|
||||
};
|
||||
|
||||
// public class CommandContextBuilder<S> {
|
||||
// private final Map<String, ParsedArgument<S, ?>> arguments = new LinkedHashMap<>();
|
||||
// private final CommandNode<S> rootNode;
|
||||
// private final List<ParsedCommandNode<S>> nodes = new ArrayList<>();
|
||||
// private final CommandDispatcher<S> dispatcher;
|
||||
// private S source;
|
||||
// private Command<S> command;
|
||||
// private CommandContextBuilder<S> child;
|
||||
// private StringRange range;
|
||||
// private RedirectModifier<S> modifier = null;
|
||||
// private boolean forks;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CommandContextBuilder<S> {
|
||||
arguments: HashMap<String, ParsedArgument<S, dyn ArgumentType>>,
|
||||
root_node: dyn CommandNode<S>,
|
||||
nodes: Vec<ParsedCommandNode<S>>,
|
||||
dispatcher: CommandDispatcher<S>,
|
||||
source: S,
|
||||
command: Box<dyn Command<S>>,
|
||||
child: Option<CommandContextBuilder<S>>,
|
||||
range: StringRange,
|
||||
modifier: Option<Box<dyn RedirectModifier<S>>>,
|
||||
forks: bool,
|
||||
}
|
||||
|
||||
// public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source, final CommandNode<S> rootNode, final int start) {
|
||||
// this.rootNode = rootNode;
|
||||
// this.dispatcher = dispatcher;
|
||||
// this.source = source;
|
||||
// this.range = StringRange.at(start);
|
||||
// }
|
||||
|
||||
impl<S> CommandContextBuilder<S> {
|
||||
pub fn new(
|
||||
dispatcher: CommandDispatcher<S>,
|
||||
source: S,
|
||||
root_node: dyn CommandNode<S>,
|
||||
start: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
root_node,
|
||||
dispatcher,
|
||||
source,
|
||||
range: StringRange::at(start),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, source: S) -> Self {
|
||||
self.source = source;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &S {
|
||||
&self.source
|
||||
}
|
||||
|
||||
pub fn root_node(&self) -> &dyn CommandNode<S> {
|
||||
&self.root_node
|
||||
}
|
||||
|
||||
pub fn with_argument(
|
||||
mut self,
|
||||
name: String,
|
||||
argument: ParsedArgument<S, dyn ArgumentType>,
|
||||
) -> Self {
|
||||
self.arguments.insert(name, argument);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn arguments(&self) -> &HashMap<String, ParsedArgument<S, dyn ArgumentType>> {
|
||||
&self.arguments
|
||||
}
|
||||
|
||||
pub fn with_command(mut self, command: Box<dyn Command<S>>) -> Self {
|
||||
self.command = command;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_node(mut self, node: dyn CommandNode<S>, range: StringRange) -> Self {
|
||||
self.nodes.push(ParsedCommandNode::new(node, range));
|
||||
self.range = StringRange::encompassing(&self.range, &range);
|
||||
self.modifier = node.redirect_modifier();
|
||||
self.forks = node.is_fork();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_child(mut self, child: CommandContextBuilder<S>) -> Self {
|
||||
self.child = Some(child);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn child(&self) -> Option<&CommandContextBuilder<S>> {
|
||||
self.child.as_ref()
|
||||
}
|
||||
|
||||
pub fn last_child(&self) -> Option<&CommandContextBuilder<S>> {
|
||||
let mut result = self;
|
||||
while let Some(child) = result.child() {
|
||||
result = child;
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
pub fn command(&self) -> &dyn Command<S> {
|
||||
&*self.command
|
||||
}
|
||||
|
||||
pub fn nodes(&self) -> &Vec<ParsedCommandNode<S>> {
|
||||
&self.nodes
|
||||
}
|
||||
|
||||
pub fn build(self, input: &str) -> CommandContext<S> {
|
||||
CommandContext {
|
||||
source: self.source,
|
||||
input,
|
||||
arguments: self.arguments,
|
||||
command: self.command,
|
||||
root_node: self.root_node,
|
||||
nodes: self.nodes,
|
||||
range: self.range,
|
||||
child: self.child.map(|child| child.build(input)),
|
||||
modifier: self.modifier,
|
||||
forks: self.forks,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatcher(&self) -> &CommandDispatcher<S> {
|
||||
&self.dispatcher
|
||||
}
|
||||
|
||||
pub fn range(&self) -> &StringRange {
|
||||
&self.range
|
||||
}
|
||||
|
||||
pub fn find_suggestion_context(&self, cursor: i32) -> Result<SuggestionContext<S>, String> {
|
||||
if self.range.start() <= cursor {
|
||||
if self.range.end() < cursor {
|
||||
if let Some(child) = self.child() {
|
||||
child.find_suggestion_context(cursor);
|
||||
} else if !self.nodes.is_empty() {
|
||||
let last = self.nodes.last().unwrap();
|
||||
let end = last.range().end() + 1;
|
||||
return SuggestionContext::new(last.node(), end);
|
||||
} else {
|
||||
return SuggestionContext::new(self.root_node, self.range.start());
|
||||
}
|
||||
} else {
|
||||
let prev = self.root_node;
|
||||
for node in &self.nodes {
|
||||
let node_range = node.range();
|
||||
if node_range.start() <= cursor && cursor <= node_range.end() {
|
||||
return SuggestionContext::new(prev, node_range.start());
|
||||
}
|
||||
prev = node.node();
|
||||
}
|
||||
if prev.is_none() {
|
||||
return Err(String::from("Can't find node before cursor"));
|
||||
}
|
||||
return SuggestionContext::new(prev.unwrap(), self.range.start());
|
||||
}
|
||||
}
|
||||
Err(String::from("Can't find node before cursor"))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
use super::string_range::StringRange;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct ParsedArgument<S, T> {
|
||||
range: StringRange,
|
||||
result: T,
|
||||
}
|
||||
|
||||
impl<S, T> ParsedArgument<S, T> {
|
||||
fn new(start: usize, end: usize, result: T) -> Self {
|
||||
Self {
|
||||
range: StringRange::between(start, end),
|
||||
result,
|
||||
}
|
||||
}
|
||||
|
||||
fn range(&self) -> &StringRange {
|
||||
&self.range
|
||||
}
|
||||
|
||||
fn result(&self) -> &T {
|
||||
&self.result
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
use super::string_range::StringRange;
|
||||
use crate::tree::command_node::CommandNode;
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
|
||||
pub struct ParsedCommandNode<S> {
|
||||
node: dyn CommandNode<S>,
|
||||
range: StringRange,
|
||||
}
|
||||
|
||||
impl<S> ParsedCommandNode<S> {
|
||||
fn new(node: dyn CommandNode<S>, range: StringRange) -> Self {
|
||||
Self { node, range }
|
||||
}
|
||||
|
||||
fn node(&self) -> &dyn CommandNode<S> {
|
||||
&self.node
|
||||
}
|
||||
|
||||
fn range(&self) -> &StringRange {
|
||||
&self.range
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
use std::cmp;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StringRange {
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl StringRange {
|
||||
pub fn new(start: usize, end: usize) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
pub fn at(pos: usize) -> Self {
|
||||
Self::new(pos, pos)
|
||||
}
|
||||
|
||||
pub fn between(start: usize, end: usize) -> Self {
|
||||
Self::new(start, end)
|
||||
}
|
||||
|
||||
pub fn encompassing(a: &Self, b: &Self) -> Self {
|
||||
Self::new(cmp::min(a.start, b.start), cmp::max(a.end, b.end))
|
||||
}
|
||||
|
||||
pub fn start(&self) -> usize {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
self.end
|
||||
}
|
||||
|
||||
pub fn get(&self, reader: &str) -> &str {
|
||||
&reader[self.start..self.end]
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.start == self.end
|
||||
}
|
||||
|
||||
pub fn length(&self) -> usize {
|
||||
self.end - self.start
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
use crate::tree::command_node::CommandNode;
|
||||
|
||||
pub struct SuggestionContext<S> {
|
||||
parent: dyn CommandNode<S>,
|
||||
start_pos: usize,
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
mod ambiguity_consumer;
|
||||
mod arguments;
|
||||
mod builder;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
use crate::{
|
||||
context::command_context::CommandContext,
|
||||
exceptions::command_syntax_exception::CommandSyntaxException,
|
||||
};
|
||||
|
||||
pub trait RedirectModifier<S> {
|
||||
fn apply(&self, context: CommandContext<S>) -> Result<Vec<S>, CommandSyntaxException>;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
use crate::{
|
||||
context::command_context::CommandContext,
|
||||
exceptions::command_syntax_exception::CommandSyntaxException,
|
||||
};
|
||||
|
||||
pub trait SingleRedirectModifier<S> {
|
||||
fn apply(&self, context: CommandContext<S>) -> Result<S, CommandSyntaxException>;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
use crate::{
|
||||
context::command_context::CommandContext,
|
||||
exceptions::command_syntax_exception::CommandSyntaxException,
|
||||
};
|
||||
|
||||
use super::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder};
|
||||
|
||||
pub trait SuggestionProvider<S> {
|
||||
fn suggestions(
|
||||
&self,
|
||||
context: &CommandContext<S>,
|
||||
builder: &SuggestionsBuilder,
|
||||
) -> Result<Suggestions, CommandSyntaxException>;
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::{
|
||||
arguments::argument_type::ArgumentType,
|
||||
context::{
|
||||
command_context::CommandContext, command_context_builder::CommandContextBuilder,
|
||||
parsed_argument::ParsedArgument,
|
||||
},
|
||||
exceptions::command_syntax_exception::CommandSyntaxException,
|
||||
string_reader::StringReader,
|
||||
suggestion::{
|
||||
suggestion_provider::SuggestionProvider, suggestions::Suggestions,
|
||||
suggestions_builder::SuggestionsBuilder,
|
||||
},
|
||||
};
|
||||
|
||||
use super::command_node::{BaseCommandNode, CommandNode};
|
||||
|
||||
const USAGE_ARGUMENT_OPEN: &str = "<";
|
||||
const USAGE_ARGUMENT_CLOSE: &str = ">";
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
|
||||
pub struct ArgumentCommandNode<S, T> {
|
||||
name: String,
|
||||
type_: dyn ArgumentType,
|
||||
custom_suggestions: dyn SuggestionProvider<S>,
|
||||
// Since Rust doesn't have extending, we put the struct this is extending as the "base" field
|
||||
pub base: BaseCommandNode<S>,
|
||||
}
|
||||
|
||||
impl<S, T> ArgumentCommandNode<S, T> {
|
||||
fn get_type(&self) -> &dyn ArgumentType {
|
||||
&self.type_
|
||||
}
|
||||
|
||||
fn custom_suggestions(&self) -> &dyn SuggestionProvider<S> {
|
||||
&self.custom_suggestions
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> CommandNode<S> for ArgumentCommandNode<S, T> {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn parse(
|
||||
&self,
|
||||
reader: StringReader,
|
||||
context_builder: CommandContextBuilder<S>,
|
||||
) -> Result<(), CommandSyntaxException> {
|
||||
// final int start = reader.getCursor();
|
||||
// final T result = type.parse(reader);
|
||||
// final ParsedArgument<S, T> parsed = new ParsedArgument<>(start, reader.getCursor(), result);
|
||||
|
||||
// contextBuilder.withArgument(name, parsed);
|
||||
// contextBuilder.withNode(this, parsed.getRange());
|
||||
|
||||
let start = reader.get_cursor();
|
||||
let result = self.get_type().parse(reader)?;
|
||||
let parsed = ParsedArgument::new(start, reader.get_cursor(), result);
|
||||
|
||||
context_builder.with_argument(&self.name, parsed);
|
||||
context_builder.with_node(self, parsed.get_range());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
context: CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Result<Suggestions, CommandSyntaxException> {
|
||||
if self.custom_suggestions.is_none() {
|
||||
self.get_type().list_suggestions(context, builder)
|
||||
} else {
|
||||
self.custom_suggestions.get_suggestions(context, builder)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_input(&self, input: &str) -> bool {
|
||||
let reader = StringReader::new(input);
|
||||
let result = self.get_type().parse(reader);
|
||||
if result.is_ok() {
|
||||
return !reader.can_read() || reader.peek() == ' ';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fn usage_text(&self) -> &str {
|
||||
USAGE_ARGUMENT_OPEN + self.name + USAGE_ARGUMENT_CLOSE
|
||||
}
|
||||
|
||||
fn create_builder(&self) -> RequiredArgumentBuilder<S, T> {
|
||||
let builder = RequiredArgumentBuilder::argument(&self.name, &self.type_);
|
||||
builder.requires(self.base.get_requirement());
|
||||
builder.forward(
|
||||
self.base.get_redirect(),
|
||||
self.base.get_redirect_modifier(),
|
||||
self.base.is_fork(),
|
||||
);
|
||||
builder.suggests(self.custom_suggestions());
|
||||
if self.base.get_command() != None {
|
||||
builder.executes(self.base.get_command().unwrap());
|
||||
}
|
||||
builder
|
||||
}
|
||||
|
||||
fn get_examples(&self) -> Vec<String> {
|
||||
self.type_.get_examples()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ArgumentCommandNode<String, String> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "<argument {}: {}>", self.name, self.type_)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
builder::argument_builder::ArgumentBuilder,
|
||||
command::Command,
|
||||
context::{command_context::CommandContext, command_context_builder::CommandContextBuilder},
|
||||
exceptions::command_syntax_exception::CommandSyntaxException,
|
||||
redirect_modifier::RedirectModifier,
|
||||
string_reader::StringReader,
|
||||
suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder},
|
||||
};
|
||||
|
||||
use super::{argument_command_node::ArgumentCommandNode, literal_command_node::LiteralCommandNode};
|
||||
|
||||
pub struct BaseCommandNode<S> {
|
||||
// private final Map<String, CommandNode<S>> children = new LinkedHashMap<>();
|
||||
// private final Map<String, LiteralCommandNode<S>> literals = new LinkedHashMap<>();
|
||||
// private final Map<String, ArgumentCommandNode<S, ?>> arguments = new LinkedHashMap<>();
|
||||
// private final Predicate<S> requirement;
|
||||
// private final CommandNode<S> redirect;
|
||||
// private final RedirectModifier<S> modifier;
|
||||
// private final boolean forks;
|
||||
// private Command<S> command;
|
||||
children: HashMap<String, dyn CommandNode<S>>,
|
||||
literals: HashMap<String, LiteralCommandNode<S>>,
|
||||
arguments: HashMap<String, ArgumentCommandNode<S, _>>,
|
||||
requirement: Option<dyn Fn(&S) -> bool>,
|
||||
redirect: Option<dyn CommandNode<S>>,
|
||||
modifier: Option<dyn RedirectModifier<S>>,
|
||||
forks: bool,
|
||||
command: Option<dyn Command<S>>,
|
||||
}
|
||||
|
||||
impl<S> BaseCommandNode<S> {}
|
||||
|
||||
pub trait CommandNode<S> {
|
||||
fn name(&self) -> &str;
|
||||
fn usage_text(&self) -> &str;
|
||||
fn parse(
|
||||
&self,
|
||||
reader: StringReader,
|
||||
context_builder: CommandContextBuilder<S>,
|
||||
) -> Result<(), CommandSyntaxException>;
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
context: CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Result<Suggestions, CommandSyntaxException>;
|
||||
fn is_valid_input(&self, input: &str) -> bool;
|
||||
fn create_builder<T>(&self) -> dyn ArgumentBuilder<S, T>;
|
||||
fn get_examples(&self) -> Vec<String>;
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
use crate::{
|
||||
context::{command_context::CommandContext, command_context_builder::CommandContextBuilder},
|
||||
exceptions::{
|
||||
builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException,
|
||||
},
|
||||
string_reader::StringReader,
|
||||
suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder},
|
||||
};
|
||||
|
||||
use super::command_node::{BaseCommandNode, CommandNode};
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
|
||||
pub struct LiteralCommandNode<S> {
|
||||
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<S>,
|
||||
}
|
||||
|
||||
impl<S> LiteralCommandNode<S> {
|
||||
pub fn literal(&self) -> &String {
|
||||
&self.literal
|
||||
}
|
||||
|
||||
pub fn parse(&self, reader: StringReader) -> i32 {
|
||||
let start = reader.get_cursor();
|
||||
if reader.can_read(self.literal.len()) {
|
||||
let end = start + self.literal.len();
|
||||
if reader.get_string()[start..end].eq(&self.literal) {
|
||||
reader.set_cursor(end);
|
||||
if !reader.can_read() || reader.peek() == ' ' {
|
||||
return end as i32;
|
||||
} else {
|
||||
reader.set_cursor(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> CommandNode<S> for LiteralCommandNode<S> {
|
||||
fn name(&self) -> &str {
|
||||
&self.literal
|
||||
}
|
||||
|
||||
fn parse(
|
||||
&self,
|
||||
reader: StringReader,
|
||||
context_builder: CommandContextBuilder<S>,
|
||||
) -> Result<(), CommandSyntaxException> {
|
||||
let start = reader.get_cursor();
|
||||
let end = self.parse(reader);
|
||||
if end > -1 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(BuiltInExceptions::LiteralIncorrect {
|
||||
expected: self.literal(),
|
||||
}
|
||||
.create_with_context(reader))
|
||||
}
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
context: CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Result<Suggestions, CommandSyntaxException> {
|
||||
if self
|
||||
.literal_lowercase
|
||||
.starts_with(&builder.remaining_lowercase())
|
||||
{
|
||||
builder.suggest(self.literal())
|
||||
} else {
|
||||
Suggestions::empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_input(&self, input: &str) -> bool {
|
||||
self.parse(StringReader::from(input)) > -1
|
||||
}
|
||||
|
||||
fn usage_text(&self) -> &str {
|
||||
self.literal
|
||||
}
|
||||
|
||||
fn create_builder(&self) -> LiteralArgumentBuilder<S> {
|
||||
let builder = LiteralArgumentBuilder::literal(self.literal());
|
||||
builder.requires(self.requirement());
|
||||
builder.forward(self.redirect(), self.redirect_modifier(), self.is_fork());
|
||||
if self.command().is_some() {
|
||||
builder.executes(self.command().unwrap());
|
||||
}
|
||||
builder
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
pub mod argument_command_node;
|
||||
pub mod command_node;
|
||||
pub mod literal_command_node;
|
||||
pub mod root_command_node;
|
|
@ -0,0 +1,61 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::{
|
||||
context::{command_context::CommandContext, command_context_builder::CommandContextBuilder},
|
||||
exceptions::{
|
||||
builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException,
|
||||
},
|
||||
string_reader::StringReader,
|
||||
suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder},
|
||||
};
|
||||
|
||||
use super::command_node::{BaseCommandNode, CommandNode};
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
|
||||
pub struct RootCommandNode<S> {
|
||||
// Since Rust doesn't have extending, we put the struct this is extending as the "base" field
|
||||
pub base: BaseCommandNode<S>,
|
||||
}
|
||||
|
||||
impl<S> CommandNode<S> for RootCommandNode<S> {
|
||||
fn name(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
fn parse(
|
||||
&self,
|
||||
reader: StringReader,
|
||||
context_builder: CommandContextBuilder<S>,
|
||||
) -> Result<(), CommandSyntaxException> {
|
||||
}
|
||||
|
||||
fn list_suggestions(
|
||||
&self,
|
||||
context: CommandContext<S>,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Result<Suggestions, CommandSyntaxException> {
|
||||
Suggestions::empty()
|
||||
}
|
||||
|
||||
fn is_valid_input(&self, input: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn usage_text(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
fn create_builder(&self) -> () {
|
||||
panic!("Cannot convert root into a builder");
|
||||
}
|
||||
|
||||
fn get_examples(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RootCommandNode<()> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "<root>")
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue