use serde::Deserialize;
use std::fs;
use std::path::Path;
use super::LoaderError;
#[derive(Debug, Clone)]
pub struct PastaConfig {
pub loader: LoaderConfig,
pub custom_fields: toml::Table,
}
impl Default for PastaConfig {
fn default() -> Self {
Self {
loader: LoaderConfig::default(),
custom_fields: toml::Table::new(),
}
}
}
impl PastaConfig {
pub fn load(base_dir: &Path) -> Result<Self, LoaderError> {
let config_path = base_dir.join("pasta.toml");
if !config_path.exists() {
return Err(LoaderError::config_not_found(&config_path));
}
let content =
fs::read_to_string(&config_path).map_err(|e| LoaderError::io(&config_path, e))?;
Self::parse(&content).map_err(|e| LoaderError::config(&config_path, e))
}
fn parse(content: &str) -> Result<Self, toml::de::Error> {
let mut table: toml::Table = toml::from_str(content)?;
let loader = if let Some(loader_value) = table.remove("loader") {
loader_value.try_into()?
} else {
LoaderConfig::default()
};
let custom_fields = table;
tracing::debug!("Parsed configuration");
Ok(Self {
loader,
custom_fields,
})
}
fn get_custom_config<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
self.custom_fields
.get(key)
.and_then(|v| v.clone().try_into().ok())
}
pub fn logging(&self) -> Option<LoggingConfig> {
self.get_custom_config("logging")
}
pub fn persistence(&self) -> Option<PersistenceConfig> {
self.get_custom_config("persistence")
}
pub fn lua(&self) -> Option<LuaConfig> {
self.get_custom_config("lua")
}
pub fn talk(&self) -> Option<TalkConfig> {
self.get_custom_config("talk")
}
pub fn debug(&self) -> Option<DebugFileConfig> {
self.get_custom_config("debug")
}
#[allow(
clippy::should_implement_trait,
reason = "Public API stability: keep the existing inherent from_str constructor name without renaming"
)]
pub fn from_str(s: &str) -> Result<Self, toml::de::Error> {
Self::parse(s)
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct LoaderConfig {
#[serde(default = "default_pasta_patterns")]
pub pasta_patterns: Vec<String>,
#[serde(default = "default_lua_search_paths")]
pub lua_search_paths: Vec<String>,
#[serde(default = "default_transpiled_output_dir")]
pub transpiled_output_dir: String,
#[serde(default = "default_debug_mode")]
pub debug_mode: bool,
}
impl Default for LoaderConfig {
fn default() -> Self {
Self {
pasta_patterns: default_pasta_patterns(),
lua_search_paths: default_lua_search_paths(),
transpiled_output_dir: default_transpiled_output_dir(),
debug_mode: default_debug_mode(),
}
}
}
fn default_pasta_patterns() -> Vec<String> {
vec!["dic/*/*.pasta".to_string()]
}
pub fn default_lua_search_paths() -> Vec<String> {
vec![
"profile/pasta/save/lua".to_string(),
"scripts".to_string(),
"profile/pasta/pasta_scripts".to_string(),
"profile/pasta/cache/lua".to_string(),
"scriptlibs".to_string(),
]
}
fn default_transpiled_output_dir() -> String {
"profile/pasta/cache/lua".to_string()
}
fn default_debug_mode() -> bool {
true
}
#[derive(Debug, Clone, Deserialize)]
pub struct LoggingConfig {
#[serde(default = "default_log_file_path")]
pub file_path: String,
#[serde(default = "default_rotation_days")]
pub rotation_days: usize,
#[serde(default = "default_log_level")]
pub level: String,
#[serde(default)]
pub filter: Option<String>,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
file_path: default_log_file_path(),
rotation_days: default_rotation_days(),
level: default_log_level(),
filter: None,
}
}
}
impl LoggingConfig {
pub fn to_filter_directive(&self) -> String {
if let Some(ref filter) = self.filter {
filter.clone()
} else {
self.level.clone()
}
}
}
pub fn default_log_file_path() -> String {
"profile/pasta/logs/pasta.log".to_string()
}
fn default_rotation_days() -> usize {
7
}
fn default_log_level() -> String {
"info".to_string()
}
#[derive(Debug, Clone, Deserialize)]
pub struct PersistenceConfig {
#[serde(default)]
pub obfuscate: bool,
#[serde(default = "default_persistence_file_path")]
pub file_path: String,
#[serde(default)]
pub debug_mode: bool,
}
impl Default for PersistenceConfig {
fn default() -> Self {
Self {
obfuscate: false,
file_path: default_persistence_file_path(),
debug_mode: false,
}
}
}
fn default_persistence_file_path() -> String {
"profile/pasta/save/save.json".to_string()
}
impl PersistenceConfig {
pub fn effective_file_path(&self) -> String {
if self.obfuscate && self.file_path.ends_with(".json") {
self.file_path.replace(".json", ".dat")
} else if self.obfuscate && !self.file_path.ends_with(".dat") {
format!("{}.dat", self.file_path)
} else {
self.file_path.clone()
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct LuaConfig {
#[serde(default = "default_libs")]
pub libs: Vec<String>,
}
pub fn default_libs() -> Vec<String> {
vec![
"std_all".into(),
"assertions".into(),
"testing".into(),
"regex".into(),
"json".into(),
"yaml".into(),
]
}
impl Default for LuaConfig {
fn default() -> Self {
Self {
libs: default_libs(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct TalkConfig {
pub script_wait_normal: i64,
pub script_wait_period: i64,
pub script_wait_comma: i64,
pub script_wait_strong: i64,
pub script_wait_leader: i64,
pub chars_period: String,
pub chars_comma: String,
pub chars_strong: String,
pub chars_leader: String,
pub chars_line_start_prohibited: String,
pub chars_line_end_prohibited: String,
}
impl Default for TalkConfig {
fn default() -> Self {
Self {
script_wait_normal: 50,
script_wait_period: 1000,
script_wait_comma: 500,
script_wait_strong: 500,
script_wait_leader: 200,
chars_period: "。。..".into(),
chars_comma: "、,,".into(),
chars_strong: "?!!?".into(),
chars_leader: "・・‥…".into(),
chars_line_start_prohibited: "゛゜ヽヾゝゞ々ー)]}」』):;]}」、・ー゙゚".into(),
chars_line_end_prohibited: "([{「『([{「".into(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct DebugFileConfig {
pub enabled: bool,
pub port: u16,
pub present_as: Option<String>,
pub source_map_sidecar: bool,
}
impl Default for DebugFileConfig {
fn default() -> Self {
Self {
enabled: false,
port: default_debug_port(),
present_as: None,
source_map_sidecar: false,
}
}
}
pub const fn default_debug_port() -> u16 {
9276
}