mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 04:56:49 +00:00
refactor(commands): separate commands definitions and other code into modules
This commit is contained in:
parent
405ac11d74
commit
af523a4c23
24 changed files with 293 additions and 216 deletions
57
crates/commands/src/commands.rs
Normal file
57
crates/commands/src/commands.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
pub mod admin;
|
||||||
|
pub mod api;
|
||||||
|
pub mod autoproxy;
|
||||||
|
pub mod checks;
|
||||||
|
pub mod commands;
|
||||||
|
pub mod config;
|
||||||
|
pub mod dashboard;
|
||||||
|
pub mod debug;
|
||||||
|
pub mod fun;
|
||||||
|
pub mod group;
|
||||||
|
pub mod help;
|
||||||
|
pub mod import_export;
|
||||||
|
pub mod member;
|
||||||
|
pub mod message;
|
||||||
|
pub mod misc;
|
||||||
|
pub mod random;
|
||||||
|
pub mod server_config;
|
||||||
|
pub mod switch;
|
||||||
|
pub mod system;
|
||||||
|
|
||||||
|
use crate::{command, token::Token};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Command {
|
||||||
|
// TODO: fix hygiene
|
||||||
|
pub tokens: Vec<Token>,
|
||||||
|
pub help: String,
|
||||||
|
pub cb: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub fn new(
|
||||||
|
tokens: impl IntoIterator<Item = Token>,
|
||||||
|
help: impl ToString,
|
||||||
|
cb: impl ToString,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
tokens: tokens.into_iter().collect(),
|
||||||
|
help: help.to_string(),
|
||||||
|
cb: cb.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! command {
|
||||||
|
([$($v:expr),+], $cb:expr, $help:expr) => {
|
||||||
|
$crate::commands::Command::new([$($v.clone()),*], $help, $cb)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all() -> Vec<Command> {
|
||||||
|
(help::cmds())
|
||||||
|
.chain(system::cmds())
|
||||||
|
.chain(member::cmds())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
1
crates/commands/src/commands/admin.rs
Normal file
1
crates/commands/src/commands/admin.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/api.rs
Normal file
1
crates/commands/src/commands/api.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/autoproxy.rs
Normal file
1
crates/commands/src/commands/autoproxy.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/checks.rs
Normal file
1
crates/commands/src/commands/checks.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/commands.rs
Normal file
1
crates/commands/src/commands/commands.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/config.rs
Normal file
1
crates/commands/src/commands/config.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/dashboard.rs
Normal file
1
crates/commands/src/commands/dashboard.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/debug.rs
Normal file
1
crates/commands/src/commands/debug.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/fun.rs
Normal file
1
crates/commands/src/commands/fun.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/group.rs
Normal file
1
crates/commands/src/commands/group.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
10
crates/commands/src/commands/help.rs
Normal file
10
crates/commands/src/commands/help.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
|
[command!(
|
||||||
|
[Token::cmd("help")],
|
||||||
|
"help",
|
||||||
|
"Shows the help command"
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
1
crates/commands/src/commands/import_export.rs
Normal file
1
crates/commands/src/commands/import_export.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
50
crates/commands/src/commands/member.rs
Normal file
50
crates/commands/src/commands/member.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
|
use Token::*;
|
||||||
|
|
||||||
|
let member = Token::cmd_with_alias(["member", "m"]);
|
||||||
|
let description = Token::cmd_with_alias(["description", "desc"]);
|
||||||
|
let privacy = Token::cmd_with_alias(["privacy", "priv"]);
|
||||||
|
let new = Token::cmd_with_alias(["new", "n"]);
|
||||||
|
|
||||||
|
[
|
||||||
|
command!(
|
||||||
|
[member, new, MemberRef],
|
||||||
|
"member_new",
|
||||||
|
"Creates a new system member"
|
||||||
|
),
|
||||||
|
command!(
|
||||||
|
[member, MemberRef],
|
||||||
|
"member_show",
|
||||||
|
"Shows information about a member"
|
||||||
|
),
|
||||||
|
command!(
|
||||||
|
[member, MemberRef, description],
|
||||||
|
"member_desc_show",
|
||||||
|
"Shows a member's description"
|
||||||
|
),
|
||||||
|
command!(
|
||||||
|
[member, MemberRef, description, FullString],
|
||||||
|
"member_desc_update",
|
||||||
|
"Changes a member's description"
|
||||||
|
),
|
||||||
|
command!(
|
||||||
|
[member, MemberRef, privacy],
|
||||||
|
"member_privacy_show",
|
||||||
|
"Displays a member's current privacy settings"
|
||||||
|
),
|
||||||
|
command!(
|
||||||
|
[
|
||||||
|
member,
|
||||||
|
MemberRef,
|
||||||
|
privacy,
|
||||||
|
MemberPrivacyTarget,
|
||||||
|
PrivacyLevel
|
||||||
|
],
|
||||||
|
"member_privacy_update",
|
||||||
|
"Changes a member's privacy settings"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
1
crates/commands/src/commands/message.rs
Normal file
1
crates/commands/src/commands/message.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/misc.rs
Normal file
1
crates/commands/src/commands/misc.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/random.rs
Normal file
1
crates/commands/src/commands/random.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/server_config.rs
Normal file
1
crates/commands/src/commands/server_config.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
1
crates/commands/src/commands/switch.rs
Normal file
1
crates/commands/src/commands/switch.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
23
crates/commands/src/commands/system.rs
Normal file
23
crates/commands/src/commands/system.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
|
use Token::*;
|
||||||
|
|
||||||
|
let system = Token::cmd_with_alias(["system", "s"]);
|
||||||
|
let new = Token::cmd_with_alias(["new", "n"]);
|
||||||
|
|
||||||
|
[
|
||||||
|
command!(
|
||||||
|
[system],
|
||||||
|
"system_show",
|
||||||
|
"Shows information about your system"
|
||||||
|
),
|
||||||
|
command!([system, new], "system_new", "Creates a new system"),
|
||||||
|
command!(
|
||||||
|
[system, new, FullString],
|
||||||
|
"system_new",
|
||||||
|
"Creates a new system"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
|
@ -1,162 +1,20 @@
|
||||||
#![feature(let_chains)]
|
#![feature(let_chains)]
|
||||||
|
|
||||||
use core::panic;
|
mod commands;
|
||||||
use std::{cmp::Ordering, collections::HashMap};
|
mod string;
|
||||||
|
mod token;
|
||||||
|
mod tree;
|
||||||
|
|
||||||
uniffi::include_scaffolding!("commands");
|
uniffi::include_scaffolding!("commands");
|
||||||
|
|
||||||
mod string;
|
use core::panic;
|
||||||
mod token;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
use token::*;
|
use tree::TreeBranch;
|
||||||
|
|
||||||
// todo!: move all this stuff into a different file
|
pub use commands::Command;
|
||||||
// lib.rs should just have exported symbols and command definitions
|
pub use token::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct TreeBranch {
|
|
||||||
current_command_key: Option<String>,
|
|
||||||
/// branches.keys(), but sorted by specificity
|
|
||||||
possible_tokens: Vec<Token>,
|
|
||||||
branches: HashMap<Token, TreeBranch>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TreeBranch {
|
|
||||||
fn register_command(&mut self, command: Command) {
|
|
||||||
let mut current_branch = self;
|
|
||||||
// iterate over tokens in command
|
|
||||||
for token in command.tokens {
|
|
||||||
// recursively get or create a sub-branch for each token
|
|
||||||
current_branch = current_branch.branches.entry(token).or_insert(TreeBranch {
|
|
||||||
current_command_key: None,
|
|
||||||
possible_tokens: vec![],
|
|
||||||
branches: HashMap::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// when we're out of tokens, add an Empty branch with the callback and no sub-branches
|
|
||||||
current_branch.branches.insert(
|
|
||||||
Token::Empty,
|
|
||||||
TreeBranch {
|
|
||||||
current_command_key: Some(command.cb),
|
|
||||||
possible_tokens: vec![],
|
|
||||||
branches: HashMap::new(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sort_tokens(&mut self) {
|
|
||||||
for branch in self.branches.values_mut() {
|
|
||||||
branch.sort_tokens();
|
|
||||||
}
|
|
||||||
|
|
||||||
// put Value tokens at the end
|
|
||||||
// i forget exactly how this works
|
|
||||||
// todo!: document this before PR mergs
|
|
||||||
self.possible_tokens = self
|
|
||||||
.branches
|
|
||||||
.keys()
|
|
||||||
.into_iter()
|
|
||||||
.map(|v| v.clone())
|
|
||||||
.collect();
|
|
||||||
self.possible_tokens.sort_by(|v, _| {
|
|
||||||
if matches!(v, Token::Value(_)) {
|
|
||||||
Ordering::Greater
|
|
||||||
} else {
|
|
||||||
Ordering::Less
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Command {
|
|
||||||
tokens: Vec<Token>,
|
|
||||||
help: String,
|
|
||||||
cb: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn command(tokens: impl IntoIterator<Item = Token>, help: impl ToString, cb: impl ToString) -> Command {
|
|
||||||
Command {
|
|
||||||
tokens: tokens.into_iter().collect(),
|
|
||||||
help: help.to_string(),
|
|
||||||
cb: cb.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! command {
|
|
||||||
([$($v:expr),+], $help:expr, $cb:expr) => {
|
|
||||||
$crate::command([$($v.clone()),*], $help, $cb)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mod commands {
|
|
||||||
use smol_str::SmolStr;
|
|
||||||
|
|
||||||
use super::Token;
|
|
||||||
|
|
||||||
fn cmd(value: impl Into<SmolStr>) -> Token {
|
|
||||||
Token::Value(vec![value.into()])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmd_with_alias(value: impl IntoIterator<Item = impl Into<SmolStr>>) -> Token {
|
|
||||||
Token::Value(value.into_iter().map(Into::into).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: this needs to have less ampersands -alyssa
|
|
||||||
pub fn happy() -> Vec<super::Command> {
|
|
||||||
use Token::*;
|
|
||||||
|
|
||||||
let system = cmd_with_alias(["system", "s"]);
|
|
||||||
let member = cmd_with_alias(["member", "m"]);
|
|
||||||
let description = cmd_with_alias(["description", "desc"]);
|
|
||||||
let privacy = cmd_with_alias(["privacy", "priv"]);
|
|
||||||
vec![
|
|
||||||
command!([cmd("help")], "help", "Shows the help command"),
|
|
||||||
command!(
|
|
||||||
[system],
|
|
||||||
"system_show",
|
|
||||||
"Shows information about your system"
|
|
||||||
),
|
|
||||||
command!([system, cmd("new")], "system_new", "Creates a new system"),
|
|
||||||
command!(
|
|
||||||
[member, cmd_with_alias(["new", "n"])],
|
|
||||||
"member_new",
|
|
||||||
"Creates a new system member"
|
|
||||||
),
|
|
||||||
command!(
|
|
||||||
[member, MemberRef],
|
|
||||||
"member_show",
|
|
||||||
"Shows information about a member"
|
|
||||||
),
|
|
||||||
command!(
|
|
||||||
[member, MemberRef, description],
|
|
||||||
"member_desc_show",
|
|
||||||
"Shows a member's description"
|
|
||||||
),
|
|
||||||
command!(
|
|
||||||
[member, MemberRef, description, FullString],
|
|
||||||
"member_desc_update",
|
|
||||||
"Changes a member's description"
|
|
||||||
),
|
|
||||||
command!(
|
|
||||||
[member, MemberRef, privacy],
|
|
||||||
"member_privacy_show",
|
|
||||||
"Displays a member's current privacy settings"
|
|
||||||
),
|
|
||||||
command!(
|
|
||||||
[
|
|
||||||
member,
|
|
||||||
MemberRef,
|
|
||||||
privacy,
|
|
||||||
MemberPrivacyTarget,
|
|
||||||
PrivacyLevel
|
|
||||||
],
|
|
||||||
"member_privacy_update",
|
|
||||||
"Changes a member's privacy settings"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref COMMAND_TREE: TreeBranch = {
|
static ref COMMAND_TREE: TreeBranch = {
|
||||||
|
|
@ -166,7 +24,7 @@ lazy_static::lazy_static! {
|
||||||
branches: HashMap::new(),
|
branches: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
commands::happy().iter().for_each(|x| tree.register_command(x.clone()));
|
crate::commands::all().iter().for_each(|x| tree.register_command(x.clone()));
|
||||||
|
|
||||||
tree.sort_tokens();
|
tree.sort_tokens();
|
||||||
|
|
||||||
|
|
@ -187,6 +45,66 @@ pub struct ParsedCommand {
|
||||||
pub flags: HashMap<String, Option<String>>,
|
pub flags: HashMap<String, Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_command(input: String) -> CommandResult {
|
||||||
|
let input: SmolStr = input.into();
|
||||||
|
let mut local_tree: TreeBranch = COMMAND_TREE.clone();
|
||||||
|
|
||||||
|
// end position of all currently matched tokens
|
||||||
|
let mut current_pos = 0;
|
||||||
|
|
||||||
|
let mut args: Vec<String> = Vec::new();
|
||||||
|
let mut flags: HashMap<String, Option<String>> = HashMap::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let next = next_token(
|
||||||
|
local_tree.possible_tokens.clone(),
|
||||||
|
input.clone(),
|
||||||
|
current_pos,
|
||||||
|
);
|
||||||
|
match next {
|
||||||
|
Ok((found_token, arg, new_pos)) => {
|
||||||
|
current_pos = new_pos;
|
||||||
|
if let Token::Flag = found_token {
|
||||||
|
flags.insert(arg.unwrap().into(), None);
|
||||||
|
// don't try matching flags as tree elements
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(arg) = arg {
|
||||||
|
args.push(arg.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(next_tree) = local_tree.branches.get(&found_token) {
|
||||||
|
local_tree = next_tree.clone();
|
||||||
|
} else {
|
||||||
|
panic!("found token could not match tree, at {input}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(None) => {
|
||||||
|
if let Some(command_ref) = local_tree.current_command_key {
|
||||||
|
return CommandResult::Ok {
|
||||||
|
command: ParsedCommand {
|
||||||
|
command_ref: command_ref.to_owned(),
|
||||||
|
args,
|
||||||
|
flags,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// todo: check if last token is a common incorrect unquote (multi-member names etc)
|
||||||
|
// todo: check if this is a system name in pk;s command
|
||||||
|
return CommandResult::Err {
|
||||||
|
error: "Command not found.".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(Some(short_circuit)) => {
|
||||||
|
return CommandResult::Err {
|
||||||
|
error: short_circuit.into(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Find the next token from an either raw or partially parsed command string
|
/// Find the next token from an either raw or partially parsed command string
|
||||||
///
|
///
|
||||||
/// Returns:
|
/// Returns:
|
||||||
|
|
@ -236,62 +154,3 @@ fn next_token(
|
||||||
|
|
||||||
Err(None)
|
Err(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_command(input: String) -> CommandResult {
|
|
||||||
let input: SmolStr = input.into();
|
|
||||||
let mut local_tree: TreeBranch = COMMAND_TREE.clone();
|
|
||||||
|
|
||||||
// end position of all currently matched tokens
|
|
||||||
let mut current_pos = 0;
|
|
||||||
|
|
||||||
let mut args: Vec<String> = Vec::new();
|
|
||||||
let mut flags: HashMap<String, Option<String>> = HashMap::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match next_token(
|
|
||||||
local_tree.possible_tokens.clone(),
|
|
||||||
input.clone(),
|
|
||||||
current_pos,
|
|
||||||
) {
|
|
||||||
Ok((found_token, arg, new_pos)) => {
|
|
||||||
current_pos = new_pos;
|
|
||||||
if let Token::Flag = found_token {
|
|
||||||
flags.insert(arg.unwrap().into(), None);
|
|
||||||
// don't try matching flags as tree elements
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(arg) = arg {
|
|
||||||
args.push(arg.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(next_tree) = local_tree.branches.get(&found_token) {
|
|
||||||
local_tree = next_tree.clone();
|
|
||||||
} else {
|
|
||||||
panic!("found token could not match tree, at {input}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(None) => {
|
|
||||||
if let Some(command_ref) = local_tree.current_command_key {
|
|
||||||
return CommandResult::Ok {
|
|
||||||
command: ParsedCommand {
|
|
||||||
command_ref,
|
|
||||||
args,
|
|
||||||
flags,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// todo: check if last token is a common incorrect unquote (multi-member names etc)
|
|
||||||
// todo: check if this is a system name in pk;s command
|
|
||||||
return CommandResult::Err {
|
|
||||||
error: "Command not found.".to_string(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Err(Some(short_circuit)) => {
|
|
||||||
return CommandResult::Err {
|
|
||||||
error: short_circuit.into(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,7 @@ pub(super) fn next_param(input: SmolStr, current_pos: usize) -> Option<(SmolStr,
|
||||||
.is_whitespace()
|
.is_whitespace()
|
||||||
{
|
{
|
||||||
// return quoted string, without quotes
|
// return quoted string, without quotes
|
||||||
return Some((
|
return Some((substr_to_match[1..pos - 1].into(), current_pos + pos + 1));
|
||||||
substr_to_match[1..pos - 1].into(),
|
|
||||||
current_pos + pos + 1,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,14 @@ lazy_static::lazy_static!(
|
||||||
);
|
);
|
||||||
|
|
||||||
impl Token {
|
impl Token {
|
||||||
|
pub fn cmd(value: impl Into<SmolStr>) -> Self {
|
||||||
|
Self::Value(vec![value.into()])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cmd_with_alias(value: impl IntoIterator<Item = impl Into<SmolStr>>) -> Self {
|
||||||
|
Self::Value(value.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_match(&self, input: Option<SmolStr>) -> TokenMatchResult {
|
pub fn try_match(&self, input: Option<SmolStr>) -> TokenMatchResult {
|
||||||
// short circuit on empty things
|
// short circuit on empty things
|
||||||
if matches!(self, Self::Empty) && input.is_none() {
|
if matches!(self, Self::Empty) && input.is_none() {
|
||||||
|
|
|
||||||
57
crates/commands/src/tree.rs
Normal file
57
crates/commands/src/tree.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::{commands::Command, Token};
|
||||||
|
use std::{cmp::Ordering, collections::HashMap};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TreeBranch {
|
||||||
|
pub current_command_key: Option<String>,
|
||||||
|
/// branches.keys(), but sorted by specificity
|
||||||
|
pub possible_tokens: Vec<Token>,
|
||||||
|
pub branches: HashMap<Token, TreeBranch>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TreeBranch {
|
||||||
|
pub fn register_command(&mut self, command: Command) {
|
||||||
|
let mut current_branch = self;
|
||||||
|
// iterate over tokens in command
|
||||||
|
for token in command.tokens {
|
||||||
|
// recursively get or create a sub-branch for each token
|
||||||
|
current_branch = current_branch.branches.entry(token).or_insert(TreeBranch {
|
||||||
|
current_command_key: None,
|
||||||
|
possible_tokens: vec![],
|
||||||
|
branches: HashMap::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// when we're out of tokens, add an Empty branch with the callback and no sub-branches
|
||||||
|
current_branch.branches.insert(
|
||||||
|
Token::Empty,
|
||||||
|
TreeBranch {
|
||||||
|
current_command_key: Some(command.cb),
|
||||||
|
possible_tokens: vec![],
|
||||||
|
branches: HashMap::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sort_tokens(&mut self) {
|
||||||
|
for branch in self.branches.values_mut() {
|
||||||
|
branch.sort_tokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
// put Value tokens at the end
|
||||||
|
// i forget exactly how this works
|
||||||
|
// todo!: document this before PR mergs
|
||||||
|
self.possible_tokens = self
|
||||||
|
.branches
|
||||||
|
.keys()
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| v.clone())
|
||||||
|
.collect();
|
||||||
|
self.possible_tokens.sort_by(|v, _| {
|
||||||
|
if matches!(v, Token::Value(_)) {
|
||||||
|
Ordering::Greater
|
||||||
|
} else {
|
||||||
|
Ordering::Less
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue