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

@ -245,6 +245,8 @@ public partial class CommandTree
Commands.MessageDelete(var param, var flags) => ctx.Execute<ProxiedMessage>(Message, m => m.GetMessage(ctx, param.target.MessageId, flags.GetReplyFormat(), true, false)), Commands.MessageDelete(var param, var flags) => ctx.Execute<ProxiedMessage>(Message, m => m.GetMessage(ctx, param.target.MessageId, flags.GetReplyFormat(), true, false)),
Commands.MessageEdit(var param, var flags) => ctx.Execute<ProxiedMessage>(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.MessageEdit(var param, var flags) => ctx.Execute<ProxiedMessage>(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<ProxiedMessage>(MessageReproxy, m => m.ReproxyMessage(ctx, param.target.MessageId)), Commands.MessageReproxy(var param, _) => ctx.Execute<ProxiedMessage>(MessageReproxy, m => m.ReproxyMessage(ctx, param.target.MessageId)),
Commands.Import(var param, _) => ctx.Execute<ImportExport>(Import, m => m.Import(ctx, param.url)),
Commands.Export(_, _) => ctx.Execute<ImportExport>(Export, m => m.Export(ctx)),
_ => _ =>
// this should only ever occur when deving if commands are not implemented... // this should only ever occur when deving if commands are not implemented...
ctx.Reply( ctx.Reply(
@ -256,10 +258,6 @@ public partial class CommandTree
return HandleConfigCommand(ctx); return HandleConfigCommand(ctx);
if (ctx.Match("serverconfig", "guildconfig", "scfg")) if (ctx.Match("serverconfig", "guildconfig", "scfg"))
return HandleServerConfigCommand(ctx); return HandleServerConfigCommand(ctx);
if (ctx.Match("import"))
return ctx.Execute<ImportExport>(Import, m => m.Import(ctx));
if (ctx.Match("export"))
return ctx.Execute<ImportExport>(Export, m => m.Export(ctx));
if (ctx.Match("log")) if (ctx.Match("log"))
if (ctx.Match("channel")) if (ctx.Match("channel"))
return ctx.Execute<ServerConfig>(LogChannel, m => m.SetLogChannel(ctx), true); return ctx.Execute<ServerConfig>(LogChannel, m => m.SetLogChannel(ctx), true);

View file

@ -20,7 +20,7 @@ public static class ContextParametersExt
); );
} }
public static async Task<List<PKMember>> ParamResolveMembers(this Context ctx, string param_name) public static async Task<List<PKMember>?> ParamResolveMembers(this Context ctx, string param_name)
{ {
return await ctx.Parameters.ResolveParameter( return await ctx.Parameters.ResolveParameter(
ctx, param_name, ctx, param_name,
@ -36,7 +36,7 @@ public static class ContextParametersExt
); );
} }
public static async Task<List<PKGroup>> ParamResolveGroups(this Context ctx, string param_name) public static async Task<List<PKGroup>?> ParamResolveGroups(this Context ctx, string param_name)
{ {
return await ctx.Parameters.ResolveParameter( return await ctx.Parameters.ResolveParameter(
ctx, param_name, ctx, param_name,

View file

@ -126,6 +126,8 @@ public class Parameters
return new Parameter.ChannelRef(await ctx.Rest.GetChannelOrNull(channelId) ?? throw new PKError($"Channel {channelId} not found")); return new Parameter.ChannelRef(await ctx.Rest.GetChannelOrNull(channelId) ?? throw new PKError($"Channel {channelId} not found"));
case uniffi.commands.Parameter.GuildRef(var guildId): case uniffi.commands.Parameter.GuildRef(var guildId):
return new Parameter.GuildRef(await ctx.Rest.GetGuildOrNull(guildId) ?? throw new PKError($"Guild {guildId} not found")); 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; return null;
} }

View file

@ -31,9 +31,9 @@ public class ImportExport
_dmCache = dmCache; _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 (inputUrl == null) throw Errors.NoImportFilePassed;
if (!Core.MiscUtils.TryMatchUri(inputUrl, out var url)) if (!Core.MiscUtils.TryMatchUri(inputUrl, out var url))

View file

@ -23,8 +23,9 @@ public class Switch
await DoSwitchCommand(ctx, []); await DoSwitchCommand(ctx, []);
} }
private async Task DoSwitchCommand(Context ctx, ICollection<PKMember> members) private async Task DoSwitchCommand(Context ctx, ICollection<PKMember>? members)
{ {
if (members == null) members = new List<PKMember>();
// Make sure there are no dupes in the 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 // 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; 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 <t:{newSwitchTime}> ({newSwitchDeltaStr} ago)."); await ctx.Reply($"{Emojis.Success} Switch moved to <t:{newSwitchTime}> ({newSwitchDeltaStr} ago).");
} }
public async Task SwitchEdit(Context ctx, List<PKMember> newMembers, bool newSwitch = false, bool first = false, bool remove = false, bool append = false, bool prepend = false) public async Task SwitchEdit(Context ctx, List<PKMember>? newMembers, bool newSwitch = false, bool first = false, bool remove = false, bool append = false, bool prepend = false)
{ {
ctx.CheckSystem(); ctx.CheckSystem();
if (newMembers == null) newMembers = new List<PKMember>();
await using var conn = await ctx.Database.Obtain(); await using var conn = await ctx.Database.Obtain();
var currentSwitch = await ctx.Repository.GetLatestSwitch(ctx.System.Id); var currentSwitch = await ctx.Repository.GetLatestSwitch(ctx.System.Id);
if (currentSwitch == null) if (currentSwitch == null)
@ -170,8 +173,10 @@ public class Switch
await DoEditCommand(ctx, []); await DoEditCommand(ctx, []);
} }
public async Task DoEditCommand(Context ctx, ICollection<PKMember> members) public async Task DoEditCommand(Context ctx, ICollection<PKMember>? members)
{ {
if (members == null) members = new List<PKMember>();
// Make sure there are no dupes in the 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 // 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; if (members.Select(m => m.Id).Distinct().Count() != members.Count) throw Errors.DuplicateSwitchMembers;

View file

@ -158,9 +158,9 @@ pub fn cmds() -> impl Iterator<Item = Command> {
.map(apply_list_opts); .map(apply_list_opts);
let group_modify_members_cmd = [ 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"])), .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"])), .flag(("all", ["a"])),
] ]
.into_iter(); .into_iter();

