use crate::dir_context::PathResolver;
use crate::runtime::Runtime;
use crate::{Error, Result};
use mlua::{Lua, Table, Value};
use std::collections::{BTreeSet, HashSet};
pub fn process_lua_eval_result(_lua: &Lua, res: mlua::Result<Value>, script: &str) -> Result<Value> {
let res = match res {
Ok(res) => res,
Err(err) => return Err(Error::from_error_with_script(&err, script)),
};
let res = match res {
Value::Error(err) => {
return Err(Error::from_error_with_script(&err, script));
}
res => res,
};
Ok(res)
}
pub fn into_vec_of_strings(value: Value, err_prefix: &'static str) -> mlua::Result<Vec<String>> {
match value {
Value::String(lua_string) => {
let string_value = lua_string.to_str()?.to_string();
Ok(vec![string_value])
}
Value::Table(lua_table) => {
let mut result = Vec::new();
for pair in lua_table.sequence_values::<String>() {
match pair {
Ok(s) => result.push(s),
Err(_) => {
return Err(mlua::Error::FromLuaConversionError {
from: "table",
to: "Vec<String>".to_string(),
message: Some(format!("{err_prefix} - Table contains non-string values")),
});
}
}
}
Ok(result)
}
_ => Err(mlua::Error::FromLuaConversionError {
from: "unknown",
to: "Vec<String>".to_string(),
message: Some(format!("{err_prefix} - Expected a string or a list of strings")),
}),
}
}
pub fn into_option_string(value: mlua::Value, err_prefix: &str) -> mlua::Result<Option<String>> {
match value {
Value::Nil => Ok(None),
Value::String(string) => Ok(Some(string.to_string_lossy())),
other => Err(crate::Error::Custom(format!(
"{err_prefix} - accepted argument types are String or Nil, but was {type_name}",
type_name = other.type_name()
))
.into()),
}
}
pub fn get_value_prop_as_string(
value: Option<&mlua::Value>,
prop_name: &str,
err_prefix: &str,
) -> mlua::Result<Option<String>> {
let Some(value) = value else { return Ok(None) };
let table = value.as_table().ok_or_else(|| {
crate::Error::custom(format!(
"{err_prefix} - value should be of type lua table, but was of another type."
))
})?;
match table.get::<Option<Value>>(prop_name)? {
Some(Value::String(string)) => {
Ok(Some(string.to_string_lossy()))
}
Some(_other) => Err(crate::Error::custom(format!(
"{err_prefix} options.base_dir must be of type string is present"
))
.into()),
None => Ok(None),
}
}
pub fn path_exists(runtime: &Runtime, path: &str) -> bool {
let dir_context = runtime.dir_context();
let full_path = dir_context
.resolve_path(runtime.session(), path.into(), PathResolver::WksDir, None)
.ok();
full_path.map(|p| p.exists()).unwrap_or(false)
}
pub fn expect_table(value: Value, ctx: &str, item_label: &str) -> mlua::Result<Table> {
match value {
Value::Table(table) => Ok(table),
other => Err(Error::custom(format!(
"{ctx} - {item_label} must be a table. Found '{}'",
other.type_name()
))
.into()),
}
}
pub fn collect_string_sequence(value: Value, ctx: &str, item_label: &str) -> mlua::Result<Vec<mlua::String>> {
let table = expect_table(value, ctx, item_label)?;
let mut items: Vec<mlua::String> = Vec::new();
for (idx, value) in table.sequence_values::<Value>().enumerate() {
let value = value?;
match value {
Value::String(s) => items.push(s),
other => {
return Err(Error::custom(format!(
"{ctx} - {item_label} must be strings. Found '{}' at index {}",
other.type_name(),
idx + 1
))
.into());
}
}
}
Ok(items)
}
pub fn collect_sequence_values(value: Value, ctx: &str, item_label: &str) -> mlua::Result<Vec<Value>> {
let table = expect_table(value, ctx, item_label)?;
let mut values: Vec<Value> = Vec::new();
for value in table.sequence_values::<Value>() {
values.push(value?);
}
Ok(values)
}
pub fn collect_rows_and_intersection(recs: Value, ctx: &str) -> mlua::Result<(Vec<Table>, BTreeSet<String>)> {
let recs = expect_table(recs, ctx, "Records list")?;
let mut rows: Vec<Table> = Vec::new();
for row_val in recs.sequence_values::<Value>() {
let row_val = row_val?;
let row_tbl = match row_val {
Value::Table(t) => t,
other => {
return Err(Error::custom(format!(
"{ctx} - Each record must be a table. Found '{}'",
other.type_name()
))
.into());
}
};
rows.push(row_tbl);
}
if rows.is_empty() {
return Ok((rows, BTreeSet::new()));
}
let mut intersect: Option<HashSet<String>> = None;
for row in &rows {
let mut keys_this_row: HashSet<String> = HashSet::new();
for pair in row.pairs::<Value, Value>() {
let (key, _value) = pair?;
let key_str = match key {
Value::String(s) => s.to_string_lossy(),
other => {
return Err(Error::custom(format!(
"{ctx} - Record keys must be strings. Found key of type '{}'",
other.type_name()
))
.into());
}
};
keys_this_row.insert(key_str);
}
intersect = Some(match intersect.take() {
None => keys_this_row,
Some(prev) => prev.intersection(&keys_this_row).cloned().collect(),
});
}
let ordered_keys = intersect.unwrap_or_default().into_iter().collect::<BTreeSet<_>>();
Ok((rows, ordered_keys))
}
pub fn build_columnar_table(lua: &Lua, rows: &[Table], ordered_keys: &BTreeSet<String>) -> mlua::Result<Table> {
let out = lua.create_table()?;
for key in ordered_keys {
let col = lua.create_table()?;
for (idx, row) in rows.iter().enumerate() {
let val: Value = row.get(key.as_str())?;
col.set(idx + 1, val)?;
}
out.set(key.as_str(), col)?;
}
Ok(out)
}