1#[cfg(any(feature = "json", feature = "yaml", feature = "toml", feature = "hcl"))]
56use serde::{de::DeserializeOwned, Serialize};
57use std::fs::{self, File};
58#[cfg(any(feature = "json", feature = "yaml", feature = "toml", feature = "hcl"))]
59use std::io::Error as IoError;
60use std::io::{ErrorKind, Read, Result as IoResult};
61use std::path::{Path, PathBuf};
62
63fn home_dir() -> PathBuf {
64 dirs::home_dir().expect("Get home dir")
65}
66
67#[derive(Debug)]
69#[cfg(feature = "json")]
70pub enum JsonError {
71 Io(IoError),
72 Serde(serde_json::Error),
73}
74
75#[derive(Debug)]
77#[cfg(feature = "yaml")]
78pub enum YamlError {
79 Io(IoError),
80 Serde(serde_yaml::Error),
81}
82
83#[derive(Debug)]
85#[cfg(feature = "toml")]
86pub enum TomlParseError {
87 Io(IoError),
88 Serde(toml::de::Error),
89}
90
91#[derive(Debug)]
93#[cfg(feature = "toml")]
94pub enum TomlSaveError {
95 Io(IoError),
96 Serde(toml::ser::Error),
97}
98
99#[derive(Debug)]
101#[cfg(feature = "hcl")]
102pub enum HclError {
103 Io(IoError),
104 Serde(hcl::Error),
105}
106
107#[derive(Debug, Clone)]
109pub struct HomeConfig {
110 path: PathBuf,
111}
112
113impl HomeConfig {
114 pub fn with_config_dir<P: AsRef<Path>>(app_name: &'static str, file_name: P) -> Self {
118 Self {
119 path: home_dir().join(".config").join(app_name).join(file_name),
120 }
121 }
122
123 pub fn with_file<P: AsRef<Path>>(p: P) -> Self {
127 Self {
128 path: home_dir().join(p),
129 }
130 }
131
132 pub fn path(&self) -> &PathBuf {
134 &self.path
135 }
136
137 pub fn read_to_string(&self) -> IoResult<String> {
139 fs::read_to_string(&self.path)
140 }
141
142 pub fn read_to_vec(&self) -> IoResult<Vec<u8>> {
144 let mut f = File::open(&self.path)?;
145 let mut buf = Vec::new();
146 f.read_to_end(&mut buf)?;
147 Ok(buf)
148 }
149
150 #[cfg(feature = "json")]
152 pub fn json<T>(&self) -> Result<T, JsonError>
153 where
154 T: DeserializeOwned,
155 {
156 let f = File::open(&self.path).map_err(JsonError::Io)?;
157 serde_json::from_reader(f).map_err(JsonError::Serde)
158 }
159
160 #[cfg(feature = "yaml")]
162 pub fn yaml<T>(&self) -> Result<T, YamlError>
163 where
164 T: DeserializeOwned,
165 {
166 let f = File::open(&self.path).map_err(YamlError::Io)?;
167 serde_yaml::from_reader(f).map_err(YamlError::Serde)
168 }
169
170 #[cfg(feature = "toml")]
172 pub fn toml<T>(&self) -> Result<T, TomlParseError>
173 where
174 T: DeserializeOwned,
175 {
176 let bytes = self.read_to_vec().map_err(TomlParseError::Io)?;
177 toml::from_slice(&bytes).map_err(TomlParseError::Serde)
178 }
179
180 #[cfg(feature = "hcl")]
182 pub fn hcl<T>(&self) -> Result<T, HclError>
183 where
184 T: DeserializeOwned,
185 {
186 let f = File::open(&self.path).map_err(HclError::Io)?;
187 hcl::from_reader(f).map_err(HclError::Serde)
188 }
189
190 fn create_parent_dir(&self) -> IoResult<()> {
191 if !self.path.exists() {
192 if let Some(parent) = self.path.parent() {
193 fs::create_dir_all(parent)?;
194 }
195 }
196 Ok(())
197 }
198
199 pub fn save<T: AsRef<[u8]>>(&self, data: T) -> IoResult<()> {
201 self.create_parent_dir()?;
202 fs::write(&self.path, data.as_ref())
203 }
204
205 #[cfg(feature = "json")]
207 pub fn save_json<T>(&self, data: T) -> Result<(), JsonError>
208 where
209 T: Serialize,
210 {
211 let bytes = serde_json::to_vec_pretty(&data).map_err(JsonError::Serde)?;
212 self.create_parent_dir().map_err(JsonError::Io)?;
213 fs::write(&self.path, &bytes).map_err(JsonError::Io)?;
214 Ok(())
215 }
216
217 #[cfg(feature = "yaml")]
219 pub fn save_yaml<T>(&self, data: T) -> Result<(), YamlError>
220 where
221 T: Serialize,
222 {
223 let bytes = serde_yaml::to_string(&data).map_err(YamlError::Serde)?;
224 self.create_parent_dir().map_err(YamlError::Io)?;
225 fs::write(&self.path, &bytes).map_err(YamlError::Io)?;
226 Ok(())
227 }
228
229 #[cfg(feature = "toml")]
231 pub fn save_toml<T>(&self, data: T) -> Result<(), TomlSaveError>
232 where
233 T: Serialize,
234 {
235 let bytes = toml::to_string_pretty(&data).map_err(TomlSaveError::Serde)?;
236 self.create_parent_dir().map_err(TomlSaveError::Io)?;
237 fs::write(&self.path, &bytes).map_err(TomlSaveError::Io)?;
238 Ok(())
239 }
240
241 #[cfg(feature = "hcl")]
243 pub fn save_hcl<T>(&self, data: T) -> Result<(), HclError>
244 where
245 T: Serialize,
246 {
247 let bytes = hcl::to_vec(&data).map_err(HclError::Serde)?;
248 self.create_parent_dir().map_err(HclError::Io)?;
249 fs::write(&self.path, &bytes).map_err(HclError::Io)?;
250 Ok(())
251 }
252
253 pub fn delete(&self) -> IoResult<()> {
255 match fs::remove_file(&self.path) {
256 Ok(()) => Ok(()),
257 Err(err) if err.kind() == ErrorKind::NotFound => Ok(()),
258 Err(err) => Err(err),
259 }
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use crate::*;
266
267 #[test]
268 fn test_content() {
269 let config = HomeConfig::with_config_dir("test", "file");
270 config.save("123").unwrap();
272 assert_eq!(config.read_to_string().unwrap(), "123");
274 }
275
276 #[test]
277 fn test_delete() {
278 let config = HomeConfig::with_config_dir("test", "delete");
279
280 assert!(!config.path().exists());
281
282 config.save("0").unwrap();
283 assert!(config.path().exists());
284
285 config.delete().unwrap();
286 assert!(!config.path().exists());
287 }
288
289 #[cfg(any(feature = "json", feature = "yaml", feature = "toml", feature = "hcl"))]
290 use serde::{Deserialize, Serialize};
291
292 #[cfg(any(feature = "json", feature = "yaml", feature = "toml", feature = "hcl"))]
293 #[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq)]
294 struct People {
295 name: String,
296 age: u32,
297 }
298
299 #[test]
300 #[cfg(feature = "json")]
301 fn test_json() {
302 let config = HomeConfig::with_config_dir("test", "config.json");
303 let data = People {
304 name: "123".to_string(),
305 age: 18,
306 };
307 config.save_json(&data).unwrap();
308 assert_eq!(config.json::<People>().unwrap(), data);
309 }
310
311 #[test]
312 #[cfg(feature = "yaml")]
313 fn test_yaml() {
314 let config = HomeConfig::with_config_dir("test", "config.yaml");
315 let data = People {
316 name: "123".to_string(),
317 age: 18,
318 };
319 config.save_yaml(&data).unwrap();
320 assert_eq!(config.yaml::<People>().unwrap(), data);
321 }
322
323 #[test]
324 #[cfg(feature = "toml")]
325 fn test_toml() {
326 let config = HomeConfig::with_config_dir("test", "config.toml");
327 let data = People {
328 name: "123".to_string(),
329 age: 18,
330 };
331 config.save_toml(&data).unwrap();
332 assert_eq!(config.toml::<People>().unwrap(), data);
333 }
334
335 #[test]
336 #[cfg(feature = "hcl")]
337 fn test_hcl() {
338 let config = HomeConfig::with_config_dir("test", "config.hcl");
339 let data = People {
340 name: "123".to_string(),
341 age: 18,
342 };
343 config.save_hcl(&data).unwrap();
344 assert_eq!(config.hcl::<People>().unwrap(), data);
345 }
346}