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 let given_vars = parse_env_file(&env_file_path)
39 .iter()
40 .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 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 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 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 let mut optional = false;
158 let mut _description: Option<String> = None;
159 for line in lines {
160 if line.is_empty() {
162 continue;
163 }
164
165 _description = None;
166 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("#") {
178 continue;
179 }
180
181 let var_string: Vec<String> = line.split("=").map(|x| x.to_string()).collect();
183 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 env_vars.push(EnvVarDeclaration {
192 name,
193 optional,
194 default: None,
195 description: _description,
196 });
197
198 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 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 for line in lines {
228 if line.is_empty() {
230 continue;
231 }
232
233 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("#") {
241 continue;
242 }
243
244 let var_string: Vec<String> = line.split("=").map(|x| x.to_string()).collect();
246 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 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 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}