From 07541d9926254b9a413df55e0dc1edd7689d06b0 Mon Sep 17 00:00:00 2001 From: dusk Date: Wed, 15 Jan 2025 03:52:32 +0900 Subject: [PATCH] refactor(commands): rewrite how parameters are handled so they work same across cmd params / flag params, and make it easier to add new parameters --- crates/commands/src/commands.rs | 20 +- crates/commands/src/commands/config.rs | 6 +- crates/commands/src/commands/member.rs | 24 +- crates/commands/src/commands/system.rs | 4 +- crates/commands/src/flag.rs | 56 ++-- crates/commands/src/lib.rs | 34 ++- crates/commands/src/parameter.rs | 314 ++++++++++++++++++++++ crates/commands/src/token.rs | 348 +++++-------------------- 8 files changed, 443 insertions(+), 363 deletions(-) create mode 100644 crates/commands/src/parameter.rs diff --git a/crates/commands/src/commands.rs b/crates/commands/src/commands.rs index 9e3c9a13..c331a3fe 100644 --- a/crates/commands/src/commands.rs +++ b/crates/commands/src/commands.rs @@ -22,11 +22,7 @@ use std::fmt::{Debug, Display}; use smol_str::SmolStr; -use crate::{ - command, - flag::{Flag, FlagValue}, - token::Token, -}; +use crate::{any, command, flag::Flag, parameter::*, token::Token}; #[derive(Debug, Clone)] pub struct Command { @@ -51,17 +47,7 @@ impl Command { let mut was_parameter = true; for (idx, token) in tokens.iter().enumerate().rev() { match token { - Token::OpaqueRemainder(_) - | Token::OpaqueString(_) - | Token::MemberRef(_) - | Token::MemberPrivacyTarget(_) - | Token::SystemRef(_) - | Token::PrivacyLevel(_) - | Token::Toggle(_) - | Token::Enable(_) - | Token::Disable(_) - | Token::Reset(_) - | Token::Any(_) => { + Token::Parameter(_, _) | Token::Any(_) => { parse_flags_before = idx; was_parameter = true; } @@ -92,7 +78,7 @@ impl Command { self } - pub fn value_flag(mut self, name: impl Into, value: FlagValue) -> Self { + pub fn value_flag(mut self, name: impl Into, value: impl Parameter + 'static) -> Self { self.flags.push(Flag::new(name).with_value(value)); self } diff --git a/crates/commands/src/commands/config.rs b/crates/commands/src/commands/config.rs index 7df70321..47c46cbc 100644 --- a/crates/commands/src/commands/config.rs +++ b/crates/commands/src/commands/config.rs @@ -1,8 +1,6 @@ use super::*; pub fn cmds() -> impl Iterator { - use Token::*; - let cfg = ["config", "cfg"]; let autoproxy = ["autoproxy", "ap"]; @@ -13,7 +11,7 @@ pub fn cmds() -> impl Iterator { "Shows autoproxy status for the account" ), command!( - [cfg, autoproxy, ["account", "ac"], Toggle("toggle")], + [cfg, autoproxy, ["account", "ac"], Toggle], "cfg_ap_account_update", "Toggles autoproxy for the account" ), @@ -27,7 +25,7 @@ pub fn cmds() -> impl Iterator { cfg, autoproxy, ["timeout", "tm"], - [Disable("toggle"), Reset("reset"), OpaqueString("timeout")] // todo: we should parse duration / time values + any!(Disable, Reset, ("timeout", OpaqueString::SINGLE)) // todo: we should parse duration / time values ], "cfg_ap_timeout_update", "Sets the autoproxy timeout" diff --git a/crates/commands/src/commands/member.rs b/crates/commands/src/commands/member.rs index ac4043ce..6f33e615 100644 --- a/crates/commands/src/commands/member.rs +++ b/crates/commands/src/commands/member.rs @@ -1,8 +1,6 @@ use super::*; pub fn cmds() -> impl Iterator { - use Token::*; - let member = ["member", "m"]; let description = ["description", "desc"]; let privacy = ["privacy", "priv"]; @@ -10,49 +8,49 @@ pub fn cmds() -> impl Iterator { [ command!( - [member, new, OpaqueString("name")], + [member, new, ("name", OpaqueString::SINGLE)], "member_new", "Creates a new system member" ), command!( - [member, MemberRef("target")], + [member, MemberRef], "member_show", "Shows information about a member" ) - .value_flag("pt", FlagValue::OpaqueString), + .value_flag("pt", Disable), command!( - [member, MemberRef("target"), description], + [member, MemberRef, description], "member_desc_show", "Shows a member's description" ), command!( [ member, - MemberRef("target"), + MemberRef, description, - OpaqueRemainder("description") + ("description", OpaqueString::REMAINDER) ], "member_desc_update", "Changes a member's description" ), command!( - [member, MemberRef("target"), privacy], + [member, MemberRef, privacy], "member_privacy_show", "Displays a member's current privacy settings" ), command!( [ member, - MemberRef("target"), + MemberRef, privacy, - MemberPrivacyTarget("privacy_target"), - PrivacyLevel("new_privacy_level") + MemberPrivacyTarget, + ("new_privacy_level", PrivacyLevel) ], "member_privacy_update", "Changes a member's privacy settings" ), command!( - [member, MemberRef("target"), "soulscream"], + [member, MemberRef, "soulscream"], "member_soulscream", "todo" ) diff --git a/crates/commands/src/commands/system.rs b/crates/commands/src/commands/system.rs index b505e23f..47eb6591 100644 --- a/crates/commands/src/commands/system.rs +++ b/crates/commands/src/commands/system.rs @@ -1,8 +1,6 @@ use super::*; pub fn cmds() -> impl Iterator { - use Token::*; - let system = ["system", "s"]; let new = ["new", "n"]; @@ -14,7 +12,7 @@ pub fn cmds() -> impl Iterator { ), command!([system, new], "system_new", "Creates a new system"), command!( - [system, new, OpaqueString("name")], + [system, new, ("name", OpaqueString::SINGLE)], "system_new", "Creates a new system" ), diff --git a/crates/commands/src/flag.rs b/crates/commands/src/flag.rs index f9facb4e..0b3fce4f 100644 --- a/crates/commands/src/flag.rs +++ b/crates/commands/src/flag.rs @@ -1,50 +1,27 @@ -use std::fmt::Display; +use std::{fmt::Display, sync::Arc}; use smol_str::SmolStr; -use crate::Parameter; - -#[derive(Debug, Clone)] -pub enum FlagValue { - OpaqueString, -} - -impl FlagValue { - fn try_match(&self, input: &str) -> Result { - if input.is_empty() { - return Err(FlagValueMatchError::ValueMissing); - } - - match self { - Self::OpaqueString => Ok(Parameter::OpaqueString { raw: input.into() }), - } - } -} - -impl Display for FlagValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - FlagValue::OpaqueString => write!(f, "value"), - } - } -} +use crate::{parameter::Parameter, Parameter as FfiParam}; #[derive(Debug)] pub enum FlagValueMatchError { ValueMissing, + InvalidValue { raw: SmolStr, msg: SmolStr }, } #[derive(Debug, Clone)] pub struct Flag { name: SmolStr, - value: Option, + value: Option>, } impl Display for Flag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "-{}", self.name)?; if let Some(value) = self.value.as_ref() { - write!(f, "={value}")?; + write!(f, "=")?; + value.format(f, value.default_name())?; } Ok(()) } @@ -55,7 +32,7 @@ pub enum FlagMatchError { ValueMatchFailed(FlagValueMatchError), } -type TryMatchFlagResult = Option, FlagMatchError>>; +type TryMatchFlagResult = Option, FlagMatchError>>; impl Flag { pub fn new(name: impl Into) -> Self { @@ -65,8 +42,8 @@ impl Flag { } } - pub fn with_value(mut self, value: FlagValue) -> Self { - self.value = Some(value); + pub fn with_value(mut self, param: impl Parameter + 'static) -> Self { + self.value = Some(Arc::new(param)); self } @@ -74,17 +51,13 @@ impl Flag { &self.name } - pub fn value(&self) -> Option<&FlagValue> { - self.value.as_ref() - } - pub fn try_match(&self, input_name: &str, input_value: Option<&str>) -> TryMatchFlagResult { // if not matching flag then skip anymore matching if self.name != input_name { return None; } // get token to try matching with, if flag doesn't have one then that means it is matched (it is without any value) - let Some(value) = self.value() else { + let Some(value) = self.value.as_deref() else { return Some(Ok(None)); }; // check if we have a non-empty flag value, we return error if not (because flag requested a value) @@ -94,9 +67,14 @@ impl Flag { ))); }; // try matching the value - match value.try_match(input_value) { + match value.match_value(input_value) { Ok(param) => Some(Ok(Some(param))), - Err(err) => Some(Err(FlagMatchError::ValueMatchFailed(err))), + Err(err) => Some(Err(FlagMatchError::ValueMatchFailed( + FlagValueMatchError::InvalidValue { + raw: input_value.into(), + msg: err, + }, + ))), } } } diff --git a/crates/commands/src/lib.rs b/crates/commands/src/lib.rs index 04b66b2f..3e3a5a49 100644 --- a/crates/commands/src/lib.rs +++ b/crates/commands/src/lib.rs @@ -3,6 +3,7 @@ pub mod commands; mod flag; +mod parameter; mod string; mod token; mod tree; @@ -91,7 +92,7 @@ pub fn parse_command(prefix: String, input: String) -> CommandResult { if let Some(next_tree) = local_tree.get_branch(&found_token) { local_tree = next_tree.clone(); } else { - panic!("found token could not match tree, at {input}"); + panic!("found token {found_token:?} could not match tree, at {input}"); } } Some(Err((token, err))) => { @@ -114,6 +115,9 @@ pub fn parse_command(prefix: String, input: String) -> CommandResult { write!(&mut msg, " in command `{prefix}{input} {token}`.").expect("oom"); msg } + TokenMatchError::ParameterMatchError { input: raw, msg } => { + format!("Parameter `{raw}` in command `{prefix}{input}` could not be parsed: {msg}.") + } }; return CommandResult::Err { error: error_msg }; } @@ -163,17 +167,23 @@ pub fn parse_command(prefix: String, input: String) -> CommandResult { flags.insert(name.into(), value); } Err((flag, err)) => { - match err { + let error = match err { FlagMatchError::ValueMatchFailed(FlagValueMatchError::ValueMissing) => { - return CommandResult::Err { - error: format!( - "Flag `-{name}` in command `{prefix}{input}` is missing a value, try passing `-{name}={value}`.", - name = flag.name(), - value = flag.value().expect("value missing error cant happen without a value"), - ), - } + format!( + "Flag `-{name}` in command `{prefix}{input}` is missing a value, try passing `{flag}`.", + name = flag.name() + ) } - } + FlagMatchError::ValueMatchFailed( + FlagValueMatchError::InvalidValue { msg, raw }, + ) => { + format!( + "Flag `-{name}` in command `{prefix}{input}` has a value (`{raw}`) that could not be parsed: {msg}.", + name = flag.name() + ) + } + }; + return CommandResult::Err { error }; } } } @@ -282,7 +292,8 @@ fn next_token<'a>( // iterate over tokens and run try_match for token in possible_tokens { - let is_match_remaining_token = |token: &Token| matches!(token, Token::OpaqueRemainder(_)); + let is_match_remaining_token = + |token: &Token| matches!(token, Token::Parameter(_, param) if param.remainder()); // check if this is a token that matches the rest of the input let match_remaining = is_match_remaining_token(token) // check for Any here if it has a "match remainder" token in it @@ -296,6 +307,7 @@ fn next_token<'a>( }); match token.try_match(input_to_match) { Some(Ok(value)) => { + println!("matched token: {}", token); let next_pos = match matched { // return last possible pos if we matched remaining, Some(_) if match_remaining => input.len(), diff --git a/crates/commands/src/parameter.rs b/crates/commands/src/parameter.rs new file mode 100644 index 00000000..7833a43e --- /dev/null +++ b/crates/commands/src/parameter.rs @@ -0,0 +1,314 @@ +use std::{fmt::Debug, str::FromStr}; + +use smol_str::SmolStr; + +use crate::{ParamName, Parameter as FfiParam}; + +pub trait Parameter: Debug + Send + Sync { + fn remainder(&self) -> bool { + false + } + fn default_name(&self) -> ParamName; + fn format(&self, f: &mut std::fmt::Formatter, name: &str) -> std::fmt::Result; + fn match_value(&self, input: &str) -> Result; +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct OpaqueString(bool); + +impl OpaqueString { + pub const SINGLE: Self = Self(false); + pub const REMAINDER: Self = Self(true); +} + +impl Parameter for OpaqueString { + fn remainder(&self) -> bool { + self.0 + } + + fn default_name(&self) -> ParamName { + "string" + } + + fn format(&self, f: &mut std::fmt::Formatter, name: &str) -> std::fmt::Result { + write!(f, "[{name}]") + } + + fn match_value(&self, input: &str) -> Result { + Ok(FfiParam::OpaqueString { raw: input.into() }) + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct MemberRef; + +impl Parameter for MemberRef { + fn default_name(&self) -> ParamName { + "member" + } + + fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result { + write!(f, "") + } + + fn match_value(&self, input: &str) -> Result { + Ok(FfiParam::MemberRef { + member: input.into(), + }) + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct SystemRef; + +impl Parameter for SystemRef { + fn default_name(&self) -> ParamName { + "system" + } + + fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result { + write!(f, "") + } + + fn match_value(&self, input: &str) -> Result { + Ok(FfiParam::SystemRef { + system: input.into(), + }) + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct MemberPrivacyTarget; + +pub enum MemberPrivacyTargetKind { + Visibility, + Name, + Description, + Banner, + Avatar, + Birthday, + Pronouns, + Proxy, + Metadata, +} + +impl AsRef for MemberPrivacyTargetKind { + fn as_ref(&self) -> &str { + 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", + } + } +} + +impl FromStr for MemberPrivacyTargetKind { + // todo: figure out how to represent these errors best + type Err = SmolStr; + + fn from_str(s: &str) -> Result { + // todo: this doesnt parse all the possible ways + 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("invalid member privacy target".into()), + } + } +} + +impl Parameter for MemberPrivacyTarget { + fn default_name(&self) -> ParamName { + "member_privacy_target" + } + + fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result { + write!(f, "") + } + + fn match_value(&self, input: &str) -> Result { + MemberPrivacyTargetKind::from_str(input).map(|target| FfiParam::MemberPrivacyTarget { + target: target.as_ref().into(), + }) + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct PrivacyLevel; + +pub enum PrivacyLevelKind { + Public, + Private, +} + +impl AsRef for PrivacyLevelKind { + fn as_ref(&self) -> &str { + match self { + Self::Public => "public", + Self::Private => "private", + } + } +} + +impl FromStr for PrivacyLevelKind { + type Err = SmolStr; // todo + + fn from_str(s: &str) -> Result { + match s { + "public" => Ok(PrivacyLevelKind::Public), + "private" => Ok(PrivacyLevelKind::Private), + _ => Err("invalid privacy level".into()), + } + } +} + +impl Parameter for PrivacyLevel { + fn default_name(&self) -> ParamName { + "privacy_level" + } + + fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result { + write!(f, "[privacy level]") + } + + fn match_value(&self, input: &str) -> Result { + PrivacyLevelKind::from_str(input).map(|level| FfiParam::PrivacyLevel { + level: level.as_ref().into(), + }) + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct Reset; + +impl AsRef for Reset { + fn as_ref(&self) -> &str { + "reset" + } +} + +impl FromStr for Reset { + type Err = SmolStr; + + fn from_str(s: &str) -> Result { + match s { + "reset" | "clear" | "default" => Ok(Self), + _ => Err("not reset".into()), + } + } +} + +impl Parameter for Reset { + fn default_name(&self) -> ParamName { + "reset" + } + + fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result { + write!(f, "reset") + } + + fn match_value(&self, input: &str) -> Result { + Self::from_str(input).map(|_| FfiParam::Toggle { toggle: true }) + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct Toggle; + +impl Parameter for Toggle { + fn default_name(&self) -> ParamName { + "toggle" + } + + fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result { + write!(f, "on/off") + } + + fn match_value(&self, input: &str) -> Result { + Enable::from_str(input) + .map(Into::::into) + .or_else(|_| Disable::from_str(input).map(Into::::into)) + .map(|toggle| FfiParam::Toggle { toggle }) + .map_err(|_| "invalid toggle".into()) + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct Enable; + +impl FromStr for Enable { + type Err = SmolStr; + + fn from_str(s: &str) -> Result { + match s { + "on" | "yes" | "true" | "enable" | "enabled" => Ok(Self), + _ => Err("invalid enable".into()), + } + } +} + +impl Parameter for Enable { + fn default_name(&self) -> ParamName { + "enable" + } + + fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result { + write!(f, "on") + } + + fn match_value(&self, input: &str) -> Result { + Self::from_str(input).map(|e| FfiParam::Toggle { toggle: e.into() }) + } +} + +impl Into for Enable { + fn into(self) -> bool { + true + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct Disable; + +impl FromStr for Disable { + type Err = SmolStr; + + fn from_str(s: &str) -> Result { + match s { + "off" | "no" | "false" | "disable" | "disabled" => Ok(Self), + _ => Err("invalid disable".into()), + } + } +} + +impl Into for Disable { + fn into(self) -> bool { + false + } +} + +impl Parameter for Disable { + fn default_name(&self) -> ParamName { + "disable" + } + + fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result { + write!(f, "off") + } + + fn match_value(&self, input: &str) -> Result { + Self::from_str(input).map(|e| FfiParam::Toggle { toggle: e.into() }) + } +} diff --git a/crates/commands/src/token.rs b/crates/commands/src/token.rs index 7f736ee5..7a525312 100644 --- a/crates/commands/src/token.rs +++ b/crates/commands/src/token.rs @@ -1,12 +1,17 @@ -use std::{fmt::Display, ops::Not, str::FromStr}; +use std::{ + fmt::{Debug, Display}, + hash::Hash, + ops::Not, + sync::Arc, +}; use smol_str::{SmolStr, ToSmolStr}; -use crate::Parameter; +use crate::{parameter::Parameter, Parameter as FfiParam}; -type ParamName = &'static str; +pub type ParamName = &'static str; -#[derive(Debug, Clone, Eq, Hash, PartialEq)] +#[derive(Debug, Clone)] pub enum Token { /// Token used to represent a finished command (i.e. no more parameters required) // todo: this is likely not the right way to represent this @@ -19,33 +24,45 @@ pub enum Token { /// A bot-defined command / subcommand (usually) (eg. "member" in `pk;member MyName`) Value(Vec), - /// Opaque string (eg. "name" in `pk;member new name`) - OpaqueString(ParamName), - /// Remainder of a command (eg. "desc" in `pk;member description [desc...]`) - OpaqueRemainder(ParamName), + /// A parameter that must be provided a value + Parameter(ParamName, Arc), +} - /// Member reference (hid or member name) - MemberRef(ParamName), - /// todo: doc - MemberPrivacyTarget(ParamName), +#[macro_export] +macro_rules! any { + ($($t:expr),+) => { + Token::Any(vec![$(Token::from($t)),+]) + }; +} - /// System reference - SystemRef(ParamName), +impl PartialEq for Token { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Any(l0), Self::Any(r0)) => l0 == r0, + (Self::Value(l0), Self::Value(r0)) => l0 == r0, + (Self::Parameter(l0, _), Self::Parameter(r0, _)) => l0 == r0, + (Self::Empty, Self::Empty) => true, + _ => false, + } + } +} +impl Eq for Token {} - /// todo: doc - PrivacyLevel(ParamName), - - /// on, off; yes, no; true, false - Enable(ParamName), - Disable(ParamName), - Toggle(ParamName), - - /// reset, clear, default - Reset(ParamName), +impl Hash for Token { + fn hash(&self, state: &mut H) { + core::mem::discriminant(self).hash(state); + match self { + Token::Empty => {} + Token::Any(vec) => vec.hash(state), + Token::Value(vec) => vec.hash(state), + Token::Parameter(name, _) => name.hash(state), + } + } } #[derive(Debug)] pub enum TokenMatchError { + ParameterMatchError { input: SmolStr, msg: SmolStr }, MissingParameter { name: ParamName }, MissingAny { tokens: Vec }, } @@ -53,7 +70,7 @@ pub enum TokenMatchError { #[derive(Debug)] pub struct TokenMatchValue { pub raw: SmolStr, - pub param: Option<(ParamName, Parameter)>, + pub param: Option<(ParamName, FfiParam)>, } impl TokenMatchValue { @@ -67,7 +84,7 @@ impl TokenMatchValue { fn new_match_param( raw: impl Into, param_name: ParamName, - param: Parameter, + param: FfiParam, ) -> TryMatchResult { Some(Ok(Some(Self { raw: raw.into(), @@ -96,17 +113,8 @@ impl Token { // empty token Self::Empty => Some(Ok(None)), // missing paramaters - Self::OpaqueRemainder(param_name) - | Self::OpaqueString(param_name) - | Self::MemberRef(param_name) - | Self::MemberPrivacyTarget(param_name) - | Self::SystemRef(param_name) - | Self::PrivacyLevel(param_name) - | Self::Toggle(param_name) - | Self::Enable(param_name) - | Self::Disable(param_name) - | Self::Reset(param_name) => { - Some(Err(TokenMatchError::MissingParameter { name: param_name })) + Self::Parameter(name, _) => { + Some(Err(TokenMatchError::MissingParameter { name })) } Self::Any(tokens) => tokens.is_empty().then_some(None).unwrap_or_else(|| { Some(Err(TokenMatchError::MissingAny { @@ -134,83 +142,13 @@ impl Token { .any(|v| v.eq(input)) .then(|| TokenMatchValue::new_match(input)) .unwrap_or(None), - Self::OpaqueRemainder(param_name) | Self::OpaqueString(param_name) => { - TokenMatchValue::new_match_param( - input, - param_name, - Parameter::OpaqueString { raw: input.into() }, - ) - } - Self::SystemRef(param_name) => TokenMatchValue::new_match_param( - input, - param_name, - Parameter::SystemRef { - system: input.into(), - }, - ), - Self::MemberRef(param_name) => TokenMatchValue::new_match_param( - input, - param_name, - Parameter::MemberRef { - member: input.into(), - }, - ), - Self::MemberPrivacyTarget(param_name) => match MemberPrivacyTarget::from_str(input) { - Ok(target) => TokenMatchValue::new_match_param( - input, - param_name, - Parameter::MemberPrivacyTarget { - target: target.as_ref().into(), - }, - ), - Err(_) => None, - }, - Self::PrivacyLevel(param_name) => match PrivacyLevel::from_str(input) { - Ok(level) => TokenMatchValue::new_match_param( - input, - param_name, - Parameter::PrivacyLevel { - level: level.as_ref().into(), - }, - ), - Err(_) => None, - }, - Self::Toggle(param_name) => match Enable::from_str(input) - .map(Into::::into) - .or_else(|_| Disable::from_str(input).map(Into::::into)) - { - Ok(toggle) => TokenMatchValue::new_match_param( - input, - param_name, - Parameter::Toggle { toggle }, - ), - Err(_) => None, - }, - Self::Enable(param_name) => match Enable::from_str(input) { - Ok(t) => TokenMatchValue::new_match_param( - input, - param_name, - Parameter::Toggle { toggle: t.into() }, - ), - Err(_) => None, - }, - Self::Disable(param_name) => match Disable::from_str(input) { - Ok(t) => TokenMatchValue::new_match_param( - input, - param_name, - Parameter::Toggle { toggle: t.into() }, - ), - Err(_) => None, - }, - Self::Reset(param_name) => match Reset::from_str(input) { - Ok(_) => TokenMatchValue::new_match_param( - input, - param_name, - Parameter::Toggle { toggle: true }, - ), - Err(_) => None, - }, - // don't add a _ match here! + Self::Parameter(name, param) => match param.match_value(input) { + Ok(matched) => TokenMatchValue::new_match_param(input, name, matched), + Err(err) => Some(Err(TokenMatchError::ParameterMatchError { + input: input.into(), + msg: err, + })), + }, // don't add a _ match here! } } } @@ -231,17 +169,7 @@ impl Display for Token { } 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::OpaqueRemainder(param_name) => write!(f, "[{}...]", param_name), - Token::OpaqueString(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::Parameter(name, param) => param.format(f, name), } } } @@ -252,163 +180,31 @@ impl From<&str> for Token { } } -impl From<[&str; L]> for Token { - fn from(value: [&str; L]) -> Self { - Token::Value(value.into_iter().map(|s| s.to_smolstr()).collect()) +impl From

