use crate::get_muse2_config_dir;
use crate::input::read_toml;
use crate::log::DEFAULT_LOG_LEVEL;
use anyhow::Result;
use documented::DocumentedFields;
use serde::{Deserialize, Serialize};
use std::env;
use std::fmt::Write;
use std::path::{Path, PathBuf};
const SETTINGS_FILE_NAME: &str = "settings.toml";
const DEFAULT_SETTINGS_FILE_HEADER: &str = concat!(
"# This file contains the program settings for MUSE2.
#
# The default options for MUSE2 v",
env!("CARGO_PKG_VERSION"),
" are shown below, commented out. To change an option, uncomment it and set the value
# appropriately.
#
# To show the default options for the current version of MUSE2, run:
# \tmuse2 settings show-default
#
# For information about the possible settings, visit:
# \thttps://energysystemsmodellinglab.github.io/MUSE2/file_formats/program_settings.html
"
);
pub fn get_settings_file_path() -> PathBuf {
let mut path = get_muse2_config_dir();
path.push(SETTINGS_FILE_NAME);
path
}
#[derive(Debug, DocumentedFields, Serialize, Deserialize, PartialEq)]
#[serde(default)]
pub struct Settings {
pub log_level: String,
pub overwrite: bool,
pub debug_model: bool,
pub results_root: PathBuf,
pub graph_results_root: PathBuf,
}
impl Default for Settings {
fn default() -> Self {
Self {
log_level: DEFAULT_LOG_LEVEL.to_string(),
overwrite: false,
debug_model: false,
results_root: PathBuf::from("muse2_results"),
graph_results_root: PathBuf::from("muse2_graphs"),
}
}
}
impl Settings {
pub fn load_or_default() -> Result<Settings> {
if env::var("MUSE2_USE_DEFAULT_SETTINGS").is_ok_and(|v| v == "1") {
Ok(Settings::default())
} else {
Self::from_path_or_default(&get_settings_file_path())
}
}
fn from_path_or_default(file_path: &Path) -> Result<Settings> {
if !file_path.is_file() {
return Ok(Settings::default());
}
read_toml(file_path)
}
pub fn default_file_contents() -> String {
let settings = Settings::default();
let settings_raw = toml::to_string(&settings).expect("Could not convert settings to TOML");
let mut out = DEFAULT_SETTINGS_FILE_HEADER.to_string();
for line in settings_raw.split('\n') {
if let Some((field, _)) = line.split_once('=') {
let field = field.trim();
let docs = Settings::get_field_docs(field).expect("Missing doc comment for field");
for line in docs.split('\n') {
write!(&mut out, "\n# # {}\n", line.trim()).unwrap();
}
writeln!(&mut out, "# {}", line.trim()).unwrap();
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
use tempfile::tempdir;
#[test]
fn settings_from_path_or_default_no_file() {
let dir = tempdir().unwrap();
let file_path = dir.path().join(SETTINGS_FILE_NAME); assert_eq!(
Settings::from_path_or_default(&file_path).unwrap(),
Settings::default()
);
}
#[test]
fn settings_from_path_or_default() {
let dir = tempdir().unwrap();
let file_path = dir.path().join(SETTINGS_FILE_NAME);
{
let mut file = File::create(&file_path).unwrap();
writeln!(file, "log_level = \"warn\"").unwrap();
}
assert_eq!(
Settings::from_path_or_default(&file_path).unwrap(),
Settings {
log_level: "warn".to_string(),
..Settings::default()
}
);
}
#[test]
fn default_file_contents() {
assert!(!Settings::default_file_contents().is_empty());
}
}