1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use gecol_core::{
prelude::{ExtractionConfig, Template},
theme::ThemeType,
};
use serde::{Deserialize, Serialize};
use crate::error::Error;
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Config {
#[serde(flatten)]
pub extraction: ExtractionConfig,
// Fallback color when extraction fails to extract a color.
#[serde(default)]
pub fallback_color: Option<String>,
// Which theme type to use. Visit [`ThemeType`] to see all the variants.
#[serde(default)]
pub theme_type: ThemeType,
// List of templates to be built.
#[serde(default)]
pub templates: HashMap<String, Template>,
// Path to the directory containing the templates.
// `~/.config/gecol/templates` by default on linux.
#[serde(default)]
pub templates_dir: Option<PathBuf>,
/// Path to the directory containing cached colors for faster repeated
/// extraction.
#[serde(default)]
pub cache_dir: Option<PathBuf>,
/// Whether no color cache should be used.
#[serde(default)]
pub no_cache: bool,
}
impl Config {
/// Loads the config from the default config file path.
///
/// It returns default config when the config file is not found.
///
/// Default config file path is given by [`Config::file`].
pub fn load_default() -> Self {
Self::load(Self::file()).unwrap_or_default()
}
/// Loads the config from the given path.
///
/// Config is required to be in TOML format.
pub fn load(path: impl AsRef<Path>) -> Result<Self, Error> {
let content = std::fs::read_to_string(path)?;
let mut config: Config = toml::from_str(&content)?;
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"));
if let Some(path) = &mut config.templates_dir {
Self::expand_tilde(path, &home_dir);
}
config.resolve_paths(&home_dir);
Ok(config)
}
/// Saves the current config to the default config file path.
///
/// Default config file path is given by [`Config::file`]. If the folders
/// don't exists, it creates them.
pub fn save_default(&self) -> Result<(), Error> {
self.save(Self::file())
}
/// Saves the current config to the given file path.
///
/// If the folder on the path don't exist, it creates them.
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), Error> {
let path = path.as_ref();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let content = toml::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
/// Gets the default config directory.
///
/// It is `gecol` folder inside of the config directory
/// (e.g. `.config` on linux)
pub fn dir() -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| ".".into())
.join("gecol")
}
/// Gets the templates directory.
///
/// If the templates directory is not set, it uses `templates` directory
/// inside the default config directory.
pub fn templates_dir(&self) -> PathBuf {
match &self.templates_dir {
Some(dir) => dir.to_owned(),
None => Self::dir().join("templates"),
}
}
/// Gets the fallback color as RGB tuple. Returns `None` if conversion
/// fails or fallback color was not set.
pub fn fallback_color(&self) -> Option<(u8, u8, u8)> {
let hex = self.fallback_color.as_ref()?.trim_start_matches('#');
if hex.len() == 6
&& let (Ok(r), Ok(g), Ok(b)) = (
u8::from_str_radix(&hex[0..2], 16),
u8::from_str_radix(&hex[2..4], 16),
u8::from_str_radix(&hex[4..6], 16),
)
{
return Some((r, g, b));
}
None
}
/// Gets the default config file path.
///
/// It uses the [`Config::dir`] to get the config directory, followed
/// by the `config.toml`.
pub fn file() -> PathBuf {
Self::dir().join("config.toml")
}
/// Resolves the template paths.
///
/// If the source path is not absolute, it is in the templates directory,
/// and if the target is not absolute, it is in the home directory.
fn resolve_paths(&mut self, home_dir: &Path) {
let dir = self.templates_dir();
for template in self.templates.values_mut() {
Self::expand_tilde(&mut template.source, home_dir);
Self::expand_tilde(&mut template.target, home_dir);
if !template.source.is_absolute() {
template.source = dir.join(&template.source);
}
if !template.target.is_absolute() {
template.target = home_dir.join(&template.target);
}
}
}
fn expand_tilde(path: &mut PathBuf, home_dir: &Path) {
if let Ok(stripped) = path.strip_prefix("~") {
*path = home_dir.join(stripped);
}
}
}