mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-06 13:57:54 +00:00
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:
parent
a1f7656276
commit
07541d9926
8 changed files with 443 additions and 363 deletions
|
|
@ -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<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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use super::*;
|
||||
|
||||
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||
use Token::*;
|
||||
|
||||
let cfg = ["config", "cfg"];
|
||||
let autoproxy = ["autoproxy", "ap"];
|
||||
|
||||
|
|
@ -13,7 +11,7 @@ pub fn cmds() -> impl Iterator<Item = Command> {
|
|||
"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<Item = Command> {
|
|||
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"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use super::*;
|
||||
|
||||
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||
use Token::*;
|
||||
|
||||
let member = ["member", "m"];
|
||||
let description = ["description", "desc"];
|
||||
let privacy = ["privacy", "priv"];
|
||||
|
|
@ -10,49 +8,49 @@ pub fn cmds() -> impl Iterator<Item = Command> {
|
|||
|
||||
[
|
||||
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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use super::*;
|
||||
|
||||
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||
use Token::*;
|
||||
|
||||
let system = ["system", "s"];
|
||||
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, OpaqueString("name")],
|
||||
[system, new, ("name", OpaqueString::SINGLE)],
|
||||
"system_new",
|
||||
"Creates a new system"
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
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<FlagValue>,
|
||||
value: Option<Arc<dyn Parameter>>,
|
||||
}
|
||||
|
||||
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<Result<Option<Parameter>, FlagMatchError>>;
|
||||
type TryMatchFlagResult = Option<Result<Option<FfiParam>, FlagMatchError>>;
|
||||
|
||||
impl Flag {
|
||||
pub fn new(name: impl Into<SmolStr>) -> 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,
|
||||
},
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
314
crates/commands/src/parameter.rs
Normal file
314
crates/commands/src/parameter.rs
Normal 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() })
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SmolStr>),
|
||||
|
||||
/// Opaque string (eg. "name" in `pk;member new name`)
|
||||
OpaqueString(ParamName),
|
||||
/// Remainder of a command (eg. "desc" in `pk;member <target> description [desc...]`)
|
||||
OpaqueRemainder(ParamName),
|
||||
/// A parameter that must be provided a value
|
||||
Parameter(ParamName, Arc<dyn Parameter>),
|
||||
}
|
||||
|
||||
/// 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<H: std::hash::Hasher>(&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<Token> },
|
||||
}
|
||||
|
|
@ -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<SmolStr>,
|
||||
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::<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!
|
||||
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<const L: usize> From<[&str; L]> for Token {
|
||||
fn from(value: [&str; L]) -> Self {
|
||||
Token::Value(value.into_iter().map(|s| s.to_smolstr()).collect())
|
||||
impl<P: Parameter + 'static> From<P> for Token {
|
||||
fn from(value: P) -> Self {
|
||||
Token::Parameter(value.default_name(), Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: usize> From<[Token; L]> for Token {
|
||||
fn from(value: [Token; L]) -> Self {
|
||||
Token::Any(value.into_iter().map(|s| s.clone()).collect())
|
||||
impl<P: Parameter + 'static> 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<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",
|
||||
impl<const L: usize, T: Into<Token>> From<[T; L]> for Token {
|
||||
fn from(value: [T; L]) -> Self {
|
||||
let tokens = value.into_iter().map(|s| s.into()).collect::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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<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
|
||||
Token::Any(tokens)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue