diff --git a/crates/commands/Cargo.toml b/crates/commands/Cargo.toml index 04a68b9c..4983cb53 100644 --- a/crates/commands/Cargo.toml +++ b/crates/commands/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "lib"] [dependencies] lazy_static = { workspace = true } diff --git a/crates/commands/src/commands.rs b/crates/commands/src/commands.rs index 692e0d50..68836497 100644 --- a/crates/commands/src/commands.rs +++ b/crates/commands/src/commands.rs @@ -18,6 +18,8 @@ pub mod server_config; pub mod switch; pub mod system; +use std::fmt::Display; + use smol_str::SmolStr; use crate::{ @@ -47,6 +49,18 @@ impl Command { } } +impl Display for Command { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (idx, token) in self.tokens.iter().enumerate() { + write!(f, "{}", token)?; + if idx < self.tokens.len() - 1 { + write!(f, " ")?; + } + } + write!(f, " - {}", self.help) + } +} + #[macro_export] macro_rules! command { ([$($v:expr),+], $cb:expr, $help:expr) => { diff --git a/crates/commands/src/commands/config.rs b/crates/commands/src/commands/config.rs index f75018be..db16e729 100644 --- a/crates/commands/src/commands/config.rs +++ b/crates/commands/src/commands/config.rs @@ -23,7 +23,12 @@ pub fn cmds() -> impl Iterator { "Shows the autoproxy timeout" ), command!( - [cfg, autoproxy, ["timeout", "tm"], [Toggle("toggle"), Reset("reset"), FullString("timeout")]], + [ + cfg, + autoproxy, + ["timeout", "tm"], + [Disable("toggle"), Reset("reset"), FullString("timeout")] + ], "cfg_ap_timeout_update", "Sets the autoproxy timeout" ), diff --git a/crates/commands/src/commands/help.rs b/crates/commands/src/commands/help.rs index 86ec0f97..f663ee68 100644 --- a/crates/commands/src/commands/help.rs +++ b/crates/commands/src/commands/help.rs @@ -3,21 +3,9 @@ use super::*; pub fn cmds() -> impl Iterator { let help = ["help", "h"]; [ - command!( - [help], - "help", - "Shows the help command" - ), - command!( - [help, "commands"], - "help_commands", - "help commands" - ), - command!( - [help, "proxy"], - "help_proxy", - "help proxy" - ), + command!([help], "help", "Shows the help command"), + command!([help, "commands"], "help_commands", "help commands"), + command!([help, "proxy"], "help_proxy", "help proxy"), ] .into_iter() } diff --git a/crates/commands/src/commands/member.rs b/crates/commands/src/commands/member.rs index 42c3e1d2..12b66155 100644 --- a/crates/commands/src/commands/member.rs +++ b/crates/commands/src/commands/member.rs @@ -30,7 +30,12 @@ pub fn cmds() -> impl Iterator { "Shows a member's description" ), command!( - [member, MemberRef("target"), description, FullString("description")], + [ + member, + MemberRef("target"), + description, + FullString("description") + ], "member_desc_update", "Changes a member's description" ), diff --git a/crates/commands/src/lib.rs b/crates/commands/src/lib.rs index b0d417e1..989d2f9b 100644 --- a/crates/commands/src/lib.rs +++ b/crates/commands/src/lib.rs @@ -40,9 +40,9 @@ pub enum CommandResult { pub enum Parameter { MemberRef { member: String }, SystemRef { system: String }, - MemberPrivacyTarget { target: String }, - PrivacyLevel { level: String }, - OpaqueString { raw: String }, + MemberPrivacyTarget { target: String }, + PrivacyLevel { level: String }, + OpaqueString { raw: String }, Toggle { toggle: bool }, Reset, } @@ -109,6 +109,7 @@ fn parse_command(input: String) -> CommandResult { }, }; } + // 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 { @@ -161,8 +162,14 @@ fn next_token( // for FullString just send the whole string let input_to_match = param.clone().map(|v| v.0); match token.try_match(input_to_match) { - TokenMatchResult::Match(value) => return Ok((token, value, param.map(|v| v.1).unwrap_or(current_pos))), - TokenMatchResult::MissingParameter { name } => return Err(Some(format_smolstr!("Missing parameter `{name}` in command `{input} [{name}]`."))), + TokenMatchResult::Match(value) => { + return Ok((token, value, param.map(|v| v.1).unwrap_or(current_pos))) + } + TokenMatchResult::MissingParameter { name } => { + return Err(Some(format_smolstr!( + "Missing parameter `{name}` in command `{input} [{name}]`." + ))) + } TokenMatchResult::NoMatch => {} } } diff --git a/crates/commands/src/main.rs b/crates/commands/src/main.rs new file mode 100644 index 00000000..aa8ce9e4 --- /dev/null +++ b/crates/commands/src/main.rs @@ -0,0 +1,7 @@ +use commands::commands as cmds; + +fn main() { + for command in cmds::all() { + println!("{}", command); + } +} diff --git a/crates/commands/src/token.rs b/crates/commands/src/token.rs index 2f82b207..fb4371ba 100644 --- a/crates/commands/src/token.rs +++ b/crates/commands/src/token.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{fmt::Display, ops::Not, str::FromStr}; use smol_str::{SmolStr, ToSmolStr}; @@ -33,6 +33,8 @@ pub enum Token { PrivacyLevel(ParamName), /// on, off; yes, no; true, false + Enable(ParamName), + Disable(ParamName), Toggle(ParamName), /// reset, clear, default @@ -100,11 +102,15 @@ impl Token { | Self::SystemRef(param_name) | Self::PrivacyLevel(param_name) | Self::Toggle(param_name) + | Self::Enable(param_name) + | Self::Disable(param_name) | Self::Reset(param_name) => MissingParameter { name: param_name }, - Self::Any(tokens) => tokens.is_empty().then_some(NoMatch).unwrap_or_else(|| { - let mut results = tokens.iter().map(|t| t.try_match(None)); - results.find(|r| !matches!(r, NoMatch)).unwrap_or(NoMatch) - }), + Self::Any(tokens) => { + tokens.is_empty().then_some(NoMatch).unwrap_or_else(|| { + let mut results = tokens.iter().map(|t| t.try_match(None)); + results.find(|r| !matches!(r, NoMatch)).unwrap_or(NoMatch) + }) + } // everything else doesnt match if no input anyway Token::Value(_) => NoMatch, Token::Flag => NoMatch, @@ -167,12 +173,30 @@ impl Token { ), Err(_) => NoMatch, }, - - Self::Toggle(param_name) => match Toggle::from_str(input) { + Self::Toggle(param_name) => match Enable::from_str(input) + .map(Into::::into) + .or_else(|_| Disable::from_str(input).map(Into::::into)) + { + Ok(toggle) => TokenMatchResult::new_match_param( + input, + param_name, + Parameter::Toggle { toggle }, + ), + Err(_) => NoMatch, + }, + Self::Enable(param_name) => match Enable::from_str(input) { Ok(t) => TokenMatchResult::new_match_param( input, param_name, - Parameter::Toggle { toggle: t.0 }, + Parameter::Toggle { toggle: t.into() }, + ), + Err(_) => NoMatch, + }, + Self::Disable(param_name) => match Disable::from_str(input) { + Ok(t) => TokenMatchResult::new_match_param( + input, + param_name, + Parameter::Toggle { toggle: t.into() }, ), Err(_) => NoMatch, }, @@ -185,6 +209,37 @@ impl Token { } } +impl Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Token::Empty => write!(f, ""), + Token::Any(vec) => { + write!(f, "(")?; + for (i, token) in vec.iter().enumerate() { + if i != 0 { + write!(f, " | ")?; + } + write!(f, "{}", token)?; + } + write!(f, ")") + } + Token::Value(vec) if vec.is_empty().not() => write!(f, "{}", vec.first().unwrap()), + Token::Value(_) => Ok(()), // if value token has no values (lol), don't print anything + // todo: it might not be the best idea to directly use param name here (what if we want to display something else but keep the name? or translations?) + Token::FullString(param_name) => write!(f, "[{}]", param_name), + Token::MemberRef(param_name) => write!(f, "<{}>", param_name), + Token::SystemRef(param_name) => write!(f, "<{}>", param_name), + Token::MemberPrivacyTarget(param_name) => write!(f, "[{}]", param_name), + Token::PrivacyLevel(param_name) => write!(f, "[{}]", param_name), + Token::Enable(_) => write!(f, "on"), + Token::Disable(_) => write!(f, "off"), + Token::Toggle(_) => write!(f, "on/off"), + Token::Reset(_) => write!(f, "reset"), + Token::Flag => unreachable!("flag tokens should never be in command definitions"), + } + } +} + /// Convenience trait to convert types into [`Token`]s. pub trait ToToken { fn to_token(&self) -> Token; @@ -218,7 +273,13 @@ impl ToToken for [Token] { pub enum MemberPrivacyTarget { Visibility, Name, - // todo + Description, + Banner, + Avatar, + Birthday, + Pronouns, + Proxy, + Metadata, } impl AsRef for MemberPrivacyTarget { @@ -226,6 +287,13 @@ impl AsRef for MemberPrivacyTarget { match self { Self::Visibility => "visibility", Self::Name => "name", + Self::Description => "description", + Self::Banner => "banner", + Self::Avatar => "avatar", + Self::Birthday => "birthday", + Self::Pronouns => "pronouns", + Self::Proxy => "proxy", + Self::Metadata => "metadata", } } } @@ -235,9 +303,16 @@ impl FromStr for MemberPrivacyTarget { type Err = (); fn from_str(s: &str) -> Result { - match s { + match s.to_lowercase().as_str() { "visibility" => Ok(Self::Visibility), "name" => Ok(Self::Name), + "description" => Ok(Self::Description), + "banner" => Ok(Self::Banner), + "avatar" => Ok(Self::Avatar), + "birthday" => Ok(Self::Birthday), + "pronouns" => Ok(Self::Pronouns), + "proxy" => Ok(Self::Proxy), + "metadata" => Ok(Self::Metadata), _ => Err(()), } } @@ -270,28 +345,6 @@ impl FromStr for PrivacyLevel { } } -#[derive(Debug, Clone, Eq, Hash, PartialEq)] -pub struct Toggle(bool); - -impl AsRef for Toggle { - fn as_ref(&self) -> &str { - // on / off better than others for docs and stuff? - self.0.then_some("on").unwrap_or("off") - } -} - -impl FromStr for Toggle { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "on" | "yes" | "true" | "enable" | "enabled" => Ok(Self(true)), - "off" | "no" | "false" | "disable" | "disabled" => Ok(Self(false)), - _ => Err(()), - } - } -} - #[derive(Debug, Clone, Eq, Hash, PartialEq)] pub struct Reset; @@ -311,3 +364,55 @@ impl FromStr for Reset { } } } + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct Enable; + +impl AsRef for Enable { + fn as_ref(&self) -> &str { + "on" + } +} + +impl FromStr for Enable { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "on" | "yes" | "true" | "enable" | "enabled" => Ok(Self), + _ => Err(()), + } + } +} + +impl Into for Enable { + fn into(self) -> bool { + true + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct Disable; + +impl AsRef for Disable { + fn as_ref(&self) -> &str { + "off" + } +} + +impl FromStr for Disable { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "off" | "no" | "false" | "disable" | "disabled" => Ok(Self), + _ => Err(()), + } + } +} + +impl Into for Disable { + fn into(self) -> bool { + false + } +} diff --git a/flake.nix b/flake.nix index 69b3d73a..3c043f57 100644 --- a/flake.nix +++ b/flake.nix @@ -90,7 +90,7 @@ set -x commandslib="''${1:-}" if [ "$commandslib" == "" ]; then - cargo -Z unstable-options build --package commands --release --artifact-dir obj/ + cargo -Z unstable-options build --package commands --lib --release --artifact-dir obj/ commandslib="obj/libcommands.so" else cp -f "$commandslib" obj/