use crate::{Environment, eval::apply::eval_apply, expand_tilde, is_number_eq, value::Value};
pub(crate) fn builtin(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if let [path] = args {
match eval_apply(path, env)? {
Value::String(file_path) => expand_tilde(file_path).and_then(|real_path| {
match std::fs::File::options()
.read(true)
.open(&real_path)
.and_then(|f| read_file(f, 0))
{
Ok(value) => Ok(value),
Err(e) => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Read]: ",
"Unable to read contents of file at `{}` with `{}`."
),
real_path.display(),
e
))),
}
}),
e => Err(std::sync::Arc::from(format!(
concat!("Error[ksl::builtin::Read]: ", "Unexpected value: `{}`."),
e
))),
}
} else if let [path, bytes] = args {
match (eval_apply(path, env.clone())?, eval_apply(bytes, env)?) {
(Value::String(file_path), Value::Number(limit)) if is_number_eq(limit.trunc(), limit) => expand_tilde(file_path)
.and_then(|real_path| {
match std::fs::File::options()
.read(true)
.open(&real_path)
.and_then(|f| read_file(f, limit as u64))
{
Ok(value) => Ok(value),
Err(e) => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Read]: ",
"Unable to read contents of file at `{}` with `{}`."
),
real_path.display(),
e
))),
}
}),
(Value::String(_), e) => Err(std::sync::Arc::from(format!(
concat!("Error[ksl::builtin::Read]: ", "Unexpected value: `{}`."),
e
))),
(e, _) => Err(std::sync::Arc::from(format!(
concat!("Error[ksl::builtin::Read]: ", "Unexpected value: `{}`."),
e
))),
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Read]: ",
"Expected 1 or 2 parameters, but {} were passed."
),
args.len()
)))
}
}
fn read_file(file: std::fs::File, limit: u64) -> std::io::Result<Value> {
let is_regular = if let Ok(meta) = file.metadata() {
meta.file_type().is_file()
} else {
false
};
if is_regular {
let mut buffer = Vec::new();
std::io::Read::read_to_end(&mut std::io::BufReader::new(file), &mut buffer).map(|_| {
match String::from_utf8(buffer.clone()) {
Ok(file_content) => Value::String(std::sync::Arc::from(if limit == 0 {
file_content
} else {
file_content
.chars()
.take(limit as usize)
.collect::<String>()
})),
Err(_) => Value::List(buffer_to_u32_numbers(buffer, limit)),
}
})
} else if limit != 0 {
let mut buffer = Vec::new();
std::io::Read::read_to_end(
&mut std::io::Read::take(std::io::BufReader::new(file), limit * 4),
&mut buffer,
)
.map(|_| Value::List(buffer_to_u32_numbers(buffer, limit)))
} else {
Err(std::io::Error::from(std::io::ErrorKind::Interrupted))
}
}
fn buffer_to_u32_numbers(buffer: Vec<u8>, limit: u64) -> std::sync::Arc<[Value]> {
let buffer = buffer.chunks_exact(4).map(|n| {
Value::Number({
let mut combined = [0u8; 4];
combined[..n.len()].copy_from_slice(n);
u32::from_le_bytes(combined) as f64
})
});
if limit == 0 {
buffer.collect::<std::sync::Arc<[Value]>>()
} else {
buffer
.take(limit as usize)
.collect::<std::sync::Arc<[Value]>>()
}
}