1#![feature(let_chains)]
2mod vec_or_str;
3use std::{
4 collections::HashMap,
5 fs::File,
6 io::Read,
7 path::{Path, PathBuf},
8 rc::Rc,
9};
10
11pub use boa_engine;
12use boa_engine::{
13 js_string,
14 module::SimpleModuleLoader,
15 object::builtins::{JsArray, JsUint8Array},
16 property::{Attribute, PropertyKey},
17 string::JsString,
18 Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsValue, Module, NativeFunction,
19 Source,
20};
21use boa_runtime::Console;
22use thiserror::Error;
23use tracing::error;
24pub use vec_or_str::VecOrStr;
25
26#[derive(Error, Debug)]
27pub enum Error {
28 #[error("Js: {0}\n{1}")]
29 Js(String, String),
30}
31
32fn read_file_to_vec(path: impl AsRef<Path>) -> std::io::Result<Vec<u8>> {
33 let mut file = File::open(path)?;
35 let mut buffer = Vec::new();
37 file.read_to_end(&mut buffer)?;
39 Ok(buffer)
41}
42
43fn vec_to_uint8array(context: &mut Context, data: Vec<u8>) -> JsResult<JsValue> {
44 Ok(JsValue::Object(
45 JsUint8Array::from_iter(data, context)?.into(),
46 ))
47}
48
49macro_rules! throw {
50 ($str:expr $(,$arg:tt)* $(,)?) => {{
51 let err: JsError = JsNativeError::typ()
52 .with_message(format!($str,$($arg,)*))
53 .into();
54 return Err(err);
55 }};
56}
57
58pub fn ctx(root: &str) -> Context {
59 let loader = Rc::new(SimpleModuleLoader::new(root).unwrap());
60 let mut binding = Context::builder()
61 .module_loader(loader.clone())
62 .build()
63 .unwrap();
64 let ctx = &mut binding;
65
66 let console = Console::init(ctx);
67
68 {
69 ctx
70 .register_global_builtin_callable("rDir".into(), 1, unsafe {
71 NativeFunction::from_closure(move |_, args, ctx| {
72 let fp = args.get_or_undefined(0);
73 if fp.is_undefined() {
74 throw!("rDir: miss arg dir")
75 }
76
77 match std::fs::read_dir(fp.to_string(ctx)?.to_std_string_escaped()) {
78 Ok(r) => {
79 let dir_li = JsArray::new(ctx);
80 let file_li = JsArray::new(ctx);
81 for i in r.flatten() {
82 if let Ok(file_type) = i.file_type() {
83 let li = if file_type.is_dir() {
84 &dir_li
85 } else {
86 &file_li
87 };
88 let name = i.file_name().to_os_string().to_string_lossy().to_string();
89 let name = JsString::from(name);
90 li.push(name, ctx)?;
91 }
92 }
93 let li = JsArray::new(ctx);
94 li.push(dir_li, ctx)?;
95 li.push(file_li, ctx)?;
96 Ok(li.into())
97 }
98 Err(e) => {
99 throw!("rDir: {e}")
100 }
101 }
102 })
103 })
104 .unwrap();
105 };
106 {
107 ctx
108 .register_global_builtin_callable("wPath".into(), 1, unsafe {
109 NativeFunction::from_closure(move |_, args, ctx| {
110 let fp = args.get_or_undefined(0);
111
112 if fp.is_undefined() {
113 throw!("wPath: miss arg path")
114 }
115
116 let fpstr = &fp.to_string(ctx)?.to_std_string_escaped();
117 let fp: PathBuf = fpstr.into();
118
119 let bin = args.get_or_undefined(1);
120 if bin.is_undefined() {
121 throw!("wPath {fpstr}: miss data")
122 }
123
124 if let Some(dir) = fp.parent() {
125 if let Err(err) = std::fs::create_dir_all(dir) {
126 throw!("wPath {fpstr}: {err}")
127 }
128 }
129
130 if let Some(txt) = bin.as_string() {
131 let txt = txt.to_std_string_escaped();
132 return match std::fs::write(fp, txt.as_bytes()) {
133 Ok(_) => Ok(JsValue::undefined()),
134 Err(e) => {
135 throw!("wPath {fpstr}: {e}")
136 }
137 };
138 };
139 throw!("wPath {fpstr}: unsupport data type")
140 })
141 })
142 .unwrap();
143 }
144
145 {
146 ctx
147 .register_global_builtin_callable("rBin".into(), 1, unsafe {
148 NativeFunction::from_closure(move |_, args, ctx| {
149 let name = args.get_or_undefined(0);
150
151 if name.is_undefined() {
152 throw!("rBin: miss arg path")
153 }
154
155 let fp = name.to_string(ctx)?.to_std_string_escaped();
156
157 match read_file_to_vec(&fp) {
158 Ok(s) => Ok(vec_to_uint8array(ctx, s)?),
159 Err(e) => {
160 throw!("rBin('{fp}') : {e}")
161 }
162 }
163 })
164 })
165 .unwrap();
166 }
167
168 ctx
169 .register_global_builtin_callable("rStr".into(), 1, unsafe {
170 NativeFunction::from_closure(move |_, args, ctx| {
171 let name = args.get_or_undefined(0);
172
173 if name.is_undefined() {
174 throw!("rStr: miss arg path")
175 }
176
177 let fp = name.to_string(ctx)?.to_std_string_escaped();
178 match std::fs::read_to_string(&fp) {
179 Ok(s) => Ok(JsValue::String(JsString::from(s))),
180 Err(e) => {
181 throw!("rStr('{fp}') : {e}")
182 }
183 }
184 })
185 })
186 .unwrap();
187
188 ctx
189 .register_global_property(js_string!(Console::NAME), console, Attribute::all())
190 .unwrap();
191
192 binding
193}
194
195pub fn _default(ctx: &mut Context, js_code: &str, args: &[JsValue]) -> JsResult<JsValue> {
197 let code = Source::from_bytes(js_code);
198 let module = Module::parse(code, None, ctx)?;
199 let _promise = module.load_link_evaluate(ctx);
200
201 ctx.run_jobs();
202 let namespace = module.namespace(ctx);
203
204 let mix = namespace
205 .get(js_string!("default"), ctx)?
206 .as_callable()
207 .cloned()
208 .ok_or_else(|| JsNativeError::typ().with_message("export default not function !"))?;
209
210 let r = mix.call(&JsValue::undefined(), args, ctx)?;
211 Ok(r)
212}
213
214pub fn default(
215 ctx: &mut Context,
216 fp: impl Into<PathBuf>,
217 args: &[JsValue],
218) -> Result<JsValue, Error> {
219 let fp = fp.into();
220
221 macro_rules! ok {
222 ($r:expr) => {
223 match $r {
224 Ok(r) => r,
225 Err(e) => return Err(Error::Js(fp.display().to_string(), e.to_string())),
226 }
227 };
228 }
229
230 let js_code = ok!(std::fs::read_to_string(&fp));
231
232 Ok(ok!(_default(ctx, &js_code, args)))
233}
234
235pub fn obj2map(obj: JsValue) -> Result<HashMap<String, JsValue>, JsError> {
236 if let JsValue::Object(ref obj) = obj {
237 let obj = obj.borrow();
238 let key_li = obj.shape().keys();
239 let mut r = HashMap::with_capacity(key_li.len());
240 let map = obj.properties();
241 for key in key_li {
242 if let Some(val) = map.get(&key) {
243 if let Some(val) = val.value() {
244 if let PropertyKey::String(key) = key {
245 r.insert(key.to_std_string_escaped(), val.clone());
246 }
247 }
248 }
249 }
250 return Ok(r);
251 }
252 Ok(Default::default())
253}
254
255pub fn obj_get(obj: &JsValue, key: &str) -> Result<Option<JsValue>, JsError> {
256 if let JsValue::Object(ref obj) = obj {
257 let obj = obj.borrow();
258 let map = obj.properties();
259
260 let key = boa_engine::property::PropertyKey::from(JsString::from(key));
261 if let Some(val) = map.get(&key) {
262 if let Some(val) = val.value() {
263 return Ok(Some(val.clone()));
264 }
265 }
266 }
267 Ok(None)
268}
269
270pub fn li_str(ctx: &mut Context, li: JsValue) -> Vec<(String, String)> {
271 if let JsValue::Object(ref li) = li
272 && li.is_array()
273 {
274 let li = JsArray::from_object(li.clone()).unwrap();
275 let len = li.length(ctx).unwrap();
276 let mut r = Vec::with_capacity(len as usize);
277 for i in 0..len {
278 if let Ok(e) = li.get(i, ctx) {
279 if let JsValue::Object(e) = e
280 && e.is_array()
281 {
282 let e = JsArray::from_object(e).unwrap();
283 let len = e.length(ctx).unwrap();
284 if len >= 2 {
285 if let Ok(fp) = e.get(0, ctx) {
286 if let JsValue::String(fp) = fp {
287 if let Ok(txt) = e.get(1, ctx) {
288 if let JsValue::String(txt) = txt {
289 r.push((fp.to_std_string_escaped(), txt.to_std_string_escaped()));
290 } else {
291 error!("{:?} is not string", txt)
292 }
293 }
294 } else {
295 error!("fp {:?} is not string", fp)
296 }
297 }
298 }
299 }
300 }
301 }
302 r
303 } else {
304 vec![]
306 }
307}
308
309pub struct JsMap<'a> {
310 pub ctx: &'a mut Context,
311 pub obj: JsObject,
312}
313
314impl<'a> JsMap<'a> {
315 pub fn new(ctx: &'a mut Context) -> Self {
316 let obj = JsObject::with_object_proto(ctx.intrinsics());
317 JsMap { ctx, obj }
318 }
319 pub fn set(&mut self, key: impl AsRef<str>, value: impl Into<JsValue>) {
320 let key_js = PropertyKey::from(JsString::from(key.as_ref()));
321 self.obj.set(key_js, value, false, self.ctx).unwrap();
322 }
323
324 pub fn value(self) -> JsValue {
325 self.obj.into()
326 }
327
328 pub fn set_str(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) {
329 let value_js = JsString::from(value.as_ref());
330 self.set(key, value_js);
331 }
332}
333
334pub fn to_str(value: JsValue) -> Option<String> {
362 if let JsValue::String(s) = value {
363 return Some(s.to_std_string_escaped());
364 }
365 None
366}