1use bat::{Input, PrettyPrinter};
2use color_eyre::eyre::{bail, Context, Result};
3use config::Config;
4use directories::ProjectDirs;
5use serde::{Deserialize, Serialize};
6use std::{fmt::Display, fs::create_dir_all, path::PathBuf};
7use thiserror::Error;
8
9#[derive(Error, Debug, PartialEq, Eq, Clone)]
11pub enum SettingsError {
12 #[error("data directory does not exist, and createdir is set to false")]
14 DataDirectoryDoesNotExist,
15}
16
17#[derive(Debug, Serialize, Deserialize, Clone)]
19#[serde(default)]
20pub struct TaskSettings {
21 pub autorelease: bool,
23 pub starttag: bool,
26 pub specialvisible: bool,
28 pub stopondone: bool,
31 pub clearpsecialtags: bool,
34}
35
36impl Default for TaskSettings {
37 fn default() -> Self {
38 Self {
39 autorelease: true,
40 starttag: true,
41 specialvisible: true,
42 stopondone: true,
43 clearpsecialtags: true,
44 }
45 }
46}
47
48#[derive(Debug, Serialize, Deserialize, Clone)]
50#[serde(default)]
51pub struct OutputSettings {
52 pub colors: bool,
54 pub grid: bool,
56 pub numbers: bool,
58 pub namespace: bool,
60 pub descriptionlength: usize,
62 pub totals: bool,
64 pub scoremultiplier: f64,
66}
67
68impl Default for OutputSettings {
69 fn default() -> Self {
70 Self {
71 colors: true,
72 grid: true,
73 numbers: true,
74 namespace: true,
75 descriptionlength: 60,
76 totals: true,
77 scoremultiplier: 1.0
78 }
79 }
80}
81
82#[cfg(feature = "note")]
84#[derive(Debug, Serialize, Deserialize, Clone)]
85#[serde(default)]
86pub struct NoteSettings {
87 pub description: bool,
90 pub timestamp: bool,
93}
94
95#[cfg(feature = "note")]
96impl Default for NoteSettings {
97 fn default() -> Self {
98 Self {
99 description: true,
100 timestamp: true,
101 }
102 }
103}
104
105#[derive(Debug, Serialize, Deserialize, Clone)]
107#[serde(default)]
108pub struct DataSettings {
109 pub path: String,
112 pub createdir: bool,
115 pub rotate: usize,
117}
118
119impl Default for DataSettings {
120 fn default() -> Self {
121 let proj_dirs = ProjectDirs::from("", "", "tsk-rs").unwrap();
122
123 Self {
124 path: String::from(proj_dirs.data_dir().to_str().unwrap()),
125 createdir: true,
126 rotate: 3,
127 }
128 }
129}
130
131#[derive(Default, Debug, Serialize, Deserialize, Clone)]
133#[serde(default)]
134pub struct Settings {
135 #[serde(skip_serializing)]
136 pub namespace: String,
140 pub data: DataSettings,
142 #[cfg(feature = "note")]
143 pub note: NoteSettings,
145 pub task: TaskSettings,
147 pub output: OutputSettings,
149}
150
151impl AsRef<Settings> for Settings {
152 fn as_ref(&self) -> &Settings {
153 self
154 }
155}
156
157impl Display for Settings {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 write!(f, "{}", toml::to_string(&self).unwrap())
160 }
161}
162
163impl Settings {
164 pub fn new(namespace: Option<String>, config_file: &str) -> Result<Self> {
167 let settings: Settings = Config::builder()
168 .set_override_option("namespace", namespace)?
169 .add_source(config::File::with_name(config_file).required(false))
170 .add_source(
171 config::Environment::with_prefix("TSK")
172 .try_parsing(true)
173 .separator("_"),
174 )
175 .build()
176 .with_context(|| "while reading configuration")?
177 .try_deserialize()
178 .with_context(|| "while applying defaults to configuration")?;
179
180 Ok(settings)
181 }
182
183 pub fn db_pathbuf(&self) -> Result<PathBuf> {
186 let pathbuf = PathBuf::from(&self.data.path).join(&self.namespace);
187 if !pathbuf.is_dir() && self.data.createdir {
188 create_dir_all(&pathbuf).with_context(|| "while creating data directory")?;
189 } else if !pathbuf.is_dir() && !self.data.createdir {
190 bail!(SettingsError::DataDirectoryDoesNotExist);
191 }
192 Ok(pathbuf)
193 }
194
195 pub fn task_db_pathbuf(&self) -> Result<PathBuf> {
197 let pathbuf = &self.db_pathbuf()?.join("tasks");
198 if !pathbuf.is_dir() && self.data.createdir {
199 create_dir_all(&pathbuf).with_context(|| "while creating tasks data directory")?;
200 } else if !pathbuf.is_dir() && !self.data.createdir {
201 bail!(SettingsError::DataDirectoryDoesNotExist);
202 }
203 Ok(pathbuf.to_path_buf())
204 }
205
206 #[cfg(feature = "note")]
208 pub fn note_db_pathbuf(&self) -> Result<PathBuf> {
209 let pathbuf = &self.db_pathbuf()?.join("notes");
210 if !pathbuf.is_dir() && self.data.createdir {
211 create_dir_all(&pathbuf).with_context(|| "while creating notes data directory")?;
212 } else if !pathbuf.is_dir() && !self.data.createdir {
213 bail!(SettingsError::DataDirectoryDoesNotExist);
214 }
215 Ok(pathbuf.to_path_buf())
216 }
217}
218
219pub fn show_config(settings: &Settings) -> Result<()> {
221 let settings_toml = format!("{}", settings);
222 PrettyPrinter::new()
223 .language("toml")
224 .input(Input::from_bytes(settings_toml.as_bytes()))
225 .colored_output(settings.output.colors)
226 .grid(settings.output.grid)
227 .line_numbers(settings.output.numbers)
228 .print()
229 .with_context(|| "while trying to prettyprint yaml")?;
230
231 Ok(())
232}
233
234pub fn default_config() -> String {
236 let proj_dirs = ProjectDirs::from("", "", "tsk-rs").unwrap();
237 proj_dirs
238 .config_dir()
239 .join("tsk.toml")
240 .to_str()
241 .unwrap()
242 .to_owned()
243}
244
245