use std::{
borrow::Cow,
ffi::OsStr,
fmt::Display,
fs::{read_dir, read_to_string},
path::{Path, PathBuf},
};
use maybe_path::MaybePathBuf;
use serde::{Deserialize, Serialize};
#[macro_export]
macro_rules! module {
($filename:literal, $contents:literal) => {
$crate::Module::new_static($filename, $contents)
};
($filename:literal) => {
Module::new_static($filename, include_str!($filename))
};
}
#[macro_export]
macro_rules! include_module {
($filename:literal) => {
Module::new_static($filename, include_str!($filename))
};
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Default)]
pub struct Module {
filename: MaybePathBuf<'static>,
contents: Cow<'static, str>,
}
impl<'de> Deserialize<'de> for Module {
fn deserialize<D>(deserializer: D) -> Result<Module, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct OwnedModule {
filename: PathBuf,
contents: String,
}
let OwnedModule { filename, contents } = OwnedModule::deserialize(deserializer)?;
Ok(Module::new(filename, contents))
}
}
impl Display for Module {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.filename().display())
}
}
impl Module {
#[must_use]
pub fn new(filename: impl AsRef<Path>, contents: impl ToString) -> Self {
let filename = MaybePathBuf::Owned(filename.as_ref().to_path_buf());
let contents = Cow::Owned(contents.to_string());
Self { filename, contents }
}
#[must_use]
pub const fn new_static(filename: &'static str, contents: &'static str) -> Self {
Self {
filename: MaybePathBuf::new_str(filename),
contents: Cow::Borrowed(contents),
}
}
pub fn load(filename: impl AsRef<Path>) -> Result<Self, std::io::Error> {
let contents = read_to_string(filename.as_ref())?;
Ok(Self::new(filename, &contents))
}
pub fn load_dir(directory: impl AsRef<Path>) -> Result<Vec<Self>, std::io::Error> {
let mut files: Vec<Self> = Vec::new();
for file in read_dir(directory)? {
let file = file?;
if let Some(filename) = file.path().to_str() {
let extension = Path::new(&filename)
.extension()
.and_then(OsStr::to_str)
.unwrap_or_default();
if !["js", "ts"].contains(&extension) {
continue;
}
files.push(Self::load(filename)?);
}
}
Ok(files)
}
#[must_use]
pub fn filename(&self) -> &Path {
self.filename.as_ref()
}
#[must_use]
pub fn contents(&self) -> &str {
&self.contents
}
}
#[cfg(test)]
mod test_module {
use super::*;
#[test]
fn test_new_module() {
let module = Module::new("module.js", "console.log('Hello, World!');");
assert_eq!(module.filename().to_str().unwrap(), "module.js");
assert_eq!(module.contents(), "console.log('Hello, World!');");
}
#[test]
fn test_load_module() {
let module =
Module::load("src/ext/rustyscript/rustyscript.js").expect("Failed to load module");
assert_eq!(
module.filename().to_str().unwrap(),
"src/ext/rustyscript/rustyscript.js"
);
}
#[test]
fn test_load_dir() {
let modules =
Module::load_dir("src/ext/rustyscript").expect("Failed to load modules from directory");
assert!(!modules.is_empty());
}
}