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.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.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...
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<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("channel"))
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(
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(
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"));
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;
}

View file

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

View file

@ -23,8 +23,9 @@ public class Switch
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
// 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 <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();
if (newMembers == null) newMembers = new List<PKMember>();
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<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
// 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;

View file

@ -158,9 +158,9 @@ pub fn cmds() -> impl Iterator<Item = Command> {
.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();

View file

@ -3,6 +3,7 @@ use super::*;
pub fn cmds() -> impl Iterator<Item = Command> {
let help = ("help", ["h"]);
[
command!(("dashboard", ["dash"]) => "dashboard"),
command!("explain" => "explain"),
command!(help => "help")
.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;
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> {
(help::cmds())
@ -34,6 +39,7 @@ pub fn all() -> impl Iterator<Item = Command> {
.chain(autoproxy::cmds())
.chain(debug::cmds())
.chain(message::cmds())
.chain(import_export::cmds())
.map(|cmd| {
cmd.hidden_flag(("plaintext", ["pt"]))
.hidden_flag(("raw", ["r"]))

View file

@ -182,11 +182,11 @@ pub fn cmds() -> impl Iterator<Item = Command> {
[
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<Item = Command> {
.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();

View file

@ -9,23 +9,22 @@ pub fn cmds() -> impl Iterator<Item = Command> {
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()
}

View file

@ -220,7 +220,7 @@ pub fn edit() -> impl Iterator<Item = Command> {
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"),

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

View file

@ -45,19 +45,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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!(

View file

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

View file

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