refactor(commands): separate commands definitions and other code into modules

This commit is contained in:
dusk 2025-01-04 07:35:04 +09:00
parent 405ac11d74
commit af523a4c23
No known key found for this signature in database
24 changed files with 293 additions and 216 deletions

View 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()
}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View 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()
}

View file

@ -0,0 +1 @@

View 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()
}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View 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()
}

View file

@ -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<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"
),
]
}
}
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<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
///
/// 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<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(),
};
}
}
}
}

View file

@ -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));
}
}
}

View file

@ -41,6 +41,14 @@ lazy_static::lazy_static!(
);
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 {
// short circuit on empty things
if matches!(self, Self::Empty) && input.is_none() {

View 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
}
});
}
}