refactor(commands): rewrite how parameters are handled so they work same across cmd params / flag params, and make it easier to add new parameters

This commit is contained in:
dusk 2025-01-15 03:52:32 +09:00
parent a1f7656276
commit 07541d9926
No known key found for this signature in database
8 changed files with 443 additions and 363 deletions

View file

@ -22,11 +22,7 @@ use std::fmt::{Debug, Display};
use smol_str::SmolStr; use smol_str::SmolStr;
use crate::{ use crate::{any, command, flag::Flag, parameter::*, token::Token};
command,
flag::{Flag, FlagValue},
token::Token,
};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Command { pub struct Command {
@ -51,17 +47,7 @@ impl Command {
let mut was_parameter = true; let mut was_parameter = true;
for (idx, token) in tokens.iter().enumerate().rev() { for (idx, token) in tokens.iter().enumerate().rev() {
match token { match token {
Token::OpaqueRemainder(_) Token::Parameter(_, _) | Token::Any(_) => {
| Token::OpaqueString(_)
| Token::MemberRef(_)
| Token::MemberPrivacyTarget(_)
| Token::SystemRef(_)
| Token::PrivacyLevel(_)
| Token::Toggle(_)
| Token::Enable(_)
| Token::Disable(_)
| Token::Reset(_)
| Token::Any(_) => {
parse_flags_before = idx; parse_flags_before = idx;
was_parameter = true; was_parameter = true;
} }
@ -92,7 +78,7 @@ impl Command {
self self
} }
pub fn value_flag(mut self, name: impl Into<SmolStr>, value: FlagValue) -> Self { pub fn value_flag(mut self, name: impl Into<SmolStr>, value: impl Parameter + 'static) -> Self {
self.flags.push(Flag::new(name).with_value(value)); self.flags.push(Flag::new(name).with_value(value));
self self
} }

View file

@ -1,8 +1,6 @@
use super::*; use super::*;
pub fn cmds() -> impl Iterator<Item = Command> { pub fn cmds() -> impl Iterator<Item = Command> {
use Token::*;
let cfg = ["config", "cfg"]; let cfg = ["config", "cfg"];
let autoproxy = ["autoproxy", "ap"]; let autoproxy = ["autoproxy", "ap"];
@ -13,7 +11,7 @@ pub fn cmds() -> impl Iterator<Item = Command> {
"Shows autoproxy status for the account" "Shows autoproxy status for the account"
), ),
command!( command!(
[cfg, autoproxy, ["account", "ac"], Toggle("toggle")], [cfg, autoproxy, ["account", "ac"], Toggle],
"cfg_ap_account_update", "cfg_ap_account_update",
"Toggles autoproxy for the account" "Toggles autoproxy for the account"
), ),
@ -27,7 +25,7 @@ pub fn cmds() -> impl Iterator<Item = Command> {
cfg, cfg,
autoproxy, autoproxy,
["timeout", "tm"], ["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", "cfg_ap_timeout_update",
"Sets the autoproxy timeout" "Sets the autoproxy timeout"

View file

@ -1,8 +1,6 @@
use super::*; use super::*;
pub fn cmds() -> impl Iterator<Item = Command> { pub fn cmds() -> impl Iterator<Item = Command> {
use Token::*;
let member = ["member", "m"]; let member = ["member", "m"];
let description = ["description", "desc"]; let description = ["description", "desc"];
let privacy = ["privacy", "priv"]; let privacy = ["privacy", "priv"];
@ -10,49 +8,49 @@ pub fn cmds() -> impl Iterator<Item = Command> {
[ [
command!( command!(
[member, new, OpaqueString("name")], [member, new, ("name", OpaqueString::SINGLE)],
"member_new", "member_new",
"Creates a new system member" "Creates a new system member"
), ),
command!( command!(
[member, MemberRef("target")], [member, MemberRef],
"member_show", "member_show",
"Shows information about a member" "Shows information about a member"
) )
.value_flag("pt", FlagValue::OpaqueString), .value_flag("pt", Disable),
command!( command!(
[member, MemberRef("target"), description], [member, MemberRef, description],
"member_desc_show", "member_desc_show",
"Shows a member's description" "Shows a member's description"
), ),
command!( command!(
[ [
member, member,
MemberRef("target"), MemberRef,
description, description,
OpaqueRemainder("description") ("description", OpaqueString::REMAINDER)
], ],
"member_desc_update", "member_desc_update",
"Changes a member's description" "Changes a member's description"
), ),
command!( command!(
[member, MemberRef("target"), 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("target"), MemberRef,
privacy, privacy,
MemberPrivacyTarget("privacy_target"), MemberPrivacyTarget,
PrivacyLevel("new_privacy_level") ("new_privacy_level", PrivacyLevel)
], ],
"member_privacy_update", "member_privacy_update",
"Changes a member's privacy settings" "Changes a member's privacy settings"
), ),
command!( command!(
[member, MemberRef("target"), "soulscream"], [member, MemberRef, "soulscream"],
"member_soulscream", "member_soulscream",
"todo" "todo"
) )

View file

@ -1,8 +1,6 @@
use super::*; use super::*;
pub fn cmds() -> impl Iterator<Item = Command> { pub fn cmds() -> impl Iterator<Item = Command> {
use Token::*;
let system = ["system", "s"]; let system = ["system", "s"];
let new = ["new", "n"]; let new = ["new", "n"];
@ -14,7 +12,7 @@ pub fn cmds() -> impl Iterator<Item = Command> {
), ),
command!([system, new], "system_new", "Creates a new system"), command!([system, new], "system_new", "Creates a new system"),
command!( command!(
[system, new, OpaqueString("name")], [system, new, ("name", OpaqueString::SINGLE)],
"system_new", "system_new",
"Creates a new system" "Creates a new system"
), ),

View file

@ -1,50 +1,27 @@
use std::fmt::Display; use std::{fmt::Display, sync::Arc};
use smol_str::SmolStr; use smol_str::SmolStr;
use crate::Parameter; use crate::{parameter::Parameter, Parameter as FfiParam};
#[derive(Debug, Clone)]
pub enum FlagValue {
OpaqueString,
}
impl FlagValue {
fn try_match(&self, input: &str) -> Result<Parameter, FlagValueMatchError> {
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"),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum FlagValueMatchError { pub enum FlagValueMatchError {
ValueMissing, ValueMissing,
InvalidValue { raw: SmolStr, msg: SmolStr },
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Flag { pub struct Flag {
name: SmolStr, name: SmolStr,
value: Option<FlagValue>, value: Option<Arc<dyn Parameter>>,
} }
impl Display for Flag { impl Display for Flag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "-{}", self.name)?; write!(f, "-{}", self.name)?;
if let Some(value) = self.value.as_ref() { if let Some(value) = self.value.as_ref() {
write!(f, "={value}")?; write!(f, "=")?;
value.format(f, value.default_name())?;
} }
Ok(()) Ok(())
} }
@ -55,7 +32,7 @@ pub enum FlagMatchError {
ValueMatchFailed(FlagValueMatchError), ValueMatchFailed(FlagValueMatchError),
} }
type TryMatchFlagResult = Option<Result<Option<Parameter>, FlagMatchError>>; type TryMatchFlagResult = Option<Result<Option<FfiParam>, FlagMatchError>>;
impl Flag { impl Flag {
pub fn new(name: impl Into<SmolStr>) -> Self { pub fn new(name: impl Into<SmolStr>) -> Self {
@ -65,8 +42,8 @@ impl Flag {
} }
} }
pub fn with_value(mut self, value: FlagValue) -> Self { pub fn with_value(mut self, param: impl Parameter + 'static) -> Self {
self.value = Some(value); self.value = Some(Arc::new(param));
self self
} }
@ -74,17 +51,13 @@ impl Flag {
&self.name &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 { pub fn try_match(&self, input_name: &str, input_value: Option<&str>) -> TryMatchFlagResult {
// if not matching flag then skip anymore matching // if not matching flag then skip anymore matching
if self.name != input_name { if self.name != input_name {
return None; 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) // 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)); return Some(Ok(None));
}; };
// check if we have a non-empty flag value, we return error if not (because flag requested a value) // 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 // try matching the value
match value.try_match(input_value) { match value.match_value(input_value) {
Ok(param) => Some(Ok(Some(param))), 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,
},
))),
} }
} }
} }

View file

@ -3,6 +3,7 @@
pub mod commands; pub mod commands;
mod flag; mod flag;
mod parameter;
mod string; mod string;
mod token; mod token;
mod tree; 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) { if let Some(next_tree) = local_tree.get_branch(&found_token) {
local_tree = next_tree.clone(); local_tree = next_tree.clone();
} else { } 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))) => { 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"); write!(&mut msg, " in command `{prefix}{input} {token}`.").expect("oom");
msg msg
} }
TokenMatchError::ParameterMatchError { input: raw, msg } => {
format!("Parameter `{raw}` in command `{prefix}{input}` could not be parsed: {msg}.")
}
}; };
return CommandResult::Err { error: error_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); flags.insert(name.into(), value);
} }
Err((flag, err)) => { Err((flag, err)) => {
match err { let error = match err {
FlagMatchError::ValueMatchFailed(FlagValueMatchError::ValueMissing) => { FlagMatchError::ValueMatchFailed(FlagValueMatchError::ValueMissing) => {
return CommandResult::Err { format!(
error: format!( "Flag `-{name}` in command `{prefix}{input}` is missing a value, try passing `{flag}`.",
"Flag `-{name}` in command `{prefix}{input}` is missing a value, try passing `-{name}={value}`.", name = flag.name()
name = flag.name(), )
value = flag.value().expect("value missing error cant happen without a value"),
),
}
} }
} 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 // iterate over tokens and run try_match
for token in possible_tokens { 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 // check if this is a token that matches the rest of the input
let match_remaining = is_match_remaining_token(token) let match_remaining = is_match_remaining_token(token)
// check for Any here if it has a "match remainder" token in it // 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) { match token.try_match(input_to_match) {
Some(Ok(value)) => { Some(Ok(value)) => {
println!("matched token: {}", token);
let next_pos = match matched { let next_pos = match matched {
// return last possible pos if we matched remaining, // return last possible pos if we matched remaining,
Some(_) if match_remaining => input.len(), Some(_) if match_remaining => input.len(),

View file

@ -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<FfiParam, SmolStr>;
}
#[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<FfiParam, SmolStr> {
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, "<target member>")
}
fn match_value(&self, input: &str) -> Result<FfiParam, SmolStr> {
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, "<target system>")
}
fn match_value(&self, input: &str) -> Result<FfiParam, SmolStr> {
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<str> 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<Self, Self::Err> {
// 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, "<privacy target>")
}
fn match_value(&self, input: &str) -> Result<FfiParam, SmolStr> {
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<str> 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<Self, Self::Err> {
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<FfiParam, SmolStr> {
PrivacyLevelKind::from_str(input).map(|level| FfiParam::PrivacyLevel {
level: level.as_ref().into(),
})
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct Reset;
impl AsRef<str> for Reset {
fn as_ref(&self) -> &str {
"reset"
}
}
impl FromStr for Reset {
type Err = SmolStr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<FfiParam, SmolStr> {
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<FfiParam, SmolStr> {
Enable::from_str(input)
.map(Into::<bool>::into)
.or_else(|_| Disable::from_str(input).map(Into::<bool>::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<Self, Self::Err> {
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<FfiParam, SmolStr> {
Self::from_str(input).map(|e| FfiParam::Toggle { toggle: e.into() })
}
}
impl Into<bool> 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<Self, Self::Err> {
match s {
"off" | "no" | "false" | "disable" | "disabled" => Ok(Self),
_ => Err("invalid disable".into()),
}
}
}
impl Into<bool> 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<FfiParam, SmolStr> {
Self::from_str(input).map(|e| FfiParam::Toggle { toggle: e.into() })
}
}

View file

@ -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 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 { 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)
// todo: this is likely not the right way to represent this // 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`) /// A bot-defined command / subcommand (usually) (eg. "member" in `pk;member MyName`)
Value(Vec<SmolStr>), Value(Vec<SmolStr>),
/// Opaque string (eg. "name" in `pk;member new name`) /// A parameter that must be provided a value
OpaqueString(ParamName), Parameter(ParamName, Arc<dyn Parameter>),
/// Remainder of a command (eg. "desc" in `pk;member <target> description [desc...]`) }
OpaqueRemainder(ParamName),
/// Member reference (hid or member name) #[macro_export]
MemberRef(ParamName), macro_rules! any {
/// todo: doc ($($t:expr),+) => {
MemberPrivacyTarget(ParamName), Token::Any(vec![$(Token::from($t)),+])
};
}
/// System reference impl PartialEq for Token {
SystemRef(ParamName), 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 impl Hash for Token {
PrivacyLevel(ParamName), fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
/// on, off; yes, no; true, false match self {
Enable(ParamName), Token::Empty => {}
Disable(ParamName), Token::Any(vec) => vec.hash(state),
Toggle(ParamName), Token::Value(vec) => vec.hash(state),
Token::Parameter(name, _) => name.hash(state),
/// reset, clear, default }
Reset(ParamName), }
} }
#[derive(Debug)] #[derive(Debug)]
pub enum TokenMatchError { pub enum TokenMatchError {
ParameterMatchError { input: SmolStr, msg: SmolStr },
MissingParameter { name: ParamName }, MissingParameter { name: ParamName },
MissingAny { tokens: Vec<Token> }, MissingAny { tokens: Vec<Token> },
} }
@ -53,7 +70,7 @@ pub enum TokenMatchError {
#[derive(Debug)] #[derive(Debug)]
pub struct TokenMatchValue { pub struct TokenMatchValue {
pub raw: SmolStr, pub raw: SmolStr,
pub param: Option<(ParamName, Parameter)>, pub param: Option<(ParamName, FfiParam)>,
} }
impl TokenMatchValue { impl TokenMatchValue {
@ -67,7 +84,7 @@ impl TokenMatchValue {
fn new_match_param( fn new_match_param(
raw: impl Into<SmolStr>, raw: impl Into<SmolStr>,
param_name: ParamName, param_name: ParamName,
param: Parameter, param: FfiParam,
) -> TryMatchResult { ) -> TryMatchResult {
Some(Ok(Some(Self { Some(Ok(Some(Self {
raw: raw.into(), raw: raw.into(),
@ -96,17 +113,8 @@ impl Token {
// empty token // empty token
Self::Empty => Some(Ok(None)), Self::Empty => Some(Ok(None)),
// missing paramaters // missing paramaters
Self::OpaqueRemainder(param_name) Self::Parameter(name, _) => {
| Self::OpaqueString(param_name) Some(Err(TokenMatchError::MissingParameter { 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::Any(tokens) => tokens.is_empty().then_some(None).unwrap_or_else(|| { Self::Any(tokens) => tokens.is_empty().then_some(None).unwrap_or_else(|| {
Some(Err(TokenMatchError::MissingAny { Some(Err(TokenMatchError::MissingAny {
@ -134,83 +142,13 @@ impl Token {
.any(|v| v.eq(input)) .any(|v| v.eq(input))
.then(|| TokenMatchValue::new_match(input)) .then(|| TokenMatchValue::new_match(input))
.unwrap_or(None), .unwrap_or(None),
Self::OpaqueRemainder(param_name) | Self::OpaqueString(param_name) => { Self::Parameter(name, param) => match param.match_value(input) {
TokenMatchValue::new_match_param( Ok(matched) => TokenMatchValue::new_match_param(input, name, matched),
input, Err(err) => Some(Err(TokenMatchError::ParameterMatchError {
param_name, input: input.into(),
Parameter::OpaqueString { raw: input.into() }, msg: err,
) })),
} }, // don't add a _ match here!
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::<bool>::into)
.or_else(|_| Disable::from_str(input).map(Into::<bool>::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!
} }
} }
} }
@ -231,17 +169,7 @@ impl Display for Token {
} }
Token::Value(vec) if vec.is_empty().not() => write!(f, "{}", vec.first().unwrap()), 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 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::Parameter(name, param) => param.format(f, name),
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"),
} }
} }
} }
@ -252,163 +180,31 @@ impl From<&str> for Token {
} }
} }
impl<const L: usize> From<[&str; L]> for Token { impl<P: Parameter + 'static> From<P> for Token {
fn from(value: [&str; L]) -> Self { fn from(value: P) -> Self {
Token::Value(value.into_iter().map(|s| s.to_smolstr()).collect()) Token::Parameter(value.default_name(), Arc::new(value))
} }
} }
impl<const L: usize> From<[Token; L]> for Token { impl<P: Parameter + 'static> From<(ParamName, P)> for Token {
fn from(value: [Token; L]) -> Self { fn from(value: (ParamName, P)) -> Self {
Token::Any(value.into_iter().map(|s| s.clone()).collect()) Token::Parameter(value.0, Arc::new(value.1))
} }
} }
#[derive(Debug, Clone, Eq, Hash, PartialEq)] impl<const L: usize, T: Into<Token>> From<[T; L]> for Token {
pub enum MemberPrivacyTarget { fn from(value: [T; L]) -> Self {
Visibility, let tokens = value.into_iter().map(|s| s.into()).collect::<Vec<_>>();
Name, if tokens.iter().all(|t| matches!(t, Token::Value(_))) {
Description, let values = tokens
Banner, .into_iter()
Avatar, .flat_map(|t| match t {
Birthday, Token::Value(v) => v,
Pronouns, _ => unreachable!(),
Proxy, })
Metadata, .collect::<Vec<_>>();
} return Token::Value(values);
impl AsRef<str> 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",
} }
} Token::Any(tokens)
}
impl FromStr for MemberPrivacyTarget {
// todo: figure out how to represent these errors best
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
// 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<str> 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<Self, Self::Err> {
match s {
"public" => Ok(Self::Public),
"private" => Ok(Self::Private),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct Reset;
impl AsRef<str> for Reset {
fn as_ref(&self) -> &str {
"reset"
}
}
impl FromStr for Reset {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"reset" | "clear" | "default" => Ok(Self),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct Enable;
impl AsRef<str> for Enable {
fn as_ref(&self) -> &str {
"on"
}
}
impl FromStr for Enable {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"on" | "yes" | "true" | "enable" | "enabled" => Ok(Self),
_ => Err(()),
}
}
}
impl Into<bool> for Enable {
fn into(self) -> bool {
true
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct Disable;
impl AsRef<str> for Disable {
fn as_ref(&self) -> &str {
"off"
}
}
impl FromStr for Disable {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"off" | "no" | "false" | "disable" | "disabled" => Ok(Self),
_ => Err(()),
}
}
}
impl Into<bool> for Disable {
fn into(self) -> bool {
false
} }
} }