use super::error::KResult;
use crate::engine::CFG_PATH;
use camino::Utf8PathBuf;
use eyre::WrapErr;
use konfigkoll_utils::safe_path_join;
use rune::Any;
use rune::ContextError;
use rune::Module;
use rune::alloc::fmt::TryWrite;
use rune::runtime::Bytes;
use rune::runtime::Formatter;
use rune::vm_write;
use std::io::ErrorKind;
use std::io::Read;
#[derive(Debug, Any, thiserror::Error)]
#[rune(item = ::filesystem)]
enum FileError {
#[error("IO Error: {0}")]
IoError(#[from] std::io::Error),
#[error("Allocation error: {0}")]
AllocError(#[from] rune::alloc::Error),
}
impl FileError {
#[rune::function(vm_result, protocol = STRING_DISPLAY)]
pub(crate) fn display(&self, f: &mut Formatter) {
vm_write!(f, "{}", self);
}
#[rune::function(vm_result, protocol = STRING_DEBUG)]
pub(crate) fn debug(&self, f: &mut Formatter) {
vm_write!(f, "{:?}", self);
}
}
#[derive(Debug, Any)]
#[rune(item = ::filesystem)]
struct TempDir {
path: Utf8PathBuf,
}
impl Drop for TempDir {
fn drop(&mut self) {
std::fs::remove_dir_all(&self.path).expect("Failed to remove temporary directory");
}
}
impl TempDir {
#[rune::function(vm_result, protocol = STRING_DEBUG)]
fn debug(&self, f: &mut Formatter) {
vm_write!(f, "{:?}", self);
}
#[rune::function(path = Self::new)]
fn new() -> KResult<Self> {
let dir = tempfile::TempDir::with_prefix("konfigkoll_")
.wrap_err("Failed to create temporary directory")?
.keep();
match Utf8PathBuf::from_path_buf(dir) {
Ok(path) => Ok(Self { path }),
Err(path) => {
std::fs::remove_dir_all(&path).expect("Failed to remove temporary directory");
Err(eyre::eyre!("Failed to convert path to utf8: {path:?}").into())
}
}
}
#[rune::function]
fn path(&self) -> String {
self.path.to_string()
}
#[rune::function]
fn write(&self, path: &str, contents: &[u8]) -> KResult<String> {
let p = safe_path_join(&self.path, path.into());
std::fs::write(&p, contents).wrap_err_with(|| format!("Failed to write to {p}"))?;
Ok(p.into_string())
}
#[rune::function]
fn read(&self, path: &str) -> KResult<Bytes> {
let p = safe_path_join(&self.path, path.into());
let data = std::fs::read(&p).wrap_err_with(|| format!("Failed to read {p}"))?;
Ok(Bytes::from_vec(
data.try_into().wrap_err("Failed to convert data")?,
))
}
}
#[derive(Debug, Any)]
#[rune(item = ::filesystem)]
struct File {
file: std::fs::File,
#[allow(dead_code)]
need_root: bool,
}
impl File {
#[rune::function(vm_result, protocol = STRING_DEBUG)]
pub(crate) fn debug(&self, f: &mut Formatter) {
vm_write!(f, "{:?}", self);
}
#[rune::function(path = Self::open)]
pub fn open(path: &str) -> KResult<Self> {
let file = std::fs::File::open(path).wrap_err_with(|| format!("Failed to open {path}"))?;
Ok(Self {
file,
need_root: false,
})
}
#[rune::function(path = Self::open_as_root)]
pub fn open_as_root(path: &str) -> KResult<Self> {
let file =
std::fs::File::open(path).wrap_err_with(|| format!("Failed to open {path} as root"))?;
Ok(Self {
file,
need_root: true,
})
}
#[rune::function(path = Self::open_from_config)]
pub fn open_from_config(path: &str) -> KResult<Self> {
let p = safe_path_join(CFG_PATH.get().expect("CFG_PATH not set"), path.into());
let file = std::fs::File::open(&p)
.wrap_err_with(|| format!("Failed to open {path} from config directory, tried {p}"))?;
Ok(Self {
file,
need_root: false,
})
}
#[rune::function]
pub fn read_all_string(&mut self) -> Result<String, std::io::Error> {
let mut buf = String::new();
self.file.read_to_string(&mut buf)?;
Ok(buf)
}
#[rune::function]
pub fn read_all_bytes(&mut self) -> Result<Bytes, FileError> {
let mut buf = Vec::new();
self.file.read_to_end(&mut buf)?;
let buf = rune::alloc::Vec::try_from(buf)?;
Ok(buf.into())
}
}
#[rune::function]
fn exists(path: &str) -> Result<bool, std::io::Error> {
let metadata = std::fs::symlink_metadata(path);
match metadata {
Ok(_) => Ok(true),
Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
Err(err) => Err(err),
}
}
#[rune::function]
fn glob(pattern: &str) -> KResult<Vec<String>> {
let paths = glob::glob(pattern).wrap_err("Failed to construct glob")?;
let mut result = Vec::new();
for path in paths {
result.push(path.wrap_err("Glob error")?.to_string_lossy().to_string());
}
Ok(result)
}
#[rune::function]
fn config_path() -> String {
CFG_PATH.get().expect("CFG_PATH not set").to_string()
}
#[rune::module(::filesystem)]
pub(crate) fn module() -> Result<Module, ContextError> {
let mut m = Module::from_meta(module_meta)?;
m.ty::<File>()?;
m.function_meta(File::debug)?;
m.function_meta(File::open)?;
m.function_meta(File::open_as_root)?;
m.function_meta(File::open_from_config)?;
m.function_meta(File::read_all_string)?;
m.function_meta(File::read_all_bytes)?;
m.ty::<FileError>()?;
m.function_meta(FileError::display)?;
m.function_meta(FileError::debug)?;
m.ty::<TempDir>()?;
m.function_meta(TempDir::debug)?;
m.function_meta(TempDir::new)?;
m.function_meta(TempDir::path)?;
m.function_meta(TempDir::read)?;
m.function_meta(TempDir::write)?;
m.function_meta(exists)?;
m.function_meta(glob)?;
m.function_meta(config_path)?;
Ok(m)
}