refactor(commands): use smolstr, use a decl macro to get rid of all the borrows while creating commands

This commit is contained in:
dusk 2025-01-04 02:49:04 +09:00
parent 11842e7637
commit 405ac11d74
No known key found for this signature in database
5 changed files with 101 additions and 59 deletions

26
Cargo.lock generated
View file

@ -445,6 +445,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "borsh"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03"
dependencies = [
"cfg_aliases",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.12.0" version = "3.12.0"
@ -528,6 +537,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.38" version = "0.4.38"
@ -554,6 +569,7 @@ name = "commands"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"smol_str",
"uniffi", "uniffi",
] ]
@ -3489,6 +3505,16 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "smol_str"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d"
dependencies = [
"borsh",
"serde",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.4.7" version = "0.4.7"

View file

@ -10,6 +10,7 @@ crate-type = ["cdylib"]
lazy_static = { workspace = true } lazy_static = { workspace = true }
uniffi = { version = "0.25" } uniffi = { version = "0.25" }
smol_str = "0.3.2"
[build-dependencies] [build-dependencies]
uniffi = { version = "0.25", features = [ "build" ] } uniffi = { version = "0.25", features = [ "build" ] }

View file

@ -7,6 +7,7 @@ uniffi::include_scaffolding!("commands");
mod string; mod string;
mod token; mod token;
use smol_str::SmolStr;
use token::*; use token::*;
// todo!: move all this stuff into a different file // todo!: move all this stuff into a different file
@ -74,77 +75,84 @@ struct Command {
cb: String, cb: String,
} }
fn command(tokens: &[&Token], help: &str, cb: &str) -> Command { fn command(tokens: impl IntoIterator<Item = Token>, help: impl ToString, cb: impl ToString) -> Command {
Command { Command {
tokens: tokens.iter().map(|&x| x.clone()).collect(), tokens: tokens.into_iter().collect(),
help: help.to_string(), help: help.to_string(),
cb: cb.to_string(), cb: cb.to_string(),
} }
} }
macro_rules! command {
([$($v:expr),+], $help:expr, $cb:expr) => {
$crate::command([$($v.clone()),*], $help, $cb)
};
}
mod commands { mod commands {
use smol_str::SmolStr;
use super::Token; use super::Token;
use super::command; fn cmd(value: impl Into<SmolStr>) -> Token {
use super::Token::*; Token::Value(vec![value.into()])
fn cmd(value: &str) -> Token {
Token::Value(vec![value.to_string()])
} }
pub fn cmd_with_alias(value: &[&str]) -> Token { pub fn cmd_with_alias(value: impl IntoIterator<Item = impl Into<SmolStr>>) -> Token {
Token::Value(value.iter().map(|x| x.to_string()).collect()) Token::Value(value.into_iter().map(Into::into).collect())
} }
// todo: this needs to have less ampersands -alyssa // todo: this needs to have less ampersands -alyssa
pub fn happy() -> Vec<super::Command> { pub fn happy() -> Vec<super::Command> {
let system = &cmd_with_alias(&["system", "s"]); use Token::*;
let member = &cmd_with_alias(&["member", "m"]);
let description = &cmd_with_alias(&["description", "desc"]); let system = cmd_with_alias(["system", "s"]);
let privacy = &cmd_with_alias(&["privacy", "priv"]); let member = cmd_with_alias(["member", "m"]);
let description = cmd_with_alias(["description", "desc"]);
let privacy = cmd_with_alias(["privacy", "priv"]);
vec![ vec![
command(&[&cmd("help")], "help", "Shows the help command"), command!([cmd("help")], "help", "Shows the help command"),
command( command!(
&[system], [system],
"system_show", "system_show",
"Shows information about your system", "Shows information about your system"
), ),
command(&[system, &cmd("new")], "system_new", "Creates a new system"), command!([system, cmd("new")], "system_new", "Creates a new system"),
command( command!(
&[member, &cmd_with_alias(&["new", "n"])], [member, cmd_with_alias(["new", "n"])],
"member_new", "member_new",
"Creates a new system member", "Creates a new system member"
), ),
command( command!(
&[member, &MemberRef], [member, MemberRef],
"member_show", "member_show",
"Shows information about a member", "Shows information about a member"
), ),
command( command!(
&[member, &MemberRef, description], [member, MemberRef, description],
"member_desc_show", "member_desc_show",
"Shows a member's description", "Shows a member's description"
), ),
command( command!(
&[member, &MemberRef, description, &FullString], [member, MemberRef, description, FullString],
"member_desc_update", "member_desc_update",
"Changes a member's description", "Changes a member's description"
), ),
command( command!(
&[member, &MemberRef, privacy], [member, MemberRef, privacy],
"member_privacy_show", "member_privacy_show",
"Displays a member's current privacy settings", "Displays a member's current privacy settings"
), ),
command( command!(
&[ [
member, member,
&MemberRef, MemberRef,
privacy, privacy,
&MemberPrivacyTarget, MemberPrivacyTarget,
&PrivacyLevel, PrivacyLevel
], ],
"member_privacy_update", "member_privacy_update",
"Changes a member's privacy settings", "Changes a member's privacy settings"
), ),
] ]
} }
@ -188,9 +196,9 @@ pub struct ParsedCommand {
/// - optionally a short-circuit error /// - optionally a short-circuit error
fn next_token( fn next_token(
possible_tokens: Vec<Token>, possible_tokens: Vec<Token>,
input: String, input: SmolStr,
current_pos: usize, current_pos: usize,
) -> Result<(Token, Option<String>, usize), Option<String>> { ) -> Result<(Token, Option<SmolStr>, usize), Option<SmolStr>> {
// get next parameter, matching quotes // get next parameter, matching quotes
let param = crate::string::next_param(input.clone(), current_pos); let param = crate::string::next_param(input.clone(), current_pos);
println!("matched: {param:?}\n---"); println!("matched: {param:?}\n---");
@ -203,7 +211,7 @@ fn next_token(
{ {
return Ok(( return Ok((
Token::Flag, Token::Flag,
Some(value.trim_start_matches('-').to_string()), Some(value.trim_start_matches('-').into()),
new_pos, new_pos,
)); ));
} }
@ -230,6 +238,7 @@ fn next_token(
} }
fn parse_command(input: String) -> CommandResult { fn parse_command(input: String) -> CommandResult {
let input: SmolStr = input.into();
let mut local_tree: TreeBranch = COMMAND_TREE.clone(); let mut local_tree: TreeBranch = COMMAND_TREE.clone();
// end position of all currently matched tokens // end position of all currently matched tokens
@ -247,13 +256,13 @@ fn parse_command(input: String) -> CommandResult {
Ok((found_token, arg, new_pos)) => { Ok((found_token, arg, new_pos)) => {
current_pos = new_pos; current_pos = new_pos;
if let Token::Flag = found_token { if let Token::Flag = found_token {
flags.insert(arg.unwrap(), None); flags.insert(arg.unwrap().into(), None);
// don't try matching flags as tree elements // don't try matching flags as tree elements
continue; continue;
} }
if let Some(arg) = arg { if let Some(arg) = arg {
args.push(arg); args.push(arg.into());
} }
if let Some(next_tree) = local_tree.branches.get(&found_token) { if let Some(next_tree) = local_tree.branches.get(&found_token) {
@ -280,7 +289,7 @@ fn parse_command(input: String) -> CommandResult {
} }
Err(Some(short_circuit)) => { Err(Some(short_circuit)) => {
return CommandResult::Err { return CommandResult::Err {
error: short_circuit, error: short_circuit.into(),
}; };
} }
} }

View file

@ -1,5 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use smol_str::SmolStr;
lazy_static::lazy_static! { lazy_static::lazy_static! {
// Dictionary of (left, right) quote pairs // Dictionary of (left, right) quote pairs
// Each char in the string is an individual quote, multi-char strings imply "one of the following chars" // Each char in the string is an individual quote, multi-char strings imply "one of the following chars"
@ -43,14 +45,14 @@ lazy_static::lazy_static! {
// very very simple quote matching // very very simple quote matching
// quotes need to be at start/end of words, and are ignored if a closing quote is not present // quotes need to be at start/end of words, and are ignored if a closing quote is not present
// WTB POSIX quoting: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html // WTB POSIX quoting: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html
pub fn next_param(input: String, current_pos: usize) -> Option<(String, usize)> { pub(super) fn next_param(input: SmolStr, current_pos: usize) -> Option<(SmolStr, usize)> {
if input.len() == current_pos { if input.len() == current_pos {
return None; return None;
} }
let leading_whitespace_count = let leading_whitespace_count =
input[..current_pos].len() - input[..current_pos].trim_start().len(); input[..current_pos].len() - input[..current_pos].trim_start().len();
let substr_to_match = input[current_pos + leading_whitespace_count..].to_string(); let substr_to_match: SmolStr = input[current_pos + leading_whitespace_count..].into();
println!("stuff: {input} {current_pos} {leading_whitespace_count}"); println!("stuff: {input} {current_pos} {leading_whitespace_count}");
println!("to match: {substr_to_match}"); println!("to match: {substr_to_match}");
@ -67,7 +69,7 @@ pub fn next_param(input: String, current_pos: usize) -> Option<(String, usize)>
{ {
// return quoted string, without quotes // return quoted string, without quotes
return Some(( return Some((
substr_to_match[1..pos - 1].to_string(), substr_to_match[1..pos - 1].into(),
current_pos + pos + 1, current_pos + pos + 1,
)); ));
} }
@ -78,7 +80,7 @@ pub fn next_param(input: String, current_pos: usize) -> Option<(String, usize)>
// find next whitespace character // find next whitespace character
for (pos, char) in substr_to_match.clone().char_indices() { for (pos, char) in substr_to_match.clone().char_indices() {
if char.is_whitespace() { if char.is_whitespace() {
return Some((substr_to_match[..pos].to_string(), current_pos + pos + 1)); return Some((substr_to_match[..pos].into(), current_pos + pos + 1));
} }
} }

View file

@ -1,3 +1,5 @@
use smol_str::SmolStr;
#[derive(Debug, Clone, Eq, Hash, PartialEq)] #[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum Token { pub enum Token {
/// Token used to represent a finished command (i.e. no more parameters required) /// Token used to represent a finished command (i.e. no more parameters required)
@ -5,10 +7,10 @@ pub enum Token {
Empty, Empty,
/// A bot-defined value ("member" in `pk;member MyName`) /// A bot-defined value ("member" in `pk;member MyName`)
Value(Vec<String>), Value(Vec<SmolStr>),
/// A command defined by multiple values /// A command defined by multiple values
// todo! // todo!
MultiValue(Vec<Vec<String>>), MultiValue(Vec<Vec<SmolStr>>),
FullString, FullString,
@ -26,20 +28,20 @@ pub enum Token {
pub enum TokenMatchResult { pub enum TokenMatchResult {
NoMatch, NoMatch,
/// Token matched, optionally with a value. /// Token matched, optionally with a value.
Match(Option<String>), Match(Option<SmolStr>),
} }
// move this somewhere else // move this somewhere else
lazy_static::lazy_static!( lazy_static::lazy_static!(
static ref MEMBER_PRIVACY_TARGETS: Vec<String> = vec![ static ref MEMBER_PRIVACY_TARGETS: Vec<SmolStr> = [
"visibility".to_string(), "visibility",
"name".to_string(), "name",
"todo".to_string() "todo",
]; ].into_iter().map(SmolStr::new_static).collect();
); );
impl Token { impl Token {
pub fn try_match(&self, input: Option<String>) -> 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() {
return TokenMatchResult::Match(None); return TokenMatchResult::Match(None);
@ -66,7 +68,9 @@ impl Token {
Self::FullString => return TokenMatchResult::Match(Some(input)), Self::FullString => return TokenMatchResult::Match(Some(input)),
Self::MemberRef => return TokenMatchResult::Match(Some(input)), Self::MemberRef => return TokenMatchResult::Match(Some(input)),
Self::MemberPrivacyTarget Self::MemberPrivacyTarget
if MEMBER_PRIVACY_TARGETS.contains(&input.trim().to_string()) => if MEMBER_PRIVACY_TARGETS
.iter()
.any(|target| target.eq(input.trim())) =>
{ {
return TokenMatchResult::Match(Some(input)) return TokenMatchResult::Match(Some(input))
} }