1use std::{
8 collections::BTreeMap,
9 fmt::Display,
10 fs,
11 io::{Read, Write},
12 process::Command,
13};
14
15use crate::{
16 container,
17 function::Function,
18 time,
19 value::{value_type::ValueType, Value},
20 EvalexprError, EvalexprResult,
21};
22
23mod predefined;
24
25pub trait Context {
27 fn get_value(&self, identifier: &str) -> Option<&Value>;
29
30 fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value>;
33}
34
35pub trait ContextWithMutableVariables: Context {
37 fn set_value(&mut self, _identifier: &str, _value: Value) -> EvalexprResult<()> {
39 Err(EvalexprError::ContextNotMutable)
40 }
41}
42
43pub trait ContextWithMutableFunctions: Context {
45 fn set_function(&mut self, _identifier: String, _function: Function) -> EvalexprResult<()> {
47 Err(EvalexprError::ContextNotMutable)
48 }
49}
50
51#[derive(Clone, Debug, Default)]
53pub struct VariableMap {
54 variables: BTreeMap<String, Value>,
55}
56
57impl Display for VariableMap {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 write!(f, "(")?;
60
61 for (key, value) in &self.variables {
62 write!(f, " {} = {};", key, value)?;
63 }
64
65 write!(f, " )")
66 }
67}
68
69impl PartialEq for VariableMap {
70 fn eq(&self, other: &Self) -> bool {
71 if self.variables.len() != other.variables.len() {
72 return false;
73 }
74
75 for variable in &self.variables {
76 for other in &other.variables {
77 if variable != other {
78 return false;
79 }
80 }
81 }
82
83 true
84 }
85}
86
87impl VariableMap {
88 pub fn new() -> Self {
90 Default::default()
91 }
92}
93
94impl Context for VariableMap {
95 fn get_value(&self, identifier: &str) -> Option<&Value> {
96 let split = identifier.split_once(".");
97 if let Some((map_name, next_identifier)) = split {
98 let value = self.variables.get(map_name)?;
99 if let Value::Map(map) = value {
100 map.get_value(next_identifier)
101 } else {
102 None
103 }
104 } else {
105 self.variables.get(identifier).or_else(|| {
106 self.call_function(identifier, &Value::Empty);
107 None
108 })
109 }
110 }
111
112 fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value> {
113 call_whale_function(identifier, argument)
114 }
115}
116
117impl ContextWithMutableVariables for VariableMap {
118 fn set_value(&mut self, identifier: &str, value: Value) -> EvalexprResult<()> {
119 let split = identifier.split_once(".");
120
121 if let Some((map_name, next_identifier)) = split {
122 if let Some(map_value) = self.variables.get_mut(map_name) {
123 if let Value::Map(map) = map_value {
124 map.set_value(next_identifier, value)
125 } else {
126 return Err(EvalexprError::ExpectedMap {
127 actual: map_value.clone(),
128 });
129 }
130 } else {
131 let mut new_map = VariableMap {
132 variables: BTreeMap::new(),
133 };
134
135 new_map.set_value(next_identifier, value)?;
136 self.variables
137 .insert(map_name.to_string(), Value::Map(new_map));
138
139 Ok(())
140 }
141 } else if self.variables.contains_key(identifier) {
142 Err(EvalexprError::ExpectedMap {
143 actual: value.clone(),
144 })
145 } else {
146 self.variables.insert(identifier.to_string(), value);
147
148 Ok(())
149 }
150 }
151}
152
153impl ContextWithMutableFunctions for VariableMap {
154 fn set_function(&mut self, identifier: String, function: Function) -> EvalexprResult<()> {
155 todo!()
156 }
157}
158
159#[macro_export]
174macro_rules! context_map {
175 ( ($ctx:expr) $k:expr => Function::new($($v:tt)*) ) =>
177 { $crate::context_map!(($ctx) $k => Function::new($($v)*),) };
178 ( ($ctx:expr) $k:expr => $v:expr ) =>
179 { $crate::context_map!(($ctx) $k => $v,) };
180 ( ($ctx:expr) ) => { Ok(()) };
182
183 ( ($ctx:expr) $k:expr => Function::new($($v:tt)*) , $($tt:tt)*) => {{
185 $crate::ContextWithMutableFunctions::set_function($ctx, $k.into(), $crate::Function::new($($v)*))
186 .and($crate::context_map!(($ctx) $($tt)*))
187 }};
188 ( ($ctx:expr) $k:expr => $v:expr , $($tt:tt)*) => {{
190 $crate::ContextWithMutableVariables::set_value($ctx, $k.into(), $v.into())
191 .and($crate::context_map!(($ctx) $($tt)*))
192 }};
193
194 ( $($tt:tt)* ) => {{
196 let mut context = $crate::VariableMap::new();
197 $crate::context_map!((&mut context) $($tt)*)
198 .map(|_| context)
199 }};
200}
201
202fn call_whale_function(identifier: &str, argument: &Value) -> Result<Value, EvalexprError> {
203 match identifier {
204 "container::image::build" => container::image::build(argument),
205 "container::image::list" => container::image::list(argument),
206 "container::list" => container::list(argument),
207 "container::run" => container::run(argument),
208 "convert" => todo!(),
209 "dir::create" => {
210 let path = argument.as_string()?;
211 std::fs::create_dir_all(path).unwrap();
212
213 Ok(Value::Empty)
214 },
215 "dir::read" => {
216 let path = argument.as_string()?;
217 let files = std::fs::read_dir(path)
218 .unwrap()
219 .map(|entry| entry.unwrap().file_name().into_string().unwrap_or_default())
220 .collect();
221
222 Ok(Value::String(files))
223 },
224 "dir::remove" => {
225 let path = argument.as_string()?;
226 std::fs::remove_file(path).unwrap();
227
228 Ok(Value::Empty)
229 },
230 "dir::trash" => todo!(),
231 "dnf::copr" => {
232 let repo_name = if let Ok(string) = argument.as_string() {
233 string
234 } else if let Ok(tuple) = argument.as_tuple() {
235 let mut repos = String::new();
236
237 for repo in tuple {
238 let repo = repo.as_string()?;
239
240 repos.push_str(&repo);
241 repos.push(' ');
242 }
243
244 repos
245 } else {
246 return Err(EvalexprError::ExpectedString {
247 actual: argument.clone(),
248 });
249 };
250 let script = format!("dnf -y copr enable {repo_name}");
251
252 Command::new("fish")
253 .arg("-c")
254 .arg(script)
255 .spawn()
256 .unwrap()
257 .wait()
258 .unwrap();
259
260 Ok(Value::Empty)
261 },
262 "dnf::packages" => {
263 let tuple = argument.as_tuple()?;
264 let mut packages = String::new();
265
266 for package in tuple {
267 let package = package.as_string()?;
268
269 packages.push_str(&package);
270 packages.push(' ');
271 }
272 let script = format!("dnf -y install {packages}");
273
274 Command::new("fish")
275 .arg("-c")
276 .arg(script)
277 .spawn()
278 .unwrap()
279 .wait()
280 .unwrap();
281
282 Ok(Value::Empty)
283 },
284 "dnf::repos" => {
285 let tuple = argument.as_tuple()?;
286 let mut repos = String::new();
287
288 for repo in tuple {
289 let repo = repo.as_string()?;
290
291 repos.push_str(&repo);
292 repos.push(' ');
293 }
294 let script = format!("dnf -y config-manager --add-repo {repos}");
295
296 Command::new("fish")
297 .arg("-c")
298 .arg(script)
299 .spawn()
300 .unwrap()
301 .wait()
302 .unwrap();
303
304 Ok(Value::Empty)
305 },
306 "dnf::upgrade" => {
307 Command::new("fish")
308 .arg("-c")
309 .arg("dnf -y upgrade")
310 .spawn()
311 .unwrap()
312 .wait()
313 .unwrap();
314
315 Ok(Value::Empty)
316 },
317 "file::append" => {
318 let strings = argument.as_tuple()?;
319
320 if strings.len() < 2 {
321 return Err(EvalexprError::WrongFunctionArgumentAmount {
322 expected: 2,
323 actual: strings.len(),
324 });
325 }
326
327 let path = strings.first().unwrap().as_string()?;
328 let mut file = std::fs::OpenOptions::new().append(true).open(path).unwrap();
329
330 for content in &strings[1..] {
331 let content = content.as_string()?;
332
333 file.write_all(content.as_bytes()).unwrap();
334 }
335
336 Ok(Value::Empty)
337 },
338 "file::metadata" => {
339 let path = argument.as_string()?;
340 let metadata = std::fs::metadata(path).unwrap();
341
342 Ok(Value::String(format!("{:#?}", metadata)))
343 },
344 "file::move" => {
345 let mut paths = argument.as_tuple()?;
346
347 if paths.len() != 2 {
348 return Err(EvalexprError::WrongFunctionArgumentAmount {
349 expected: 2,
350 actual: paths.len(),
351 });
352 }
353
354 let to = paths.pop().unwrap().as_string()?;
355 let from = paths.pop().unwrap().as_string()?;
356
357 std::fs::copy(&from, to)
358 .and_then(|_| std::fs::remove_file(from))
359 .unwrap();
360
361 Ok(Value::Empty)
362 },
363 "file::read" => {
364 let path = argument.as_string()?;
365 let mut contents = String::new();
366
367 fs::OpenOptions::new()
368 .read(true)
369 .create(false)
370 .open(&path)
371 .unwrap()
372 .read_to_string(&mut contents)
373 .unwrap();
374
375 Ok(Value::String(contents))
376 },
377 "file::remove" => {
378 let path = argument.as_string()?;
379 std::fs::remove_file(path).unwrap();
380
381 Ok(Value::Empty)
382 },
383 "file::trash" => todo!(),
384 "file::write" => {
385 let strings = argument.as_tuple()?;
386
387 if strings.len() < 2 {
388 return Err(EvalexprError::WrongFunctionArgumentAmount {
389 expected: 2,
390 actual: strings.len(),
391 });
392 }
393
394 let path = strings.first().unwrap().as_string()?;
395 let mut file = fs::OpenOptions::new()
396 .write(true)
397 .create(true)
398 .truncate(true)
399 .open(path)
400 .unwrap();
401
402 for content in &strings[1..] {
403 let content = content.as_string()?;
404
405 file.write_all(content.as_bytes()).unwrap();
406 }
407
408 Ok(Value::Empty)
409 },
410 "gui::window" => todo!(),
411 "gui" => todo!(),
412 "map" => todo!(),
413 "network::download" => todo!(),
414 "network::hostname" => todo!(),
415 "network::scan" => todo!(),
416 "os::status" => {
417 Command::new("fish")
418 .arg("-c")
419 .arg("rpm-ostree status")
420 .spawn()
421 .unwrap()
422 .wait()
423 .unwrap();
424 Ok(Value::Empty)
425 },
426 "os::upgrade" => {
427 Command::new("fish")
428 .arg("-c")
429 .arg("rpm-ostree upgrade")
430 .spawn()
431 .unwrap()
432 .wait()
433 .unwrap();
434 Ok(Value::Empty)
435 },
436 "output" => {
437 println!("{}", argument);
438 Ok(Value::Empty)
439 },
440 "random::boolean" => todo!(),
441 "random::float" => todo!(),
442 "random::int" => todo!(),
443 "random::string" => todo!(),
444 "run::async" => todo!(),
445 "run::sync" => todo!(),
446 "run" => todo!(),
447 "rust::packages" => todo!(),
448 "rust::toolchain" => todo!(),
449 "shell::bash" => todo!(),
450 "shell::fish" => todo!(),
451 "shell::nushell" => todo!(),
452 "shell::sh" => todo!(),
453 "shell::zsh" => todo!(),
454 "system::info" => todo!(),
455 "system::monitor" => todo!(),
456 "system::processes" => todo!(),
457 "system::services" => todo!(),
458 "system::users" => todo!(),
459 "time::now" => time::now(),
460 "time::today" => time::today(),
461 "time::tomorrow" => time::tomorrow(),
462 "time" => time::now(),
463 "toolbox::create" => todo!(),
464 "toolbox::enter" => todo!(),
465 "toolbox::image::build" => todo!(),
466 "toolbox::image::list" => todo!(),
467 "toolbox::list" => todo!(),
468 "trash" => todo!(),
469 "wait" => todo!(),
470 "watch" => todo!(),
471 _ => Err(EvalexprError::FunctionIdentifierNotFound(
472 identifier.to_string(),
473 )),
474 }
475}