toml_base_config/lib.rs
1//! Shared configuration behaviour for projects. Using a config.toml file
2use std::path::{Path, PathBuf};
3use std::{fs, io};
4
5use serde::{Deserialize, Serialize};
6/// Base configuration schema. Maintains a config.toml for configs. Handles the
7/// reading/writing of config.toml. The config file is stored in the
8/// [`dirs::config_dir()`] and the full relative path can be obtained via
9/// [`BaseConfig::path`].
10pub trait BaseConfig: Sized + Default + Serialize + for<'a> Deserialize<'a> {
11 /// The Package name is usually `env!("CARGO_PKG_NAME")`. This is the name
12 /// of the folder inside the config dir. We store the config.toml inside
13 /// this folder.
14 ///
15 /// Calling [`BaseConfig::load`] will create the config file at
16 /// [`BaseConfig::path`].
17 const PACKAGE: &'static str;
18
19 /// Compute path for the `config.toml`.
20 ///
21 /// # Example
22 ///
23 /// ```
24 /// use serde::{Deserialize, Serialize};
25 /// use dusk_cdf::BaseConfig;
26 /// use std::path::{Path, Component};
27 /// use std::ffi::OsStr;
28 ///
29 /// #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30 /// struct T {}
31 ///
32 /// impl Default for T {
33 /// fn default() -> Self {
34 /// T {}
35 /// }
36 /// }
37 ///
38 /// impl BaseConfig for T {
39 /// const PACKAGE: &'static str = "Lisa";
40 /// }
41 ///
42 /// let path = T::path().unwrap();
43 /// let mut components = path.components().rev(); // look at the last parts
44 ///
45 /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("config.toml"))));
46 /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("Lisa"))));
47 /// ```
48 fn path() -> Option<PathBuf> {
49 dirs::config_dir()
50 .map(|p| p.join(Self::PACKAGE))
51 .map(|p| p.join("config.toml"))
52 }
53
54 /// Serialize the toml if config.toml exists, else create a config.toml at
55 /// the path returned by [`BaseConfig::path`].
56 ///
57 /// The contents for the created config.toml is obtained by
58 /// `BaseConfig::default()` implementation
59 fn load() -> io::Result<Self> {
60 Self::path()
61 .ok_or_else(|| {
62 io::Error::new(io::ErrorKind::Other, "unable to define configuration path")
63 })
64 .and_then(Self::load_path)
65 }
66
67 /// Load a config file from a given path
68 fn load_path<P>(path: P) -> io::Result<Self>
69 where
70 P: AsRef<Path>,
71 {
72 let path = path.as_ref();
73
74 if !path.exists() {
75 let config = Self::default();
76
77 // config serialization is optional
78 path.parent()
79 .ok_or_else(|| {
80 io::Error::new(
81 io::ErrorKind::Other,
82 "unable to fetch parent dir of config file",
83 )
84 })
85 .and_then(fs::create_dir_all)
86 .and_then(|_| {
87 toml::to_string_pretty(&config)
88 .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
89 })
90 .and_then(|contents| fs::write(path, contents))
91 .unwrap_or_else(|e| eprintln!("failed to serialize config file: {}", e));
92
93 return Ok(config);
94 }
95
96 let contents = fs::read_to_string(path)?;
97
98 toml::from_str(&contents).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
99 }
100}