feat(commands): implement Display traits for Token and Command to have some basic 'doc gen', split Toggle into Enable and Disable

This commit is contained in:
dusk 2025-01-08 18:31:59 +09:00
parent 482c923507
commit 4f7e9c22a1
No known key found for this signature in database
9 changed files with 187 additions and 56 deletions

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
crate-type = ["cdylib", "lib"]
[dependencies]
lazy_static = { workspace = true }

View file

@ -18,6 +18,8 @@ pub mod server_config;
pub mod switch;
pub mod system;
use std::fmt::Display;
use smol_str::SmolStr;
use crate::{
@ -47,6 +49,18 @@ impl Command {
}
}
impl Display for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (idx, token) in self.tokens.iter().enumerate() {
write!(f, "{}", token)?;
if idx < self.tokens.len() - 1 {
write!(f, " ")?;
}
}
write!(f, " - {}", self.help)
}
}
#[macro_export]
macro_rules! command {
([$($v:expr),+], $cb:expr, $help:expr) => {

View file

@ -23,7 +23,12 @@ pub fn cmds() -> impl Iterator<Item = Command> {
"Shows the autoproxy timeout"
),
command!(
[cfg, autoproxy, ["timeout", "tm"], [Toggle("toggle"), Reset("reset"), FullString("timeout")]],
[
cfg,
autoproxy,
["timeout", "tm"],
[Disable("toggle"), Reset("reset"), FullString("timeout")]
],
"cfg_ap_timeout_update",
"Sets the autoproxy timeout"
),

View file

@ -3,21 +3,9 @@ use super::*;
pub fn cmds() -> impl Iterator<Item = Command> {
let help = ["help", "h"];
[
command!(
[help],
"help",
"Shows the help command"
),
command!(
[help, "commands"],
"help_commands",
"help commands"
),
command!(
[help, "proxy"],
"help_proxy",
"help proxy"
),
command!([help], "help", "Shows the help command"),
command!([help, "commands"], "help_commands", "help commands"),
command!([help, "proxy"], "help_proxy", "help proxy"),
]
.into_iter()
}

View file

@ -30,7 +30,12 @@ pub fn cmds() -> impl Iterator<Item = Command> {
"Shows a member's description"
),
command!(
[member, MemberRef("target"), description, FullString("description")],
[
member,
MemberRef("target"),
description,
FullString("description")
],
"member_desc_update",
"Changes a member's description"
),

View file

@ -40,9 +40,9 @@ pub enum CommandResult {
pub enum Parameter {
MemberRef { member: String },
SystemRef { system: String },
MemberPrivacyTarget { target: String },
PrivacyLevel { level: String },
OpaqueString { raw: String },
MemberPrivacyTarget { target: String },
PrivacyLevel { level: String },
OpaqueString { raw: String },
Toggle { toggle: bool },
Reset,
}
@ -109,6 +109,7 @@ fn parse_command(input: String) -> CommandResult {
},
};
}
// todo: check if last token is a common incorrect unquote (multi-member names etc)
// todo: check if this is a system name in pk;s command
return CommandResult::Err {
@ -161,8 +162,14 @@ fn next_token(
// for FullString just send the whole string
let input_to_match = param.clone().map(|v| v.0);
match token.try_match(input_to_match) {
TokenMatchResult::Match(value) => return Ok((token, value, param.map(|v| v.1).unwrap_or(current_pos))),
TokenMatchResult::MissingParameter { name } => return Err(Some(format_smolstr!("Missing parameter `{name}` in command `{input} [{name}]`."))),
TokenMatchResult::Match(value) => {
return Ok((token, value, param.map(|v| v.1).unwrap_or(current_pos)))
}
TokenMatchResult::MissingParameter { name } => {
return Err(Some(format_smolstr!(
"Missing parameter `{name}` in command `{input} [{name}]`."
)))
}
TokenMatchResult::NoMatch => {}
}
}

View file

@ -0,0 +1,7 @@
use commands::commands as cmds;
fn main() {
for command in cmds::all() {
println!("{}", command);
}
}

View file

@ -1,4 +1,4 @@
use std::str::FromStr;
use std::{fmt::Display, ops::Not, str::FromStr};
use smol_str::{SmolStr, ToSmolStr};
@ -33,6 +33,8 @@ pub enum Token {
PrivacyLevel(ParamName),
/// on, off; yes, no; true, false
Enable(ParamName),
Disable(ParamName),
Toggle(ParamName),
/// reset, clear, default
@ -100,11 +102,15 @@ impl Token {
| Self::SystemRef(param_name)
| Self::PrivacyLevel(param_name)
| Self::Toggle(param_name)
| Self::Enable(param_name)
| Self::Disable(param_name)
| Self::Reset(param_name) => MissingParameter { name: param_name },
Self::Any(tokens) => tokens.is_empty().then_some(NoMatch).unwrap_or_else(|| {
let mut results = tokens.iter().map(|t| t.try_match(None));
results.find(|r| !matches!(r, NoMatch)).unwrap_or(NoMatch)
}),
Self::Any(tokens) => {
tokens.is_empty().then_some(NoMatch).unwrap_or_else(|| {
let mut results = tokens.iter().map(|t| t.try_match(None));
results.find(|r| !matches!(r, NoMatch)).unwrap_or(NoMatch)
})
}
// everything else doesnt match if no input anyway
Token::Value(_) => NoMatch,
Token::Flag => NoMatch,
@ -167,12 +173,30 @@ impl Token {
),
Err(_) => NoMatch,
},
Self::Toggle(param_name) => match Toggle::from_str(input) {
Self::Toggle(param_name) => match Enable::from_str(input)
.map(Into::<bool>::into)
.or_else(|_| Disable::from_str(input).map(Into::<bool>::into))
{
Ok(toggle) => TokenMatchResult::new_match_param(
input,
param_name,
Parameter::Toggle { toggle },
),
Err(_) => NoMatch,
},
Self::Enable(param_name) => match Enable::from_str(input) {
Ok(t) => TokenMatchResult::new_match_param(
input,
param_name,
Parameter::Toggle { toggle: t.0 },
Parameter::Toggle { toggle: t.into() },
),
Err(_) => NoMatch,
},
Self::Disable(param_name) => match Disable::from_str(input) {
Ok(t) => TokenMatchResult::new_match_param(
input,
param_name,
Parameter::Toggle { toggle: t.into() },
),
Err(_) => NoMatch,
},
@ -185,6 +209,37 @@ impl Token {
}
}
impl Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
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(_) => Ok(()), // if value token has no values (lol), don't print anything
// todo: it might not be the best idea to directly use param name here (what if we want to display something else but keep the name? or translations?)
Token::FullString(param_name) => write!(f, "[{}]", param_name),
Token::MemberRef(param_name) => write!(f, "<{}>", param_name),
Token::SystemRef(param_name) => write!(f, "<{}>", param_name),
Token::MemberPrivacyTarget(param_name) => write!(f, "[{}]", param_name),
Token::PrivacyLevel(param_name) => write!(f, "[{}]", param_name),
Token::Enable(_) => write!(f, "on"),
Token::Disable(_) => write!(f, "off"),
Token::Toggle(_) => write!(f, "on/off"),
Token::Reset(_) => write!(f, "reset"),
Token::Flag => unreachable!("flag tokens should never be in command definitions"),
}
}
}
/// Convenience trait to convert types into [`Token`]s.
pub trait ToToken {
fn to_token(&self) -> Token;
@ -218,7 +273,13 @@ impl ToToken for [Token] {
pub enum MemberPrivacyTarget {
Visibility,
Name,
// todo
Description,
Banner,
Avatar,
Birthday,
Pronouns,
Proxy,
Metadata,
}
impl AsRef<str> for MemberPrivacyTarget {
@ -226,6 +287,13 @@ impl AsRef<str> for MemberPrivacyTarget {
match self {
Self::Visibility => "visibility",
Self::Name => "name",
Self::Description => "description",
Self::Banner => "banner",
Self::Avatar => "avatar",
Self::Birthday => "birthday",
Self::Pronouns => "pronouns",
Self::Proxy => "proxy",
Self::Metadata => "metadata",
}
}
}
@ -235,9 +303,16 @@ impl FromStr for MemberPrivacyTarget {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
match s.to_lowercase().as_str() {
"visibility" => Ok(Self::Visibility),
"name" => Ok(Self::Name),
"description" => Ok(Self::Description),
"banner" => Ok(Self::Banner),
"avatar" => Ok(Self::Avatar),
"birthday" => Ok(Self::Birthday),
"pronouns" => Ok(Self::Pronouns),
"proxy" => Ok(Self::Proxy),
"metadata" => Ok(Self::Metadata),
_ => Err(()),
}
}
@ -270,28 +345,6 @@ impl FromStr for PrivacyLevel {
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct Toggle(bool);
impl AsRef<str> for Toggle {
fn as_ref(&self) -> &str {
// on / off better than others for docs and stuff?
self.0.then_some("on").unwrap_or("off")
}
}
impl FromStr for Toggle {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"on" | "yes" | "true" | "enable" | "enabled" => Ok(Self(true)),
"off" | "no" | "false" | "disable" | "disabled" => Ok(Self(false)),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct Reset;
@ -311,3 +364,55 @@ impl FromStr for Reset {
}
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct Enable;
impl AsRef<str> for Enable {
fn as_ref(&self) -> &str {
"on"
}
}
impl FromStr for Enable {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"on" | "yes" | "true" | "enable" | "enabled" => Ok(Self),
_ => Err(()),
}
}
}
impl Into<bool> for Enable {
fn into(self) -> bool {
true
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct Disable;
impl AsRef<str> for Disable {
fn as_ref(&self) -> &str {
"off"
}
}
impl FromStr for Disable {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"off" | "no" | "false" | "disable" | "disabled" => Ok(Self),
_ => Err(()),
}
}
}
impl Into<bool> for Disable {
fn into(self) -> bool {
false
}
}

View file

@ -90,7 +90,7 @@
set -x
commandslib="''${1:-}"
if [ "$commandslib" == "" ]; then
cargo -Z unstable-options build --package commands --release --artifact-dir obj/
cargo -Z unstable-options build --package commands --lib --release --artifact-dir obj/
commandslib="obj/libcommands.so"
else
cp -f "$commandslib" obj/