From af523a4c232e5f08dae2cd14bb1e0ef85433849b Mon Sep 17 00:00:00 2001 From: dusk Date: Sat, 4 Jan 2025 07:35:04 +0900 Subject: [PATCH] refactor(commands): separate commands definitions and other code into modules --- crates/commands/src/commands.rs | 57 ++++ crates/commands/src/commands/admin.rs | 1 + crates/commands/src/commands/api.rs | 1 + crates/commands/src/commands/autoproxy.rs | 1 + crates/commands/src/commands/checks.rs | 1 + crates/commands/src/commands/commands.rs | 1 + crates/commands/src/commands/config.rs | 1 + crates/commands/src/commands/dashboard.rs | 1 + crates/commands/src/commands/debug.rs | 1 + crates/commands/src/commands/fun.rs | 1 + crates/commands/src/commands/group.rs | 1 + crates/commands/src/commands/help.rs | 10 + crates/commands/src/commands/import_export.rs | 1 + crates/commands/src/commands/member.rs | 50 ++++ crates/commands/src/commands/message.rs | 1 + crates/commands/src/commands/misc.rs | 1 + crates/commands/src/commands/random.rs | 1 + crates/commands/src/commands/server_config.rs | 1 + crates/commands/src/commands/switch.rs | 1 + crates/commands/src/commands/system.rs | 23 ++ crates/commands/src/lib.rs | 283 +++++------------- crates/commands/src/string.rs | 5 +- crates/commands/src/token.rs | 8 + crates/commands/src/tree.rs | 57 ++++ 24 files changed, 293 insertions(+), 216 deletions(-) create mode 100644 crates/commands/src/commands.rs create mode 100644 crates/commands/src/commands/admin.rs create mode 100644 crates/commands/src/commands/api.rs create mode 100644 crates/commands/src/commands/autoproxy.rs create mode 100644 crates/commands/src/commands/checks.rs create mode 100644 crates/commands/src/commands/commands.rs create mode 100644 crates/commands/src/commands/config.rs create mode 100644 crates/commands/src/commands/dashboard.rs create mode 100644 crates/commands/src/commands/debug.rs create mode 100644 crates/commands/src/commands/fun.rs create mode 100644 crates/commands/src/commands/group.rs create mode 100644 crates/commands/src/commands/help.rs create mode 100644 crates/commands/src/commands/import_export.rs create mode 100644 crates/commands/src/commands/member.rs create mode 100644 crates/commands/src/commands/message.rs create mode 100644 crates/commands/src/commands/misc.rs create mode 100644 crates/commands/src/commands/random.rs create mode 100644 crates/commands/src/commands/server_config.rs create mode 100644 crates/commands/src/commands/switch.rs create mode 100644 crates/commands/src/commands/system.rs create mode 100644 crates/commands/src/tree.rs diff --git a/crates/commands/src/commands.rs b/crates/commands/src/commands.rs new file mode 100644 index 00000000..1e9e06e8 --- /dev/null +++ b/crates/commands/src/commands.rs @@ -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, + pub help: String, + pub cb: String, +} + +impl Command { + pub fn new( + tokens: impl IntoIterator, + 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 { + (help::cmds()) + .chain(system::cmds()) + .chain(member::cmds()) + .collect() +} diff --git a/crates/commands/src/commands/admin.rs b/crates/commands/src/commands/admin.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/admin.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/api.rs b/crates/commands/src/commands/api.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/api.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/autoproxy.rs b/crates/commands/src/commands/autoproxy.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/autoproxy.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/checks.rs b/crates/commands/src/commands/checks.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/checks.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/commands.rs b/crates/commands/src/commands/commands.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/commands.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/config.rs b/crates/commands/src/commands/config.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/config.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/dashboard.rs b/crates/commands/src/commands/dashboard.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/dashboard.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/debug.rs b/crates/commands/src/commands/debug.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/debug.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/fun.rs b/crates/commands/src/commands/fun.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/fun.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/group.rs b/crates/commands/src/commands/group.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/group.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/help.rs b/crates/commands/src/commands/help.rs new file mode 100644 index 00000000..e91d13f3 --- /dev/null +++ b/crates/commands/src/commands/help.rs @@ -0,0 +1,10 @@ +use super::*; + +pub fn cmds() -> impl Iterator { + [command!( + [Token::cmd("help")], + "help", + "Shows the help command" + )] + .into_iter() +} diff --git a/crates/commands/src/commands/import_export.rs b/crates/commands/src/commands/import_export.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/import_export.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/member.rs b/crates/commands/src/commands/member.rs new file mode 100644 index 00000000..d92e59cc --- /dev/null +++ b/crates/commands/src/commands/member.rs @@ -0,0 +1,50 @@ +use super::*; + +pub fn cmds() -> impl Iterator { + 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() +} diff --git a/crates/commands/src/commands/message.rs b/crates/commands/src/commands/message.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/message.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/misc.rs b/crates/commands/src/commands/misc.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/misc.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/random.rs b/crates/commands/src/commands/random.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/random.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/server_config.rs b/crates/commands/src/commands/server_config.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/server_config.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/switch.rs b/crates/commands/src/commands/switch.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/commands/src/commands/switch.rs @@ -0,0 +1 @@ + diff --git a/crates/commands/src/commands/system.rs b/crates/commands/src/commands/system.rs new file mode 100644 index 00000000..9d1bdf1f --- /dev/null +++ b/crates/commands/src/commands/system.rs @@ -0,0 +1,23 @@ +use super::*; + +pub fn cmds() -> impl Iterator { + 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() +} diff --git a/crates/commands/src/lib.rs b/crates/commands/src/lib.rs index a98bdd9e..4a86d841 100644 --- a/crates/commands/src/lib.rs +++ b/crates/commands/src/lib.rs @@ -1,162 +1,20 @@ #![feature(let_chains)] -use core::panic; -use std::{cmp::Ordering, collections::HashMap}; +mod commands; +mod string; +mod token; +mod tree; uniffi::include_scaffolding!("commands"); -mod string; -mod token; +use core::panic; +use std::collections::HashMap; + use smol_str::SmolStr; -use token::*; +use tree::TreeBranch; -// todo!: move all this stuff into a different file -// lib.rs should just have exported symbols and command definitions - -#[derive(Debug, Clone)] -struct TreeBranch { - current_command_key: Option, - /// branches.keys(), but sorted by specificity - possible_tokens: Vec, - branches: HashMap, -} - -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, - help: String, - cb: String, -} - -fn command(tokens: impl IntoIterator, 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) -> Token { - Token::Value(vec![value.into()]) - } - - pub fn cmd_with_alias(value: impl IntoIterator>) -> Token { - Token::Value(value.into_iter().map(Into::into).collect()) - } - - // todo: this needs to have less ampersands -alyssa - pub fn happy() -> Vec { - 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" - ), - ] - } -} +pub use commands::Command; +pub use token::*; lazy_static::lazy_static! { static ref COMMAND_TREE: TreeBranch = { @@ -166,7 +24,7 @@ lazy_static::lazy_static! { 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(); @@ -187,6 +45,66 @@ pub struct ParsedCommand { pub flags: HashMap>, } +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 = Vec::new(); + let mut flags: HashMap> = 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 /// /// Returns: @@ -236,62 +154,3 @@ fn next_token( 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 = Vec::new(); - let mut flags: HashMap> = 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(), - }; - } - } - } -} diff --git a/crates/commands/src/string.rs b/crates/commands/src/string.rs index dd48fbf0..e6ca6c72 100644 --- a/crates/commands/src/string.rs +++ b/crates/commands/src/string.rs @@ -68,10 +68,7 @@ pub(super) fn next_param(input: SmolStr, current_pos: usize) -> Option<(SmolStr, .is_whitespace() { // return quoted string, without quotes - return Some(( - substr_to_match[1..pos - 1].into(), - current_pos + pos + 1, - )); + return Some((substr_to_match[1..pos - 1].into(), current_pos + pos + 1)); } } } diff --git a/crates/commands/src/token.rs b/crates/commands/src/token.rs index e66ca9d4..60df6dfc 100644 --- a/crates/commands/src/token.rs +++ b/crates/commands/src/token.rs @@ -41,6 +41,14 @@ lazy_static::lazy_static!( ); impl Token { + pub fn cmd(value: impl Into) -> Self { + Self::Value(vec![value.into()]) + } + + pub fn cmd_with_alias(value: impl IntoIterator>) -> Self { + Self::Value(value.into_iter().map(Into::into).collect()) + } + pub fn try_match(&self, input: Option) -> TokenMatchResult { // short circuit on empty things if matches!(self, Self::Empty) && input.is_none() { diff --git a/crates/commands/src/tree.rs b/crates/commands/src/tree.rs new file mode 100644 index 00000000..d897dc4f --- /dev/null +++ b/crates/commands/src/tree.rs @@ -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, + /// branches.keys(), but sorted by specificity + pub possible_tokens: Vec, + pub branches: HashMap, +} + +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 + } + }); + } +}