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 pub copy_input_files: bool,
57}
58
59impl Default for Settings {
60 fn default() -> Self {
61 Self {
62 log_level: DEFAULT_LOG_LEVEL.to_string(),
63 overwrite: false,
64 debug_model: false,
65 results_root: PathBuf::from("muse2_results"),
66 graph_results_root: PathBuf::from("muse2_graphs"),
67 copy_input_files: true,
68 }
69 }
70}
71
72impl Settings {
73 pub fn load_or_default() -> Result<Settings> {
82 if env::var("MUSE2_USE_DEFAULT_SETTINGS").is_ok_and(|v| v == "1") {
83 Ok(Settings::default())
84 } else {
85 Self::from_path_or_default(&get_settings_file_path())
86 }
87 }
88
89 fn from_path_or_default(file_path: &Path) -> Result<Settings> {
92 if !file_path.is_file() {
93 return Ok(Settings::default());
94 }
95
96 read_toml(file_path)
97 }
98
99 pub fn default_file_contents() -> String {
101 let settings = Settings::default();
103
104 let settings_raw = toml::to_string(&settings).expect("Could not convert settings to TOML");
106
107 let mut out = DEFAULT_SETTINGS_FILE_HEADER.to_string();
110 for line in settings_raw.split('\n') {
111 if let Some((field, _)) = line.split_once('=') {
112 let field = field.trim();
114
115 let docs = Settings::get_field_docs(field).expect("Missing doc comment for field");
117 for line in docs.split('\n') {
118 write!(&mut out, "\n# # {}\n", line.trim()).unwrap();
119 }
120
121 writeln!(&mut out, "# {}", line.trim()).unwrap();
122 }
123 }
124
125 out
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use std::fs::File;
133 use std::io::Write;
134 use tempfile::tempdir;
135
136 #[test]
137 fn settings_from_path_or_default_no_file() {
138 let dir = tempdir().unwrap();
139 let file_path = dir.path().join(SETTINGS_FILE_NAME); assert_eq!(
141 Settings::from_path_or_default(&file_path).unwrap(),
142 Settings::default()
143 );
144 }
145
146 #[test]
147 fn settings_from_path_or_default() {
148 let dir = tempdir().unwrap();
149 let file_path = dir.path().join(SETTINGS_FILE_NAME);
150
151 {
152 let mut file = File::create(&file_path).unwrap();
153 writeln!(file, "log_level = \"warn\"").unwrap();
154 }
155
156 assert_eq!(
157 Settings::from_path_or_default(&file_path).unwrap(),
158 Settings {
159 log_level: "warn".to_string(),
160 ..Settings::default()
161 }
162 );
163 }
164
165 #[test]
166 fn default_file_contents() {
167 assert!(!Settings::default_file_contents().is_empty());
168 }
169}