better parameters handling, implement import export

This commit is contained in:
dusk 2025-10-03 15:50:54 +00:00
parent e4f38c76a9
commit 5198f7d83b
No known key found for this signature in database
19 changed files with 250 additions and 174 deletions

View file

@ -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> {

View file

@ -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

View file

@ -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",
}
}
}

View file

@ -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())
}
}