bjs/
lib.rs

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  // 打开文件
34  let mut file = File::open(path)?;
35  // 创建一个空的 Vec<u8>
36  let mut buffer = Vec::new();
37  // 读取文件内容到 Vec<u8>
38  file.read_to_end(&mut buffer)?;
39  // 返回结果
40  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
195// https://github.com/boa-dev/boa/blob/main/examples/src/bin/modules.rs
196pub 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    // error!("return {:?} is not array", li);
305    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
334// pub fn li_str_to_jsvalue<S: Copy + Into<JsString>>(ctx: &mut Context, li: &[S]) -> JsValue {
335//   let array = JsArray::new(ctx);
336//   for (i, s) in li.iter().enumerate() {
337//     let s: JsString = (*s).into();
338//     array.set(i as u32, s, false, ctx).unwrap();
339//   }
340//   array.into()
341// }
342//
343// pub fn li_hashmap_to_jsvalue(ctx: &mut Context, li: &[HashMap<&str, String>]) -> JsValue {
344//   let array = JsArray::new(ctx);
345//
346//   for (i, hashmap) in li.iter().enumerate() {
347//     let obj = JsObject::with_object_proto(ctx.intrinsics());
348//
349//     for (key, value) in hashmap {
350//       let key_js = PropertyKey::from(JsString::from(*key));
351//       let value_js = JsString::from(value.as_str());
352//       obj.set(key_js, value_js, false, ctx).unwrap();
353//     }
354//
355//     array.set(i as u32, obj, false, ctx).unwrap();
356//   }
357//
358//   array.into()
359// }
360
361pub 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}