for Token { + fn from(value: P) -> Self { + Token::Parameter(value.default_name(), Arc::new(value)) } } -impl From<[Token; L]> for Token { - fn from(value: [Token; L]) -> Self { - Token::Any(value.into_iter().map(|s| s.clone()).collect()) +impl From<(ParamName, P)> for Token { + fn from(value: (ParamName, P)) -> Self { + Token::Parameter(value.0, Arc::new(value.1)) } } -#[derive(Debug, Clone, Eq, Hash, PartialEq)] -pub enum MemberPrivacyTarget { - Visibility, - Name, - Description, - Banner, - Avatar, - Birthday, - Pronouns, - Proxy, - Metadata, -} - -impl AsRef for MemberPrivacyTarget { - fn as_ref(&self) -> &str { - 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", +impl> From<[T; L]> for Token { + fn from(value: [T; L]) -> Self { + let tokens = value.into_iter().map(|s| s.into()).collect::>(); + if tokens.iter().all(|t| matches!(t, Token::Value(_))) { + let values = tokens + .into_iter() + .flat_map(|t| match t { + Token::Value(v) => v, + _ => unreachable!(), + }) + .collect::>(); + return Token::Value(values); } - } -} - -impl FromStr for MemberPrivacyTarget { - // todo: figure out how to represent these errors best - type Err = (); - - fn from_str(s: &str) -> Result { - // todo: this doesnt parse all the possible ways - 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(()), - } - } -} - -#[derive(Debug, Clone, Eq, Hash, PartialEq)] -pub enum PrivacyLevel { - Public, - Private, -} - -impl AsRef for PrivacyLevel { - fn as_ref(&self) -> &str { - match self { - Self::Public => "public", - Self::Private => "private", - } - } -} - -impl FromStr for PrivacyLevel { - type Err = (); // todo - - fn from_str(s: &str) -> Result { - match s { - "public" => Ok(Self::Public), - "private" => Ok(Self::Private), - _ => Err(()), - } - } -} - -#[derive(Debug, Clone, Eq, Hash, PartialEq)] -pub struct Reset; - -impl AsRef for Reset { - fn as_ref(&self) -> &str { - "reset" - } -} - -impl FromStr for Reset { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "reset" | "clear" | "default" => Ok(Self), - _ => Err(()), - } - } -} - -#[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 + Token::Any(tokens) } }