.env file support
introduced a macro to make management of program properties easier program properties are obtained in this order: environment, .env file, commandline arguments a configuration file will be included at a later date and will be loaded between .env and commandline args
This commit is contained in:
parent
3da1cfc1f5
commit
baa14a47e6
9 changed files with 305 additions and 295 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,7 +1,9 @@
|
|||
bot_token*
|
||||
.env
|
||||
|
||||
/target
|
||||
/.vscode
|
||||
/.rust-analyzer
|
||||
**/*.rs.bk
|
||||
*.pdb
|
||||
|
||||
|
|
39
Cargo.lock
generated
39
Cargo.lock
generated
|
@ -17,15 +17,6 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
|
@ -1032,7 +1023,6 @@ dependencies = [
|
|||
"mlua",
|
||||
"num_cpus",
|
||||
"rand 0.9.0",
|
||||
"regex",
|
||||
"serenity",
|
||||
"sqlite",
|
||||
"tokio",
|
||||
|
@ -1257,35 +1247,6 @@ dependencies = [
|
|||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.27"
|
||||
|
|
|
@ -17,11 +17,14 @@ serenity = "0.12.4"
|
|||
|
||||
num_cpus = "1.16.0"
|
||||
rand = "0.9.0"
|
||||
regex = "1.10.4"
|
||||
#regex = "1.10.4"
|
||||
sqlite = { version = "0.36.1", features = ["bundled"] }
|
||||
ctrlc = "3.4.5"
|
||||
|
||||
[features]
|
||||
dot_env_file = []
|
||||
|
||||
default = ["dot_env_file"]
|
||||
|
||||
# slated for removal, originally this engine was going to be a multipurpose and publicly available bot, however plans change and im limiting the scope
|
||||
nsfw_features = []
|
||||
|
|
|
@ -4,7 +4,9 @@ use std::collections::HashMap;
|
|||
pub struct ArgumentStorage(HashMap<String, String>);
|
||||
|
||||
impl ArgumentStorage {
|
||||
pub fn new() -> Self { Self(HashMap::new()) }
|
||||
pub fn new() -> Self {
|
||||
Self(HashMap::new())
|
||||
}
|
||||
|
||||
pub fn add_argument() {}
|
||||
}
|
||||
|
|
139
src/bot.rs
139
src/bot.rs
|
@ -17,6 +17,7 @@ use tracing::info;
|
|||
use tracing::warn;
|
||||
|
||||
use crate::PROPERTIES;
|
||||
use crate::commands;
|
||||
use crate::commands::COMMANDS;
|
||||
use crate::constants;
|
||||
use crate::tasks;
|
||||
|
@ -32,126 +33,6 @@ pub struct BotHandler;
|
|||
unsafe impl Sync for BotHandler {}
|
||||
unsafe impl Send for BotHandler {}
|
||||
|
||||
const ESCAPE_CHAR: char = '\\';
|
||||
|
||||
fn group(string: &dyn ToString) -> Vec<String> {
|
||||
let string = string.to_string(); // binding
|
||||
|
||||
let mut argument_results = vec![];
|
||||
|
||||
let mut quote_depth = 0;
|
||||
let mut double_quote_depth = 0;
|
||||
let mut bucket = String::new();
|
||||
//let mut bucket2 = String::new();
|
||||
|
||||
let mut chars = string.chars().peekable();
|
||||
while let Some(c) = chars.next() {
|
||||
//dbg!(c);
|
||||
|
||||
// skip current char and move next char into the bucket, doesnt matter what it is, its been escaped
|
||||
if c == ESCAPE_CHAR {
|
||||
if let Some(c) = chars.next() {
|
||||
bucket.push(c);
|
||||
} else {
|
||||
error!("Expected another character after escape char");
|
||||
bucket.push(ESCAPE_CHAR);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//dbg!(quote_depth);
|
||||
//dbg!(double_quote_depth);
|
||||
|
||||
if c == '\'' {
|
||||
// outside quotes
|
||||
if quote_depth % 2 == 0 {
|
||||
quote_depth += 1;
|
||||
} else {
|
||||
// inside quotes
|
||||
quote_depth -= 1;
|
||||
}
|
||||
// outside double quotes
|
||||
if double_quote_depth % 2 == 0 {
|
||||
continue; // continue to avoid adding this to the bucket
|
||||
}
|
||||
}
|
||||
|
||||
if c == '"' {
|
||||
// outside double quotes
|
||||
if double_quote_depth % 2 == 0 {
|
||||
double_quote_depth += 1;
|
||||
} else {
|
||||
// inside double quotes
|
||||
double_quote_depth -= 1;
|
||||
}
|
||||
// outside quotes
|
||||
if quote_depth % 2 == 0 {
|
||||
continue; // continue to avoid adding this to the bucket
|
||||
}
|
||||
}
|
||||
|
||||
//dbg!(&bucket);
|
||||
|
||||
// if in quotes of some kind, just add to the bucket, if not we must move the bucket to the result and empty the bucket
|
||||
if c == ' ' {
|
||||
if quote_depth == 0 && double_quote_depth == 0 {
|
||||
if !bucket.is_empty() {
|
||||
argument_results.push(bucket.clone());
|
||||
bucket.clear();
|
||||
}
|
||||
//dbg!(&argument_results);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
bucket.push(c);
|
||||
}
|
||||
|
||||
if !bucket.is_empty() {
|
||||
argument_results.push(bucket);
|
||||
}
|
||||
|
||||
argument_results
|
||||
}
|
||||
|
||||
fn bash(str_vec: Vec<String>) -> Vec<String> {
|
||||
let mut return_vec = vec![];
|
||||
|
||||
let mut str_vec = str_vec.iter();
|
||||
|
||||
//let mut enabled = true;
|
||||
while let Some(item) = str_vec.next() {
|
||||
// abandon parsing since a bare -- means end parsing
|
||||
if item.len() == 2 && &item[..2] == "--" {
|
||||
return_vec.push(item.to_string());
|
||||
while let Some(item) = str_vec.next() {
|
||||
return_vec.push(item.to_string());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
match (item.get(0..1), item.get(1..1)) {
|
||||
// long option
|
||||
(Some("-"), Some("-")) => {
|
||||
for (cidx, c) in item.char_indices() {
|
||||
if c == '=' {
|
||||
let (rhs, lhs) = item.split_at(cidx);
|
||||
dbg!(lhs);
|
||||
return_vec.push(rhs.to_string());
|
||||
}
|
||||
}
|
||||
},
|
||||
// short option
|
||||
(Some("-"), Some(s)) if s != "-" => {
|
||||
|
||||
},
|
||||
// not an option, just ignore it
|
||||
_ => return_vec.push(item.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
return_vec
|
||||
}
|
||||
|
||||
#[serenity::async_trait]
|
||||
impl EventHandler for BotHandler {
|
||||
|
@ -166,7 +47,7 @@ impl EventHandler for BotHandler {
|
|||
if msg.content.starts_with(constants::COMMAND_PREFIX) {
|
||||
offset = constants::COMMAND_PREFIX.len();
|
||||
} else if msg.content.starts_with(constants::CONSTANT_PREFIX) {
|
||||
offset = constants::COMMAND_PREFIX.len();
|
||||
offset = constants::CONSTANT_PREFIX.len();
|
||||
} else if msg.content.starts_with(format!("<@{}>", ctx.cache.current_user().id).as_str()) {
|
||||
// Magic numbers: +1 is for ilog10, +3 is for the <@> characters
|
||||
offset = ctx.cache.current_user().id.get().checked_ilog10().unwrap_or(0) as usize + 1 + 3;
|
||||
|
@ -174,6 +55,11 @@ impl EventHandler for BotHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
// whitespace should be handled by group()
|
||||
//if msg.content.chars().nth(offset).map_or(false, char::is_whitespace) {
|
||||
// offset += 1;
|
||||
//}
|
||||
|
||||
dbg!(&offset);
|
||||
|
||||
if DO_SHUTDOWN.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
|
@ -184,8 +70,8 @@ impl EventHandler for BotHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
let grouped = group(&&msg.content[offset..]);
|
||||
let bashed = bash(grouped);
|
||||
//let grouped = group(&&msg.content[offset..]);
|
||||
//let bashed = bash(grouped);
|
||||
|
||||
let target_cmd_name = dbg!(&msg.content[offset..]);
|
||||
|
||||
|
@ -194,10 +80,11 @@ impl EventHandler for BotHandler {
|
|||
// TODO fix this unwrap
|
||||
if let Some(command) = COMMANDS.read().unwrap().get(target_cmd_name) {
|
||||
match (msg.guild_id, &command.run_guild_command, &command.run_dm_command) {
|
||||
//(Some(gid), Some(commands::CommandFnKind::Lua(())), _) => todo!(),
|
||||
(Some(gid), Some(commands::CommandFnKind::Rust(gcmd)), _) => (gcmd)(ctx, msg, Some(gid)),
|
||||
//(None, _, Some(commands::CommandFnKind::Lua(()))) => todo!(),
|
||||
(None, _, Some(commands::CommandFnKind::Rust(dcmd))) => (dcmd)(ctx, msg, None),
|
||||
|
||||
(Some(gid), Some(commands::CommandFnKind::Lua(gcmd)), _) => todo!(),
|
||||
(None, _, Some(commands::CommandFnKind::Lua(dcmd))) => todo!(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -233,7 +120,7 @@ pub fn start() {
|
|||
|
||||
// Load this at runtime so the key will not be stored in the binary? at the very least we could disguise the string as garbage data so it cannot be easily extracted via static analysis, it seems like the bot library only uses references as well, so it should not be stored plaintext in memory either
|
||||
// Will need a protected storage medium for the token, if we get to that point the user will need to suggest something, storing the token is not my responsibility, databases might be a good alternative?
|
||||
const TOKEN: &'static str = if cfg!(debug_assertions) {
|
||||
const TOKEN: &str = if cfg!(debug_assertions) {
|
||||
include_str!("bot_token.dev")
|
||||
} else {
|
||||
include_str!("bot_token.prod")
|
||||
|
|
|
@ -5,8 +5,8 @@ use sqlite::Connection;
|
|||
use tracing::error;
|
||||
use tracing::info;
|
||||
|
||||
use crate::errors::DatabaseStoredError;
|
||||
use crate::PROPERTIES;
|
||||
use crate::errors::DatabaseStoredError;
|
||||
|
||||
use super::DatabaseAccessor;
|
||||
|
||||
|
|
|
@ -205,11 +205,11 @@ fn set_logging(lua: &Lua, globals: &Table) {
|
|||
}
|
||||
|
||||
mod rust_lua_functions {
|
||||
use mlua::prelude::LuaResult;
|
||||
use mlua::ExternalError;
|
||||
use mlua::Lua;
|
||||
use mlua::Value;
|
||||
use mlua::Variadic;
|
||||
use mlua::prelude::LuaResult;
|
||||
use tracing::info;
|
||||
|
||||
use crate::lua::value_to_string;
|
||||
|
|
404
src/main.rs
404
src/main.rs
|
@ -29,27 +29,7 @@ use constants::HELP_STRING;
|
|||
|
||||
use databases::DBKind;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProgramProperties {
|
||||
// TODO bool_bucket: u8,
|
||||
pub print_help: bool,
|
||||
pub nodiscord: bool,
|
||||
pub dbuser: Option<String>,
|
||||
pub dbpass: Option<String>,
|
||||
pub dbaddress: Option<String>,
|
||||
pub dbport: Option<String>,
|
||||
pub dbpath: Option<String>,
|
||||
pub dbkind: Option<String>,
|
||||
pub use_cli: bool,
|
||||
pub lua_engine_count: Option<String>,
|
||||
pub log_level: Option<LevelFilter>,
|
||||
pub connect_remote: bool,
|
||||
pub remote_address: Option<String>,
|
||||
pub remote_port: Option<String>,
|
||||
pub remote_key: Option<String>,
|
||||
pub remote_key_type: Option<String>,
|
||||
}
|
||||
|
||||
// TODO remove this from static memory
|
||||
/// In most cases this would just be a once lock or similar
|
||||
/// however i dont like needing to unwrap it everywhere in the program after i know for a fact it was already initialized
|
||||
/// at the start of execution and will never be written to again
|
||||
|
@ -62,6 +42,272 @@ pub static PROPERTIES: LazyLock<ProgramProperties> = LazyLock::new(get_cmd_args)
|
|||
// TODO custom panic handler?
|
||||
pub static SHUTDOWN_SENDER: OnceLock<Sender<()>> = OnceLock::new();
|
||||
|
||||
fn string_to_bool(str: String) -> bool {
|
||||
str.to_lowercase().trim() == "true"
|
||||
}
|
||||
fn string_to_log_level(str: String) -> Option<LevelFilter> {
|
||||
match str.to_lowercase().trim() {
|
||||
"trace" => Some(tracing_subscriber::filter::LevelFilter::TRACE),
|
||||
"debug" => Some(tracing_subscriber::filter::LevelFilter::DEBUG),
|
||||
"info" => Some(tracing_subscriber::filter::LevelFilter::INFO),
|
||||
"warn" => Some(tracing_subscriber::filter::LevelFilter::WARN),
|
||||
"error" => Some(tracing_subscriber::filter::LevelFilter::ERROR),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### This macro has a "header" of sorts which is used to configure the macro
|
||||
/// ---
|
||||
/// This is the first line of an example usage
|
||||
/// ```
|
||||
/// #[derive(Debug, Clone)] pub struct ProgramProperties;
|
||||
/// ```
|
||||
/// `#[]` is required and you may put `,` seperated attributes inside
|
||||
///
|
||||
/// `pub` is omittable but will be the visibility of the generated struct
|
||||
///
|
||||
/// `struct` is required for readability sake
|
||||
///
|
||||
/// `ProgramProperties` is any identifier you want and will be used to name the generated structure
|
||||
///
|
||||
/// ---
|
||||
/// The second line is identical in all ways to the first line, however instead of creating a struct we create a function
|
||||
/// ```
|
||||
/// #[] pub fn get_cmd_args;
|
||||
/// ```
|
||||
///
|
||||
/// ---
|
||||
/// These two lines are used to name variables used later in the macro, the selected names being `item` and `arglist`
|
||||
/// ```
|
||||
/// envfixer item;
|
||||
/// argumentfixer arglist;
|
||||
/// ```
|
||||
///
|
||||
/// ---
|
||||
/// Just like with the fn and struct you may change the visibility before the items name
|
||||
///
|
||||
/// The type within the structure is defined afterwards just like a normal struct
|
||||
///
|
||||
/// However the next item must be `,` and afterwards we shift to assigning data
|
||||
///
|
||||
/// `optnames`: is an 'array' of &str values which will be transparently used in a match statement when reading `env::args`
|
||||
///
|
||||
/// `envfixer`: is a 'datafixer' used to map the data to the correct type, optionally using the variable name defined in the header
|
||||
///
|
||||
/// `argumentfixer`: is identical to `envfixer` but used for the `env::args` input, in this case all we want is whether the option is present or not
|
||||
/// ```
|
||||
/// pub print_help: bool,
|
||||
/// optnames: ["h", "help","-h", "--help", "-help","/h", "/help","/?", "?"],
|
||||
/// envfixer: item.is_some_and(|x| x.to_lowercase().trim() == "true"),
|
||||
/// argumentfixer: true;
|
||||
/// pub nodiscord: bool,
|
||||
/// optnames: [],
|
||||
/// envfixer: item.is_some_and(string_to_bool),
|
||||
/// argumentfixer: true;
|
||||
/// pub dbuser : Option<String>,
|
||||
/// optnames: [],
|
||||
/// envfixer: item,
|
||||
/// argumentfixer: arglist.next();
|
||||
/// pub dbpass : Option<String>,
|
||||
/// optnames: [],
|
||||
/// envfixer: item,
|
||||
/// argumentfixer: arglist.next();
|
||||
/// pub dbaddress : Option<String>,
|
||||
/// optnames: [],
|
||||
/// envfixer: item,
|
||||
/// argumentfixer: arglist.next();
|
||||
/// pub dbport: Option<String>,
|
||||
/// optnames: [],
|
||||
/// envfixer: item,
|
||||
/// argumentfixer: arglist.next();
|
||||
/// pub dbpath: Option<String>,
|
||||
/// optnames: [],
|
||||
/// envfixer: item,
|
||||
/// argumentfixer: arglist.next();
|
||||
/// pub dbkind: Option<String>,
|
||||
/// optnames: [],
|
||||
/// envfixer: item,
|
||||
/// argumentfixer: arglist.next();
|
||||
/// ```
|
||||
///
|
||||
/// This macro is licensed under [The Unlicense](https://spdx.org/licenses/Unlicense.html)
|
||||
///
|
||||
macro_rules! program_properties {
|
||||
{
|
||||
#[$($sattr:meta),*] $svis:vis struct $sident:ident;
|
||||
#[$($fattr:meta),*] $fvis:vis fn $fident:ident;
|
||||
envfixer $vname:ident;
|
||||
argumentfixer $vargsname:ident;
|
||||
$($itemvis:vis $itemident:ident: $itemtype:ty, optnames: [$($options:literal),*], envfixer: $envfixer:expr, argumentfixer: $argumentfixer:expr;)+
|
||||
} => {
|
||||
$(#[$sattr])* $svis struct $sident {
|
||||
$($itemvis $itemident: $itemtype,)+
|
||||
}
|
||||
|
||||
$(#[$fattr])* $fvis fn $fident() -> $sident {
|
||||
info!("Initializing with env variables.");
|
||||
let mut resval = $sident {
|
||||
$($itemident: {
|
||||
#[allow(unused)]
|
||||
let $vname = std::env::var(stringify!($itemident)).ok();
|
||||
$envfixer
|
||||
},)+
|
||||
};
|
||||
|
||||
#[cfg(feature = "dot_env_file")] {
|
||||
info!("Grabbing .env file input.");
|
||||
|
||||
if let Ok(cdir) = std::env::current_dir() {
|
||||
if let Ok(string) = std::fs::read_to_string(cdir.join(".env")) {
|
||||
let mut strbuf = vec![];
|
||||
let mut bucket = String::new();
|
||||
for c in string.chars() {
|
||||
if c == '\n' {
|
||||
strbuf.push(bucket);
|
||||
bucket = String::new();
|
||||
} else {
|
||||
bucket.push(c);
|
||||
}
|
||||
}
|
||||
if !bucket.trim().is_empty() {
|
||||
strbuf.push(bucket);
|
||||
}
|
||||
|
||||
let mut eqvec = vec![];
|
||||
for s in &strbuf {
|
||||
if let Some(eq) = s.split_once('=') {
|
||||
eqvec.push(eq);
|
||||
} else {
|
||||
warn!("Invalid value in .env file! {s}");
|
||||
}
|
||||
}
|
||||
|
||||
for (lhs,rhs) in eqvec {
|
||||
match lhs.trim() {
|
||||
$(stringify!($itemident) => {
|
||||
#[allow(unused)]
|
||||
let $vname = Some(rhs.to_string());
|
||||
resval.$itemident = $envfixer;
|
||||
},)+
|
||||
value => {
|
||||
warn!("Unknown or misplaced value: {value}={rhs}"); // TODO make custom tracing message formatter that moves newlines forward to match the message offset from header info
|
||||
warn!("Use argument help for more information.");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
info!("Grabbing commandline options.");
|
||||
let mut args = std::env::args().peekable();
|
||||
|
||||
// The first arg is the path to the executable on all systems that i know of
|
||||
// But this is not always guaranteed, however this program does not expect file paths as the first element so we can safely skip it if the path exists
|
||||
// We could check this to see if it matches our environment executable path but that needs too much error handling for something like this
|
||||
if let Some(argpath) = args.peek() {
|
||||
if std::path::PathBuf::from(argpath).exists() {
|
||||
args.next();
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(item) = args.next() {
|
||||
match item.trim() {
|
||||
$($($options)|* | stringify!($itemident) => {
|
||||
#[allow(unused)]
|
||||
let $vargsname = &mut args;
|
||||
resval.$itemident = $argumentfixer;
|
||||
},)+
|
||||
value => {
|
||||
warn!("Unknown or misplaced value: {value}"); // TODO make custom tracing message formatter that moves newlines forward to match the message offset from header info
|
||||
warn!("Use argument help for more information.");
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
resval
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// TODO basic type/validity/enum checking via helper functions
|
||||
program_properties! {
|
||||
|
||||
#[derive(Debug, Clone)] pub struct ProgramProperties;
|
||||
#[] pub fn get_cmd_args;
|
||||
envfixer item;
|
||||
argumentfixer arglist;
|
||||
|
||||
pub print_help: bool,
|
||||
optnames: ["h", "help","-h", "--help", "-help","/h", "/help","/?", "?"],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub nodiscord: bool,
|
||||
optnames: [],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub dbuser : Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbpass : Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbaddress : Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbport: Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbpath: Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub dbkind: Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub use_cli: bool,
|
||||
optnames: [],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub lua_engine_count: Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub log_level: Option<LevelFilter>,
|
||||
optnames: [],
|
||||
envfixer: string_to_log_level(item.unwrap_or_default()),
|
||||
argumentfixer: string_to_log_level(arglist.next().unwrap_or_default());
|
||||
pub connect_remote: bool,
|
||||
optnames: [],
|
||||
envfixer: item.is_some_and(string_to_bool),
|
||||
argumentfixer: true;
|
||||
pub remote_address: Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub remote_port: Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub remote_key: Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
pub remote_key_type: Option<String>,
|
||||
optnames: [],
|
||||
envfixer: item,
|
||||
argumentfixer: arglist.next();
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn shutdown_handler() {
|
||||
static SHUTDOWN_COUNT: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(1);
|
||||
|
@ -76,112 +322,21 @@ pub fn shutdown_handler() {
|
|||
debug!("Shutdown requested!");
|
||||
}
|
||||
|
||||
pub fn get_cmd_args() -> ProgramProperties {
|
||||
info!("Grabbing commandline input.");
|
||||
|
||||
fn string_to_bool(str: String) -> bool {
|
||||
str.to_lowercase().trim() == "true"
|
||||
}
|
||||
|
||||
// TODO macro this? could simplify the amount of str definitions and repetitiveness
|
||||
let mut print_help = std::env::var("print_help").map(string_to_bool).unwrap_or(false);
|
||||
let mut nodiscord = std::env::var("nodiscord").map(string_to_bool).unwrap_or(false);
|
||||
let mut dbuser = std::env::var("dbuser").map_or(None, |val| Some(val));
|
||||
let mut dbpass = std::env::var("dbpass").map_or(None, |val| Some(val));
|
||||
let mut dbaddress = std::env::var("dbaddress").map_or(None, |val| Some(val));
|
||||
let mut dbport = std::env::var("dbport").map_or(None, |val| Some(val));
|
||||
let mut dbpath = std::env::var("dbpath").map_or(None, |val| Some(val));
|
||||
let mut dbkind = std::env::var("dbkind").map_or(None, |val| Some(val));
|
||||
let mut use_cli = std::env::var("use_cli").map(string_to_bool).unwrap_or(false);
|
||||
let mut lua_engine_count = std::env::var("lua_engine_count").map_or(None, |val| Some(val));
|
||||
let mut log_level = std::env::var("log_level").map_or(None, |val| Some(val));
|
||||
let mut connect_remote = std::env::var("connect_remote").map(string_to_bool).unwrap_or(false);
|
||||
let mut remote_address = std::env::var("remote_address").map_or(None, |val| Some(val));
|
||||
let mut remote_port = std::env::var("remote_port").map_or(None, |val| Some(val));
|
||||
let mut remote_key = std::env::var("remote_key").map_or(None, |val| Some(val));
|
||||
let mut remote_key_type = std::env::var("remote_key_type").map_or(None, |val| Some(val));
|
||||
|
||||
let mut args = std::env::args().peekable();
|
||||
|
||||
// The first arg is the path to the executable on all systems that i know of
|
||||
// But this is not always guaranteed, however this program does not expect file paths as the first element so we can safely skip it if the path exists
|
||||
// We could check this to see if it matches our environment executable path but that needs too much error handling for something like this
|
||||
if let Some(argpath) = args.peek() {
|
||||
if std::path::PathBuf::from(argpath).exists() {
|
||||
args.next();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO basic type/validity/enum checking via helper functions
|
||||
while let Some(item) = args.next() {
|
||||
match item.to_lowercase().trim() {
|
||||
"help" | "-h" | "--help" | "-help" | "/help" | "/h" | "?" | "/?" => print_help = true,
|
||||
"nodiscord" => nodiscord = true,
|
||||
"dbuser" => dbuser = args.next(),
|
||||
"dbpass" => dbpass = args.next(),
|
||||
"dbaddress" => dbaddress = args.next(),
|
||||
"dbport" => dbport = args.next(),
|
||||
"dbpath" => dbpath = args.next(),
|
||||
"dbkind" => dbkind = args.next(),
|
||||
"use_cli" => use_cli = true,
|
||||
"lua_engine_count" => lua_engine_count = args.next(),
|
||||
"log_level" => log_level = args.next(),
|
||||
"connect_remote" => connect_remote = true,
|
||||
"remote_address" => remote_address = args.next(),
|
||||
"remote_port" => remote_port = args.next(),
|
||||
"remote_key" => remote_key = args.next(),
|
||||
"remote_key_type" => remote_key_type = args.next(),
|
||||
value => {
|
||||
warn!("Unknown or misplaced value: {value}"); // TODO make custom tracing message formatter that moves newlines forward to match the message offset from header info
|
||||
warn!("Use argument help for more information.")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let log_level = match log_level.unwrap_or_default().to_lowercase().trim() {
|
||||
"trace" => Some(tracing_subscriber::filter::LevelFilter::TRACE),
|
||||
"debug" => Some(tracing_subscriber::filter::LevelFilter::DEBUG),
|
||||
"info" => Some(tracing_subscriber::filter::LevelFilter::INFO),
|
||||
"warn" => Some(tracing_subscriber::filter::LevelFilter::WARN),
|
||||
"error" => Some(tracing_subscriber::filter::LevelFilter::ERROR),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
ProgramProperties {
|
||||
print_help,
|
||||
nodiscord,
|
||||
dbuser,
|
||||
dbpass,
|
||||
dbaddress,
|
||||
dbport,
|
||||
dbpath,
|
||||
dbkind,
|
||||
use_cli,
|
||||
lua_engine_count,
|
||||
log_level,
|
||||
connect_remote,
|
||||
remote_address,
|
||||
remote_port,
|
||||
remote_key,
|
||||
remote_key_type,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO remove async from the main function and restrict its usage to just the discord bot
|
||||
// We can manually manage worker threads everywhere else, especially with the lua engines
|
||||
//#[tokio::main]
|
||||
fn main() {
|
||||
// why do we need to set global default again?
|
||||
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
let (level_filter, level_filter_reload_handle) =
|
||||
tracing_subscriber::reload::Layer::new(if cfg!(debug_assertions) { LevelFilter::DEBUG } else { LevelFilter::INFO });
|
||||
tracing_subscriber::registry()
|
||||
.with(level_filter)
|
||||
.with(
|
||||
tracing_subscriber::fmt::Layer::default()
|
||||
{
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
tracing_subscriber::registry()
|
||||
.with(level_filter)
|
||||
.with(
|
||||
tracing_subscriber::fmt::Layer::default()
|
||||
.compact()
|
||||
.with_level(true)
|
||||
.with_file(cfg!(debug_assertions))
|
||||
|
@ -189,15 +344,16 @@ fn main() {
|
|||
// TODO make configurable
|
||||
.with_timer(tracing_subscriber::fmt::time::uptime())
|
||||
// TODO detect ansi capabilities do something to allow colors if none supported, if fails just disable colors
|
||||
.with_ansi(true)
|
||||
.with_ansi(true)
|
||||
// TODO allow in release?
|
||||
.with_thread_names(cfg!(debug_assertions)),
|
||||
)
|
||||
.init();
|
||||
)
|
||||
.init();
|
||||
}
|
||||
|
||||
// Manually init the lazy lock
|
||||
// Manually init the lazy lock here rather than later for reliability sake
|
||||
LazyLock::force(&PROPERTIES);
|
||||
//debug!("{:?}", *PROPERTIES);
|
||||
debug!("{:?}", *PROPERTIES);
|
||||
|
||||
if let Some(val) = PROPERTIES.log_level {
|
||||
level_filter_reload_handle.modify(|filter| *filter = val).unwrap();
|
||||
|
|
|
@ -60,9 +60,8 @@ async fn forum_checker() {
|
|||
let mut interval = tokio::time::interval(Duration::from_millis(16));
|
||||
|
||||
loop {
|
||||
|
||||
//check_forum().await;
|
||||
|
||||
|
||||
interval.tick().await;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue