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::fmt::Write;
9use std::path::{Path, PathBuf};
10
11const SETTINGS_FILE_NAME: &str = "settings.toml";
12
13const DEFAULT_SETTINGS_FILE_HEADER: &str = concat!(
14 "# This file contains the program settings for MUSE2.
15#
16# The default options for MUSE2 v",
17 env!("CARGO_PKG_VERSION"),
18 " are shown below, commented out. To change an option, uncomment it and set the value
19# appropriately.
20#
21# To show the default options for the current version of MUSE2, run:
22# \tmuse2 settings show-default
23#
24# For information about the possible settings, visit:
25# \thttps://energysystemsmodellinglab.github.io/MUSE2/file_formats/program_settings.html
26"
27);
28
29pub fn get_settings_file_path() -> PathBuf {
31 let mut path = get_muse2_config_dir();
32 path.push(SETTINGS_FILE_NAME);
33
34 path
35}
36
37#[derive(Debug, DocumentedFields, Serialize, Deserialize, PartialEq)]
42#[serde(default)]
43pub struct Settings {
44 pub log_level: String,
46 pub overwrite: bool,
48 pub debug_model: bool,
50}
51
52impl Default for Settings {
53 fn default() -> Self {
54 Self {
55 log_level: DEFAULT_LOG_LEVEL.to_string(),
56 overwrite: false,
57 debug_model: false,
58 }
59 }
60}
61
62impl Settings {
63 pub fn load() -> Result<Settings> {
71 Self::load_from_path(&get_settings_file_path())
72 }
73
74 fn load_from_path(file_path: &Path) -> Result<Settings> {
76 if !file_path.is_file() {
77 return Ok(Settings::default());
78 }
79
80 read_toml(file_path)
81 }
82
83 pub fn default_file_contents() -> String {
85 let settings = Settings::default();
87
88 let settings_raw = toml::to_string(&settings).expect("Could not convert settings to TOML");
90
91 let mut out = DEFAULT_SETTINGS_FILE_HEADER.to_string();
93 for line in settings_raw.split('\n') {
94 if let Some(last) = line.find('=') {
95 let field = line[..last].trim();
97
98 let docs = Settings::get_field_docs(field).expect("Missing doc comment for field");
100 for line in docs.split('\n') {
101 write!(&mut out, "\n# # {}\n", line.trim()).unwrap();
102 }
103
104 writeln!(&mut out, "# {}", line.trim()).unwrap();
105 }
106 }
107
108 out
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use std::fs::File;
116 use std::io::Write;
117 use tempfile::tempdir;
118
119 #[test]
120 fn test_settings_load_from_path_no_file() {
121 let dir = tempdir().unwrap();
122 let file_path = dir.path().join(SETTINGS_FILE_NAME); assert_eq!(
124 Settings::load_from_path(&file_path).unwrap(),
125 Settings::default()
126 );
127 }
128
129 #[test]
130 fn test_settings_load_from_path() {
131 let dir = tempdir().unwrap();
132 let file_path = dir.path().join(SETTINGS_FILE_NAME);
133
134 {
135 let mut file = File::create(&file_path).unwrap();
136 writeln!(file, "log_level = \"warn\"").unwrap();
137 }
138
139 assert_eq!(
140 Settings::load_from_path(&file_path).unwrap(),
141 Settings {
142 log_level: "warn".to_string(),
143 debug_model: false,
144 overwrite: false
145 }
146 );
147 }
148
149 #[test]
150 fn test_default_file_contents() {
151 assert!(!Settings::default_file_contents().is_empty());
152 }
153}