use std::{
collections::BTreeMap,
fmt::Display,
fs,
io::{Read, Write},
process::Command,
};
use crate::{
container,
function::Function,
time,
value::{value_type::ValueType, Value},
EvalexprError, EvalexprResult,
};
mod predefined;
pub trait Context {
fn get_value(&self, identifier: &str) -> Option<&Value>;
fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value>;
}
pub trait ContextWithMutableVariables: Context {
fn set_value(&mut self, _identifier: &str, _value: Value) -> EvalexprResult<()> {
Err(EvalexprError::ContextNotMutable)
}
}
pub trait ContextWithMutableFunctions: Context {
fn set_function(&mut self, _identifier: String, _function: Function) -> EvalexprResult<()> {
Err(EvalexprError::ContextNotMutable)
}
}
#[derive(Clone, Debug, Default)]
pub struct VariableMap {
variables: BTreeMap<String, Value>,
}
impl Display for VariableMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(")?;
for (key, value) in &self.variables {
write!(f, " {} = {};", key, value)?;
}
write!(f, " )")
}
}
impl PartialEq for VariableMap {
fn eq(&self, other: &Self) -> bool {
if self.variables.len() != other.variables.len() {
return false;
}
for variable in &self.variables {
for other in &other.variables {
if variable != other {
return false;
}
}
}
true
}
}
impl VariableMap {
pub fn new() -> Self {
Default::default()
}
}
impl Context for VariableMap {
fn get_value(&self, identifier: &str) -> Option<&Value> {
let split = identifier.split_once(".");
if let Some((map_name, next_identifier)) = split {
let value = self.variables.get(map_name)?;
if let Value::Map(map) = value {
map.get_value(next_identifier)
} else {
None
}
} else {
self.variables.get(identifier)
}
}
fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value> {
call_whale_function(identifier, argument)
}
}
impl ContextWithMutableVariables for VariableMap {
fn set_value(&mut self, identifier: &str, value: Value) -> EvalexprResult<()> {
let split = identifier.split_once(".");
if let Some((map_name, next_identifier)) = split {
if let Some(map_value) = self.variables.get_mut(map_name) {
if let Value::Map(map) = map_value {
map.set_value(next_identifier, value)
} else {
return Err(EvalexprError::ExpectedMap {
actual: map_value.clone(),
});
}
} else {
let mut new_map = VariableMap {
variables: BTreeMap::new(),
};
new_map.set_value(next_identifier, value)?;
self.variables
.insert(map_name.to_string(), Value::Map(new_map));
Ok(())
}
} else if self.variables.contains_key(identifier) {
Err(EvalexprError::ExpectedMap {
actual: value.clone(),
})
} else {
self.variables.insert(identifier.to_string(), value);
Ok(())
}
}
}
impl ContextWithMutableFunctions for VariableMap {
fn set_function(&mut self, identifier: String, function: Function) -> EvalexprResult<()> {
todo!()
}
}
#[macro_export]
macro_rules! context_map {
( ($ctx:expr) $k:expr => Function::new($($v:tt)*) ) =>
{ $crate::context_map!(($ctx) $k => Function::new($($v)*),) };
( ($ctx:expr) $k:expr => $v:expr ) =>
{ $crate::context_map!(($ctx) $k => $v,) };
( ($ctx:expr) ) => { Ok(()) };
( ($ctx:expr) $k:expr => Function::new($($v:tt)*) , $($tt:tt)*) => {{
$crate::ContextWithMutableFunctions::set_function($ctx, $k.into(), $crate::Function::new($($v)*))
.and($crate::context_map!(($ctx) $($tt)*))
}};
( ($ctx:expr) $k:expr => $v:expr , $($tt:tt)*) => {{
$crate::ContextWithMutableVariables::set_value($ctx, $k.into(), $v.into())
.and($crate::context_map!(($ctx) $($tt)*))
}};
( $($tt:tt)* ) => {{
let mut context = $crate::VariableMap::new();
$crate::context_map!((&mut context) $($tt)*)
.map(|_| context)
}};
}
fn call_whale_function(identifier: &str, argument: &Value) -> Result<Value, EvalexprError> {
match identifier {
"container::image::build" => container::image::build(argument),
"container::image::list" => container::image::list(argument),
"container::list" => container::list(argument),
"container::run" => container::run(argument),
"convert" => todo!(),
"dir::create" => {
let path = argument.as_string()?;
std::fs::create_dir_all(path).unwrap();
Ok(Value::Empty)
},
"dir::read" => {
let path = argument.as_string()?;
let files = std::fs::read_dir(path)
.unwrap()
.map(|entry| entry.unwrap().file_name().into_string().unwrap_or_default())
.collect();
Ok(Value::String(files))
},
"dir::remove" => {
let path = argument.as_string()?;
std::fs::remove_file(path).unwrap();
Ok(Value::Empty)
},
"dir::trash" => todo!(),
"dnf::copr" => {
let repo_name = if let Ok(string) = argument.as_string() {
string
} else if let Ok(tuple) = argument.as_tuple() {
let mut repos = String::new();
for repo in tuple {
let repo = repo.as_string()?;
repos.push_str(&repo);
repos.push(' ');
}
repos
} else {
return Err(EvalexprError::ExpectedString {
actual: argument.clone(),
});
};
let script = format!("dnf -y copr enable {repo_name}");
Command::new("fish")
.arg("-c")
.arg(script)
.spawn()
.unwrap()
.wait()
.unwrap();
Ok(Value::Empty)
},
"dnf::packages" => {
let tuple = argument.as_tuple()?;
let mut packages = String::new();
for package in tuple {
let package = package.as_string()?;
packages.push_str(&package);
packages.push(' ');
}
let script = format!("dnf -y install {packages}");
Command::new("fish")
.arg("-c")
.arg(script)
.spawn()
.unwrap()
.wait()
.unwrap();
Ok(Value::Empty)
},
"dnf::repos" => {
let tuple = argument.as_tuple()?;
let mut repos = String::new();
for repo in tuple {
let repo = repo.as_string()?;
repos.push_str(&repo);
repos.push(' ');
}
let script = format!("dnf -y config-manager --add-repo {repos}");
Command::new("fish")
.arg("-c")
.arg(script)
.spawn()
.unwrap()
.wait()
.unwrap();
Ok(Value::Empty)
},
"dnf::upgrade" => {
Command::new("fish")
.arg("-c")
.arg("dnf -y upgrade")
.spawn()
.unwrap()
.wait()
.unwrap();
Ok(Value::Empty)
},
"file::append" => {
let strings = argument.as_tuple()?;
if strings.len() < 2 {
return Err(EvalexprError::WrongFunctionArgumentAmount {
expected: 2,
actual: strings.len(),
});
}
let path = strings.first().unwrap().as_string()?;
let mut file = std::fs::OpenOptions::new().append(true).open(path).unwrap();
for content in &strings[1..] {
let content = content.as_string()?;
file.write_all(content.as_bytes()).unwrap();
}
Ok(Value::Empty)
},
"file::metadata" => {
let path = argument.as_string()?;
let metadata = std::fs::metadata(path).unwrap();
Ok(Value::String(format!("{:#?}", metadata)))
},
"file::move" => {
let mut paths = argument.as_tuple()?;
if paths.len() != 2 {
return Err(EvalexprError::WrongFunctionArgumentAmount {
expected: 2,
actual: paths.len(),
});
}
let to = paths.pop().unwrap().as_string()?;
let from = paths.pop().unwrap().as_string()?;
std::fs::copy(&from, to)
.and_then(|_| std::fs::remove_file(from))
.unwrap();
Ok(Value::Empty)
},
"file::read" => {
let path = argument.as_string()?;
let mut contents = String::new();
fs::OpenOptions::new()
.read(true)
.create(false)
.open(&path)
.unwrap()
.read_to_string(&mut contents)
.unwrap();
Ok(Value::String(contents))
},
"file::remove" => {
let path = argument.as_string()?;
std::fs::remove_file(path).unwrap();
Ok(Value::Empty)
},
"file::trash" => todo!(),
"file::write" => {
let strings = argument.as_tuple()?;
if strings.len() < 2 {
return Err(EvalexprError::WrongFunctionArgumentAmount {
expected: 2,
actual: strings.len(),
});
}
let path = strings.first().unwrap().as_string()?;
let mut file = fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
for content in &strings[1..] {
let content = content.as_string()?;
file.write_all(content.as_bytes()).unwrap();
}
Ok(Value::Empty)
},
"gui::window" => todo!(),
"gui" => todo!(),
"map" => todo!(),
"network::download" => todo!(),
"network::hostname" => todo!(),
"network::scan" => todo!(),
"os::status" => {
Command::new("fish")
.arg("-c")
.arg("rpm-ostree status")
.spawn()
.unwrap()
.wait()
.unwrap();
Ok(Value::Empty)
},
"os::upgrade" => {
Command::new("fish")
.arg("-c")
.arg("rpm-ostree upgrade")
.spawn()
.unwrap()
.wait()
.unwrap();
Ok(Value::Empty)
},
"output" => {
println!("{}", argument);
Ok(Value::Empty)
},
"random::boolean" => todo!(),
"random::float" => todo!(),
"random::int" => todo!(),
"random::string" => todo!(),
"run::async" => todo!(),
"run::sync" => todo!(),
"run" => todo!(),
"rust::packages" => todo!(),
"rust::toolchain" => todo!(),
"shell::bash" => todo!(),
"shell::fish" => todo!(),
"shell::nushell" => todo!(),
"shell::sh" => todo!(),
"shell::zsh" => todo!(),
"system::info" => todo!(),
"system::monitor" => todo!(),
"system::processes" => todo!(),
"system::services" => todo!(),
"system::users" => todo!(),
"time::now" => time::now(),
"time::today" => time::today(),
"time::tomorrow" => time::tomorrow(),
"time" => time::now(),
"toolbox::create" => todo!(),
"toolbox::enter" => todo!(),
"toolbox::image::build" => todo!(),
"toolbox::image::list" => todo!(),
"toolbox::list" => todo!(),
"trash" => todo!(),
"wait" => todo!(),
"watch" => todo!(),
_ => Err(EvalexprError::FunctionIdentifierNotFound(
identifier.to_string(),
)),
}
}