envful/
checker.rs

1use crate::EnvVar;
2use crate::EnvVarDeclaration;
3use colored::*;
4use std::fs;
5use std::path::PathBuf;
6
7pub fn check_command(
8    file: Option<PathBuf>,
9    manifest: &PathBuf,
10    silent: bool,
11    show_undeclared: bool,
12    show_missing_optional: bool,
13) {
14    if !silent {
15        println!("{}", "Checking environment...".cyan());
16    }
17
18    let manifest_path = manifest.clone();
19    let declared_vars: Vec<EnvVarDeclaration> = parse_manifest_file(&manifest_path);
20
21    let mut all_vars: Vec<String> = Vec::new();
22
23    let env_file_path = match file {
24        Some(path) => Some(path),
25        None => {
26            let path = PathBuf::from(".env");
27            if path.exists() {
28                Some(path)
29            } else {
30                None
31            }
32        }
33    };
34
35    if env_file_path.is_some() {
36        let env_file_path = env_file_path.unwrap();
37        // Get given vars from .env file
38        let given_vars = parse_env_file(&env_file_path)
39            .iter()
40            // Remove empty vars
41            .filter(|var| !var.value.is_none() && !var.value.as_ref().unwrap().is_empty())
42            .map(|var| var.name.clone())
43            .collect::<Vec<String>>();
44        all_vars.extend(given_vars);
45    }
46
47    // Push to given vars the ones set in the system env
48    let system_vars: Vec<String> = std::env::vars()
49        .filter(|(key, _value)| {
50            declared_vars
51                .iter()
52                .map(|x| x.name.clone())
53                .collect::<Vec<String>>()
54                .contains(key)
55        })
56        .map(|(key, _value)| key)
57        .collect();
58    all_vars.extend(system_vars);
59
60    let required_missing_vars: Vec<String> = declared_vars
61        .iter()
62        .filter(|v| !v.optional && !all_vars.contains(&v.name))
63        .map(|v| v.name.clone())
64        .collect();
65    let optional_missing_vars: Vec<String> = declared_vars
66        .iter()
67        .filter(|v| v.optional && !all_vars.contains(&v.name))
68        .map(|v| v.name.clone())
69        .collect();
70    let undeclared_vars: Vec<String> = all_vars
71        .iter()
72        .filter(|v| {
73            !declared_vars
74                .iter()
75                .map(|dec_var| dec_var.name.clone())
76                .collect::<Vec<String>>()
77                .contains(v)
78        })
79        .map(|v| v.clone())
80        .collect();
81    let error = required_missing_vars.len() > 0;
82
83    if show_undeclared {
84        if undeclared_vars.len() > 0 {
85            println!(
86                "{}",
87                "Found variables not declared in the manifest:"
88                    .yellow()
89                    .bold()
90            );
91            for undeclared_var in undeclared_vars {
92                println!(
93                    "{} {}",
94                    " Undeclared variable:".yellow(),
95                    undeclared_var.yellow()
96                );
97            }
98        }
99    }
100
101    if optional_missing_vars.len() > 0 && show_missing_optional {
102        println!("{}", "Some optional variables are missing:".yellow().bold());
103        for optional_var in optional_missing_vars {
104            println!(
105                "{} {}",
106                " Missing optional variable:".yellow(),
107                optional_var.yellow()
108            );
109        }
110    }
111
112    if error {
113        // Print message for every missing var
114        eprintln!(
115            "{}",
116            "The process is missing required environment variables:"
117                .red()
118                .bold()
119        );
120        for missing_var in required_missing_vars {
121            eprintln!(
122                "{} {}",
123                "❌ Missing variable:".yellow(),
124                missing_var.yellow()
125            );
126        }
127        // Exit with error code
128        std::process::exit(1);
129    }
130
131    if !silent {
132        println!("{}", "All variables are present ✅".green());
133    }
134}
135
136fn parse_manifest_file(path: &PathBuf) -> Vec<EnvVarDeclaration> {
137    let content = fs::read_to_string(path);
138
139    if content.is_err() {
140        eprintln!(
141            "{}{}{}",
142            "Could not find manifest file: ".red().bold(),
143            path.to_str().unwrap().red().bold(),
144            ". If not in the working dir, use the -m option."
145                .red()
146                .bold()
147        );
148        std::process::exit(1);
149    }
150    let content = content.unwrap();
151
152    let lines: Vec<&str> = content.lines().collect();
153
154    let mut env_vars: Vec<EnvVarDeclaration> = Vec::new();
155
156    // Iterate variables
157    let mut optional = false;
158    let mut _description: Option<String> = None;
159    for line in lines {
160        // If line is empty, skip
161        if line.is_empty() {
162            continue;
163        }
164
165        _description = None;
166        // Get variable description
167        let comment_marker = "###";
168        if line.starts_with(comment_marker) {
169            _description = Some(line.replace(comment_marker, ""));
170            if _description.clone().unwrap().contains("[optional]") {
171                optional = true;
172            }
173            continue;
174        }
175
176        // If line starts with #, skip
177        if line.starts_with("#") {
178            continue;
179        }
180
181        // Convert to String vector
182        let var_string: Vec<String> = line.split("=").map(|x| x.to_string()).collect();
183        // Check key
184        let name = var_string.get(0);
185        if name.is_none() {
186            panic!("Name is not present in the line: {}", line);
187        }
188        let name = parse_token(name.unwrap());
189        // Check value
190
191        env_vars.push(EnvVarDeclaration {
192            name,
193            optional,
194            default: None,
195            description: _description,
196        });
197
198        // Clean cursor variables
199        optional = false;
200        _description = None;
201    }
202    return env_vars;
203}
204
205fn parse_env_file(path: &PathBuf) -> Vec<EnvVar> {
206    let content = fs::read_to_string(path);
207
208    // If file is not found, return empty vector as vars could be set via system
209    if content.is_err() {
210        eprintln!(
211            "{}{}{}",
212            "Could not find environment file: ".red().bold(),
213            path.to_str().unwrap().red().bold(),
214            ". If not in the working dir, use the -f option."
215                .red()
216                .bold()
217        );
218        std::process::exit(1);
219    }
220
221    let content = content.unwrap();
222    let lines: Vec<&str> = content.lines().collect();
223
224    let mut env_vars: Vec<EnvVar> = Vec::new();
225
226    // Iterate variables
227    for line in lines {
228        // If line is empty, skip
229        if line.is_empty() {
230            continue;
231        }
232
233        // Get variable description
234        if line.starts_with("###") {
235            let chars = line.split("#!").collect::<Vec<&str>>();
236            let _description = chars.last().unwrap();
237        }
238
239        // If line starts with #, skip
240        if line.starts_with("#") {
241            continue;
242        }
243
244        // Convert to String vector
245        let var_string: Vec<String> = line.split("=").map(|x| x.to_string()).collect();
246        // Check key
247        let name = var_string.get(0);
248        if name.is_none() {
249            panic!("Name is not present in the line: {}", line);
250        }
251
252        // If keys not there, skip
253        if name.unwrap().is_empty() {
254            continue;
255        }
256
257        let name = parse_token(name.unwrap());
258        let value = var_string.get(1).cloned();
259
260        // Convert value to owned
261        let value = value.clone();
262        env_vars.push(EnvVar { name, value });
263    }
264    return env_vars;
265}
266
267fn parse_token(text: &String) -> String {
268    let value = text.clone();
269    let value = value.trim();
270    return value.to_string();
271}