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:
parent
f878530e44
commit
1afa975c6a
12 changed files with 276 additions and 76 deletions
41
Cargo.lock
generated
41
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 = "../../" }
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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
109
src/lib/arguments.rs
Normal 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)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pub struct Argument {
|
||||
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pub struct DiscordPermission {
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
54
src/main.rs
54
src/main.rs
|
@ -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
|
||||
);
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
pub struct Permission {
|
||||
|
||||
}
|
|
@ -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"),
|
||||
];
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue