1use crate::get_muse2_config_dir;
3use crate::input::read_toml;
4use crate::log::DEFAULT_LOG_LEVEL;
5use anyhow::Result;
6use documented::DocumentedFields;
7use serde::{Deserialize, Serialize};
8use std::env;
9use std::fmt::Write;
10use std::path::{Path, PathBuf};
11
12const SETTINGS_FILE_NAME: &str = "settings.toml";
13
14const DEFAULT_SETTINGS_FILE_HEADER: &str = concat!(
15 "# This file contains the program settings for MUSE2.
16#
17# The default options for MUSE2 v",
18 env!("CARGO_PKG_VERSION"),
19 " are shown below, commented out. To change an option, uncomment it and set the value
20# appropriately.
21#
22# To show the default options for the current version of MUSE2, run:
23# \tmuse2 settings show-default
24#
25# For information about the possible settings, visit:
26# \thttps://energysystemsmodellinglab.github.io/MUSE2/file_formats/program_settings.html
27"
28);
29
30pub fn get_settings_file_path() -> PathBuf {
32 let mut path = get_muse2_config_dir();
33 path.push(SETTINGS_FILE_NAME);
34
35 path
36}
37
38#[derive(Debug, DocumentedFields, Serialize, Deserialize, PartialEq)]
43#[serde(default)]
44pub struct Settings {
45 pub log_level: String,
47 pub overwrite: bool,
49 pub debug_model: bool,
51 pub results_root: PathBuf,
53 pub graph_results_root: PathBuf,
55}
56
57impl Default for Settings {
58 fn default() -> Self {
59 Self {
60 log_level: DEFAULT_LOG_LEVEL.to_string(),
61 overwrite: false,
62 debug_model: false,
63 results_root: PathBuf::from("muse2_results"),
64 graph_results_root: PathBuf::from("muse2_graphs"),
65 }
66 }
67}
68
69impl Settings {
70 pub fn load_or_default() -> Result<Settings> {
79 if env::var("MUSE2_USE_DEFAULT_SETTINGS").is_ok_and(|v| v == "1") {
80 Ok(Settings::default())
81 } else {
82 Self::from_path_or_default(&get_settings_file_path())
83 }
84 }
85
86 fn from_path_or_default(file_path: &Path) -> Result<Settings> {
89 if !file_path.is_file() {
90 return Ok(Settings::default());
91 }
92
93 read_toml(file_path)
94 }
95
96 pub fn default_file_contents() -> String {
98 let settings = Settings::default();
100
101 let settings_raw = toml::to_string(&settings).expect("Could not convert settings to TOML");
103
104 let mut out = DEFAULT_SETTINGS_FILE_HEADER.to_string();
107 for line in settings_raw.split('\n') {
108 if let Some((field, _)) = line.split_once('=') {
109 let field = field.trim();
111
112 let docs = Settings::get_field_docs(field).expect("Missing doc comment for field");
114 for line in docs.split('\n') {
115 write!(&mut out, "\n# # {}\n", line.trim()).unwrap();
116 }
117
118 writeln!(&mut out, "# {}", line.trim()).unwrap();
119 }
120 }
121
122 out
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use std::fs::File;
130 use std::io::Write;
131 use tempfile::tempdir;
132
133 #[test]
134 fn settings_from_path_or_default_no_file() {
135 let dir = tempdir().unwrap();
136 let file_path = dir.path().join(SETTINGS_FILE_NAME); assert_eq!(
138 Settings::from_path_or_default(&file_path).unwrap(),
139 Settings::default()
140 );
141 }
142
143 #[test]
144 fn settings_from_path_or_default() {
145 let dir = tempdir().unwrap();
146 let file_path = dir.path().join(SETTINGS_FILE_NAME);
147
148 {
149 let mut file = File::create(&file_path).unwrap();
150 writeln!(file, "log_level = \"warn\"").unwrap();
151 }
152
153 assert_eq!(
154 Settings::from_path_or_default(&file_path).unwrap(),
155 Settings {
156 log_level: "warn".to_string(),
157 ..Settings::default()
158 }
159 );
160 }
161
162 #[test]
163 fn default_file_contents() {
164 assert!(!Settings::default_file_contents().is_empty());
165 }
166}