1use std::fs;
2use std::io;
3use std::path::Path;
4use std::path::PathBuf;
5
6use serde::{de::DeserializeOwned, Serialize};
7use snafu::{ResultExt, Snafu};
8
9use polyglot::Format;
10
11#[derive(Debug, Snafu)]
12pub enum Error {
13 #[snafu(display("Unknown config directory"))]
14 UnknownConfigDirectory,
15
16 FailedToCreateConfigDir {
17 source: io::Error,
18 },
19
20 FailedToCreateDefaultConfigFile {
21 source: io::Error,
22 },
23
24 FailedToSerializeDefaultConfig {
25 source: polyglot::Error,
26 },
27
28 #[snafu(display("Failed to find config file"))]
29 FailedToFindConfigFile,
30
31 FailedToOpenConfigFile {
32 source: io::Error,
33 },
34
35 FailedToDeserializeConfigFile {
36 source: polyglot::Error,
37 },
38}
39
40type Result<T> = std::result::Result<T, Error>;
41
42fn deser<T: DeserializeOwned>(file: &Path, format: Format) -> Result<T> {
43 let f = fs::File::open(file).context(FailedToOpenConfigFile)?;
44 polyglot::from_reader(f, format).context(FailedToDeserializeConfigFile)
45}
46
47fn find_config_file(namespace: &str, name: &str) -> Result<Option<(PathBuf, Format)>> {
48 let config_dir = dirs::config_dir().ok_or(Error::UnknownConfigDirectory)?;
49 let file_path = config_dir.join(namespace).join(name);
50
51 let (ext, format) = if file_path.with_extension("toml").exists() {
52 ("toml", Format::TOML)
53 } else if file_path.with_extension("json").exists() {
54 ("json", Format::JSON)
55 } else if file_path.with_extension("yml").exists() {
56 ("yml", Format::YAML)
57 } else {
58 return Ok(None);
59 };
60
61 Ok(Some((file_path.with_extension(ext), format)))
62}
63
64pub fn load<T: DeserializeOwned>(namespace: &str, name: &str) -> Result<T> {
65 let (file_path, format) =
66 find_config_file(namespace, name)?.ok_or(Error::FailedToFindConfigFile)?;
67 deser(&file_path, format)
68}
69
70pub fn load_or_default<T: DeserializeOwned + Serialize>(
71 namespace: &str,
72 name: &str,
73 default: T,
74) -> Result<T> {
75 if let Some((file, format)) = find_config_file(namespace, name)? {
76 deser(&file, format)
77 } else {
78 let config_dir = dirs::config_dir().ok_or(Error::UnknownConfigDirectory)?;
80 let namespace_dir = config_dir.join(namespace);
81 fs::create_dir_all(&namespace_dir).context(FailedToCreateConfigDir)?;
82 let file_path = namespace_dir.join(name).with_extension("toml");
83
84 let f = fs::File::create(&file_path).context(FailedToCreateDefaultConfigFile)?;
85 polyglot::to_writer(f, &default, Format::TOML).context(FailedToSerializeDefaultConfig)?;
86
87 Ok(default)
88 }
89}