added initial structure and dependencies
This commit is contained in:
@@ -4,3 +4,11 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
dotenvy = "^0"
|
||||
ron = "^0"
|
||||
toml = "^1"
|
||||
serde_json = "^1"
|
||||
serde = { version = "^1", features = ["derive"] }
|
||||
|
||||
thiserror = "^2"
|
||||
tracing-subscriber = { version = "^0", features = ["env-filter"] }
|
||||
|
||||
99
src/app/config.rs
Normal file
99
src/app/config.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use std::{ env, path::PathBuf };
|
||||
use serde::{ Serialize, Deserialize };
|
||||
|
||||
use crate::app::parser::FromConfig;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct Config {
|
||||
pub defaults: Defaults,
|
||||
pub settings: Settings,
|
||||
pub rules: Rules,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl Config
|
||||
{
|
||||
pub fn load() -> Self { FromConfig::load() }
|
||||
}
|
||||
|
||||
impl FromConfig for Config
|
||||
{
|
||||
fn load() -> Self
|
||||
{
|
||||
let root = env::current_exe()
|
||||
.expect("Unable to locate executable.").parent()
|
||||
.expect("Unable to locate root directory.").to_owned();
|
||||
|
||||
// Removed expectation for dockerized-environments.
|
||||
dotenvy::dotenv()
|
||||
.map(|_| ())
|
||||
.or_else(|_| dotenvy::from_path(root.join(".env")))
|
||||
.ok(); //.expect("Unable to load .env file.");
|
||||
|
||||
let assets_directory: PathBuf = env::var("RNV_ASSETS_DIRECTORY")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.map(|p: PathBuf| root.join(p))
|
||||
.unwrap();
|
||||
|
||||
Self::deserialize_config(
|
||||
assets_directory.join("config"),
|
||||
&[
|
||||
("ron", Self::from_ron),
|
||||
("toml", Self::from_toml),
|
||||
("json", Self::from_json),
|
||||
],
|
||||
).expect("Failed to deserialize config.")
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct Defaults {
|
||||
pub songs_directory: String,
|
||||
pub generic_directory: String,
|
||||
|
||||
pub log_directory: String,
|
||||
pub temp_directory: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct Settings {
|
||||
pub debug: bool,
|
||||
pub weight: f64,
|
||||
pub volume: f64,
|
||||
pub crossfade: f64,
|
||||
pub weights: Weights,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct Weights {
|
||||
pub news: NewsWeight,
|
||||
pub genre: RecoveringWeight,
|
||||
pub composite: RecoveringWeight,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct NewsWeight {
|
||||
pub chance: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct RecoveringWeight {
|
||||
pub floor: f64,
|
||||
pub recover_after: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct Rules {
|
||||
pub shuffle_mode: ShuffleMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ShuffleMode {
|
||||
Weighted,
|
||||
StrictRandom,
|
||||
}
|
||||
7
src/app/mod.rs
Normal file
7
src/app/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod state;
|
||||
pub mod models;
|
||||
pub mod config;
|
||||
|
||||
mod parser;
|
||||
|
||||
pub use parser::FromConfig;
|
||||
2
src/app/models.rs
Normal file
2
src/app/models.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
pub struct Scheduler {}
|
||||
3
src/app/parser/mod.rs
Normal file
3
src/app/parser/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod parser;
|
||||
|
||||
pub use parser::FromConfig;
|
||||
81
src/app/parser/parser.rs
Normal file
81
src/app/parser/parser.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use std::path::Path;
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
|
||||
/// A trait for types that can be loaded from a configuration file.
|
||||
///
|
||||
/// Implementors must define [`load`](FromConfig::load), which is responsible for
|
||||
/// resolving the config path and calling [`deserialize_config`](FromConfig::deserialize_config).
|
||||
/// All parsing methods have default implementations supporting RON, TOML, and JSON,
|
||||
/// and can be overridden if custom parsing behaviour is needed.
|
||||
pub trait FromConfig
|
||||
{
|
||||
/// Loads and deserializes the config into `Self`.
|
||||
///
|
||||
/// Implementations should resolve the config file path and call
|
||||
/// [`deserialize_config`](FromConfig::deserialize_config) with the desired formats.
|
||||
fn load() -> Self;
|
||||
|
||||
/// Deserializes a config file into a rust struct.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// let cfg: Config = Config::deserialize_config(
|
||||
/// "path/to/config",
|
||||
/// &[
|
||||
/// ("ron", Config::from_ron),
|
||||
/// ("toml", Config::from_toml),
|
||||
/// ("json", Config::from_json),
|
||||
/// ],
|
||||
/// )?;
|
||||
/// ```
|
||||
fn deserialize_config<T>(file: impl AsRef<Path>, formats: &[(&str, ParseFn<T>)]) -> Result<T, ConfigError>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
let base = file.as_ref();
|
||||
|
||||
for (ext, parse) in formats
|
||||
{
|
||||
let path = base.with_extension(ext);
|
||||
if path.exists()
|
||||
{
|
||||
let raw = std::fs::read_to_string(&path)
|
||||
.map_err(ConfigError::Io)?;
|
||||
return parse(&raw);
|
||||
}
|
||||
}
|
||||
|
||||
Err(ConfigError::NotFound { path: file.as_ref().to_string_lossy().into() })
|
||||
}
|
||||
|
||||
fn from_ron<T: for<'de> Deserialize<'de>>(s: &str) -> Result<T, ConfigError> {
|
||||
ron::from_str(s).map_err(ConfigError::Ron)
|
||||
}
|
||||
|
||||
fn from_toml<T: for<'de> Deserialize<'de>>(s: &str) -> Result<T, ConfigError> {
|
||||
toml::from_str(s).map_err(ConfigError::Toml)
|
||||
}
|
||||
|
||||
fn from_json<T: for<'de> Deserialize<'de>>(s: &str) -> Result<T, ConfigError> {
|
||||
serde_json::from_str(s).map_err(ConfigError::Json)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub type ParseFn<T> = fn(&str) -> Result<T, ConfigError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("No config file found at '{path}.(ron|toml|json)'.")]
|
||||
NotFound { path: String },
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Ron parse error: {0}")]
|
||||
Ron(#[from] ron::error::SpannedError),
|
||||
#[error("TOML parse error: {0}")]
|
||||
Toml(#[from] toml::de::Error),
|
||||
#[error("JSON parse error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
}
|
||||
29
src/app/state.rs
Normal file
29
src/app/state.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use tracing_subscriber::{
|
||||
EnvFilter,
|
||||
fmt::{
|
||||
SubscriberBuilder,
|
||||
format::{ Format, DefaultFields }
|
||||
}
|
||||
};
|
||||
|
||||
use crate::app::{
|
||||
models::*,
|
||||
config::Config
|
||||
};
|
||||
|
||||
pub struct AppState {
|
||||
pub config: Config,
|
||||
pub scheduler: Scheduler
|
||||
}
|
||||
|
||||
impl AppState
|
||||
{
|
||||
pub fn new(config: Config, scheduler: Scheduler) -> Self
|
||||
{
|
||||
Self { config, scheduler }
|
||||
}
|
||||
|
||||
pub fn get_tracer(filter: impl Into<EnvFilter>) -> SubscriberBuilder<DefaultFields, Format, EnvFilter> {
|
||||
tracing_subscriber::fmt().with_env_filter(filter)
|
||||
}
|
||||
}
|
||||
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod app;
|
||||
|
||||
pub use app::{ config, state };
|
||||
11
src/main.rs
11
src/main.rs
@@ -1,4 +1,13 @@
|
||||
use new_vegas_radio::app::{ config::Config, state::AppState, models::Scheduler };
|
||||
|
||||
fn main() {
|
||||
fn main()
|
||||
{
|
||||
let cfg = Config::load();
|
||||
let _app = AppState::new(cfg.clone(), Scheduler {});
|
||||
|
||||
if cfg.settings.debug {
|
||||
AppState::get_tracer("new_vegas_radio=debug").init();
|
||||
}
|
||||
|
||||
println!("{:?}", cfg);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user