diff --git a/crates/neon/src/lib.rs b/crates/neon/src/lib.rs index 13ff73251..5cbf38c2a 100644 --- a/crates/neon/src/lib.rs +++ b/crates/neon/src/lib.rs @@ -83,6 +83,7 @@ pub mod event; pub mod handle; mod macros; pub mod meta; +pub mod node; pub mod object; pub mod prelude; pub mod reflect; diff --git a/crates/neon/src/node/console.rs b/crates/neon/src/node/console.rs new file mode 100644 index 000000000..3ff8c0fb8 --- /dev/null +++ b/crates/neon/src/node/console.rs @@ -0,0 +1,89 @@ +use std::marker::PhantomData; + +use crate::{ + context::Context, + handle::{Handle, Root}, + object::Object, + result::{JsResult, NeonResult}, + thread::LocalKey, + types::{extract::TryIntoJs, function::BindOptions, JsFunction, JsObject}, +}; + +static CONSOLE: LocalKey> = LocalKey::new(); +static LOG: LocalKey> = LocalKey::new(); +static ERROR: LocalKey> = LocalKey::new(); +static INFO: LocalKey> = LocalKey::new(); +static WARN: LocalKey> = LocalKey::new(); +static CLEAR: LocalKey> = LocalKey::new(); + +pub struct Console<'a, 'cx: 'a, C: Context<'cx>> { + pub(crate) cx: &'a mut C, + pub(crate) marker: PhantomData<&'cx ()>, +} + +impl<'a, 'cx: 'a, C: Context<'cx>> Console<'a, 'cx, C> { + fn memo( + &mut self, + cache: &'static LocalKey>, + get_container: F, + key: &str, + ) -> JsResult<'cx, T> + where + T: Object, + F: FnOnce(&mut Self) -> JsResult<'cx, JsObject>, + { + let container = get_container(self)?; + let v = cache.get_or_try_init(self.cx, |cx| { + let v: Handle = container.get(cx, key)?; + Ok(v.root(cx)) + })?; + Ok(v.to_inner(self.cx)) + } + + fn memo_method( + &mut self, + cache: &'static LocalKey>, + get_container: F, + key: &str, + ) -> NeonResult> + where + F: FnOnce(&mut Self) -> JsResult<'cx, JsObject>, + { + let container = get_container(self)?; + let function = self.memo(cache, |_| Ok(container), key)?; + let mut method = function.bind(self.cx.cx_mut()); + method.this(container)?; + Ok(method) + } + + pub(crate) fn new(cx: &'a mut C) -> Self { + Self { + cx, + marker: PhantomData, + } + } + + fn console_object(&mut self) -> JsResult<'cx, JsObject> { + self.memo(&CONSOLE, |c| Ok(c.cx.global_object()), "console") + } + + pub fn log>(&mut self, msg: T) -> NeonResult<()> { + self.memo_method(&LOG, |c| c.console_object(), "log")?.arg(msg)?.exec() + } + + pub fn error>(&mut self, msg: T) -> NeonResult<()> { + self.memo_method(&ERROR, |c| c.console_object(), "error")?.arg(msg)?.exec() + } + + pub fn info>(&mut self, msg: T) -> NeonResult<()> { + self.memo_method(&INFO, |c| c.console_object(), "info")?.arg(msg)?.exec() + } + + pub fn warn>(&mut self, msg: T) -> NeonResult<()> { + self.memo_method(&WARN, |c| c.console_object(), "warn")?.arg(msg)?.exec() + } + + pub fn clear>(&mut self) -> NeonResult<()> { + self.memo_method(&CLEAR, |c| c.console_object(), "clear")?.exec() + } +} diff --git a/crates/neon/src/node/mod.rs b/crates/neon/src/node/mod.rs new file mode 100644 index 000000000..224dea3d8 --- /dev/null +++ b/crates/neon/src/node/mod.rs @@ -0,0 +1,22 @@ +mod console; +mod process; +use crate::context::{Context, Cx, FunctionContext, ModuleContext}; + +pub use console::Console; +pub use process::Process; + +pub trait Node<'cx>: Context<'cx> { + fn console<'a>(&'a mut self) -> Console<'a, 'cx, Self> { + Console::new(self) + } + + fn process<'a>(&'a mut self) -> Process<'a, 'cx, Self> { + Process::new(self) + } +} + +impl<'cx> Node<'cx> for ModuleContext<'cx> {} + +impl<'cx> Node<'cx> for FunctionContext<'cx> {} + +impl<'cx> Node<'cx> for Cx<'cx> {} diff --git a/crates/neon/src/node/process.rs b/crates/neon/src/node/process.rs new file mode 100644 index 000000000..a45e46a8d --- /dev/null +++ b/crates/neon/src/node/process.rs @@ -0,0 +1,106 @@ +use std::marker::PhantomData; + +use crate::{ + context::Context, + handle::{Handle, Root}, + object::Object, + result::{JsResult, NeonResult}, + thread::LocalKey, + types::{JsObject, JsString}, +}; + +static PROCESS: LocalKey> = LocalKey::new(); +static VERSIONS: LocalKey> = LocalKey::new(); +static VERSION_DATA: LocalKey = LocalKey::new(); + +pub struct Process<'a, 'cx: 'a, C: Context<'cx>> { + pub(crate) cx: &'a mut C, + pub(crate) marker: PhantomData<&'cx ()>, +} + +impl<'a, 'cx: 'a, C: Context<'cx>> Process<'a, 'cx, C> { + // FIXME: this can be abstracted in a private super-trait + fn memo( + &mut self, + cache: &'static LocalKey>, + get_container: F, + key: &str, + ) -> JsResult<'cx, T> + where + T: Object, + F: FnOnce(&mut Self) -> JsResult<'cx, JsObject>, + { + let container = get_container(self)?; + let v = cache.get_or_try_init(self.cx, |cx| { + let v: Handle = container.get(cx, key)?; + Ok(v.root(cx)) + })?; + Ok(v.to_inner(self.cx)) + } + + pub(crate) fn new(cx: &'a mut C) -> Self { + Self { + cx, + marker: PhantomData, + } + } + + fn process_object(&mut self) -> JsResult<'cx, JsObject> { + self.memo(&PROCESS, |c| Ok(c.cx.global_object()), "process") + } + + fn versions_object(&mut self) -> JsResult<'cx, JsObject> { + self.memo(&VERSIONS, |c| c.process_object(), "versions") + } + + pub fn versions(&mut self) -> NeonResult> { + let object = self.versions_object()?; + + Versions::new(self.cx, object) + } +} + +pub struct Versions<'cx> { + pub(crate) object: Handle<'cx, JsObject>, + pub(crate) data: &'cx VersionData, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub struct VersionData { + pub node: String, + pub modules: String, + pub napi: String, + pub unicode: String, + pub uv: String, +} + +impl<'cx> Versions<'cx> { + fn new>(cx: &mut C, object: Handle<'cx, JsObject>) -> NeonResult { + Ok(Self { + object, + data: VERSION_DATA.get_or_try_init(cx, |cx| { + let node = object.get::(cx, "node")?.value(cx); + let modules = object.get::(cx, "modules")?.value(cx); + let napi = object.get::(cx, "napi")?.value(cx); + let unicode = object.get::(cx, "unicode")?.value(cx); + let uv = object.get::(cx, "uv")?.value(cx); + Ok(VersionData { + node, + modules, + napi, + unicode, + uv, + }) + })?, + }) + } + + pub fn object(&self) -> Handle<'cx, JsObject> { + self.object + } + + pub fn data(&self) -> &VersionData { + &self.data + } +} diff --git a/test/napi/lib/node.js b/test/napi/lib/node.js new file mode 100644 index 000000000..106e94140 --- /dev/null +++ b/test/napi/lib/node.js @@ -0,0 +1,12 @@ +var addon = require(".."); +var assert = require("chai").assert; + +describe("Node Standard Library", function () { + it("should print to console", function () { + addon.call_console_log_and_error(); + }); + + it("process.versions.node", function () { + assert.strictEqual(addon.get_node_version(), process.versions.node); + }); +}); diff --git a/test/napi/src/js/node.rs b/test/napi/src/js/node.rs new file mode 100644 index 000000000..814cf4191 --- /dev/null +++ b/test/napi/src/js/node.rs @@ -0,0 +1,14 @@ +use neon::node::Node; +use neon::prelude::*; + +pub fn call_console_log_and_error(mut cx: FunctionContext) -> JsResult { + cx.console().log("This is cx.console().log()!")?; + cx.console().error("This is cx.console().error()!")?; + Ok(cx.undefined()) +} + +pub fn get_node_version(mut cx: FunctionContext) -> JsResult { + let versions = cx.process().versions()?; + let data = versions.data(); + Ok(cx.string(&data.node)) +} diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index 3568e7842..dca645e2b 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -3,8 +3,8 @@ use once_cell::sync::OnceCell; use tokio::runtime::Runtime; use crate::js::{ - arrays::*, boxed::*, coercions::*, date::*, errors::*, functions::*, numbers::*, objects::*, - strings::*, threads::*, typedarrays::*, types::*, + arrays::*, boxed::*, coercions::*, date::*, errors::*, functions::*, node::*, numbers::*, + objects::*, strings::*, threads::*, typedarrays::*, types::*, }; mod js { @@ -18,6 +18,7 @@ mod js { pub mod extract; pub mod functions; pub mod futures; + pub mod node; pub mod numbers; pub mod objects; pub mod strings; @@ -444,6 +445,9 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { js::extract::extract_single_add_one, )?; + cx.export_function("call_console_log_and_error", call_console_log_and_error)?; + cx.export_function("get_node_version", get_node_version)?; + Ok(()) }