mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-06 13:57:54 +00:00
better parameters handling, implement import export
This commit is contained in:
parent
e4f38c76a9
commit
5198f7d83b
19 changed files with 250 additions and 174 deletions
|
|
@ -14,7 +14,7 @@ pub enum FlagValueMatchError {
|
|||
pub struct Flag {
|
||||
name: SmolStr,
|
||||
aliases: Vec<SmolStr>,
|
||||
value: Option<ParameterKind>,
|
||||
value: Option<Parameter>,
|
||||
}
|
||||
|
||||
impl Display for Flag {
|
||||
|
|
@ -22,7 +22,7 @@ impl Display for Flag {
|
|||
write!(f, "-{}", self.name)?;
|
||||
if let Some(value) = self.value.as_ref() {
|
||||
write!(f, "=")?;
|
||||
Parameter::from(*value).fmt(f)?;
|
||||
value.fmt(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -58,8 +58,8 @@ impl Flag {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn value(mut self, param: ParameterKind) -> Self {
|
||||
self.value = Some(param);
|
||||
pub fn value(mut self, param: impl Into<Parameter>) -> Self {
|
||||
self.value = Some(param.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -72,8 +72,8 @@ impl Flag {
|
|||
&self.name
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> Option<ParameterKind> {
|
||||
self.value
|
||||
pub fn get_value(&self) -> Option<&Parameter> {
|
||||
self.value.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_aliases(&self) -> impl Iterator<Item = &str> {
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ 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::Parameter(param) if param.kind().remainder());
|
||||
|token: &Token| matches!(token, Token::Parameter(param) if param.is_remainder());
|
||||
// check if this is a token that matches the rest of the input
|
||||
let match_remaining = is_match_remaining_token(token);
|
||||
// either use matched param or rest of the input if matching remaining
|
||||
|
|
|
|||
|
|
@ -24,12 +24,23 @@ pub enum ParameterValue {
|
|||
PrivacyLevel(String),
|
||||
Toggle(bool),
|
||||
Avatar(String),
|
||||
Null,
|
||||
}
|
||||
|
||||
fn is_remainder(kind: ParameterKind) -> bool {
|
||||
matches!(
|
||||
kind,
|
||||
ParameterKind::OpaqueStringRemainder | ParameterKind::MemberRefs | ParameterKind::GroupRefs
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Parameter {
|
||||
name: SmolStr,
|
||||
kind: ParameterKind,
|
||||
remainder: bool,
|
||||
optional: bool,
|
||||
skip: bool,
|
||||
}
|
||||
|
||||
impl Parameter {
|
||||
|
|
@ -40,106 +51,36 @@ impl Parameter {
|
|||
pub fn kind(&self) -> ParameterKind {
|
||||
self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Parameter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
pub fn remainder(mut self) -> Self {
|
||||
self.remainder = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn optional(mut self) -> Self {
|
||||
self.optional = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn skip(mut self) -> Self {
|
||||
self.skip = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_remainder(&self) -> bool {
|
||||
self.remainder
|
||||
}
|
||||
|
||||
pub fn is_optional(&self) -> bool {
|
||||
self.optional
|
||||
}
|
||||
|
||||
pub fn is_skip(&self) -> bool {
|
||||
self.skip
|
||||
}
|
||||
|
||||
pub fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
||||
match self.kind {
|
||||
ParameterKind::OpaqueString => {
|
||||
write!(f, "[{}]", self.name)
|
||||
}
|
||||
ParameterKind::OpaqueStringRemainder => {
|
||||
write!(f, "[{}]...", self.name)
|
||||
}
|
||||
ParameterKind::MemberRef => write!(f, "<target member>"),
|
||||
ParameterKind::MemberRefs => write!(f, "<member 1> <member 2> <member 3>..."),
|
||||
ParameterKind::GroupRef => write!(f, "<target group>"),
|
||||
ParameterKind::GroupRefs => write!(f, "<group 1> <group 2> <group 3>..."),
|
||||
ParameterKind::SystemRef => write!(f, "<target system>"),
|
||||
ParameterKind::MessageRef => write!(f, "<target message>"),
|
||||
ParameterKind::ChannelRef => write!(f, "<target channel>"),
|
||||
ParameterKind::GuildRef => write!(f, "<target guild>"),
|
||||
ParameterKind::MemberPrivacyTarget => write!(f, "<privacy target>"),
|
||||
ParameterKind::GroupPrivacyTarget => write!(f, "<privacy target>"),
|
||||
ParameterKind::SystemPrivacyTarget => write!(f, "<privacy target>"),
|
||||
ParameterKind::PrivacyLevel => write!(f, "[privacy level]"),
|
||||
ParameterKind::Toggle => write!(f, "on/off"),
|
||||
ParameterKind::Avatar => write!(f, "<url|@mention>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParameterKind> for Parameter {
|
||||
fn from(value: ParameterKind) -> Self {
|
||||
Parameter {
|
||||
name: value.default_name().into(),
|
||||
kind: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&str, ParameterKind)> for Parameter {
|
||||
fn from((name, kind): (&str, ParameterKind)) -> Self {
|
||||
Parameter {
|
||||
name: name.into(),
|
||||
kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ParameterKind {
|
||||
OpaqueString,
|
||||
OpaqueStringRemainder,
|
||||
MemberRef,
|
||||
MemberRefs,
|
||||
GroupRef,
|
||||
GroupRefs,
|
||||
SystemRef,
|
||||
MessageRef,
|
||||
ChannelRef,
|
||||
GuildRef,
|
||||
MemberPrivacyTarget,
|
||||
GroupPrivacyTarget,
|
||||
SystemPrivacyTarget,
|
||||
PrivacyLevel,
|
||||
Toggle,
|
||||
Avatar,
|
||||
}
|
||||
|
||||
impl ParameterKind {
|
||||
pub(crate) fn default_name(&self) -> &str {
|
||||
match self {
|
||||
ParameterKind::OpaqueString => "string",
|
||||
ParameterKind::OpaqueStringRemainder => "string",
|
||||
ParameterKind::MemberRef => "target",
|
||||
ParameterKind::MemberRefs => "targets",
|
||||
ParameterKind::GroupRef => "target",
|
||||
ParameterKind::GroupRefs => "targets",
|
||||
ParameterKind::SystemRef => "target",
|
||||
ParameterKind::MessageRef => "target",
|
||||
ParameterKind::ChannelRef => "target",
|
||||
ParameterKind::GuildRef => "target",
|
||||
ParameterKind::MemberPrivacyTarget => "member_privacy_target",
|
||||
ParameterKind::GroupPrivacyTarget => "group_privacy_target",
|
||||
ParameterKind::SystemPrivacyTarget => "system_privacy_target",
|
||||
ParameterKind::PrivacyLevel => "privacy_level",
|
||||
ParameterKind::Toggle => "toggle",
|
||||
ParameterKind::Avatar => "avatar",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn remainder(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ParameterKind::OpaqueStringRemainder
|
||||
| ParameterKind::MemberRefs
|
||||
| ParameterKind::GroupRefs
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
||||
match self {
|
||||
// TODO: actually parse image url
|
||||
ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => {
|
||||
Ok(ParameterValue::OpaqueString(input.into()))
|
||||
|
|
@ -217,13 +158,130 @@ impl ParameterKind {
|
|||
.map_err(|_| SmolStr::new("invalid guild ID")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn skip_if_cant_match(&self) -> Option<Option<ParameterValue>> {
|
||||
impl Display for Parameter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.kind {
|
||||
ParameterKind::OpaqueString => {
|
||||
write!(f, "[{}]", self.name)
|
||||
}
|
||||
ParameterKind::OpaqueStringRemainder => {
|
||||
write!(f, "[{}]...", self.name)
|
||||
}
|
||||
ParameterKind::MemberRef => write!(f, "<target member>"),
|
||||
ParameterKind::MemberRefs => write!(f, "<member 1> <member 2> <member 3>..."),
|
||||
ParameterKind::GroupRef => write!(f, "<target group>"),
|
||||
ParameterKind::GroupRefs => write!(f, "<group 1> <group 2> <group 3>..."),
|
||||
ParameterKind::SystemRef => write!(f, "<target system>"),
|
||||
ParameterKind::MessageRef => write!(f, "<target message>"),
|
||||
ParameterKind::ChannelRef => write!(f, "<target channel>"),
|
||||
ParameterKind::GuildRef => write!(f, "<target guild>"),
|
||||
ParameterKind::MemberPrivacyTarget => write!(f, "<privacy target>"),
|
||||
ParameterKind::GroupPrivacyTarget => write!(f, "<privacy target>"),
|
||||
ParameterKind::SystemPrivacyTarget => write!(f, "<privacy target>"),
|
||||
ParameterKind::PrivacyLevel => write!(f, "[privacy level]"),
|
||||
ParameterKind::Toggle => write!(f, "on/off"),
|
||||
ParameterKind::Avatar => write!(f, "<url|@mention>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParameterKind> for Parameter {
|
||||
fn from(value: ParameterKind) -> Self {
|
||||
Parameter {
|
||||
name: value.default_name().into(),
|
||||
kind: value,
|
||||
remainder: is_remainder(value),
|
||||
optional: false,
|
||||
skip: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&str, ParameterKind)> for Parameter {
|
||||
fn from((name, kind): (&str, ParameterKind)) -> Self {
|
||||
Parameter {
|
||||
name: name.into(),
|
||||
kind,
|
||||
remainder: is_remainder(kind),
|
||||
optional: false,
|
||||
skip: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Optional<P: Into<Parameter>>(pub P);
|
||||
|
||||
impl<P: Into<Parameter>> From<Optional<P>> for Parameter {
|
||||
fn from(value: Optional<P>) -> Self {
|
||||
let p = value.0.into();
|
||||
p.optional()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Remainder<P: Into<Parameter>>(pub P);
|
||||
|
||||
impl<P: Into<Parameter>> From<Remainder<P>> for Parameter {
|
||||
fn from(value: Remainder<P>) -> Self {
|
||||
let p = value.0.into();
|
||||
p.remainder()
|
||||
}
|
||||
}
|
||||
|
||||
// todo(dusk): this is kind of annoying to use, should probably introduce
|
||||
// a way to match multiple parameters in a single parameter
|
||||
#[derive(Clone)]
|
||||
pub struct Skip<P: Into<Parameter>>(pub P);
|
||||
|
||||
impl<P: Into<Parameter>> From<Skip<P>> for Parameter {
|
||||
fn from(value: Skip<P>) -> Self {
|
||||
let p = value.0.into();
|
||||
p.skip()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ParameterKind {
|
||||
OpaqueString,
|
||||
OpaqueStringRemainder,
|
||||
MemberRef,
|
||||
MemberRefs,
|
||||
GroupRef,
|
||||
GroupRefs,
|
||||
SystemRef,
|
||||
MessageRef,
|
||||
ChannelRef,
|
||||
GuildRef,
|
||||
MemberPrivacyTarget,
|
||||
GroupPrivacyTarget,
|
||||
SystemPrivacyTarget,
|
||||
PrivacyLevel,
|
||||
Toggle,
|
||||
Avatar,
|
||||
}
|
||||
|
||||
impl ParameterKind {
|
||||
pub(crate) fn default_name(&self) -> &str {
|
||||
match self {
|
||||
ParameterKind::Toggle => Some(None),
|
||||
ParameterKind::MemberRefs => Some(Some(ParameterValue::MemberRefs(Vec::new()))),
|
||||
ParameterKind::GroupRefs => Some(Some(ParameterValue::GroupRefs(Vec::new()))),
|
||||
_ => None,
|
||||
ParameterKind::OpaqueString => "string",
|
||||
ParameterKind::OpaqueStringRemainder => "string",
|
||||
ParameterKind::MemberRef => "target",
|
||||
ParameterKind::MemberRefs => "targets",
|
||||
ParameterKind::GroupRef => "target",
|
||||
ParameterKind::GroupRefs => "targets",
|
||||
ParameterKind::SystemRef => "target",
|
||||
ParameterKind::MessageRef => "target",
|
||||
ParameterKind::ChannelRef => "target",
|
||||
ParameterKind::GuildRef => "target",
|
||||
ParameterKind::MemberPrivacyTarget => "member_privacy_target",
|
||||
ParameterKind::GroupPrivacyTarget => "group_privacy_target",
|
||||
ParameterKind::SystemPrivacyTarget => "system_privacy_target",
|
||||
ParameterKind::PrivacyLevel => "privacy_level",
|
||||
ParameterKind::Toggle => "toggle",
|
||||
ParameterKind::Avatar => "avatar",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::fmt::{Debug, Display};
|
|||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::parameter::{Parameter, ParameterKind, ParameterValue};
|
||||
use crate::parameter::{Optional, Parameter, ParameterKind, ParameterValue};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Token {
|
||||
|
|
@ -46,9 +46,17 @@ impl Token {
|
|||
// short circuit on:
|
||||
return match self {
|
||||
// missing paramaters
|
||||
Self::Parameter(param) => Some(TokenMatchResult::MissingParameter {
|
||||
name: param.name().into(),
|
||||
}),
|
||||
Self::Parameter(param) => Some(
|
||||
param
|
||||
.is_optional()
|
||||
.then(|| TokenMatchResult::MatchedParameter {
|
||||
name: param.name().into(),
|
||||
value: ParameterValue::Null,
|
||||
})
|
||||
.unwrap_or_else(|| TokenMatchResult::MissingParameter {
|
||||
name: param.name().into(),
|
||||
}),
|
||||
),
|
||||
// everything else doesnt match if no input anyway
|
||||
Self::Value { .. } => None,
|
||||
// don't add a _ match here!
|
||||
|
|
@ -62,20 +70,14 @@ impl Token {
|
|||
Self::Value { name, aliases } => (aliases.iter().chain(std::iter::once(name)))
|
||||
.any(|v| v.eq(input))
|
||||
.then(|| TokenMatchResult::MatchedValue),
|
||||
Self::Parameter(param) => Some(match param.kind().match_value(input) {
|
||||
Self::Parameter(param) => Some(match param.match_value(input) {
|
||||
Ok(matched) => TokenMatchResult::MatchedParameter {
|
||||
name: param.name().into(),
|
||||
value: matched,
|
||||
},
|
||||
Err(err) => {
|
||||
if let Some(maybe_empty) = param.kind().skip_if_cant_match() {
|
||||
match maybe_empty {
|
||||
Some(matched) => TokenMatchResult::MatchedParameter {
|
||||
name: param.name().into(),
|
||||
value: matched,
|
||||
},
|
||||
None => return None,
|
||||
}
|
||||
if param.is_skip() {
|
||||
return None;
|
||||
} else {
|
||||
TokenMatchResult::ParameterMatchError {
|
||||
input: input.into(),
|
||||
|
|
@ -115,21 +117,10 @@ impl From<&str> for Token {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Parameter> for Token {
|
||||
fn from(value: Parameter) -> Self {
|
||||
Self::Parameter(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParameterKind> for Token {
|
||||
fn from(value: ParameterKind) -> Self {
|
||||
Self::from(Parameter::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&str, ParameterKind)> for Token {
|
||||
fn from(value: (&str, ParameterKind)) -> Self {
|
||||
Self::from(Parameter::from(value))
|
||||
// parameter -> Token::Parameter
|
||||
impl<P: Into<Parameter>> From<P> for Token {
|
||||
fn from(value: P) -> Self {
|
||||
Self::Parameter(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue