nu_data/config/
nuconfig.rs

1use crate::config::{last_modified, read, Conf, Status};
2use indexmap::IndexMap;
3use nu_errors::ShellError;
4use nu_protocol::Value;
5use nu_source::Tag;
6use nu_test_support::NATIVE_PATH_ENV_VAR;
7use std::{fmt::Debug, path::PathBuf};
8
9use super::write;
10
11#[derive(Debug, Clone, Default)]
12pub struct NuConfig {
13    pub vars: IndexMap<String, Value>,
14    pub file_path: PathBuf,
15    pub modified_at: Status,
16}
17
18impl Conf for NuConfig {
19    fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
20        self.is_modified()
21    }
22
23    fn var(&self, key: &str) -> Option<Value> {
24        self.var(key)
25    }
26
27    fn env(&self) -> Option<Value> {
28        self.env()
29    }
30
31    fn path(&self) -> Result<Option<Vec<PathBuf>>, ShellError> {
32        self.path()
33    }
34
35    fn reload(&mut self) {
36        if let Ok(variables) = read(Tag::unknown(), &Some(self.file_path.clone())) {
37            self.vars = variables;
38
39            self.modified_at = if let Ok(status) = last_modified(&Some(self.file_path.clone())) {
40                status
41            } else {
42                Status::Unavailable
43            };
44        }
45    }
46
47    fn clone_box(&self) -> Box<dyn Conf> {
48        Box::new(self.clone())
49    }
50}
51
52impl NuConfig {
53    pub fn load(cfg_file_path: Option<PathBuf>) -> Result<NuConfig, ShellError> {
54        let vars = read(Tag::unknown(), &cfg_file_path)?;
55        let modified_at = NuConfig::get_last_modified(&cfg_file_path);
56        let file_path = if let Some(file_path) = cfg_file_path {
57            file_path
58        } else {
59            crate::config::default_path()?
60        };
61
62        Ok(NuConfig {
63            vars,
64            file_path,
65            modified_at,
66        })
67    }
68
69    /// Writes self.values under self.file_path
70    pub fn write(&self) -> Result<(), ShellError> {
71        write(&self.vars, &Some(self.file_path.clone()))
72    }
73
74    pub fn new() -> NuConfig {
75        let vars = if let Ok(variables) = read(Tag::unknown(), &None) {
76            variables
77        } else {
78            IndexMap::default()
79        };
80        let path = if let Ok(path) = crate::config::default_path() {
81            path
82        } else {
83            PathBuf::new()
84        };
85
86        NuConfig {
87            vars,
88            modified_at: NuConfig::get_last_modified(&None),
89            file_path: path,
90        }
91    }
92
93    pub fn get_last_modified(config_file: &Option<std::path::PathBuf>) -> Status {
94        if let Ok(status) = last_modified(config_file) {
95            status
96        } else {
97            Status::Unavailable
98        }
99    }
100
101    pub fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
102        let modified_at = &self.modified_at;
103
104        Ok(match (NuConfig::get_last_modified(&None), modified_at) {
105            (Status::LastModified(left), Status::LastModified(right)) => {
106                let left = left.duration_since(std::time::UNIX_EPOCH)?;
107                let right = (*right).duration_since(std::time::UNIX_EPOCH)?;
108
109                left != right
110            }
111            (_, _) => false,
112        })
113    }
114
115    pub fn var(&self, key: &str) -> Option<Value> {
116        let vars = &self.vars;
117
118        if let Some(value) = vars.get(key) {
119            return Some(value.clone());
120        }
121
122        None
123    }
124
125    /// Return environment variables as map
126    pub fn env_map(&self) -> IndexMap<String, String> {
127        let mut result = IndexMap::new();
128        if let Some(variables) = self.env() {
129            for var in variables.row_entries() {
130                if let Ok(value) = var.1.as_string() {
131                    result.insert(var.0.clone(), value);
132                }
133            }
134        }
135        result
136    }
137
138    pub fn env(&self) -> Option<Value> {
139        let vars = &self.vars;
140
141        if let Some(env_vars) = vars.get("env") {
142            return Some(env_vars.clone());
143        }
144
145        None
146    }
147
148    pub fn path(&self) -> Result<Option<Vec<PathBuf>>, ShellError> {
149        let vars = &self.vars;
150
151        if let Some(path) = vars.get("path").or_else(|| vars.get(NATIVE_PATH_ENV_VAR)) {
152            path
153                .table_entries()
154                .map(|p| {
155                    p.as_string().map(PathBuf::from).map_err(|_| {
156                        ShellError::untagged_runtime_error("Could not format path entry as string!\nPath entry from config won't be added")
157                    })
158                })
159            .collect::<Result<Vec<PathBuf>, ShellError>>().map(Some)
160        } else {
161            Ok(None)
162        }
163    }
164
165    fn load_scripts_if_present(&self, scripts_name: &str) -> Result<Vec<String>, ShellError> {
166        if let Some(array) = self.var(scripts_name) {
167            if !array.is_table() {
168                Err(ShellError::untagged_runtime_error(format!(
169                    "expected an array of strings as {} commands",
170                    scripts_name
171                )))
172            } else {
173                array.table_entries().map(Value::as_string).collect()
174            }
175        } else {
176            Ok(vec![])
177        }
178    }
179
180    pub fn exit_scripts(&self) -> Result<Vec<String>, ShellError> {
181        self.load_scripts_if_present("on_exit")
182    }
183
184    pub fn startup_scripts(&self) -> Result<Vec<String>, ShellError> {
185        self.load_scripts_if_present("startup")
186    }
187}