mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 04:56:49 +00:00
feat(commands): add cs codegen to statically use params and flags in bot code, remove Any
This commit is contained in:
parent
0c012e98b5
commit
07e8a4851a
20 changed files with 297 additions and 417 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -32,6 +32,7 @@ logs/
|
||||||
recipe.json
|
recipe.json
|
||||||
.docker-bin/
|
.docker-bin/
|
||||||
PluralKit.Bot/commands.cs
|
PluralKit.Bot/commands.cs
|
||||||
|
PluralKit.Bot/commandtypes.cs
|
||||||
|
|
||||||
# nix
|
# nix
|
||||||
.nix-process-compose
|
.nix-process-compose
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,26 @@ namespace PluralKit.Bot;
|
||||||
|
|
||||||
public partial class CommandTree
|
public partial class CommandTree
|
||||||
{
|
{
|
||||||
public Task ExecuteCommand(Context ctx)
|
public Task ExecuteCommand(Context ctx, Commands command)
|
||||||
{
|
{
|
||||||
return ctx.Parameters.Callback() switch
|
return command switch
|
||||||
{
|
{
|
||||||
"help" => ctx.Execute<Help>(Help, m => m.HelpRoot(ctx)),
|
Commands.Help => ctx.Execute<Help>(Help, m => m.HelpRoot(ctx)),
|
||||||
"help_commands" => ctx.Reply(
|
Commands.HelpCommands => ctx.Reply(
|
||||||
"For the list of commands, see the website: <https://pluralkit.me/commands>"),
|
"For the list of commands, see the website: <https://pluralkit.me/commands>"),
|
||||||
"help_proxy" => ctx.Reply(
|
Commands.HelpProxy => ctx.Reply(
|
||||||
"The proxy help page has been moved! See the website: https://pluralkit.me/guide#proxying"),
|
"The proxy help page has been moved! See the website: https://pluralkit.me/guide#proxying"),
|
||||||
"member_show" => ctx.Execute<Member>(MemberInfo, m => m.ViewMember(ctx)),
|
Commands.MemberShow(MemberShowParams param, _) => ctx.Execute<Member>(MemberInfo, m => m.ViewMember(ctx, param.target)),
|
||||||
"member_new" => ctx.Execute<Member>(MemberNew, m => m.NewMember(ctx)),
|
Commands.MemberNew(MemberNewParams param, _) => ctx.Execute<Member>(MemberNew, m => m.NewMember(ctx, param.name)),
|
||||||
"member_soulscream" => ctx.Execute<Member>(MemberInfo, m => m.Soulscream(ctx)),
|
Commands.MemberSoulscream(MemberSoulscreamParams param, _) => ctx.Execute<Member>(MemberInfo, m => m.Soulscream(ctx, param.target)),
|
||||||
"cfg_ap_account_show" => ctx.Execute<Config>(null, m => m.ViewAutoproxyAccount(ctx)),
|
Commands.CfgApAccountShow => ctx.Execute<Config>(null, m => m.ViewAutoproxyAccount(ctx)),
|
||||||
"cfg_ap_account_update" => ctx.Execute<Config>(null, m => m.EditAutoproxyAccount(ctx)),
|
Commands.CfgApAccountUpdate(CfgApAccountUpdateParams param, _) => ctx.Execute<Config>(null, m => m.EditAutoproxyAccount(ctx, param.toggle)),
|
||||||
"cfg_ap_timeout_show" => ctx.Execute<Config>(null, m => m.ViewAutoproxyTimeout(ctx)),
|
Commands.CfgApTimeoutShow => ctx.Execute<Config>(null, m => m.ViewAutoproxyTimeout(ctx)),
|
||||||
"cfg_ap_timeout_update" => ctx.Execute<Config>(null, m => m.EditAutoproxyTimeout(ctx)),
|
Commands.CfgApTimeoutOff => ctx.Execute<Config>(null, m => m.DisableAutoproxyTimeout(ctx)),
|
||||||
"fun_thunder" => ctx.Execute<Fun>(null, m => m.Thunder(ctx)),
|
Commands.CfgApTimeoutReset => ctx.Execute<Config>(null, m => m.ResetAutoproxyTimeout(ctx)),
|
||||||
"fun_meow" => ctx.Execute<Fun>(null, m => m.Meow(ctx)),
|
Commands.CfgApTimeoutUpdate(CfgApTimeoutUpdateParams param, _) => ctx.Execute<Config>(null, m => m.EditAutoproxyTimeout(ctx, param.timeout)),
|
||||||
|
Commands.FunThunder => ctx.Execute<Fun>(null, m => m.Thunder(ctx)),
|
||||||
|
Commands.FunMeow => ctx.Execute<Fun>(null, m => m.Meow(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(
|
||||||
|
|
|
||||||
|
|
@ -197,10 +197,9 @@ public class Config
|
||||||
await ctx.Reply($"Autoproxy is currently **{EnabledDisabled(allowAutoproxy)}** for account <@{ctx.Author.Id}>.");
|
await ctx.Reply($"Autoproxy is currently **{EnabledDisabled(allowAutoproxy)}** for account <@{ctx.Author.Id}>.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EditAutoproxyAccount(Context ctx)
|
public async Task EditAutoproxyAccount(Context ctx, bool allow)
|
||||||
{
|
{
|
||||||
var allowAutoproxy = await ctx.Repository.GetAutoproxyEnabled(ctx.Author.Id);
|
var allowAutoproxy = await ctx.Repository.GetAutoproxyEnabled(ctx.Author.Id);
|
||||||
var allow = await ctx.ParamResolveToggle("toggle") ?? throw new PKSyntaxError("You need to specify whether to enable or disable autoproxy for this account.");
|
|
||||||
|
|
||||||
var statusString = EnabledDisabled(allow);
|
var statusString = EnabledDisabled(allow);
|
||||||
if (allowAutoproxy == allow)
|
if (allowAutoproxy == allow)
|
||||||
|
|
@ -227,41 +226,44 @@ public class Config
|
||||||
await ctx.Reply($"The current latch timeout duration for your system is {timeout.Value.ToTimeSpan().Humanize(4)}.");
|
await ctx.Reply($"The current latch timeout duration for your system is {timeout.Value.ToTimeSpan().Humanize(4)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EditAutoproxyTimeout(Context ctx)
|
public async Task DisableAutoproxyTimeout(Context ctx)
|
||||||
{
|
{
|
||||||
var _newTimeout = await ctx.ParamResolveOpaque("timeout");
|
await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { LatchTimeout = (int)Duration.Zero.TotalSeconds });
|
||||||
var _reset = await ctx.ParamResolveToggle("reset");
|
|
||||||
var _toggle = await ctx.ParamResolveToggle("toggle");
|
|
||||||
|
|
||||||
Duration? newTimeout;
|
await ctx.Reply($"{Emojis.Success} Latch timeout disabled. Latch mode autoproxy will never time out.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetAutoproxyTimeout(Context ctx)
|
||||||
|
{
|
||||||
|
await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { LatchTimeout = null });
|
||||||
|
|
||||||
|
await ctx.Reply($"{Emojis.Success} Latch timeout reset to default ({ProxyMatcher.DefaultLatchExpiryTime.ToTimeSpan().Humanize(4)}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task EditAutoproxyTimeout(Context ctx, string timeout)
|
||||||
|
{
|
||||||
|
Duration newTimeout;
|
||||||
Duration overflow = Duration.Zero;
|
Duration overflow = Duration.Zero;
|
||||||
if (_toggle == false) newTimeout = Duration.Zero;
|
// todo: we should parse date in the command parser
|
||||||
else if (_reset == true) newTimeout = null;
|
var timeoutStr = timeout;
|
||||||
else
|
var timeoutPeriod = DateUtils.ParsePeriod(timeoutStr)
|
||||||
|
?? throw new PKError($"Could not parse '{timeoutStr}' as a valid duration. Try using a syntax such as \"3h5m\" (i.e. 3 hours and 5 minutes).");
|
||||||
|
if (timeoutPeriod.TotalHours > 100000)
|
||||||
{
|
{
|
||||||
// todo: we should parse date in the command parser
|
// sanity check to prevent seconds overflow if someone types in 999999999
|
||||||
var timeoutStr = _newTimeout;
|
overflow = timeoutPeriod;
|
||||||
var timeoutPeriod = DateUtils.ParsePeriod(timeoutStr);
|
newTimeout = Duration.Zero;
|
||||||
if (timeoutPeriod == null) throw new PKError($"Could not parse '{timeoutStr}' as a valid duration. Try using a syntax such as \"3h5m\" (i.e. 3 hours and 5 minutes).");
|
|
||||||
if (timeoutPeriod.Value.TotalHours > 100000)
|
|
||||||
{
|
|
||||||
// sanity check to prevent seconds overflow if someone types in 999999999
|
|
||||||
overflow = timeoutPeriod.Value;
|
|
||||||
newTimeout = Duration.Zero;
|
|
||||||
}
|
|
||||||
else newTimeout = timeoutPeriod;
|
|
||||||
}
|
}
|
||||||
|
else newTimeout = timeoutPeriod;
|
||||||
|
|
||||||
await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { LatchTimeout = (int?)newTimeout?.TotalSeconds });
|
await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { LatchTimeout = (int?)newTimeout.TotalSeconds });
|
||||||
|
|
||||||
if (newTimeout == null)
|
if (newTimeout == Duration.Zero && overflow != Duration.Zero)
|
||||||
await ctx.Reply($"{Emojis.Success} Latch timeout reset to default ({ProxyMatcher.DefaultLatchExpiryTime.ToTimeSpan().Humanize(4)}).");
|
|
||||||
else if (newTimeout == Duration.Zero && overflow != Duration.Zero)
|
|
||||||
await ctx.Reply($"{Emojis.Success} Latch timeout disabled. Latch mode autoproxy will never time out. ({overflow.ToTimeSpan().Humanize(4)} is too long)");
|
await ctx.Reply($"{Emojis.Success} Latch timeout disabled. Latch mode autoproxy will never time out. ({overflow.ToTimeSpan().Humanize(4)} is too long)");
|
||||||
else if (newTimeout == Duration.Zero)
|
else if (newTimeout == Duration.Zero)
|
||||||
await ctx.Reply($"{Emojis.Success} Latch timeout disabled. Latch mode autoproxy will never time out.");
|
await ctx.Reply($"{Emojis.Success} Latch timeout disabled. Latch mode autoproxy will never time out.");
|
||||||
else
|
else
|
||||||
await ctx.Reply($"{Emojis.Success} Latch timeout set to {newTimeout.Value!.ToTimeSpan().Humanize(4)}.");
|
await ctx.Reply($"{Emojis.Success} Latch timeout set to {newTimeout.ToTimeSpan().Humanize(4)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SystemTimezone(Context ctx)
|
public async Task SystemTimezone(Context ctx)
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,8 @@ public class Member
|
||||||
_avatarHosting = avatarHosting;
|
_avatarHosting = avatarHosting;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task NewMember(Context ctx)
|
public async Task NewMember(Context ctx, string? memberName)
|
||||||
{
|
{
|
||||||
var memberName = await ctx.ParamResolveOpaque("name");
|
|
||||||
|
|
||||||
if (ctx.System == null) throw Errors.NoSystemError(ctx.DefaultPrefix);
|
if (ctx.System == null) throw Errors.NoSystemError(ctx.DefaultPrefix);
|
||||||
memberName = memberName ?? throw new PKSyntaxError("You must pass a member name.");
|
memberName = memberName ?? throw new PKSyntaxError("You must pass a member name.");
|
||||||
|
|
||||||
|
|
@ -122,19 +120,17 @@ public class Member
|
||||||
await ctx.Reply(replyStr);
|
await ctx.Reply(replyStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ViewMember(Context ctx)
|
public async Task ViewMember(Context ctx, PKMember target)
|
||||||
{
|
{
|
||||||
var target = await ctx.ParamResolveMember("target");
|
|
||||||
var system = await ctx.Repository.GetSystem(target.System);
|
var system = await ctx.Repository.GetSystem(target.System);
|
||||||
await ctx.Reply(
|
await ctx.Reply(
|
||||||
embed: await _embeds.CreateMemberEmbed(system, target, ctx.Guild, ctx.Config, ctx.LookupContextFor(system.Id), ctx.Zone));
|
embed: await _embeds.CreateMemberEmbed(system, target, ctx.Guild, ctx.Config, ctx.LookupContextFor(system.Id), ctx.Zone));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Soulscream(Context ctx)
|
public async Task Soulscream(Context ctx, PKMember target)
|
||||||
{
|
{
|
||||||
// this is for a meme, please don't take this code seriously. :)
|
// this is for a meme, please don't take this code seriously. :)
|
||||||
|
|
||||||
var target = await ctx.ParamResolveMember("target");
|
|
||||||
var name = target.NameFor(ctx.LookupContextFor(target.System));
|
var name = target.NameFor(ctx.LookupContextFor(target.System));
|
||||||
var encoded = HttpUtility.UrlEncode(name);
|
var encoded = HttpUtility.UrlEncode(name);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,19 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctx = new Context(_services, shardId, guild, channel, evt, cmdStart, system, config, guildConfig, _config.Prefixes ?? BotConfig.DefaultPrefixes, parameters);
|
var ctx = new Context(_services, shardId, guild, channel, evt, cmdStart, system, config, guildConfig, _config.Prefixes ?? BotConfig.DefaultPrefixes, parameters);
|
||||||
await _tree.ExecuteCommand(ctx);
|
|
||||||
|
Commands command;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
command = await Commands.FromContext(ctx);
|
||||||
|
}
|
||||||
|
catch (PKError e)
|
||||||
|
{
|
||||||
|
await ctx.Reply($"{Emojis.Error} {e.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _tree.ExecuteCommand(ctx, command);
|
||||||
}
|
}
|
||||||
catch (PKError)
|
catch (PKError)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,25 @@
|
||||||
|
use command_parser::parameter;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn cmds() -> impl Iterator<Item = Command> {
|
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
let cfg = ["config", "cfg"];
|
let ap = tokens!(["config", "cfg"], ["autoproxy", "ap"]);
|
||||||
let autoproxy = ["autoproxy", "ap"];
|
|
||||||
|
let ap_account = concat_tokens!(ap, [["account", "ac"]]);
|
||||||
|
let ap_timeout = concat_tokens!(ap, [["timeout", "tm"]]);
|
||||||
|
|
||||||
[
|
[
|
||||||
command!([cfg, autoproxy, ["account", "ac"]], "cfg_ap_account_show")
|
command!(ap_account => "cfg_ap_account_show")
|
||||||
.help("Shows autoproxy status for the account"),
|
.help("Shows autoproxy status for the account"),
|
||||||
command!(
|
command!(ap_account, Toggle => "cfg_ap_account_update")
|
||||||
[cfg, autoproxy, ["account", "ac"], Toggle],
|
.help("Toggles autoproxy for the account"),
|
||||||
"cfg_ap_account_update"
|
command!(ap_timeout => "cfg_ap_timeout_show").help("Shows the autoproxy timeout"),
|
||||||
)
|
command!(ap_timeout, parameter::RESET => "cfg_ap_timeout_reset")
|
||||||
.help("Toggles autoproxy for the account"),
|
.help("Resets the autoproxy timeout"),
|
||||||
command!([cfg, autoproxy, ["timeout", "tm"]], "cfg_ap_timeout_show")
|
command!(ap_timeout, parameter::DISABLE => "cfg_ap_timeout_off")
|
||||||
.help("Shows the autoproxy timeout"),
|
.help("Disables the autoproxy timeout"),
|
||||||
command!(
|
command!(ap_timeout, ("timeout", OpaqueString) => "cfg_ap_timeout_update")
|
||||||
[
|
.help("Sets the autoproxy timeout"),
|
||||||
cfg,
|
|
||||||
autoproxy,
|
|
||||||
["timeout", "tm"],
|
|
||||||
any!(Disable, Reset, ("timeout", OpaqueString::SINGLE)) // todo: we should parse duration / time values
|
|
||||||
],
|
|
||||||
"cfg_ap_timeout_update"
|
|
||||||
)
|
|
||||||
.help("Sets the autoproxy timeout"),
|
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ use super::*;
|
||||||
|
|
||||||
pub fn cmds() -> impl Iterator<Item = Command> {
|
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
[
|
[
|
||||||
command!(["thunder"], "fun_thunder"),
|
command!(["thunder"] => "fun_thunder"),
|
||||||
command!(["meow"], "fun_meow"),
|
command!(["meow"] => "fun_meow"),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ 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!([help], "help").help("Shows the help command"),
|
command!([help] => "help").help("Shows the help command"),
|
||||||
command!([help, "commands"], "help_commands").help("help commands"),
|
command!([help, "commands"] => "help_commands").help("help commands"),
|
||||||
command!([help, "proxy"], "help_proxy").help("help proxy"),
|
command!([help, "proxy"] => "help_proxy").help("help proxy"),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ pub mod server_config;
|
||||||
pub mod switch;
|
pub mod switch;
|
||||||
pub mod system;
|
pub mod system;
|
||||||
|
|
||||||
use command_parser::{any, command, command::Command, parameter::*};
|
use command_parser::{
|
||||||
|
command, command::Command, concat_tokens, parameter::ParameterKind::*, tokens,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn all() -> impl Iterator<Item = Command> {
|
pub fn all() -> impl Iterator<Item = Command> {
|
||||||
(help::cmds())
|
(help::cmds())
|
||||||
|
|
|
||||||
|
|
@ -6,38 +6,27 @@ pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
let privacy = ["privacy", "priv"];
|
let privacy = ["privacy", "priv"];
|
||||||
let new = ["new", "n"];
|
let new = ["new", "n"];
|
||||||
|
|
||||||
|
let member_target = tokens!(member, MemberRef);
|
||||||
|
let member_desc = concat_tokens!(member_target, [description]);
|
||||||
|
let member_privacy = concat_tokens!(member_target, [privacy]);
|
||||||
|
|
||||||
[
|
[
|
||||||
command!([member, new, ("name", OpaqueString::SINGLE)], "member_new")
|
command!([member, new, ("name", OpaqueString)] => "member_new")
|
||||||
.help("Creates a new system member"),
|
.help("Creates a new system member"),
|
||||||
command!([member, MemberRef], "member_show")
|
command!(member_target => "member_show")
|
||||||
.help("Shows information about a member")
|
.flag("pt")
|
||||||
.value_flag("pt", Disable),
|
.help("Shows information about a member"),
|
||||||
command!([member, MemberRef, description], "member_desc_show")
|
command!(member_desc => "member_desc_show").help("Shows a member's description"),
|
||||||
.help("Shows a member's description"),
|
command!(member_desc, ("description", OpaqueStringRemainder) => "member_desc_update")
|
||||||
command!(
|
.help("Changes a member's description"),
|
||||||
[
|
command!(member_privacy => "member_privacy_show")
|
||||||
member,
|
|
||||||
MemberRef,
|
|
||||||
description,
|
|
||||||
("description", OpaqueString::REMAINDER)
|
|
||||||
],
|
|
||||||
"member_desc_update"
|
|
||||||
)
|
|
||||||
.help("Changes a member's description"),
|
|
||||||
command!([member, MemberRef, privacy], "member_privacy_show")
|
|
||||||
.help("Displays a member's current privacy settings"),
|
.help("Displays a member's current privacy settings"),
|
||||||
command!(
|
command!(
|
||||||
[
|
member_privacy, MemberPrivacyTarget, ("new_privacy_level", PrivacyLevel)
|
||||||
member,
|
=> "member_privacy_update"
|
||||||
MemberRef,
|
|
||||||
privacy,
|
|
||||||
MemberPrivacyTarget,
|
|
||||||
("new_privacy_level", PrivacyLevel)
|
|
||||||
],
|
|
||||||
"member_privacy_update"
|
|
||||||
)
|
)
|
||||||
.help("Changes a member's privacy settings"),
|
.help("Changes a member's privacy settings"),
|
||||||
command!([member, MemberRef, "soulscream"], "member_soulscream").show_in_suggestions(false),
|
command!(member_target, "soulscream" => "member_soulscream").show_in_suggestions(false),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
let system = ["system", "s"];
|
let system = ["system", "s"];
|
||||||
let new = ["new", "n"];
|
let new = ["new", "n"];
|
||||||
|
|
||||||
|
let system_new = tokens!(system, new);
|
||||||
|
|
||||||
[
|
[
|
||||||
command!([system], "system_show").help("Shows information about your system"),
|
command!([system] => "system_show").help("Shows information about your system"),
|
||||||
command!([system, new], "system_new").help("Creates a new system"),
|
command!(system_new => "system_new").help("Creates a new system"),
|
||||||
command!([system, new, ("name", OpaqueString::SINGLE)], "system_new")
|
command!(system_new, ("name", OpaqueString) => "system_new_name")
|
||||||
.help("Creates a new system"),
|
.help("Creates a new system (using the provided name)"),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ impl Command {
|
||||||
for (idx, token) in tokens.iter().enumerate().rev() {
|
for (idx, token) in tokens.iter().enumerate().rev() {
|
||||||
match token {
|
match token {
|
||||||
// we want flags to go before any parameters
|
// we want flags to go before any parameters
|
||||||
Token::Parameter(_, _) | Token::Any(_) => {
|
Token::Parameter(_) => {
|
||||||
parse_flags_before = idx;
|
parse_flags_before = idx;
|
||||||
was_parameter = true;
|
was_parameter = true;
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ impl Command {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value_flag(mut self, name: impl Into<SmolStr>, value: impl Parameter + 'static) -> Self {
|
pub fn value_flag(mut self, name: impl Into<SmolStr>, value: ParameterKind) -> Self {
|
||||||
self.flags.push(Flag::new(name).with_value(value));
|
self.flags.push(Flag::new(name).with_value(value));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,27 @@ impl Display for Command {
|
||||||
// (and something like &dyn Trait would require everything to be referenced which doesnt look nice anyway)
|
// (and something like &dyn Trait would require everything to be referenced which doesnt look nice anyway)
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! command {
|
macro_rules! command {
|
||||||
([$($v:expr),+], $cb:expr$(,)*) => {
|
([$($v:expr),+] => $cb:expr$(,)*) => {
|
||||||
$crate::command::Command::new([$($crate::token::Token::from($v)),*], $cb)
|
$crate::command::Command::new($crate::tokens!($($v),+), $cb)
|
||||||
|
};
|
||||||
|
($tokens:expr => $cb:expr$(,)*) => {
|
||||||
|
$crate::command::Command::new($tokens.clone(), $cb)
|
||||||
|
};
|
||||||
|
($tokens:expr, $($v:expr),+ => $cb:expr$(,)*) => {
|
||||||
|
$crate::command::Command::new($crate::concat_tokens!($tokens.clone(), [$($v),+]), $cb)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! tokens {
|
||||||
|
($($v:expr),+$(,)*) => {
|
||||||
|
[$($crate::token::Token::from($v)),+]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! concat_tokens {
|
||||||
|
($tokens:expr, [$($v:expr),+]$(,)*) => {
|
||||||
|
$tokens.clone().into_iter().chain($crate::tokens!($($v),+).into_iter()).collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::{fmt::Display, sync::Arc};
|
use std::fmt::Display;
|
||||||
|
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
|
|
||||||
use crate::parameter::{Parameter, ParameterValue};
|
use crate::parameter::{ParameterKind, ParameterValue};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FlagValueMatchError {
|
pub enum FlagValueMatchError {
|
||||||
|
|
@ -13,7 +13,7 @@ pub enum FlagValueMatchError {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Flag {
|
pub struct Flag {
|
||||||
name: SmolStr,
|
name: SmolStr,
|
||||||
value: Option<Arc<dyn Parameter>>,
|
value: Option<ParameterKind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Flag {
|
impl Display for Flag {
|
||||||
|
|
@ -42,8 +42,8 @@ impl Flag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_value(mut self, param: impl Parameter + 'static) -> Self {
|
pub fn with_value(mut self, param: ParameterKind) -> Self {
|
||||||
self.value = Some(Arc::new(param));
|
self.value = Some(param);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,13 +51,17 @@ impl Flag {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn value_kind(&self) -> Option<ParameterKind> {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_match(&self, input_name: &str, input_value: Option<&str>) -> TryMatchFlagResult {
|
pub fn try_match(&self, input_name: &str, input_value: Option<&str>) -> TryMatchFlagResult {
|
||||||
// if not matching flag then skip anymore matching
|
// if not matching flag then skip anymore matching
|
||||||
if self.name != input_name {
|
if self.name != input_name {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// get token to try matching with, if flag doesn't have one then that means it is matched (it is without any value)
|
// get token to try matching with, if flag doesn't have one then that means it is matched (it is without any value)
|
||||||
let Some(value) = self.value.as_deref() else {
|
let Some(value) = self.value.as_ref() else {
|
||||||
return Some(Ok(None));
|
return Some(Ok(None));
|
||||||
};
|
};
|
||||||
// check if we have a non-empty flag value, we return error if not (because flag requested a value)
|
// check if we have a non-empty flag value, we return error if not (because flag requested a value)
|
||||||
|
|
|
||||||
|
|
@ -77,21 +77,6 @@ pub fn parse_command(
|
||||||
TokenMatchError::MissingParameter { name } => {
|
TokenMatchError::MissingParameter { name } => {
|
||||||
format!("Expected parameter `{name}` in command `{prefix}{input} {token}`.")
|
format!("Expected parameter `{name}` in command `{prefix}{input} {token}`.")
|
||||||
}
|
}
|
||||||
TokenMatchError::MissingAny { tokens } => {
|
|
||||||
let mut msg = format!("Expected one of ");
|
|
||||||
for (idx, token) in tokens.iter().enumerate() {
|
|
||||||
write!(&mut msg, "`{token}`").expect("oom");
|
|
||||||
if idx < tokens.len() - 1 {
|
|
||||||
if tokens.len() > 2 && idx == tokens.len() - 2 {
|
|
||||||
msg.push_str(" or ");
|
|
||||||
} else {
|
|
||||||
msg.push_str(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(&mut msg, " in command `{prefix}{input} {token}`.").expect("oom");
|
|
||||||
msg
|
|
||||||
}
|
|
||||||
TokenMatchError::ParameterMatchError { input: raw, msg } => {
|
TokenMatchError::ParameterMatchError { input: raw, msg } => {
|
||||||
format!("Parameter `{raw}` in command `{prefix}{input}` could not be parsed: {msg}.")
|
format!("Parameter `{raw}` in command `{prefix}{input}` could not be parsed: {msg}.")
|
||||||
}
|
}
|
||||||
|
|
@ -254,12 +239,9 @@ 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.remainder());
|
|token: &Token| matches!(token, Token::Parameter(param) if param.kind().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);
|
||||||
// check for Any here if it has a "match remainder" token in it
|
|
||||||
// if there is a "match remainder" token in a command there shouldn't be a command descending from that
|
|
||||||
|| matches!(token, Token::Any(ref tokens) if tokens.iter().any(is_match_remaining_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
|
||||||
let input_to_match = matched.as_ref().map(|v| {
|
let input_to_match = matched.as_ref().map(|v| {
|
||||||
match_remaining
|
match_remaining
|
||||||
|
|
|
||||||
|
|
@ -2,89 +2,108 @@ use std::{fmt::Debug, str::FromStr};
|
||||||
|
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
|
|
||||||
use crate::token::ParamName;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ParameterValue {
|
pub enum ParameterValue {
|
||||||
|
OpaqueString(String),
|
||||||
MemberRef(String),
|
MemberRef(String),
|
||||||
SystemRef(String),
|
SystemRef(String),
|
||||||
MemberPrivacyTarget(String),
|
MemberPrivacyTarget(String),
|
||||||
PrivacyLevel(String),
|
PrivacyLevel(String),
|
||||||
OpaqueString(String),
|
|
||||||
Toggle(bool),
|
Toggle(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Parameter: Debug + Send + Sync {
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
fn remainder(&self) -> bool {
|
pub struct Parameter {
|
||||||
false
|
name: SmolStr,
|
||||||
}
|
kind: ParameterKind,
|
||||||
fn default_name(&self) -> ParamName;
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, name: &str) -> std::fmt::Result;
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
impl Parameter {
|
||||||
pub struct OpaqueString(bool);
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
impl OpaqueString {
|
|
||||||
pub const SINGLE: Self = Self(false);
|
|
||||||
pub const REMAINDER: Self = Self(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parameter for OpaqueString {
|
|
||||||
fn remainder(&self) -> bool {
|
|
||||||
self.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_name(&self) -> ParamName {
|
pub fn kind(&self) -> ParameterKind {
|
||||||
"string"
|
self.kind
|
||||||
}
|
|
||||||
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, name: &str) -> std::fmt::Result {
|
|
||||||
write!(f, "[{name}]")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
|
||||||
Ok(ParameterValue::OpaqueString(input.into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
impl From<ParameterKind> for Parameter {
|
||||||
pub struct MemberRef;
|
fn from(value: ParameterKind) -> Self {
|
||||||
|
Parameter {
|
||||||
impl Parameter for MemberRef {
|
name: value.default_name().into(),
|
||||||
fn default_name(&self) -> ParamName {
|
kind: value,
|
||||||
"member"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result {
|
|
||||||
write!(f, "<target member>")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
|
||||||
Ok(ParameterValue::MemberRef(input.into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
impl From<(&str, ParameterKind)> for Parameter {
|
||||||
pub struct SystemRef;
|
fn from((name, kind): (&str, ParameterKind)) -> Self {
|
||||||
|
Parameter {
|
||||||
impl Parameter for SystemRef {
|
name: name.into(),
|
||||||
fn default_name(&self) -> ParamName {
|
kind,
|
||||||
"system"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result {
|
|
||||||
write!(f, "<target system>")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
|
||||||
Ok(ParameterValue::SystemRef(input.into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct MemberPrivacyTarget;
|
pub enum ParameterKind {
|
||||||
|
OpaqueString,
|
||||||
|
OpaqueStringRemainder,
|
||||||
|
MemberRef,
|
||||||
|
SystemRef,
|
||||||
|
MemberPrivacyTarget,
|
||||||
|
PrivacyLevel,
|
||||||
|
Toggle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParameterKind {
|
||||||
|
pub(crate) fn default_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ParameterKind::OpaqueString => "string",
|
||||||
|
ParameterKind::OpaqueStringRemainder => "string",
|
||||||
|
ParameterKind::MemberRef => "target",
|
||||||
|
ParameterKind::SystemRef => "target",
|
||||||
|
ParameterKind::MemberPrivacyTarget => "member_privacy_target",
|
||||||
|
ParameterKind::PrivacyLevel => "privacy_level",
|
||||||
|
ParameterKind::Toggle => "toggle",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn remainder(&self) -> bool {
|
||||||
|
matches!(self, ParameterKind::OpaqueStringRemainder)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn format(&self, f: &mut std::fmt::Formatter, param_name: &str) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => {
|
||||||
|
write!(f, "[{param_name}]")
|
||||||
|
}
|
||||||
|
ParameterKind::MemberRef => write!(f, "<target member>"),
|
||||||
|
ParameterKind::SystemRef => write!(f, "<target system>"),
|
||||||
|
ParameterKind::MemberPrivacyTarget => write!(f, "<privacy target>"),
|
||||||
|
ParameterKind::PrivacyLevel => write!(f, "[privacy level]"),
|
||||||
|
ParameterKind::Toggle => write!(f, "on/off"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
||||||
|
match self {
|
||||||
|
ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => {
|
||||||
|
Ok(ParameterValue::OpaqueString(input.into()))
|
||||||
|
}
|
||||||
|
ParameterKind::MemberRef => Ok(ParameterValue::MemberRef(input.into())),
|
||||||
|
ParameterKind::SystemRef => Ok(ParameterValue::SystemRef(input.into())),
|
||||||
|
ParameterKind::MemberPrivacyTarget => MemberPrivacyTargetKind::from_str(input)
|
||||||
|
.map(|target| ParameterValue::MemberPrivacyTarget(target.as_ref().into())),
|
||||||
|
ParameterKind::PrivacyLevel => PrivacyLevelKind::from_str(input)
|
||||||
|
.map(|level| ParameterValue::PrivacyLevel(level.as_ref().into())),
|
||||||
|
ParameterKind::Toggle => {
|
||||||
|
Toggle::from_str(input).map(|t| ParameterValue::Toggle(t.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum MemberPrivacyTargetKind {
|
pub enum MemberPrivacyTargetKind {
|
||||||
Visibility,
|
Visibility,
|
||||||
|
|
@ -135,24 +154,6 @@ impl FromStr for MemberPrivacyTargetKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parameter for MemberPrivacyTarget {
|
|
||||||
fn default_name(&self) -> ParamName {
|
|
||||||
"member_privacy_target"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result {
|
|
||||||
write!(f, "<privacy target>")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
|
||||||
MemberPrivacyTargetKind::from_str(input)
|
|
||||||
.map(|target| ParameterValue::MemberPrivacyTarget(target.as_ref().into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
|
||||||
pub struct PrivacyLevel;
|
|
||||||
|
|
||||||
pub enum PrivacyLevelKind {
|
pub enum PrivacyLevelKind {
|
||||||
Public,
|
Public,
|
||||||
Private,
|
Private,
|
||||||
|
|
@ -179,140 +180,34 @@ impl FromStr for PrivacyLevelKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parameter for PrivacyLevel {
|
pub const ENABLE: [&str; 5] = ["on", "yes", "true", "enable", "enabled"];
|
||||||
fn default_name(&self) -> ParamName {
|
pub const DISABLE: [&str; 5] = ["off", "no", "false", "disable", "disabled"];
|
||||||
"privacy_level"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result {
|
|
||||||
write!(f, "[privacy level]")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
|
||||||
PrivacyLevelKind::from_str(input)
|
|
||||||
.map(|level| ParameterValue::PrivacyLevel(level.as_ref().into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||||
pub struct Reset;
|
pub enum Toggle {
|
||||||
|
On,
|
||||||
impl AsRef<str> for Reset {
|
Off,
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
"reset"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Reset {
|
impl FromStr for Toggle {
|
||||||
type Err = SmolStr;
|
type Err = SmolStr;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"reset" | "clear" | "default" => Ok(Self),
|
ref s if ENABLE.contains(s) => Ok(Self::On),
|
||||||
_ => Err("not reset".into()),
|
ref s if DISABLE.contains(s) => Ok(Self::Off),
|
||||||
|
_ => Err("invalid toggle, must be on/off".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parameter for Reset {
|
impl Into<bool> for Toggle {
|
||||||
fn default_name(&self) -> ParamName {
|
|
||||||
"reset"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result {
|
|
||||||
write!(f, "reset")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
|
||||||
Self::from_str(input).map(|_| ParameterValue::Toggle(true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
|
||||||
pub struct Toggle;
|
|
||||||
|
|
||||||
impl Parameter for Toggle {
|
|
||||||
fn default_name(&self) -> ParamName {
|
|
||||||
"toggle"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result {
|
|
||||||
write!(f, "on/off")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
|
||||||
Enable::from_str(input)
|
|
||||||
.map(Into::<bool>::into)
|
|
||||||
.or_else(|_| Disable::from_str(input).map(Into::<bool>::into))
|
|
||||||
.map(ParameterValue::Toggle)
|
|
||||||
.map_err(|_| "invalid toggle".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
|
||||||
pub struct Enable;
|
|
||||||
|
|
||||||
impl FromStr for Enable {
|
|
||||||
type Err = SmolStr;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"on" | "yes" | "true" | "enable" | "enabled" => Ok(Self),
|
|
||||||
_ => Err("invalid enable".into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parameter for Enable {
|
|
||||||
fn default_name(&self) -> ParamName {
|
|
||||||
"enable"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result {
|
|
||||||
write!(f, "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
|
||||||
Self::from_str(input).map(|e| ParameterValue::Toggle(e.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<bool> for Enable {
|
|
||||||
fn into(self) -> bool {
|
fn into(self) -> bool {
|
||||||
true
|
match self {
|
||||||
}
|
Toggle::On => true,
|
||||||
}
|
Toggle::Off => false,
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
|
||||||
pub struct Disable;
|
|
||||||
|
|
||||||
impl FromStr for Disable {
|
|
||||||
type Err = SmolStr;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"off" | "no" | "false" | "disable" | "disabled" => Ok(Self),
|
|
||||||
_ => Err("invalid disable".into()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<bool> for Disable {
|
pub const RESET: [&str; 3] = ["reset", "clear", "default"];
|
||||||
fn into(self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parameter for Disable {
|
|
||||||
fn default_name(&self) -> ParamName {
|
|
||||||
"disable"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format(&self, f: &mut std::fmt::Formatter, _: &str) -> std::fmt::Result {
|
|
||||||
write!(f, "off")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
|
||||||
Self::from_str(input).map(|e| ParameterValue::Toggle(e.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,35 @@
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
hash::Hash,
|
|
||||||
ops::Not,
|
ops::Not,
|
||||||
sync::Arc,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use smol_str::SmolStr;
|
use smol_str::SmolStr;
|
||||||
|
|
||||||
use crate::parameter::{Parameter, ParameterValue};
|
use crate::parameter::{Parameter, ParameterKind, ParameterValue};
|
||||||
|
|
||||||
pub type ParamName = &'static str;
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Token {
|
pub enum Token {
|
||||||
/// Token used to represent a finished command (i.e. no more parameters required)
|
/// Token used to represent a finished command (i.e. no more parameters required)
|
||||||
// todo: this is likely not the right way to represent this
|
// todo: this is likely not the right way to represent this
|
||||||
Empty,
|
Empty,
|
||||||
|
|
||||||
/// multi-token matching
|
|
||||||
/// todo: FullString tokens don't work properly in this (they don't get passed the rest of the input)
|
|
||||||
Any(Vec<Token>),
|
|
||||||
|
|
||||||
/// A bot-defined command / subcommand (usually) (eg. "member" in `pk;member MyName`)
|
/// A bot-defined command / subcommand (usually) (eg. "member" in `pk;member MyName`)
|
||||||
Value(Vec<SmolStr>),
|
Value(Vec<SmolStr>),
|
||||||
|
|
||||||
/// A parameter that must be provided a value
|
/// A parameter that must be provided a value
|
||||||
Parameter(ParamName, Arc<dyn Parameter>),
|
Parameter(Parameter),
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! any {
|
|
||||||
($($t:expr),+) => {
|
|
||||||
$crate::token::Token::Any(vec![$($crate::token::Token::from($t)),+])
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Token {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
match (self, other) {
|
|
||||||
(Self::Any(l0), Self::Any(r0)) => l0 == r0,
|
|
||||||
(Self::Value(l0), Self::Value(r0)) => l0 == r0,
|
|
||||||
(Self::Parameter(l0, _), Self::Parameter(r0, _)) => l0 == r0,
|
|
||||||
(Self::Empty, Self::Empty) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Eq for Token {}
|
|
||||||
|
|
||||||
impl Hash for Token {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
core::mem::discriminant(self).hash(state);
|
|
||||||
match self {
|
|
||||||
Token::Empty => {}
|
|
||||||
Token::Any(vec) => vec.hash(state),
|
|
||||||
Token::Value(vec) => vec.hash(state),
|
|
||||||
Token::Parameter(name, _) => name.hash(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TokenMatchError {
|
pub enum TokenMatchError {
|
||||||
ParameterMatchError { input: SmolStr, msg: SmolStr },
|
ParameterMatchError { input: SmolStr, msg: SmolStr },
|
||||||
MissingParameter { name: ParamName },
|
MissingParameter { name: SmolStr },
|
||||||
MissingAny { tokens: Vec<Token> },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) struct TokenMatchValue {
|
pub(super) struct TokenMatchValue {
|
||||||
pub raw: SmolStr,
|
pub raw: SmolStr,
|
||||||
pub param: Option<(ParamName, ParameterValue)>,
|
pub param: Option<(SmolStr, ParameterValue)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TokenMatchValue {
|
impl TokenMatchValue {
|
||||||
|
|
@ -83,12 +42,12 @@ impl TokenMatchValue {
|
||||||
|
|
||||||
fn new_match_param(
|
fn new_match_param(
|
||||||
raw: impl Into<SmolStr>,
|
raw: impl Into<SmolStr>,
|
||||||
param_name: ParamName,
|
param_name: impl Into<SmolStr>,
|
||||||
param: ParameterValue,
|
param: ParameterValue,
|
||||||
) -> TryMatchResult {
|
) -> TryMatchResult {
|
||||||
Some(Ok(Some(Self {
|
Some(Ok(Some(Self {
|
||||||
raw: raw.into(),
|
raw: raw.into(),
|
||||||
param: Some((param_name, param)),
|
param: Some((param_name.into(), param)),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,14 +72,9 @@ impl Token {
|
||||||
// empty token
|
// empty token
|
||||||
Self::Empty => Some(Ok(None)),
|
Self::Empty => Some(Ok(None)),
|
||||||
// missing paramaters
|
// missing paramaters
|
||||||
Self::Parameter(name, _) => {
|
Self::Parameter(param) => Some(Err(TokenMatchError::MissingParameter {
|
||||||
Some(Err(TokenMatchError::MissingParameter { name }))
|
name: param.name().into(),
|
||||||
}
|
})),
|
||||||
Self::Any(tokens) => tokens.is_empty().then_some(None).unwrap_or_else(|| {
|
|
||||||
Some(Err(TokenMatchError::MissingAny {
|
|
||||||
tokens: tokens.clone(),
|
|
||||||
}))
|
|
||||||
}),
|
|
||||||
// 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!
|
||||||
|
|
@ -132,18 +86,13 @@ impl Token {
|
||||||
// try actually matching stuff
|
// try actually matching stuff
|
||||||
match self {
|
match self {
|
||||||
Self::Empty => None,
|
Self::Empty => None,
|
||||||
Self::Any(tokens) => tokens
|
|
||||||
.iter()
|
|
||||||
.map(|t| t.try_match(Some(input)))
|
|
||||||
.find(|r| !matches!(r, None))
|
|
||||||
.unwrap_or(None),
|
|
||||||
Self::Value(values) => values
|
Self::Value(values) => values
|
||||||
.iter()
|
.iter()
|
||||||
.any(|v| v.eq(input))
|
.any(|v| v.eq(input))
|
||||||
.then(|| TokenMatchValue::new_match(input))
|
.then(|| TokenMatchValue::new_match(input))
|
||||||
.unwrap_or(None),
|
.unwrap_or(None),
|
||||||
Self::Parameter(name, param) => match param.match_value(input) {
|
Self::Parameter(param) => match param.kind().match_value(input) {
|
||||||
Ok(matched) => TokenMatchValue::new_match_param(input, name, matched),
|
Ok(matched) => TokenMatchValue::new_match_param(input, param.name(), matched),
|
||||||
Err(err) => Some(Err(TokenMatchError::ParameterMatchError {
|
Err(err) => Some(Err(TokenMatchError::ParameterMatchError {
|
||||||
input: input.into(),
|
input: input.into(),
|
||||||
msg: err,
|
msg: err,
|
||||||
|
|
@ -157,19 +106,9 @@ impl Display for Token {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Token::Empty => write!(f, ""),
|
Token::Empty => write!(f, ""),
|
||||||
Token::Any(vec) => {
|
|
||||||
write!(f, "(")?;
|
|
||||||
for (i, token) in vec.iter().enumerate() {
|
|
||||||
if i != 0 {
|
|
||||||
write!(f, "|")?;
|
|
||||||
}
|
|
||||||
write!(f, "{}", token)?;
|
|
||||||
}
|
|
||||||
write!(f, ")")
|
|
||||||
}
|
|
||||||
Token::Value(vec) if vec.is_empty().not() => write!(f, "{}", vec.first().unwrap()),
|
Token::Value(vec) if vec.is_empty().not() => write!(f, "{}", vec.first().unwrap()),
|
||||||
Token::Value(_) => Ok(()), // if value token has no values (lol), don't print anything
|
Token::Value(_) => Ok(()), // if value token has no values (lol), don't print anything
|
||||||
Token::Parameter(name, param) => param.format(f, name),
|
Token::Parameter(param) => param.kind().format(f, param.name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,14 +125,20 @@ impl<const L: usize> From<[&str; L]> for Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Parameter + 'static> From<P> for Token {
|
impl From<Parameter> for Token {
|
||||||
fn from(value: P) -> Self {
|
fn from(value: Parameter) -> Self {
|
||||||
Token::Parameter(value.default_name(), Arc::new(value))
|
Token::Parameter(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Parameter + 'static> From<(ParamName, P)> for Token {
|
impl From<ParameterKind> for Token {
|
||||||
fn from(value: (ParamName, P)) -> Self {
|
fn from(value: ParameterKind) -> Self {
|
||||||
Token::Parameter(value.0, Arc::new(value.1))
|
Token::from(Parameter::from(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, ParameterKind)> for Token {
|
||||||
|
fn from(value: (&str, ParameterKind)) -> Self {
|
||||||
|
Token::from(Parameter::from(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,15 @@ impl TreeBranch {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn command(&self) -> Option<Command> {
|
pub fn command(&self) -> Option<Command> {
|
||||||
self.current_command.clone()
|
self.current_command.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn possible_tokens(&self) -> impl Iterator<Item = &Token> {
|
pub fn possible_tokens(&self) -> impl Iterator<Item = &Token> {
|
||||||
self.branches.keys()
|
self.branches.keys()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn possible_commands(&self, max_depth: usize) -> impl Iterator<Item = &Command> {
|
pub fn possible_commands(&self, max_depth: usize) -> impl Iterator<Item = &Command> {
|
||||||
// dusk: i am too lazy to write an iterator for this without using recursion so we box everything
|
// dusk: i am too lazy to write an iterator for this without using recursion so we box everything
|
||||||
fn box_iter<'a>(
|
fn box_iter<'a>(
|
||||||
iter: impl Iterator<Item = &'a Command> + 'a,
|
iter: impl Iterator<Item = &'a Command> + 'a,
|
||||||
|
|
@ -69,7 +69,11 @@ impl TreeBranch {
|
||||||
commands
|
commands
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_branch(&self, token: &Token) -> Option<&TreeBranch> {
|
pub fn get_branch(&self, token: &Token) -> Option<&Self> {
|
||||||
self.branches.get(token)
|
self.branches.get(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn branches(&self) -> impl Iterator<Item = (&Token, &Self)> {
|
||||||
|
self.branches.iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,11 @@
|
||||||
name = "commands"
|
name = "commands"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
default-run = "commands"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "write_cs_glue"
|
||||||
|
path = "src/bin/write_cs_glue.rs"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
#![feature(iter_intersperse)]
|
#![feature(iter_intersperse)]
|
||||||
|
|
||||||
|
use command_parser::{token::Token, Tree};
|
||||||
|
use commands::COMMAND_TREE;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let cmd = std::env::args()
|
let cmd = std::env::args()
|
||||||
.skip(1)
|
.skip(1)
|
||||||
|
|
@ -18,3 +21,21 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_tree(tree: &Tree, depth: usize) {
|
||||||
|
println!();
|
||||||
|
for (token, branch) in tree.branches() {
|
||||||
|
for _ in 0..depth {
|
||||||
|
print!(" ");
|
||||||
|
}
|
||||||
|
for _ in 0..depth {
|
||||||
|
print!("-");
|
||||||
|
}
|
||||||
|
print!("> {token:?}");
|
||||||
|
if matches!(token, Token::Empty) {
|
||||||
|
println!(": {}", branch.command().unwrap().cb)
|
||||||
|
} else {
|
||||||
|
print_tree(branch, depth + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,8 @@
|
||||||
cp -f "$commandslib" obj/
|
cp -f "$commandslib" obj/
|
||||||
fi
|
fi
|
||||||
uniffi-bindgen-cs "$commandslib" --library --out-dir="''${2:-./PluralKit.Bot}"
|
uniffi-bindgen-cs "$commandslib" --library --out-dir="''${2:-./PluralKit.Bot}"
|
||||||
|
cargo run --package commands --bin write_cs_glue -- "''${2:-./PluralKit.Bot}"/commandtypes.cs
|
||||||
|
dotnet format ./PluralKit.Bot/PluralKit.Bot.csproj
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue