laying the groundwork for the new argument system

cleaning up the source tree
work towards dynamic loading of command plugins
This commit is contained in:
deepCurse 2024-08-04 21:46:42 -03:00
parent f878530e44
commit 1afa975c6a
Signed by: u1
GPG key ID: AD770D25A908AFF4
12 changed files with 276 additions and 76 deletions

41
Cargo.lock generated
View file

@ -645,6 +645,8 @@ name = "info"
version = "0.1.0"
dependencies = [
"nopalmo",
"tokio",
"tracing",
]
[[package]]
@ -680,6 +682,16 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libloading"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets 0.52.5",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
@ -750,19 +762,21 @@ dependencies = [
[[package]]
name = "mio"
version = "0.8.11"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
dependencies = [
"hermit-abi",
"libc",
"wasi",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
name = "nopalmo"
version = "0.1.0"
dependencies = [
"libloading",
"phf",
"rand",
"regex",
@ -786,16 +800,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.32.2"
@ -1491,28 +1495,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.37.0"
version = "1.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
version = "2.2.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",

View file

@ -16,6 +16,7 @@ path = "src/main.rs"
members = ["commands/info"]
[dependencies]
libloading = "0.8.5"
phf = { version = "0.11.2", features = ["phf_macros"] }
rand = "0.8.5"
regex = "1.10.4"

View file

@ -2,9 +2,9 @@
name = "info"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["rlib"]
crate-type = ["cdylib"]
[dependencies]
tokio = { version = "1.39.2", features = ["full"] }
tracing = "0.1.40"
nopalmo = { path = "../../" }

View file

@ -0,0 +1,43 @@
use nopalmo_lib::PluginError;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tokio::task;
type AsyncFn = Box<dyn Fn() -> task::JoinHandle<Result<(), PluginError>> + Send + Sync>;
static FUNCTION_MAP: ::std::sync::OnceLock<Arc<Mutex<HashMap<String, AsyncFn>>>> =
::std::sync::OnceLock::new();
#[allow(improper_ctypes_definitions)]
#[no_mangle]
pub extern "C" fn register_functions(function_map: Arc<Mutex<HashMap<String, AsyncFn>>>) {
if let Err(_) = FUNCTION_MAP.set(Arc::clone(&function_map)) {
tracing::error!("Could not set function map oncelock in `info` plugin.");
return;
}
let example_function: AsyncFn = Box::new(|| {
task::spawn(async {
tracing::info!("example_function is running");
// Simulate some async work
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
tracing::info!("example_function completed");
// Return an error for demonstration
Err(PluginError::FunctionError(
"Something went wrong".to_string(),
))
})
});
function_map
.lock()
.unwrap()
.insert("example_function".to_string(), example_function);
}
#[no_mangle]
pub extern "C" fn unregister_functions() {
if let Some(function_map) = FUNCTION_MAP.get() {
function_map.lock().unwrap().remove("example_function");
}
}

View file

@ -1 +1,3 @@
pub const GLOBAL_PREFIX: char = ';';
pub const COMMAND_PREFIX: &'static str = ";";
pub const SHORT_ARGUMENT_PREFIX: &'static str = "-";
pub const LONG_ARGUMENT_PREFIX: &'static str = "--";

109
src/lib/arguments.rs Normal file
View file

@ -0,0 +1,109 @@
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ArgumentStorage {
arguments: Vec<Argument>,
long_key_to_id: HashMap<String, usize>,
short_key_to_id: HashMap<String, usize>,
}
impl ArgumentStorage {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, long: Option<String>, short: Option<String>, argument: Argument) {
let mut index = None;
for i in 0..self.arguments.len() {
if self.arguments.get(i).is_none() {
index = Some(i);
}
}
if let Some(index) = index {
self.arguments[index] = argument;
if let Some(long) = long {
self.long_key_to_id.insert(long, index);
}
if let Some(short) = short {
self.long_key_to_id.insert(short, index);
}
}
}
pub fn remove(&mut self, long: Option<String>, short: Option<String>) {
match (long, short) {
(None, None) => todo!(),
(None, Some(short_key)) => {
let short_index = *self.short_key_to_id.get(&short_key).unwrap();
self.short_key_to_id.remove(&short_key);
let mut long_to_remove = None;
for (long_key, long_index) in &self.long_key_to_id {
// hate cloning here but i couldnt figure out how to avoid it
if short_index == *long_index {
long_to_remove = Some(long_key.clone());
}
}
if let Some(long_to_remove) = long_to_remove {
self.long_key_to_id.remove(&long_to_remove);
}
}
(Some(long_key), None) => {
let long_index = *self.long_key_to_id.get(&long_key).unwrap();
self.long_key_to_id.remove(&long_key);
let mut short_to_remove = None;
for (short_key, short_index) in &self.short_key_to_id {
// hate cloning here but i couldnt figure out how to avoid it
if long_index == *short_index {
short_to_remove = Some(short_key.clone());
}
}
if let Some(short_to_remove) = short_to_remove {
self.short_key_to_id.remove(&short_to_remove);
}
}
(Some(long_key), Some(short_key)) => {
self.long_key_to_id.remove(&long_key);
self.short_key_to_id.remove(&short_key);
}
}
}
}
pub enum ArgumentKind {
Short,
Long,
WildCard,
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Argument {
pub pretty_name: &'static str,
// we will use usize as an argument id system, the arguments will be available inside the argument cache
// the bool is for if its required or not
pub sub_arguments: Vec<(ArgumentContainer, bool)>,
pub requires_prefix: bool,
pub long_name: Option<&'static str>, // change to vec?
pub short_name: Option<&'static str>, // change to vec?
// /// use 0 for any position, 1 or more for positionals
// pub position: usize,
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum ArgumentContainer {
Value(String),
Argument(usize)
}

View file

@ -1,3 +0,0 @@
pub struct Argument {
}

View file

@ -1,3 +0,0 @@
pub struct DiscordPermission {
}

View file

@ -1,54 +1,73 @@
use std::{fmt::Debug, time::Duration};
pub mod arguments;
pub mod discord_permissions;
use self::arguments::Argument;
use ::serenity::all::{Context, GuildId, Message};
use ::std::collections::HashMap;
use ::std::time::Duration;
use discord_permissions::DiscordPermission;
use arguments::ArgumentStorage;
use serenity::all::{Context, GuildId, Message};
#[cfg(feature = "premium_features")]
pub enum PremiumLevel {
Free,
Tier1,
Tier2,
Tier3,
Super,
#[derive(Debug)]
pub enum PluginError {
FunctionError(String),
Other(String),
}
pub enum CommandType {
General,
Moderation,
Fun,
Info,
Extra,
Unknown,
impl core::fmt::Display for PluginError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PluginError::FunctionError(msg) => write!(f, "Function error: {}", msg),
PluginError::Other(msg) => write!(f, "Other error: {}", msg),
}
}
}
pub struct Command<DMC: Sync + Send, GMC: Sync + Send, CommandReturn: Send + Sync> {
impl std::error::Error for PluginError {}
pub struct Command<DMC, GMC, CommandReturn> {
pub run_dm_command: Box<dyn Fn(&DMC, Context, Message) -> CommandReturn>,
pub run_guild_command: Box<dyn Fn(&GMC, Context, Message, GuildId) -> CommandReturn>,
pub aliases: Vec<String>,
pub name: String,
pub command_type: CommandType,
pub command_category: String,
pub help: String,
pub usage: String,
pub timeout: Duration, // TODO make this dynamic?
pub arguments: HashMap<String, Argument>,
pub permissions: Vec<DiscordPermission>,
pub arguments: ArgumentStorage,
pub required_caller_discord_permissions: ::serenity::all::Permissions,
#[cfg(feature = "nsfw_features")]
pub is_nsfw: bool,
#[cfg(feature = "premium_features")]
pub premium_kind: PremiumLevel,
pub premium_kind: usize,
}
unsafe impl<DMC: Send + Sync, GMC: Send + Sync, CommandReturn: Send + Sync> Sync
for Command<DMC, GMC, CommandReturn>
{
}
unsafe impl<DMC: Send + Sync, GMC: Send + Sync, CommandReturn: Send + Sync> Send
for Command<DMC, GMC, CommandReturn>
{
unsafe impl<DMC, GMC, CommandReturn> Sync for Command<DMC, GMC, CommandReturn> {}
unsafe impl<DMC, GMC, CommandReturn> Send for Command<DMC, GMC, CommandReturn> {}
impl<DMC, GMC, CommandReturn> Debug for Command<DMC, GMC, CommandReturn> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut binding = f.debug_struct("Command");
binding
.field(
"run_dm_command",
&::std::any::type_name_of_val(&self.run_dm_command),
)
.field(
"run_guild_command",
&::std::any::type_name_of_val(&self.run_guild_command),
)
.field("aliases", &self.aliases)
.field("name", &self.name)
.field("command_type", &self.command_category)
.field("help", &self.help)
.field("usage", &self.usage)
.field("timeout", &self.timeout)
.field("arguments", &self.arguments)
.field("permissions", &self.required_caller_discord_permissions);
#[cfg(feature = "nsfw_features")]
binding.field("is_nsfw", &self.is_nsfw);
#[cfg(feature = "premium_features")]
binding.field("premium_kind", &self.premium_kind);
binding.finish()
}
}

View file

@ -1,25 +1,29 @@
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::pedantic)]
//#![feature(async_fn_traits)]
// mod commands;
// use nopalmo_lib;
mod constants;
mod permissions;
// mod permissions;
mod system_messages;
mod tasks;
use ::std::collections::hash_map::HashMap;
// use ::std::collections::hash_map::HashMap;
use std::error::Error;
use std::future::Future;
use std::pin::Pin;
// use std::error::Error;
// use std::future::Future;
// use std::pin::Pin;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::Duration;
// use std::sync::Arc;
// use std::time::Duration;
use nopalmo_lib::discord_permissions::DiscordPermission;
use nopalmo_lib::Command;
use serenity::all::GuildId;
// use discord_permissions::DiscordPermission;
// use nopalmo_lib::Command;
// use serenity::all::GuildId;
// use commands::Command;
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
@ -31,6 +35,34 @@ use serenity::prelude::*;
// pub type CommandReturn = Pin<Box<dyn Future<Output = ()>>>;
// pub type CommandReturn = ();
// pub mod discord_permissions;
// use self::arguments::Argument;
use ::serenity::all::Context;
// use discord_permissions::DiscordPermission;
// #[cfg(feature = "premium_features")]
// pub enum PremiumLevel {
// Free,
// Tier1,
// Tier2,
// Tier3,
// Super,
// }
// pub enum CommandType {
// General,
// Moderation,
// Fun,
// Info,
// Extra,
// Unknown,
// }
struct Handler {
// TODO use data field instead?
// system_sender: Mutex<mpsc::Sender<system_messages::SystemMessage>>,
@ -54,7 +86,7 @@ impl EventHandler for Handler {
async fn message(&self, ctx: Context, msg: Message) {
let prefix_regex = format!(
r"^({}|{}|<@{}>)\s?",
constants::GLOBAL_PREFIX,
constants::COMMAND_PREFIX,
"NPO_PFX",
ctx.cache.current_user().id
);

View file

@ -1,3 +0,0 @@
pub struct Permission {
}

View file

@ -18,7 +18,7 @@ pub async fn status_timer(
cache.user_count(),
cache.guild_count()
)),
ActivityData::watching(format!("for {}help", constants::GLOBAL_PREFIX)),
ActivityData::watching(format!("for {}help", constants::COMMAND_PREFIX)),
ActivityData::listening("Infected Mushroom"),
];