axl_lib/config/
config_file.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::{
4    fs,
5    path::{Path, PathBuf},
6};
7use tracing::{info, instrument};
8
9#[derive(Serialize, Deserialize, Debug, Clone, Default)]
10pub struct AxlContext {
11    pub config_path: PathBuf,
12    pub config: AxlConfig,
13}
14
15/// Command Line Flags Should Overtake File Values.
16/// How can I show that a config option is available
17/// in the config file and in the cli flags?
18#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)]
19pub struct AxlConfig {
20    pub general: GeneralConfig,
21}
22
23#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
24pub struct GeneralConfig {
25    #[serde(default = "art_default")]
26    pub show_art: Option<bool>,
27    #[serde(default = "version_default")]
28    pub show_version: bool,
29}
30
31impl Default for GeneralConfig {
32    fn default() -> Self {
33        Self {
34            show_art: art_default(),
35            show_version: version_default(),
36        }
37    }
38}
39
40const fn art_default() -> Option<bool> {
41    // Set to false since most commands pull up a prompt immediately
42    None
43}
44
45const fn version_default() -> bool {
46    true
47}
48
49impl AxlConfig {
50    #[instrument(err)]
51    pub fn from_file(config_path: &Path) -> Result<Self> {
52        let config_string = &fs::read_to_string(config_path)?;
53        let mut loaded_config = if !config_string.trim().is_empty() {
54            serde_yaml::from_str(config_string)?
55        } else {
56            let mut config = Self::default();
57            config.general.show_version = true;
58            config
59        };
60
61        let env_show_art = std::env::var("AXL_SHOW_ART").map_or(None, |val| Some(val == "true"));
62        if env_show_art.is_some() {
63            loaded_config.general.show_art = env_show_art;
64        }
65        info!("config: {:#?}", loaded_config);
66        Ok(loaded_config)
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use anyhow::Result;
73    use assert_fs::{prelude::FileWriteStr, NamedTempFile};
74    use rstest::{fixture, rstest};
75    use similar_asserts::assert_eq;
76
77    use crate::config::config_file::GeneralConfig;
78
79    use super::AxlConfig;
80
81    #[fixture]
82    fn config_file_full() -> NamedTempFile {
83        // Arrange
84        let file = NamedTempFile::new("config_file_test_1.txt")
85            .expect("test fixture tmp file can be created");
86        file.write_str(
87            "general:
88    show_art: true
89    show_version: false",
90        )
91        .expect("test fixture tmp file can be written to");
92        file
93    }
94
95    #[fixture]
96    fn config_file_empty() -> NamedTempFile {
97        // Arrange
98        let file = NamedTempFile::new("config_file_test_empty.txt")
99            .expect("test fixture tmp file can be created");
100        file.write_str("")
101            .expect("test fixture tmp file can be written to");
102        file
103    }
104
105    #[rstest]
106    fn should_read_config_from_file(
107        #[from(config_file_full)] config_file: NamedTempFile,
108    ) -> Result<()> {
109        let loaded_config = AxlConfig::from_file(config_file.path())?;
110
111        assert_eq!(
112            loaded_config,
113            AxlConfig {
114                general: GeneralConfig {
115                    show_art: Some(true),
116                    show_version: false,
117                }
118            }
119        );
120
121        Ok(())
122    }
123
124    #[rstest]
125    fn should_default_empty_config_file(
126        #[from(config_file_empty)] config_file: NamedTempFile,
127    ) -> Result<()> {
128        let loaded_config = AxlConfig::from_file(config_file.path())?;
129
130        assert_eq!(
131            loaded_config,
132            AxlConfig {
133                general: GeneralConfig {
134                    show_art: None,
135                    show_version: true
136                }
137            }
138        );
139
140        Ok(())
141    }
142}