diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 41b6af9c..bdb9f831 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -245,6 +245,8 @@ public partial class CommandTree Commands.MessageDelete(var param, var flags) => ctx.Execute(Message, m => m.GetMessage(ctx, param.target.MessageId, flags.GetReplyFormat(), true, false)), Commands.MessageEdit(var param, var flags) => ctx.Execute(MessageEdit, m => m.EditMessage(ctx, param.target.MessageId, param.new_content, flags.regex, flags.mutate_space, flags.append, flags.prepend, flags.clear_embeds, flags.clear_attachments)), Commands.MessageReproxy(var param, _) => ctx.Execute(MessageReproxy, m => m.ReproxyMessage(ctx, param.target.MessageId)), + Commands.Import(var param, _) => ctx.Execute(Import, m => m.Import(ctx, param.url)), + Commands.Export(_, _) => ctx.Execute(Export, m => m.Export(ctx)), _ => // this should only ever occur when deving if commands are not implemented... ctx.Reply( @@ -256,10 +258,6 @@ public partial class CommandTree return HandleConfigCommand(ctx); if (ctx.Match("serverconfig", "guildconfig", "scfg")) return HandleServerConfigCommand(ctx); - if (ctx.Match("import")) - return ctx.Execute(Import, m => m.Import(ctx)); - if (ctx.Match("export")) - return ctx.Execute(Export, m => m.Export(ctx)); if (ctx.Match("log")) if (ctx.Match("channel")) return ctx.Execute(LogChannel, m => m.SetLogChannel(ctx), true); diff --git a/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs b/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs index fe60eb5f..cc101e46 100644 --- a/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs +++ b/PluralKit.Bot/CommandSystem/Context/ContextParametersExt.cs @@ -20,7 +20,7 @@ public static class ContextParametersExt ); } - public static async Task> ParamResolveMembers(this Context ctx, string param_name) + public static async Task?> ParamResolveMembers(this Context ctx, string param_name) { return await ctx.Parameters.ResolveParameter( ctx, param_name, @@ -36,7 +36,7 @@ public static class ContextParametersExt ); } - public static async Task> ParamResolveGroups(this Context ctx, string param_name) + public static async Task?> ParamResolveGroups(this Context ctx, string param_name) { return await ctx.Parameters.ResolveParameter( ctx, param_name, diff --git a/PluralKit.Bot/CommandSystem/Parameters.cs b/PluralKit.Bot/CommandSystem/Parameters.cs index 3d63efdd..22bbebf4 100644 --- a/PluralKit.Bot/CommandSystem/Parameters.cs +++ b/PluralKit.Bot/CommandSystem/Parameters.cs @@ -126,6 +126,8 @@ public class Parameters return new Parameter.ChannelRef(await ctx.Rest.GetChannelOrNull(channelId) ?? throw new PKError($"Channel {channelId} not found")); case uniffi.commands.Parameter.GuildRef(var guildId): return new Parameter.GuildRef(await ctx.Rest.GetGuildOrNull(guildId) ?? throw new PKError($"Guild {guildId} not found")); + case uniffi.commands.Parameter.Null: + return null; } return null; } diff --git a/PluralKit.Bot/Commands/ImportExport.cs b/PluralKit.Bot/Commands/ImportExport.cs index d626c70d..dbecc3f7 100644 --- a/PluralKit.Bot/Commands/ImportExport.cs +++ b/PluralKit.Bot/Commands/ImportExport.cs @@ -31,9 +31,9 @@ public class ImportExport _dmCache = dmCache; } - public async Task Import(Context ctx) + public async Task Import(Context ctx, string? inputUrl) { - var inputUrl = ctx.RemainderOrNull() ?? ctx.Message.Attachments.FirstOrDefault()?.Url; + inputUrl = inputUrl ?? ctx.Message.Attachments.FirstOrDefault()?.Url; if (inputUrl == null) throw Errors.NoImportFilePassed; if (!Core.MiscUtils.TryMatchUri(inputUrl, out var url)) diff --git a/PluralKit.Bot/Commands/Switch.cs b/PluralKit.Bot/Commands/Switch.cs index 624774da..82c63e14 100644 --- a/PluralKit.Bot/Commands/Switch.cs +++ b/PluralKit.Bot/Commands/Switch.cs @@ -23,8 +23,9 @@ public class Switch await DoSwitchCommand(ctx, []); } - private async Task DoSwitchCommand(Context ctx, ICollection members) + private async Task DoSwitchCommand(Context ctx, ICollection? members) { + if (members == null) members = new List(); // Make sure there are no dupes in the list // We do this by checking if removing duplicate member IDs results in a list of different length if (members.Select(m => m.Id).Distinct().Count() != members.Count) throw Errors.DuplicateSwitchMembers; @@ -101,10 +102,12 @@ public class Switch await ctx.Reply($"{Emojis.Success} Switch moved to ({newSwitchDeltaStr} ago)."); } - public async Task SwitchEdit(Context ctx, List newMembers, bool newSwitch = false, bool first = false, bool remove = false, bool append = false, bool prepend = false) + public async Task SwitchEdit(Context ctx, List? newMembers, bool newSwitch = false, bool first = false, bool remove = false, bool append = false, bool prepend = false) { ctx.CheckSystem(); + if (newMembers == null) newMembers = new List(); + await using var conn = await ctx.Database.Obtain(); var currentSwitch = await ctx.Repository.GetLatestSwitch(ctx.System.Id); if (currentSwitch == null) @@ -170,8 +173,10 @@ public class Switch await DoEditCommand(ctx, []); } - public async Task DoEditCommand(Context ctx, ICollection members) + public async Task DoEditCommand(Context ctx, ICollection? members) { + if (members == null) members = new List(); + // Make sure there are no dupes in the list // We do this by checking if removing duplicate member IDs results in a list of different length if (members.Select(m => m.Id).Distinct().Count() != members.Count) throw Errors.DuplicateSwitchMembers; diff --git a/crates/command_definitions/src/group.rs b/crates/command_definitions/src/group.rs index 99fe4047..fec62ccf 100644 --- a/crates/command_definitions/src/group.rs +++ b/crates/command_definitions/src/group.rs @@ -158,9 +158,9 @@ pub fn cmds() -> impl Iterator { .map(apply_list_opts); let group_modify_members_cmd = [ - command!(group_target, "add", MemberRefs => "group_add_member") + command!(group_target, "add", Optional(MemberRefs) => "group_add_member") .flag(("all", ["a"])), - command!(group_target, ("remove", ["delete", "del", "rem"]), MemberRefs => "group_remove_member") + command!(group_target, ("remove", ["delete", "del", "rem"]), Optional(MemberRefs) => "group_remove_member") .flag(("all", ["a"])), ] .into_iter(); diff --git a/crates/command_definitions/src/help.rs b/crates/command_definitions/src/help.rs index da83e879..9a94dea8 100644 --- a/crates/command_definitions/src/help.rs +++ b/crates/command_definitions/src/help.rs @@ -3,6 +3,7 @@ use super::*; pub fn cmds() -> impl Iterator { let help = ("help", ["h"]); [ + command!(("dashboard", ["dash"]) => "dashboard"), command!("explain" => "explain"), command!(help => "help") .flag(("foo", OpaqueString)) // todo: just for testing diff --git a/crates/command_definitions/src/import_export.rs b/crates/command_definitions/src/import_export.rs index 8b137891..49b66f11 100644 --- a/crates/command_definitions/src/import_export.rs +++ b/crates/command_definitions/src/import_export.rs @@ -1 +1,9 @@ +use super::*; +pub fn cmds() -> impl Iterator { + [ + command!("import", Optional(("url", OpaqueStringRemainder)) => "import"), + command!("export" => "export"), + ] + .into_iter() +} diff --git a/crates/command_definitions/src/lib.rs b/crates/command_definitions/src/lib.rs index 68dace2f..4204dad2 100644 --- a/crates/command_definitions/src/lib.rs +++ b/crates/command_definitions/src/lib.rs @@ -19,7 +19,12 @@ pub mod system; pub mod utils; -use command_parser::{command, command::Command, parameter::ParameterKind::*, tokens}; +use command_parser::{ + command, + command::Command, + parameter::{Optional, Parameter, ParameterKind::*, Remainder, Skip}, + tokens, +}; pub fn all() -> impl Iterator { (help::cmds()) @@ -34,6 +39,7 @@ pub fn all() -> impl Iterator { .chain(autoproxy::cmds()) .chain(debug::cmds()) .chain(message::cmds()) + .chain(import_export::cmds()) .map(|cmd| { cmd.hidden_flag(("plaintext", ["pt"])) .hidden_flag(("raw", ["r"])) diff --git a/crates/command_definitions/src/member.rs b/crates/command_definitions/src/member.rs index e855d4f0..f6acb28e 100644 --- a/crates/command_definitions/src/member.rs +++ b/crates/command_definitions/src/member.rs @@ -182,11 +182,11 @@ pub fn cmds() -> impl Iterator { [ command!(member_keep_proxy => "member_keepproxy_show") .help("Shows a member's keep-proxy setting"), - command!(member_keep_proxy, ("value", Toggle) => "member_keepproxy_update") + command!(member_keep_proxy, Skip(("value", Toggle)) => "member_keepproxy_update") .help("Changes a member's keep-proxy setting"), command!(member_server_keep_proxy => "member_server_keepproxy_show") .help("Shows a member's server-specific keep-proxy setting"), - command!(member_server_keep_proxy, ("value", Toggle) => "member_server_keepproxy_update") + command!(member_server_keep_proxy, Skip(("value", Toggle)) => "member_server_keepproxy_update") .help("Changes a member's server-specific keep-proxy setting"), command!(member_server_keep_proxy, ("clear", ["c"]) => "member_server_keepproxy_clear") .flag(("yes", ["y"])) @@ -303,9 +303,9 @@ pub fn cmds() -> impl Iterator { .map(|cmd| cmd.flags(get_list_flags())); let member_add_remove_group_cmds = [ - command!(member_group, "add", ("groups", GroupRefs) => "member_group_add") + command!(member_group, "add", Optional(("groups", GroupRefs)) => "member_group_add") .help("Adds a member to one or more groups"), - command!(member_group, ("remove", ["rem"]), ("groups", GroupRefs) => "member_group_remove") + command!(member_group, ("remove", ["rem"]), Optional(("groups", GroupRefs)) => "member_group_remove") .help("Removes a member from one or more groups"), ] .into_iter(); diff --git a/crates/command_definitions/src/switch.rs b/crates/command_definitions/src/switch.rs index fce7f760..14ef6a0d 100644 --- a/crates/command_definitions/src/switch.rs +++ b/crates/command_definitions/src/switch.rs @@ -9,23 +9,22 @@ pub fn cmds() -> impl Iterator { let copy = ("copy", ["add", "duplicate", "dupe"]); let out = "out"; + let edit_flags = [ + ("first", ["f"]), + ("remove", ["r"]), + ("append", ["a"]), + ("prepend", ["p"]), + ]; + [ command!(switch, out => "switch_out"), command!(switch, r#move, OpaqueString => "switch_move"), // TODO: datetime parsing command!(switch, delete => "switch_delete").flag(("all", ["clear", "c"])), command!(switch, edit, out => "switch_edit_out"), - command!(switch, edit, MemberRefs => "switch_edit") - .flag(("first", ["f"])) - .flag(("remove", ["r"])) - .flag(("append", ["a"])) - .flag(("prepend", ["p"])), - command!(switch, copy, MemberRefs => "switch_copy") - .flag(("first", ["f"])) - .flag(("remove", ["r"])) - .flag(("append", ["a"])) - .flag(("prepend", ["p"])), + command!(switch, edit, Optional(MemberRefs) => "switch_edit").flags(edit_flags), + command!(switch, copy, Optional(MemberRefs) => "switch_copy").flags(edit_flags), command!(switch, ("commands", ["help"]) => "switch_commands"), - command!(switch, MemberRefs => "switch_do"), + command!(switch, Optional(MemberRefs) => "switch_do"), ] .into_iter() } diff --git a/crates/command_definitions/src/system.rs b/crates/command_definitions/src/system.rs index 1d90398c..88dbb39b 100644 --- a/crates/command_definitions/src/system.rs +++ b/crates/command_definitions/src/system.rs @@ -220,7 +220,7 @@ pub fn edit() -> impl Iterator { let system_proxy_cmd = [ command!(system_proxy => "system_show_proxy_current") .help("Shows your system's proxy setting for the guild you are in"), - command!(system_proxy, Toggle => "system_toggle_proxy_current") + command!(system_proxy, Skip(Toggle) => "system_toggle_proxy_current") .help("Toggle your system's proxy for the guild you are in"), command!(system_proxy, GuildRef => "system_show_proxy") .help("Shows your system's proxy setting for a guild"), diff --git a/crates/command_parser/src/flag.rs b/crates/command_parser/src/flag.rs index 9fa8ad38..2df4221a 100644 --- a/crates/command_parser/src/flag.rs +++ b/crates/command_parser/src/flag.rs @@ -14,7 +14,7 @@ pub enum FlagValueMatchError { pub struct Flag { name: SmolStr, aliases: Vec, - value: Option, + value: Option, } 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) -> Self { + self.value = Some(param.into()); self } @@ -72,8 +72,8 @@ impl Flag { &self.name } - pub fn get_value(&self) -> Option { - self.value + pub fn get_value(&self) -> Option<&Parameter> { + self.value.as_ref() } pub fn get_aliases(&self) -> impl Iterator { diff --git a/crates/command_parser/src/lib.rs b/crates/command_parser/src/lib.rs index 5c170f02..ee3a8ee3 100644 --- a/crates/command_parser/src/lib.rs +++ b/crates/command_parser/src/lib.rs @@ -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 diff --git a/crates/command_parser/src/parameter.rs b/crates/command_parser/src/parameter.rs index de187aef..28d7a693 100644 --- a/crates/command_parser/src/parameter.rs +++ b/crates/command_parser/src/parameter.rs @@ -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 { match self.kind { - ParameterKind::OpaqueString => { - write!(f, "[{}]", self.name) - } - ParameterKind::OpaqueStringRemainder => { - write!(f, "[{}]...", self.name) - } - ParameterKind::MemberRef => write!(f, ""), - ParameterKind::MemberRefs => write!(f, " ..."), - ParameterKind::GroupRef => write!(f, ""), - ParameterKind::GroupRefs => write!(f, " ..."), - ParameterKind::SystemRef => write!(f, ""), - ParameterKind::MessageRef => write!(f, ""), - ParameterKind::ChannelRef => write!(f, ""), - ParameterKind::GuildRef => write!(f, ""), - ParameterKind::MemberPrivacyTarget => write!(f, ""), - ParameterKind::GroupPrivacyTarget => write!(f, ""), - ParameterKind::SystemPrivacyTarget => write!(f, ""), - ParameterKind::PrivacyLevel => write!(f, "[privacy level]"), - ParameterKind::Toggle => write!(f, "on/off"), - ParameterKind::Avatar => write!(f, ""), - } - } -} - -impl From 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 { - 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> { +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, ""), + ParameterKind::MemberRefs => write!(f, " ..."), + ParameterKind::GroupRef => write!(f, ""), + ParameterKind::GroupRefs => write!(f, " ..."), + ParameterKind::SystemRef => write!(f, ""), + ParameterKind::MessageRef => write!(f, ""), + ParameterKind::ChannelRef => write!(f, ""), + ParameterKind::GuildRef => write!(f, ""), + ParameterKind::MemberPrivacyTarget => write!(f, ""), + ParameterKind::GroupPrivacyTarget => write!(f, ""), + ParameterKind::SystemPrivacyTarget => write!(f, ""), + ParameterKind::PrivacyLevel => write!(f, "[privacy level]"), + ParameterKind::Toggle => write!(f, "on/off"), + ParameterKind::Avatar => write!(f, ""), + } + } +} + +impl From 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>(pub P); + +impl> From> for Parameter { + fn from(value: Optional

) -> Self { + let p = value.0.into(); + p.optional() + } +} + +#[derive(Clone)] +pub struct Remainder>(pub P); + +impl> From> for Parameter { + fn from(value: Remainder

) -> 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>(pub P); + +impl> From> for Parameter { + fn from(value: Skip

) -> 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", } } } diff --git a/crates/command_parser/src/token.rs b/crates/command_parser/src/token.rs index 93c4e210..c18ce0f3 100644 --- a/crates/command_parser/src/token.rs +++ b/crates/command_parser/src/token.rs @@ -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 for Token { - fn from(value: Parameter) -> Self { - Self::Parameter(value) - } -} - -impl From 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> From

for Token { + fn from(value: P) -> Self { + Self::Parameter(value.into()) } } diff --git a/crates/commands/src/bin/write_cs_glue.rs b/crates/commands/src/bin/write_cs_glue.rs index d2aeb5ef..ff1a0886 100644 --- a/crates/commands/src/bin/write_cs_glue.rs +++ b/crates/commands/src/bin/write_cs_glue.rs @@ -45,19 +45,23 @@ fn main() -> Result<(), Box> { for param in &command_params { writeln!( &mut command_params_init, - r#"@{name} = await ctx.ParamResolve{extract_fn_name}("{name}") ?? throw new PKError("this is a bug"),"#, + r#"@{name} = await ctx.ParamResolve{extract_fn_name}("{name}"){throw_null},"#, name = param.name().replace("-", "_"), extract_fn_name = get_param_param_ty(param.kind()), + throw_null = param + .is_optional() + .then_some("") + .unwrap_or(" ?? throw new PKError(\"this is a bug\")"), )?; } let mut command_flags_init = String::new(); for flag in &command.flags { - if let Some(kind) = flag.get_value() { + if let Some(param) = flag.get_value() { writeln!( &mut command_flags_init, r#"@{name} = await ctx.FlagResolve{extract_fn_name}("{name}"),"#, name = flag.get_name().replace("-", "_"), - extract_fn_name = get_param_param_ty(kind), + extract_fn_name = get_param_param_ty(param.kind()), )?; } else { writeln!( @@ -109,19 +113,20 @@ fn main() -> Result<(), Box> { for param in &command_params { writeln!( &mut command_params_fields, - r#"public required {ty} @{name};"#, + r#"public required {ty}{nullable} @{name};"#, name = param.name().replace("-", "_"), ty = get_param_ty(param.kind()), + nullable = param.is_optional().then_some("?").unwrap_or(""), )?; } let mut command_flags_fields = String::new(); for flag in &command.flags { - if let Some(kind) = flag.get_value() { + if let Some(param) = flag.get_value() { writeln!( &mut command_flags_fields, r#"public {ty}? @{name};"#, name = flag.get_name().replace("-", "_"), - ty = get_param_ty(kind), + ty = get_param_ty(param.kind()), )?; } else { writeln!( diff --git a/crates/commands/src/commands.udl b/crates/commands/src/commands.udl index 899e1cc4..757e6615 100644 --- a/crates/commands/src/commands.udl +++ b/crates/commands/src/commands.udl @@ -23,6 +23,7 @@ interface Parameter { OpaqueString(string raw); Toggle(boolean toggle); Avatar(string avatar); + Null(); }; dictionary ParsedCommand { string command_ref; diff --git a/crates/commands/src/lib.rs b/crates/commands/src/lib.rs index dd4f6025..65199a7d 100644 --- a/crates/commands/src/lib.rs +++ b/crates/commands/src/lib.rs @@ -69,6 +69,7 @@ pub enum Parameter { Avatar { avatar: String, }, + Null, } impl From for Parameter { @@ -93,6 +94,7 @@ impl From for Parameter { }, ParameterValue::ChannelRef(channel_id) => Self::ChannelRef { channel_id }, ParameterValue::GuildRef(guild_id) => Self::GuildRef { guild_id }, + ParameterValue::Null => Self::Null, } } }