#![feature(let_chains)]
mod vec_or_str;
use std::{
collections::HashMap,
fs::File,
io::Read,
path::{Path, PathBuf},
rc::Rc,
};
pub use boa_engine;
use boa_engine::{
js_string,
module::SimpleModuleLoader,
object::builtins::{JsArray, JsUint8Array},
property::{Attribute, PropertyKey},
string::JsString,
Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsValue, Module, NativeFunction,
Source,
};
use boa_runtime::Console;
use thiserror::Error;
use tracing::error;
pub use vec_or_str::VecOrStr;
#[derive(Error, Debug)]
pub enum Error {
#[error("Js: {0}\n{1}")]
Js(String, String),
}
fn read_file_to_vec(path: impl AsRef<Path>) -> std::io::Result<Vec<u8>> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Ok(buffer)
}
fn vec_to_uint8array(context: &mut Context, data: Vec<u8>) -> JsResult<JsValue> {
Ok(JsValue::Object(
JsUint8Array::from_iter(data, context)?.into(),
))
}
macro_rules! throw {
($str:expr $(,$arg:tt)* $(,)?) => {{
let err: JsError = JsNativeError::typ()
.with_message(format!($str,$($arg,)*))
.into();
return Err(err);
}};
}
pub fn ctx(root: &str) -> Context {
let loader = Rc::new(SimpleModuleLoader::new(root).unwrap());
let mut binding = Context::builder()
.module_loader(loader.clone())
.build()
.unwrap();
let ctx = &mut binding;
let console = Console::init(ctx);
{
ctx
.register_global_builtin_callable("rDir".into(), 1, unsafe {
NativeFunction::from_closure(move |_, args, ctx| {
let fp = args.get_or_undefined(0);
if fp.is_undefined() {
throw!("rDir: miss arg dir")
}
match std::fs::read_dir(fp.to_string(ctx)?.to_std_string_escaped()) {
Ok(r) => {
let dir_li = JsArray::new(ctx);
let file_li = JsArray::new(ctx);
for i in r.flatten() {
if let Ok(file_type) = i.file_type() {
let li = if file_type.is_dir() {
&dir_li
} else {
&file_li
};
let name = i.file_name().to_os_string().to_string_lossy().to_string();
let name = JsString::from(name);
li.push(name, ctx)?;
}
}
let li = JsArray::new(ctx);
li.push(dir_li, ctx)?;
li.push(file_li, ctx)?;
Ok(li.into())
}
Err(e) => {
throw!("rDir: {e}")
}
}
})
})
.unwrap();
};
{
ctx
.register_global_builtin_callable("wPath".into(), 1, unsafe {
NativeFunction::from_closure(move |_, args, ctx| {
let fp = args.get_or_undefined(0);
if fp.is_undefined() {
throw!("wPath: miss arg path")
}
let fpstr = &fp.to_string(ctx)?.to_std_string_escaped();
let fp: PathBuf = fpstr.into();
let bin = args.get_or_undefined(1);
if bin.is_undefined() {
throw!("wPath {fpstr}: miss data")
}
if let Some(dir) = fp.parent() {
if let Err(err) = std::fs::create_dir_all(dir) {
throw!("wPath {fpstr}: {err}")
}
}
if let Some(txt) = bin.as_string() {
let txt = txt.to_std_string_escaped();
return match std::fs::write(fp, txt.as_bytes()) {
Ok(_) => Ok(JsValue::undefined()),
Err(e) => {
throw!("wPath {fpstr}: {e}")
}
};
};
throw!("wPath {fpstr}: unsupport data type")
})
})
.unwrap();
}
{
ctx
.register_global_builtin_callable("rBin".into(), 1, unsafe {
NativeFunction::from_closure(move |_, args, ctx| {
let name = args.get_or_undefined(0);
if name.is_undefined() {
throw!("rBin: miss arg path")
}
let fp = name.to_string(ctx)?.to_std_string_escaped();
match read_file_to_vec(&fp) {
Ok(s) => Ok(vec_to_uint8array(ctx, s)?),
Err(e) => {
throw!("rBin('{fp}') : {e}")
}
}
})
})
.unwrap();
}
ctx
.register_global_builtin_callable("rStr".into(), 1, unsafe {
NativeFunction::from_closure(move |_, args, ctx| {
let name = args.get_or_undefined(0);
if name.is_undefined() {
throw!("rStr: miss arg path")
}
let fp = name.to_string(ctx)?.to_std_string_escaped();
match std::fs::read_to_string(&fp) {
Ok(s) => Ok(JsValue::String(JsString::from(s))),
Err(e) => {
throw!("rStr('{fp}') : {e}")
}
}
})
})
.unwrap();
ctx
.register_global_property(js_string!(Console::NAME), console, Attribute::all())
.unwrap();
binding
}
pub fn _default(ctx: &mut Context, js_code: &str, args: &[JsValue]) -> JsResult<JsValue> {
let code = Source::from_bytes(js_code);
let module = Module::parse(code, None, ctx)?;
let _promise = module.load_link_evaluate(ctx);
ctx.run_jobs();
let namespace = module.namespace(ctx);
let mix = namespace
.get(js_string!("default"), ctx)?
.as_callable()
.cloned()
.ok_or_else(|| JsNativeError::typ().with_message("export default not function !"))?;
let r = mix.call(&JsValue::undefined(), args, ctx)?;
Ok(r)
}
pub fn default(
ctx: &mut Context,
fp: impl Into<PathBuf>,
args: &[JsValue],
) -> Result<JsValue, Error> {
let fp = fp.into();
macro_rules! ok {
($r:expr) => {
match $r {
Ok(r) => r,
Err(e) => return Err(Error::Js(fp.display().to_string(), e.to_string())),
}
};
}
let js_code = ok!(std::fs::read_to_string(&fp));
Ok(ok!(_default(ctx, &js_code, args)))
}
pub fn obj2map(obj: JsValue) -> Result<HashMap<String, JsValue>, JsError> {
if let JsValue::Object(ref obj) = obj {
let obj = obj.borrow();
let key_li = obj.shape().keys();
let mut r = HashMap::with_capacity(key_li.len());
let map = obj.properties();
for key in key_li {
if let Some(val) = map.get(&key) {
if let Some(val) = val.value() {
if let PropertyKey::String(key) = key {
r.insert(key.to_std_string_escaped(), val.clone());
}
}
}
}
return Ok(r);
}
Ok(Default::default())
}
pub fn obj_get(obj: &JsValue, key: &str) -> Result<Option<JsValue>, JsError> {
if let JsValue::Object(ref obj) = obj {
let obj = obj.borrow();
let map = obj.properties();
let key = boa_engine::property::PropertyKey::from(JsString::from(key));
if let Some(val) = map.get(&key) {
if let Some(val) = val.value() {
return Ok(Some(val.clone()));
}
}
}
Ok(None)
}
pub fn li_str(ctx: &mut Context, li: JsValue) -> Vec<(String, String)> {
if let JsValue::Object(ref li) = li
&& li.is_array()
{
let li = JsArray::from_object(li.clone()).unwrap();
let len = li.length(ctx).unwrap();
let mut r = Vec::with_capacity(len as usize);
for i in 0..len {
if let Ok(e) = li.get(i, ctx) {
if let JsValue::Object(e) = e
&& e.is_array()
{
let e = JsArray::from_object(e).unwrap();
let len = e.length(ctx).unwrap();
if len >= 2 {
if let Ok(fp) = e.get(0, ctx) {
if let JsValue::String(fp) = fp {
if let Ok(txt) = e.get(1, ctx) {
if let JsValue::String(txt) = txt {
r.push((fp.to_std_string_escaped(), txt.to_std_string_escaped()));
} else {
error!("{:?} is not string", txt)
}
}
} else {
error!("fp {:?} is not string", fp)
}
}
}
}
}
}
r
} else {
vec![]
}
}
pub struct JsMap<'a> {
pub ctx: &'a mut Context,
pub obj: JsObject,
}
impl<'a> JsMap<'a> {
pub fn new(ctx: &'a mut Context) -> Self {
let obj = JsObject::with_object_proto(ctx.intrinsics());
JsMap { ctx, obj }
}
pub fn set(&mut self, key: impl AsRef<str>, value: impl Into<JsValue>) {
let key_js = PropertyKey::from(JsString::from(key.as_ref()));
self.obj.set(key_js, value, false, self.ctx).unwrap();
}
pub fn value(self) -> JsValue {
self.obj.into()
}
pub fn set_str(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) {
let value_js = JsString::from(value.as_ref());
self.set(key, value_js);
}
}
pub fn to_str(value: JsValue) -> Option<String> {
if let JsValue::String(s) = value {
return Some(s.to_std_string_escaped());
}
None
}