View file

@ -3,6 +3,7 @@ use super::*;
pub fn cmds() -> impl Iterator<Item = Command> { pub fn cmds() -> impl Iterator<Item = Command> {
let help = ("help", ["h"]); let help = ("help", ["h"]);
[ [
command!(("dashboard", ["dash"]) => "dashboard"),
command!("explain" => "explain"), command!("explain" => "explain"),
command!(help => "help") command!(help => "help")
.flag(("foo", OpaqueString)) // todo: just for testing .flag(("foo", OpaqueString)) // todo: just for testing

View file

@ -1 +1,9 @@
use super::*;
pub fn cmds() -> impl Iterator<Item = Command> {
[
command!("import", Optional(("url", OpaqueStringRemainder)) => "import"),
command!("export" => "export"),
]
.into_iter()
}

View file

@ -19,7 +19,12 @@ pub mod system;
pub mod utils; 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<Item = Command> { pub fn all() -> impl Iterator<Item = Command> {
(help::cmds()) (help::cmds())
@ -34,6 +39,7 @@ pub fn all() -> impl Iterator<Item = Command> {
.chain(autoproxy::cmds()) .chain(autoproxy::cmds())
.chain(debug::cmds()) .chain(debug::cmds())
.chain(message::cmds()) .chain(message::cmds())
.chain(import_export::cmds())
.map(|cmd| { .map(|cmd| {
cmd.hidden_flag(("plaintext", ["pt"])) cmd.hidden_flag(("plaintext", ["pt"]))
.hidden_flag(("raw", ["r"])) .hidden_flag(("raw", ["r"]))

View file

@ -182,11 +182,11 @@ pub fn cmds() -> impl Iterator<Item = Command> {
[ [
command!(member_keep_proxy => "member_keepproxy_show") command!(member_keep_proxy => "member_keepproxy_show")
.help("Shows a member's keep-proxy setting"), .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"), .help("Changes a member's keep-proxy setting"),
command!(member_server_keep_proxy => "member_server_keepproxy_show") command!(member_server_keep_proxy => "member_server_keepproxy_show")
.help("Shows a member's server-specific keep-proxy setting"), .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"), .help("Changes a member's server-specific keep-proxy setting"),
command!(member_server_keep_proxy, ("clear", ["c"]) => "member_server_keepproxy_clear") command!(member_server_keep_proxy, ("clear", ["c"]) => "member_server_keepproxy_clear")
.flag(("yes", ["y"])) .flag(("yes", ["y"]))
@ -303,9 +303,9 @@ pub fn cmds() -> impl Iterator<Item = Command> {
.map(|cmd| cmd.flags(get_list_flags())); .map(|cmd| cmd.flags(get_list_flags()));
let member_add_remove_group_cmds = [ 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"), .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"), .help("Removes a member from one or more groups"),
] ]
.into_iter(); .into_iter();

View file

@ -9,23 +9,22 @@ pub fn cmds() -> impl Iterator<Item = Command> {
let copy = ("copy", ["add", "duplicate", "dupe"]); let copy = ("copy", ["add", "duplicate", "dupe"]);
let out = "out"; let out = "out";
let edit_flags = [
("first", ["f"]),
("remove", ["r"]),
("append", ["a"]),
("prepend", ["p"]),
];
[ [
command!(switch, out => "switch_out"), command!(switch, out => "switch_out"),
command!(switch, r#move, OpaqueString => "switch_move"), // TODO: datetime parsing command!(switch, r#move, OpaqueString => "switch_move"), // TODO: datetime parsing
command!(switch, delete => "switch_delete").flag(("all", ["clear", "c"])), command!(switch, delete => "switch_delete").flag(("all", ["clear", "c"])),
command!(switch, edit, out => "switch_edit_out"), command!(switch, edit, out => "switch_edit_out"),
command!(switch, edit, MemberRefs => "switch_edit") command!(switch, edit, Optional(MemberRefs) => "switch_edit").flags(edit_flags),
.flag(("first", ["f"])) command!(switch, copy, Optional(MemberRefs) => "switch_copy").flags(edit_flags),
.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, ("commands", ["help"]) => "switch_commands"), command!(switch, ("commands", ["help"]) => "switch_commands"),
command!(switch, MemberRefs => "switch_do"), command!(switch, Optional(MemberRefs) => "switch_do"),
] ]
.into_iter() .into_iter()
} }

View file

@ -220,7 +220,7 @@ pub fn edit() -> impl Iterator<Item = Command> {
let system_proxy_cmd = [ let system_proxy_cmd = [
command!(system_proxy => "system_show_proxy_current") command!(system_proxy => "system_show_proxy_current")
.help("Shows your system's proxy setting for the guild you are in"), .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"), .help("Toggle your system's proxy for the guild you are in"),
command!(system_proxy, GuildRef => "system_show_proxy") command!(system_proxy, GuildRef => "system_show_proxy")
.help("Shows your system's proxy setting for a guild"), .help("Shows your system's proxy setting for a guild"),

View file

@ -14,7 +14,7 @@ pub enum FlagValueMatchError {
pub struct Flag { pub struct Flag {
name: SmolStr, name: SmolStr,
aliases: Vec<SmolStr>, aliases: Vec<SmolStr>,
value: Option<ParameterKind>, value: Option<Parameter>,
} }
impl Display for Flag { impl Display for Flag {
@ -22,7 +22,7 @@ impl Display for Flag {
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, "=")?; write!(f, "=")?;
Parameter::from(*value).fmt(f)?; value.fmt(f)?;
} }
Ok(()) Ok(())
} }
@ -58,8 +58,8 @@ impl Flag {
} }
} }
pub fn value(mut self, param: ParameterKind) -> Self { pub fn value(mut self, param: impl Into<Parameter>) -> Self {
self.value = Some(param); self.value = Some(param.into());
self self
} }
@ -72,8 +72,8 @@ impl Flag {
&self.name &self.name
} }
pub fn get_value(&self) -> Option<ParameterKind> { pub fn get_value(&self) -> Option<&Parameter> {
self.value self.value.as_ref()
} }
pub fn get_aliases(&self) -> impl Iterator<Item = &str> { 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 // iterate over tokens and run try_match
for token in possible_tokens { for token in possible_tokens {
let is_match_remaining_token = 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 // 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);
// either use matched param or rest of the input if matching remaining // either use matched param or rest of the input if matching remaining

View file

@ -24,12 +24,23 @@ pub enum ParameterValue {
PrivacyLevel(String), PrivacyLevel(String),
Toggle(bool), Toggle(bool),
Avatar(String), Avatar(String),
Null,
}
fn is_remainder(kind: ParameterKind) -> bool {
matches!(
kind,
ParameterKind::OpaqueStringRemainder | ParameterKind::MemberRefs | ParameterKind::GroupRefs
)
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Parameter { pub struct Parameter {
name: SmolStr, name: SmolStr,
kind: ParameterKind, kind: ParameterKind,
remainder: bool,
optional: bool,
skip: bool,
} }
impl Parameter { impl Parameter {
@ -40,106 +51,36 @@ impl Parameter {
pub fn kind(&self) -> ParameterKind { pub fn kind(&self) -> ParameterKind {
self.kind self.kind
} }
}
impl Display for Parameter { pub fn remainder(mut self) -> Self {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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 { 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 // TODO: actually parse image url
ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => { ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => {
Ok(ParameterValue::OpaqueString(input.into())) Ok(ParameterValue::OpaqueString(input.into()))
@ -217,13 +158,130 @@ impl ParameterKind {
.map_err(|_| SmolStr::new("invalid guild ID")), .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 { match self {
ParameterKind::Toggle => Some(None), ParameterKind::OpaqueString => "string",
ParameterKind::MemberRefs => Some(Some(ParameterValue::MemberRefs(Vec::new()))), ParameterKind::OpaqueStringRemainder => "string",
ParameterKind::GroupRefs => Some(Some(ParameterValue::GroupRefs(Vec::new()))), ParameterKind::MemberRef => "target",
_ => None, 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 smol_str::SmolStr;
use crate::parameter::{Parameter, ParameterKind, ParameterValue}; use crate::parameter::{Optional, Parameter, ParameterKind, ParameterValue};
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Token { pub enum Token {
@ -46,9 +46,17 @@ impl Token {
// short circuit on: // short circuit on:
return match self { return match self {
// missing paramaters // missing paramaters
Self::Parameter(param) => Some(TokenMatchResult::MissingParameter { Self::Parameter(param) => Some(
name: param.name().into(), 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 // everything else doesnt match if no input anyway
Self::Value { .. } => None, Self::Value { .. } => None,
// don't add a _ match here! // don't add a _ match here!
@ -62,20 +70,14 @@ impl Token {
Self::Value { name, aliases } => (aliases.iter().chain(std::iter::once(name))) Self::Value { name, aliases } => (aliases.iter().chain(std::iter::once(name)))
.any(|v| v.eq(input)) .any(|v| v.eq(input))
.then(|| TokenMatchResult::MatchedValue), .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 { Ok(matched) => TokenMatchResult::MatchedParameter {
name: param.name().into(), name: param.name().into(),
value: matched, value: matched,
}, },
Err(err) => { Err(err) => {
if let Some(maybe_empty) = param.kind().skip_if_cant_match() { if param.is_skip() {
match maybe_empty { return None;
Some(matched) => TokenMatchResult::MatchedParameter {
name: param.name().into(),
value: matched,
},
None => return None,
}
} else { } else {
TokenMatchResult::ParameterMatchError { TokenMatchResult::ParameterMatchError {
input: input.into(), input: input.into(),
@ -115,21 +117,10 @@ impl From<&str> for Token {
} }
} }
impl From<Parameter> for Token { // parameter -> Token::Parameter
fn from(value: Parameter) -> Self { impl<P: Into<Parameter>> From<P> for Token {
Self::Parameter(value) fn from(value: P) -> Self {
} Self::Parameter(value.into())
}
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))
} }
} }

View file

@ -45,19 +45,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
for param in &command_params { for param in &command_params {
writeln!( writeln!(
&mut command_params_init, &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("-", "_"), name = param.name().replace("-", "_"),
extract_fn_name = get_param_param_ty(param.kind()), 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(); let mut command_flags_init = String::new();
for flag in &command.flags { for flag in &command.flags {
if let Some(kind) = flag.get_value() { if let Some(param) = flag.get_value() {
writeln!( writeln!(
&mut command_flags_init, &mut command_flags_init,
r#"@{name} = await ctx.FlagResolve{extract_fn_name}("{name}"),"#, r#"@{name} = await ctx.FlagResolve{extract_fn_name}("{name}"),"#,
name = flag.get_name().replace("-", "_"), name = flag.get_name().replace("-", "_"),
extract_fn_name = get_param_param_ty(kind), extract_fn_name = get_param_param_ty(param.kind()),
)?; )?;
} else { } else {
writeln!( writeln!(
@ -109,19 +113,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
for param in &command_params { for param in &command_params {
writeln!( writeln!(
&mut command_params_fields, &mut command_params_fields,
r#"public required {ty} @{name};"#, r#"public required {ty}{nullable} @{name};"#,
name = param.name().replace("-", "_"), name = param.name().replace("-", "_"),
ty = get_param_ty(param.kind()), ty = get_param_ty(param.kind()),
nullable = param.is_optional().then_some("?").unwrap_or(""),
)?; )?;
} }
let mut command_flags_fields = String::new(); let mut command_flags_fields = String::new();
for flag in &command.flags { for flag in &command.flags {
if let Some(kind) = flag.get_value() { if let Some(param) = flag.get_value() {
writeln!( writeln!(
&mut command_flags_fields, &mut command_flags_fields,
r#"public {ty}? @{name};"#, r#"public {ty}? @{name};"#,
name = flag.get_name().replace("-", "_"), name = flag.get_name().replace("-", "_"),
ty = get_param_ty(kind), ty = get_param_ty(param.kind()),
)?; )?;
} else { } else {
writeln!( writeln!(

View file

@ -23,6 +23,7 @@ interface Parameter {
OpaqueString(string raw); OpaqueString(string raw);
Toggle(boolean toggle); Toggle(boolean toggle);
Avatar(string avatar); Avatar(string avatar);
Null();
}; };
dictionary ParsedCommand { dictionary ParsedCommand {
string command_ref; string command_ref;

View file

@ -69,6 +69,7 @@ pub enum Parameter {
Avatar { Avatar {
avatar: String, avatar: String,
}, },
Null,
} }
impl From<ParameterValue> for Parameter { impl From<ParameterValue> for Parameter {
@ -93,6 +94,7 @@ impl From<ParameterValue> for Parameter {
}, },
ParameterValue::ChannelRef(channel_id) => Self::ChannelRef { channel_id }, ParameterValue::ChannelRef(channel_id) => Self::ChannelRef { channel_id },
ParameterValue::GuildRef(guild_id) => Self::GuildRef { guild_id }, ParameterValue::GuildRef(guild_id) => Self::GuildRef { guild_id },
ParameterValue::Null => Self::Null,
} }
} }
} }