diff --git a/README.md b/README.md index e7cb03f..15645db 100644 --- a/README.md +++ b/README.md @@ -12,536 +12,23 @@ This is a part of [Node3D](https://github.com/node-3d) project. ## Synopsis -Helpers for Node.js **NAPI** addons and dependency packages: - -* Supported platforms (x64): Windows, Linux, OSX. -* C++ helpers: - * Macro shortcuts for NAPI. - * `eventEmit()` function. - * `getData()` function. - * **Es5** class wrapper (like `Napi::ObjectWrap`, but Es5 functions). -* Module helpers: - * Crossplatform commands for GYP: `cp`, `rm`, `mkdir`. - * Deps unzip installer. - * Url-to-buffer downloader. - * Binary copy helper. - -Useful links: [N-API Docs](https://nodejs.org/api/n-api.html), -[Napi Docs](https://github.com/nodejs/node-addon-api/blob/master/doc/setup.md), -[GYP Docs](https://gyp.gsrc.io/docs/UserDocumentation.md). - -**Jump to**: - -[Snippets](#snippets) - -[include/addon-tools.hpp](#includeaddon-toolshpp) - -[index.js](#indexjs) -[cpbin.js](#cpbinjs) -[download.js](#downloadjs) -[install.js](#installjs) -[writable-buffer.js](#writablebufferjs) - -[Crossplatform commands](#crossplatform-commands) - -[Function eventEmit](#function-eventEmit) - -[Es5 class wrapper](#es5-class-wrapper) - ---- - - -## Snippets - - -### Crossplatform commands - -``` -'variables': { - 'rm' : '<!(node -p "require(\'addon-tools-raub\').rm")', - 'cp' : '<!(node -p "require(\'addon-tools-raub\').cp")', - 'mkdir' : '<!(node -p "require(\'addon-tools-raub\').mkdir")', -}, -... -'action': ['<(mkdir)', '-p', '<(binary)'] -``` - -On both Windows and Unix those commands now have the same result. - - -### Addon binary directory - -``` -'variables': { - 'binary' : '<!(node -p "require(\'addon-tools-raub\').bin")', -}, -``` - - -### Include directories - -``` - 'include_dirs': [ - '<!@(node -p "require(\'addon-tools-raub\').include")', - ], -``` - -Those are the directory paths to C++ include files for **Addon Tools** and -**Napi** (which is preinstalled with Addon Tools). - - -### Binary dependency package - -If you design a module with binary dependencies for several platforms, -**Addon Tools** may help within the following guidelines: - -* In **package.json** use a `"postinstall"` script to download the libraries. -For example the following structure might work. Note that **Addon Tools** will -append any given URL with ``/${platform}.zip`` - - ``` - "config" : { - "install" : "v1.0.0" - }, - "scripts": { - "postinstall": "install", - }, - ``` - - where `config.install` is **YOUR install.js** is: - - ``` - const install = require('addon-tools-raub/install'); - const prefix = 'https://github.com/user/addon/releases/download'; - const tag = process.env.npm_package_config_install; - install(`${prefix}/${tag}`); - ``` - - **Addon Tools** will unzip the downloaded file into the platform binary - directory. E.g. on Windows it will be **bin-windows**. - -* Per platform binary directories: - - * bin-windows - * bin-linux - * bin-osx - -* The following piece of code in your `index.js` without changes. Method `paths()` -is described [here](#indexjs). - - ``` - module.exports = require('addon-tools-raub').paths(__dirname); - ``` - -* Publishing is done by attaching a zipped platform folder to the Github -release. Zip file must NOT contain platform folder as a subfolder, but rather -contain the final binaries. The tag of the release should be the same as in -`npm_package_config_install` - that is the way installer will find it. - -* NOTE: You can publish your binaries to anywhere, not necessarily Github. -Just tweak **YOUR install.js** script as appropriate. The only limitation -from **Addon Tools** is that it should be a zipped set of files/folders. - - -### Compiled addon - -N-API ABI is compatible across Node.js versions, now addons -are just plain DLLs. Therefore distribution of the binaries is covered in the -previous section. But for an addon you have to provide a GYP compilation step. -N-API changes it's role to out-of-install compilation, so we can't/shouldnt -put the file **binding.gyp** to the module root anymore. - -The workaround would be to have a separate directory within your project -(with simplified package.json) for the sake of addon compilation. - -``` -{ - "name": "build", - "version": "0.0.0", - "private": true, - "dependencies": { - "addon-tools-raub": "5.0.0", - "deps-EXT_LIB": "1.0.0" - } -} -``` - -* Assume `EXT_LIB` is the name of a binary dependency. -* Assume `deps-EXT_LIB` is the name of an Addon Tools compliant dependency module. -* Assume `MY_ADDON` is the name of this addon's resulting binary. -* Assume C++ code goes to `cpp` subdirectory. - -That together with **binding.gyp**, this would be enough to get the addon compiled. -Then the binaries are published to the Github release. When the addon -is installed, its **index.js** is responsible for reexport of the binary. -Just require the built module like this: - -``` -const { bin } = require('addon-tools-raub'); -const core = require(`./${bin}/MY_ADDON`); -``` - -<details> - -<summary>See a snipped for binding.gyp here</summary> - -* Assume `EXT_LIB` is the name of a binary dependency. -* Assume `deps-EXT_LIB` is the name of an Addon Tools compliant dependency module. -* Assume `MY_ADDON` is the name of this addon's resulting binary. -* Assume C++ code goes to `cpp` subdirectory. - -``` -{ - 'variables': { - 'rm' : '<!(node -e "require(\'addon-tools-raub\').rm")', - 'cp' : '<!(node -e "require(\'addon-tools-raub\').cp")', - 'mkdir' : '<!(node -e "require(\'addon-tools-raub\').mkdir")', - 'binary' : '<!(node -e "require(\'addon-tools-raub\').bin")', - 'EXT_LIB_include' : '<!(node -e "require(\'deps-EXT_LIB\').include")', - 'EXT_LIB_bin' : '<!(node -e "require(\'deps-EXT_LIB\').bin")', - }, - 'targets': [ - { - 'target_name': 'MY_ADDON', - 'sources': [ - 'cpp/MY_ADDON.cpp', - ], - 'include_dirs': [ - '<!(node -e "require(\'addon-tools-raub\').include")', - '<(EXT_LIB_include)', - '<(module_root_dir)/include', - ], - 'library_dirs': [ '<(EXT_LIB_bin)' ], - 'conditions': [ - [ - 'OS=="linux"', - { - 'libraries': [ - '-Wl,-rpath,<(EXT_LIB_bin)', - '<(EXT_LIB_bin)/libEXT_LIB.so', - ], - } - ], - [ - 'OS=="mac"', - { - 'libraries': [ - '-Wl,-rpath,<(EXT_LIB_bin)', - '<(EXT_LIB_bin)/EXT_LIB.dylib', - ], - 'xcode_settings': { - 'DYLIB_INSTALL_NAME_BASE': '@rpath', - }, - } - ], - [ - 'OS=="win"', - { - 'libraries': [ 'EXT_LIB.lib' ], - 'defines' : [ - 'WIN32_LEAN_AND_MEAN', - 'VC_EXTRALEAN' - ], - 'msvs_version' : '2013', - 'msvs_settings' : { - 'VCCLCompilerTool' : { - 'AdditionalOptions' : [ - '/O2','/Oy', # Comment this for debugging - # '/Z7', # Unomment this for debugging - '/GL','/GF','/Gm-','/EHsc', - '/MT','/GS','/Gy','/GR-','/Gd', - ] - }, - 'VCLinkerTool' : { - 'AdditionalOptions' : ['/OPT:REF','/OPT:ICF','/LTCG'] - }, - }, - } - ], - ], - }, - - { - 'target_name' : 'make_directory', - 'type' : 'none', - 'dependencies' : ['MY_ADDON'], - 'actions' : [{ - 'action_name' : 'Directory created.', - 'inputs' : [], - 'outputs' : ['build'], - 'action': ['<(mkdir)', '-p', '<(binary)'] - }], - }, - { - 'target_name' : 'copy_binary', - 'type' : 'none', - 'dependencies' : ['make_directory'], - 'actions' : [{ - 'action_name' : 'Module copied.', - 'inputs' : [], - 'outputs' : ['binary'], - 'action' : [ - '<(cp)', - 'build/Release/MY_ADDON.node', - '<(binary)/MY_ADDON.node', - ], - }], - }, - - ] -} -``` - -</details> - ---- - - -## include/addon-tools.hpp - -There is a C++ header file, `addon-tools.hpp`, shipped with this package. It -introduces several useful macros and utilities. Also it includes Napi automatically, -so that you can replace: - -``` -#include <napi.h> -``` - -with - -``` -#include <addon-tools.hpp> -``` - -In gyp, the include directory should be set for your addon to know where -to get it. An actual path to the directory is exported from the module -and is accessible like this: - -``` -require('addon-tools-raub').include // a string -``` - -### Helpers in **addon-tools.hpp**: - -Usually all the helpers work within the context of JS call. In this case we -have `CallbackInfo info` passed as an argument. - -``` -#define NAPI_ENV Napi::Env env = info.Env(); -#define NAPI_HS Napi::HandleScope scope(env); -``` - -<details> - -<summary>Return value</summary> - -* `RET_VALUE(VAL)`- return a given Napi::Value. -* `RET_UNDEFINED`- return `undefined`. -* `RET_NULL` - return `null`. -* `RET_STR(VAL)` - return `Napi::String`, expected `VAL` is `const char *`. -* `RET_NUM(VAL)` - return `Napi::Number`, expected `VAL` is `double`. -* `RET_EXT(VAL)` - return `Napi::External`, expected `VAL` is `void *`. -* `RET_BOOL(VAL)` - return `Napi::Boolean`, expected `VAL` is `bool`. -* `RET_FUN(VAL)` - return `Napi::Function`, expected `VAL` is a `napi_value`. -* `RET_OBJ(VAL)` - return `Napi::Object`, expected `VAL` is a `napi_value`. - -</details> - - - -<details> - -<summary>New JS value</summary> - -* `JS_STR(VAL)` - create a `Napi::String` value. -* `JS_NUM(VAL)` - create a `Napi::Number` value. -* `JS_EXT(VAL)` - create a `Napi::External` (from pointer) value. -* `JS_BOOL(VAL)` - create a `Napi::Boolean` value. -* `JS_FUN(VAL)` - create a `Napi::Function` value. -* `JS_OBJ(VAL)` - create a `Napi::Object` value. - -</details> - - -<details> - -<summary>Method check</summary> - -These checks throw JS TypeError if not passed. Here `T` is always used as a typename -in error messages. `C` is a -[Napi::Value](https://github.com/nodejs/node-addon-api/blob/master/doc/value.md#isboolean) -check method, like `IsObject()`. `I` is the index of argument as in `info[I]`, -starting from `0`. - -* `REQ_ARGS(N)` - check if at least `N` arguments passed -* `IS_ARG_EMPTY(I)` - check if argument `I` is `undefined` or `null` -* `CHECK_REQ_ARG(I, C, T)` - check if argument `I` is approved by `C` check. -* `CHECK_LET_ARG(I, C, T)` - check if argument `I` is approved by `C` check or empty. -* `CTOR_CHECK(T)` - check if method is called as a constructor -* `SETTER_CHECK(C, T)` - check if setter `value` is approved by `C` check. -* `DES_CHECK` - within dynamic method check if the instance wasn't -destroyed by `destroy()`. - -</details> - - -<details> - -<summary>Method arguments</summary> - -The idea is to ease the transition from what inside the `CallbackInfo` to -what you work with in C++. -Three types of argument retrieval are supported: `REQ_`, `USE_` and `LET_`. -The difference: -* `REQ_` - 2 params, requires an argument to have a value -* `USE_` - 3 params, allows the argument to be empty and have a default -* `LET_` - 2 params, is `USE_` with a preset zero-default. - -What it does, basically: -``` -// REQ_DOUBLE_ARG(0, x) -double x = info[0].ToNumber().DoubleValue(); - -// USE_DOUBLE_ARG(0, x, 5.7) -double x = IS_ARG_EMPTY(0) ? 5.7 : info[0].ToNumber().DoubleValue(); - -// LET_DOUBLE_ARG(0, x) -double x = IS_ARG_EMPTY(0) ? 0.0 : info[0].ToNumber().DoubleValue(); -``` - -That extrapolates well to all the helpers below: -* `REQ_STR_ARG` - JS `string` => C++ `std::string`. -* `USE_STR_ARG` -* `LET_STR_ARG` - default: `""`. -* `REQ_INT32_ARG` - JS `number` => C++ `int32_t`. -* `USE_INT32_ARG` -* `LET_INT32_ARG` - default: `0`. -* `REQ_INT_ARG` - JS `number` => C++ `int32_t`. -* `USE_INT_ARG` -* `LET_INT_ARG` - default: `0`. -* `REQ_UINT32_ARG` - JS `number` => C++ `uint32_t`. -* `USE_UINT32_ARG` -* `LET_UINT32_ARG` - default: `0`. -* `REQ_UINT_ARG` - JS `number` => C++ `uint32_t`. -* `USE_UINT_ARG` -* `LET_UINT_ARG` - default: `0`. -* `REQ_BOOL_ARG` - JS `Boolean` => C++ `bool`. -* `USE_BOOL_ARG` -* `LET_BOOL_ARG` - default: `false`. -* `REQ_OFFS_ARG` - JS `number` => C++ `size_t`. -* `USE_OFFS_ARG` -* `LET_OFFS_ARG` - default: `0`. -* `REQ_DOUBLE_ARG` - JS `number` => C++ `double`. -* `USE_DOUBLE_ARG` -* `LET_DOUBLE_ARG` - default: `0.0`. -* `REQ_FLOAT_ARG` - JS `number` => C++ `float`. -* `USE_FLOAT_ARG` -* `LET_FLOAT_ARG` - default: `0.f`. -* `REQ_EXT_ARG` - JS `native` => C++ `void*`. -* `USE_EXT_ARG` -* `LET_EXT_ARG` - default: `nullptr`. -* `REQ_FUN_ARG` - JS `function` => C++ `Napi::Function`. -* `REQ_OBJ_ARG` - JS `object` => C++ `Napi::Object`. -* `USE_OBJ_ARG` -* `LET_OBJ_ARG` - default: `{}`. -* `REQ_ARRV_ARG` - JS `ArrayBuffer` => C++ `Napi::ArrayBuffer`. -* `REQ_BUF_ARG` - JS `Buffer` => C++ `Napi::Buffer<uint8_t>`. - - -``` -NAN_METHOD(test) { - - REQ_UINT32_ARG(0, width); - REQ_UINT32_ARG(1, height); - LET_FLOAT_ARG(2, z); - // Variables created: unsigned int width, height; float z; - ... -``` - -</details> - - -<details> - -<summary>Set object accessors</summary> - -Simplified accessor assignment, adds accessors of NAME for OBJ. Read accessor is -assumed to have the name `NAME+'Getter'` and write accessor is `NAME+'Setter'`. - -* `ACCESSOR_RW(CLASS, NAME)` - add read and write accessors NAME of CLASS. -* `ACCESSOR_R(CLASS, NAME)` - add read accessor NAME of CLASS. -* `ACCESSOR_M(CLASS, NAME)` - add method NAME of CLASS. - - -``` -void MyClass::init(Napi::Env env, Napi::Object exports) { - ... - Napi::Function ctor = DefineClass(env, "MyClass", { - ACCESSOR_R(MyClass, isDestroyed), - ACCESSOR_RW(MyClass, x), - ACCESSOR_M(MyClass, reset), - }); - ... -} -JS_GETTER(MyClass::isDestroyedGetter) { ... -JS_GETTER(MyClass::xGetter) { ... -JS_SETTER(MyClass::xSetter) { ... -JS_METHOD(MyClass::save) { ... -``` - -</details> - - -<details> - -<summary>Setter argument</summary> - -Works similar to method arguments. But there is always `value` -argument, from which a C++ value is extracted. - -* `SETTER_STR_ARG` -* `SETTER_INT32_ARG` -* `SETTER_INT_ARG` -* `SETTER_BOOL_ARG` -* `SETTER_UINT32_ARG` -* `SETTER_UINT_ARG` -* `SETTER_OFFS_ARG` -* `SETTER_DOUBLE_ARG` -* `SETTER_FLOAT_ARG` -* `SETTER_EXT_ARG` -* `SETTER_FUN_ARG` -* `SETTER_OBJ_ARG` -* `SETTER_ARRV_ARG` - -``` -JS_SETTER(MyClass::x) { SETTER_STR_ARG; - // Variable created: std::string v; - ... -``` - -</details> - - -<details> - -<summary>Data retrieval</summary> - -* `T *getArrayData(value, num = NULL)` - extracts TypedArray data of any type from -the given JS value. Does not accept `Array`. Checks with `IsArrayBuffer()`. -Returns `nullptr` for empty JS values. For unacceptable values throws TypeError. - -* `void *getData(value)` - if value is a TypedArray, then the result of -`getArrayData(value)` is returned. Otherwise if value has `'data'` property, it's -content is then returned as `node::Buffer`. Returns `nullptr` in other cases. - -</details> - ---- +Helpers for Node.js **NAPI** addons and dependency packages. + +**Go to**: + +[include/addon-tools.hpp](doc/addon-tools.md) +Macro shortcuts for NAPI. +[Es5 Class Wrapping](doc/class-wrapping.md) +An alternative, lightweight native class-defining mechanism for addons. +* [Script Helpers](doc/script-helpers.md) +Additional scripts for addon management. +* [Snippets](doc/snippets.md) +Some repetitive bits of code for addons. ## index.js -Exports: +Main exports for cross-platform addon configuration. Exports: * `paths(dir)` - function. Returns a set of platform dependent paths depending on input `dir`. * `bin` - platform binary directory absolute path. @@ -552,105 +39,3 @@ Use with `node -p` through list context command expansion `<!@(...)` * `cp` - the location of `'_cp.bat'` file on Windows and plain `cp` on Unix. * `mkdir` - the location of `'_mkdir.bat'` file on Windows and plain `mkdir` on Unix. * `bin` - platform binary directory name. - ---- - - -### mkdir - -On Unix, it will be an actual system `mkdir`, whereas on Windows it will use the -**mkdir.bat** file, located at the root of this package. This BAT file behaves -as if it was a `mkdir -p ...` call. You can still pass `-p` switch, which is -ignored. And the limitation is that you can not create a relative-path **-p** -folder. This can possibly be bypassed by supplying `./-p` or something like this. - - -``` -'variables': { - 'mkdir' : '<!(node -p "require(\'addon-tools-raub\').mkdir")', -}, -... -'action' : ['<(mkdir)', '-p', 'binary'], -``` - - -### rm - -Disregard `del` and `rd` on Windows command line. Now the same command can -be used on all platforms to remove single and multiple files and directories. - -``` -'variables': { - 'rm' : '<!(node -e "require(\'addon-tools-raub\').rm")', -}, -... -'action' : ['<(rm)', '-rf', 'dirname'], -``` - -### cp - -For Windows the `/y` flag was embedded. - -``` -'variables': { - 'cp' : '<!(node -e "require(\'addon-tools-raub\').cp")', -}, -... -'action' : ['<(cp)', 'a', 'b'], -``` - - -## Function consoleLog - -In C++ addons, the use of **iostream** is discouraged because **Node.js** has its own -perspective on **stdout** behavior. -At first it may look as if `cout << "msg" << endl;` works nice, but it doesn't. -After a while, it just ceases on a midword, and you end up thinking something has -broken really hard in your addon. - -To overcome this, we can use some `eval` magic to make a real `console.log` -call from C++ land. And this is where `consoleLog` comes into play. - -* `inline void consoleLog(int argc, V8_VAR_VAL *argv)` - a generic logger, -receives any set of arguments. - -* `inline void consoleLog(const std::string &message)` - an alias to log a single -string. - -> Note: only use this within JS function stack - - -## Function eventEmit - -In N-API there is no inheritance. And no `eval('require(...)')` possible at -the time of module init. But `require('events')` is very tempting... -The solution is: - -``` -// JS -const EventEmitter = require('events'); -const { bin } = require('addon-tools-raub'); -const MyClass = require(`./${bin}/addon`); -MyClass.prototype.__proto__ = EventEmitter.prototype; -// Since now it is possible to call `emit` from instancess of MyClass -``` - -``` -// C++ -void MyClass::emit(const Napi::CallbackInfo& info, const char* name) { - NAPI_ENV; - THIS_OBJ(that); - eventEmit(env, that, name); -} -``` - -Signature: -``` -inline void eventEmit( - Napi::Env env, - Napi::Object that, - const std::string &name, - int argc = 0, - Napi::Value *argv = nullptr -) -``` diff --git a/bat/cp.bat b/bat/cp.bat deleted file mode 100644 index 1f14d31..0000000 --- a/bat/cp.bat +++ /dev/null @@ -1 +0,0 @@ -copy /y %1 %2 diff --git a/bat/mkdir.bat b/bat/mkdir.bat deleted file mode 100644 index 6fd346a..0000000 --- a/bat/mkdir.bat +++ /dev/null @@ -1,5 +0,0 @@ -for %%x in (%*) do ( - - if not %%x=="-p" if not exist %%x md %%x - -) diff --git a/bat/rm.bat b/bat/rm.bat deleted file mode 100644 index 48dd3b5..0000000 --- a/bat/rm.bat +++ /dev/null @@ -1,6 +0,0 @@ -for %%x in (%*) do ( - - if not %%x=="-rf" if not %%x=="-r" if not %%x=="-f" if exist %%~x\ rd /s /q %%x - if not %%x=="-rf" if not %%x=="-r" if not %%x=="-f" if not exist %%~x\ if exist %%x del /f /q %%x - -) diff --git a/doc/addon-tools.md b/doc/addon-tools.md new file mode 100644 index 0000000..525f802 --- /dev/null +++ b/doc/addon-tools.md @@ -0,0 +1,238 @@ +# include/addon-tools.hpp + +There is a C++ header file, `addon-tools.hpp`, shipped with this package. It +introduces several useful macros and utilities. Also it includes Napi automatically, +so that you can replace: + +``` +#include <napi.h> +``` + +with + +``` +#include <addon-tools.hpp> +``` + +In gyp, the include directory should be set for your addon to know where +to get it. An actual path to the directory is exported from the module +and is accessible like this: + +``` +require('addon-tools-raub').include // a string +``` + +### Helpers in **addon-tools.hpp**: + +Usually all the helpers work within the context of JS call. In this case we +have `CallbackInfo info` passed as an argument. + +``` +#define NAPI_ENV Napi::Env env = info.Env(); +#define NAPI_HS Napi::HandleScope scope(env); +``` + +<details> + +<summary>Return value</summary> + +* `RET_VALUE(VAL)`- return a given Napi::Value. +* `RET_UNDEFINED`- return `undefined`. +* `RET_NULL` - return `null`. +* `RET_STR(VAL)` - return `Napi::String`, expected `VAL` is `const char *`. +* `RET_NUM(VAL)` - return `Napi::Number`, expected `VAL` is `double`. +* `RET_EXT(VAL)` - return `Napi::External`, expected `VAL` is `void *`. +* `RET_BOOL(VAL)` - return `Napi::Boolean`, expected `VAL` is `bool`. +* `RET_FUN(VAL)` - return `Napi::Function`, expected `VAL` is a `napi_value`. +* `RET_OBJ(VAL)` - return `Napi::Object`, expected `VAL` is a `napi_value`. + +</details> + + + +<details> + +<summary>New JS value</summary> + +* `JS_STR(VAL)` - create a `Napi::String` value. +* `JS_NUM(VAL)` - create a `Napi::Number` value. +* `JS_EXT(VAL)` - create a `Napi::External` (from pointer) value. +* `JS_BOOL(VAL)` - create a `Napi::Boolean` value. +* `JS_FUN(VAL)` - create a `Napi::Function` value. +* `JS_OBJ(VAL)` - create a `Napi::Object` value. + +</details> + + +<details> + +<summary>Method check</summary> + +These checks throw JS TypeError if not passed. Here `T` is always used as a typename +in error messages. `C` is a +[Napi::Value](https://github.com/nodejs/node-addon-api/blob/master/doc/value.md#isboolean) +check method, like `IsObject()`. `I` is the index of argument as in `info[I]`, +starting from `0`. + +* `REQ_ARGS(N)` - check if at least `N` arguments passed +* `IS_ARG_EMPTY(I)` - check if argument `I` is `undefined` or `null` +* `CHECK_REQ_ARG(I, C, T)` - check if argument `I` is approved by `C` check. +* `CHECK_LET_ARG(I, C, T)` - check if argument `I` is approved by `C` check or empty. +* `CTOR_CHECK(T)` - check if method is called as a constructor +* `SETTER_CHECK(C, T)` - check if setter `value` is approved by `C` check. +* `DES_CHECK` - within dynamic method check if the instance wasn't +destroyed by `destroy()`. + +</details> + + +<details> + +<summary>Method arguments</summary> + +The idea is to ease the transition from what inside the `CallbackInfo` to +what you work with in C++. +Three types of argument retrieval are supported: `REQ_`, `USE_` and `LET_`. +The difference: +* `REQ_` - 2 params, requires an argument to have a value +* `USE_` - 3 params, allows the argument to be empty and have a default +* `LET_` - 2 params, is `USE_` with a preset zero-default. + +What it does, basically: +``` +// REQ_DOUBLE_ARG(0, x) +double x = info[0].ToNumber().DoubleValue(); + +// USE_DOUBLE_ARG(0, x, 5.7) +double x = IS_ARG_EMPTY(0) ? 5.7 : info[0].ToNumber().DoubleValue(); + +// LET_DOUBLE_ARG(0, x) +double x = IS_ARG_EMPTY(0) ? 0.0 : info[0].ToNumber().DoubleValue(); +``` + +That extrapolates well to all the helpers below: +* `REQ_STR_ARG` - JS `string` => C++ `std::string`. +* `USE_STR_ARG` +* `LET_STR_ARG` - default: `""`. +* `REQ_INT32_ARG` - JS `number` => C++ `int32_t`. +* `USE_INT32_ARG` +* `LET_INT32_ARG` - default: `0`. +* `REQ_INT_ARG` - JS `number` => C++ `int32_t`. +* `USE_INT_ARG` +* `LET_INT_ARG` - default: `0`. +* `REQ_UINT32_ARG` - JS `number` => C++ `uint32_t`. +* `USE_UINT32_ARG` +* `LET_UINT32_ARG` - default: `0`. +* `REQ_UINT_ARG` - JS `number` => C++ `uint32_t`. +* `USE_UINT_ARG` +* `LET_UINT_ARG` - default: `0`. +* `REQ_BOOL_ARG` - JS `Boolean` => C++ `bool`. +* `USE_BOOL_ARG` +* `LET_BOOL_ARG` - default: `false`. +* `REQ_OFFS_ARG` - JS `number` => C++ `size_t`. +* `USE_OFFS_ARG` +* `LET_OFFS_ARG` - default: `0`. +* `REQ_DOUBLE_ARG` - JS `number` => C++ `double`. +* `USE_DOUBLE_ARG` +* `LET_DOUBLE_ARG` - default: `0.0`. +* `REQ_FLOAT_ARG` - JS `number` => C++ `float`. +* `USE_FLOAT_ARG` +* `LET_FLOAT_ARG` - default: `0.f`. +* `REQ_EXT_ARG` - JS `native` => C++ `void*`. +* `USE_EXT_ARG` +* `LET_EXT_ARG` - default: `nullptr`. +* `REQ_FUN_ARG` - JS `function` => C++ `Napi::Function`. +* `REQ_OBJ_ARG` - JS `object` => C++ `Napi::Object`. +* `USE_OBJ_ARG` +* `LET_OBJ_ARG` - default: `{}`. +* `REQ_ARRV_ARG` - JS `ArrayBuffer` => C++ `Napi::ArrayBuffer`. +* `REQ_BUF_ARG` - JS `Buffer` => C++ `Napi::Buffer<uint8_t>`. + + +``` +NAN_METHOD(test) { + + REQ_UINT32_ARG(0, width); + REQ_UINT32_ARG(1, height); + LET_FLOAT_ARG(2, z); + // Variables created: unsigned int width, height; float z; + ... +``` + +</details> + + +<details> + +<summary>Set object accessors</summary> + +Simplified accessor assignment, adds accessors of NAME for OBJ. Read accessor is +assumed to have the name `NAME+'Getter'` and write accessor is `NAME+'Setter'`. + +* `ACCESSOR_RW(CLASS, NAME)` - add read and write accessors NAME of CLASS. +* `ACCESSOR_R(CLASS, NAME)` - add read accessor NAME of CLASS. +* `ACCESSOR_M(CLASS, NAME)` - add method NAME of CLASS. + + +``` +void MyClass::init(Napi::Env env, Napi::Object exports) { + ... + Napi::Function ctor = DefineClass(env, "MyClass", { + ACCESSOR_R(MyClass, isDestroyed), + ACCESSOR_RW(MyClass, x), + ACCESSOR_M(MyClass, reset), + }); + ... +} +JS_GETTER(MyClass::isDestroyedGetter) { ... +JS_GETTER(MyClass::xGetter) { ... +JS_SETTER(MyClass::xSetter) { ... +JS_METHOD(MyClass::save) { ... +``` + +</details> + + +<details> + +<summary>Setter argument</summary> + +Works similar to method arguments. But there is always `value` +argument, from which a C++ value is extracted. + +* `SETTER_STR_ARG` +* `SETTER_INT32_ARG` +* `SETTER_INT_ARG` +* `SETTER_BOOL_ARG` +* `SETTER_UINT32_ARG` +* `SETTER_UINT_ARG` +* `SETTER_OFFS_ARG` +* `SETTER_DOUBLE_ARG` +* `SETTER_FLOAT_ARG` +* `SETTER_EXT_ARG` +* `SETTER_FUN_ARG` +* `SETTER_OBJ_ARG` +* `SETTER_ARRV_ARG` + +``` +JS_SETTER(MyClass::x) { SETTER_STR_ARG; + // Variable created: std::string v; + ... +``` + +</details> + + +<details> + +<summary>Data retrieval</summary> + +* `T *getArrayData(value, num = NULL)` - extracts TypedArray data of any type from +the given JS value. Does not accept `Array`. Checks with `IsArrayBuffer()`. +Returns `nullptr` for empty JS values. For unacceptable values throws TypeError. + +* `void *getData(value)` - if value is a TypedArray, then the result of +`getArrayData(value)` is returned. Otherwise if value has `'data'` property, it's +content is then returned as `node::Buffer`. Returns `nullptr` in other cases. + +</details> diff --git a/doc/class-wrapping.md b/doc/class-wrapping.md new file mode 100644 index 0000000..c9a368a --- /dev/null +++ b/doc/class-wrapping.md @@ -0,0 +1 @@ +# Es5 class wrapping diff --git a/doc/script-helpers.md b/doc/script-helpers.md new file mode 100644 index 0000000..5b755c2 --- /dev/null +++ b/doc/script-helpers.md @@ -0,0 +1 @@ +# Script Helpers diff --git a/doc/snippets.md b/doc/snippets.md new file mode 100644 index 0000000..2b7a19e --- /dev/null +++ b/doc/snippets.md @@ -0,0 +1,197 @@ +# Snippets + +## C++ Addon building + +N-API addons are built separately from the installation, so we can't/shouldn't +put the file **binding.gyp** to the module root anymore. It is better to have a +separate folder with a separate **package.json**, **binding.gyp** and the sources. + +A snippet for **src/package.json**: +``` +{ + "name": "build", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "node-gyp rebuild && node -e \"require('addon-tools-raub/cpbin')('ADDON')\"" + }, + "dependencies": { + "addon-tools-raub": "5.0.0", + "DEPS": "1.0.0" + } +} +``` + +* `ADDON` - the name of this addon (and subsequently of its binary). +* `DEPS` - dependency package(s). + + + +### Binary distribution + +In **package.json** use the `"postinstall"` script to download the libraries. +For example the following structure might work. Note that **Addon Tools** will +append any given URL with `/${platform}.zip` + +``` + "config" : { + "install" : "v1.0.0" + }, + "scripts": { + "postinstall": "install", + }, +``` + +Here `config.install` is the tag name to download the binaries from. +To use it, create the *install.js* file in your addon: + +``` +const install = require('addon-tools-raub/install'); +const prefix = 'https://github.com/USER/ADDON/releases/download'; +const tag = process.env.npm_package_config_install; +install(`${prefix}/${tag}`); +``` + +**Addon Tools** will unzip the downloaded file into the platform binary +directory. E.g. on Windows it will be **bin-windows**. + +* For a dependency package: + + Place the following piece of code in the `index.js` without changes. Method `paths()` + is described [here](../README.md). + ``` + module.exports = require('addon-tools-raub').paths(__dirname); + ``` + +* For a compiled addon: + + Require it in your **index.js** from the platform-specific directory. + ``` + const { bin } = require('addon-tools-raub'); + const core = require(`./${bin}/ADDON`); + ``` + + +Publishing binaries is done by attaching a zipped platform folder to the Github +release. Zip file must NOT contain platform folder as a subfolder, but rather +contain the final binaries. The tag of the release should be the same as in +`npm_package_config_install` - that is the way installer will find it. + +> NOTE: You can publish your binaries to anywhere, not necessarily Github. +Just tweak **YOUR install.js** script as appropriate. The only limitation +from **Addon Tools** is that it should be a zipped set of files/folders. + + +### GYP Variables + +``` + 'variables': { + 'bin' : '<!(node -p "require(\'addon-tools-raub\').bin")', + 'DEPS_include' : '<!(node -p "require(\'DEPS\').include")', + 'DEPS_bin' : '<!(node -p "require(\'DEPS\').bin")', + }, +``` + +* `bin` - the name of this platform's binary directory, e.g. *bin-linux*. +* `DEPS_include` - the include folder for some dependency package. +* `DEPS_bin` - the binary folder for some dependency package. + + + +### Include directories + +``` + 'include_dirs' : [ + '<!@(node -p "require(\'addon-tools-raub\').include")', + '<(DEPS_include)', + ], +``` + +The former contains both the path to include **Addon Tools** and the one for +**Napi** (which is preinstalled with Addon Tools). The latter can be any other +dependency include path(s). + + +<details> + +<summary>See a snipped for src/binding.gyp here</summary> + +* Assume `DEPS` is the name of an Addon Tools compliant dependency module. +* Assume `ADDON` is the name of this addon's resulting binary. +* Assume C++ code goes to `cpp` subdirectory. + +``` +{ + 'variables': { + 'bin' : '<!(node -p "require(\'addon-tools-raub\').bin")', + 'DEPS_include' : '<!(node -p "require(\'DEPS\').include")', + 'DEPS_bin' : '<!(node -p "require(\'DEPS\').bin")', + }, + 'targets': [ + { + 'target_name' : 'bullet', + 'sources' : [ + 'cpp/addon.cpp', + ], + 'include_dirs' : [ + '<!@(node -p "require(\'addon-tools-raub\').include")', + '<(DEPS_include)', + ], + 'library_dirs' : [ '<(DEPS_bin)' ], + 'libraries' : [ '-lDEPS' ], + 'cflags!': ['-fno-exceptions'], + 'cflags_cc!': ['-fno-exceptions'], + 'conditions': [ + + [ + 'OS=="linux"', + { + 'libraries': [ + "-Wl,-rpath,'$$ORIGIN'", + "-Wl,-rpath,'$$ORIGIN/../node_modules/DEPS/<(bin)'", + "-Wl,-rpath,'$$ORIGIN/../../DEPS/<(bin)'", + ], + 'defines': ['__linux__'], + } + ], + + [ + 'OS=="mac"', + { + 'libraries': [ + '-Wl,-rpath,@loader_path', + '-Wl,-rpath,@loader_path/../node_modules/DEPS/<(bin)', + '-Wl,-rpath,@loader_path/../../DEPS/<(bin)', + ], + 'defines': ['__APPLE__'], + } + ], + + [ + 'OS=="win"', + { + 'defines' : [ + 'WIN32_LEAN_AND_MEAN', + 'VC_EXTRALEAN', + '_WIN32', + ], + 'msvs_settings' : { + 'VCCLCompilerTool' : { + 'AdditionalOptions' : [ + '/GL', '/GF', '/EHsc', '/GS', '/Gy', '/GR-', + ] + }, + 'VCLinkerTool' : { + 'AdditionalOptions' : ['/RELEASE','/OPT:REF','/OPT:ICF','/LTCG'], + }, + }, + }, + ], + + ], + }, + ] +} +``` + +</details> diff --git a/include/addon-tools.hpp b/include/addon-tools.hpp index 5e6b611..ebdca20 100644 --- a/include/addon-tools.hpp +++ b/include/addon-tools.hpp @@ -1,5 +1,5 @@ -#ifndef _ADDON_TOOLS_HPP_ -#define _ADDON_TOOLS_HPP_ +#ifndef ADDON_TOOLS_HPP +#define ADDON_TOOLS_HPP #define NODE_ADDON_API_DISABLE_DEPRECATED #define NAPI_DISABLE_CPP_EXCEPTIONS @@ -7,7 +7,7 @@ #ifdef _WIN32 - #define strcasestr(s, t) strstr(strupr(s), strupr(t)) + #define strcasestr(s, t) strstr(strupr(s), strupr(t)) #endif @@ -41,6 +41,7 @@ #define REQ_ARGS(N) \ if (info.Length() < (N)) { \ JS_THROW("Expected at least " #N " arguments"); \ + RET_UNDEFINED; \ } @@ -51,6 +52,7 @@ #define CHECK_REQ_ARG(I, C, T) \ if (info.Length() <= (I) || ! info[I].C) { \ JS_THROW("Argument " #I " must be of type `" T "`"); \ + RET_UNDEFINED; \ } #define CHECK_LET_ARG(I, C, T) \ @@ -60,6 +62,7 @@ " must be of type `" T \ "` or be `null`/`undefined`" \ ); \ + RET_UNDEFINED; \ } @@ -195,6 +198,7 @@ REQ_OBJ_ARG(I, _obj_##VAR); \ if ( ! _obj_##VAR.IsArray() ) { \ JS_THROW("Argument " #I " must be of type `Array`"); \ + RET_UNDEFINED; \ } \ Napi::Array VAR = _obj_##VAR.As<Napi::Array>(); @@ -203,14 +207,11 @@ REQ_OBJ_ARG(I, _obj_##VAR); \ if ( ! _obj_##VAR.IsTypedArray() ) { \ JS_THROW("Argument " #I " must be of type `TypedArray`"); \ + RET_UNDEFINED; \ } \ Napi::TypedArray VAR = _obj_##VAR.As<Napi::TypedArray>(); -#define CTOR_CHECK(T) \ - if ( ! info.IsConstructCall() ) \ - JS_THROW(T " must be called with the 'new' keyword."); - #define DES_CHECK \ if (_isDestroyed) return; @@ -220,17 +221,15 @@ #define CACHE_CAS(CACHE, V) \ if (CACHE == V) { \ - return; \ + RET_UNDEFINED; \ } \ CACHE = V; -#define THIS_SETTER_CHECK \ - if (_isDestroyed) return; \ - NAPI_ENV; - #define SETTER_CHECK(C, T) \ - if ( ! value.C ) \ - JS_THROW("Value must be " T); + if ( ! value.C ) { \ + JS_THROW("Value must be " T); \ + RET_UNDEFINED; \ + } #define JS_METHOD(NAME) Napi::Value NAME(const Napi::CallbackInfo &info) @@ -335,7 +334,11 @@ template<typename Type = uint8_t> -inline Type* getArrayData(Napi::Env env, Napi::Object obj, int *num = nullptr) { +inline Type* getArrayData( + Napi::Env env, + Napi::Object obj, + int *num = nullptr +) { Type *data = nullptr; @@ -366,7 +369,11 @@ inline Type* getArrayData(Napi::Env env, Napi::Object obj, int *num = nullptr) { } template<typename Type = uint8_t> -inline Type* getBufferData(Napi::Env env, Napi::Object obj, int *num = nullptr) { +inline Type* getBufferData( + Napi::Env env, + Napi::Object obj, + int *num = nullptr +) { Type *data = nullptr; @@ -410,7 +417,11 @@ inline void *getData(Napi::Env env, Napi::Object obj) { } -inline void consoleLog(Napi::Env env, int argc, const Napi::Value *argv) { +inline void consoleLog( + Napi::Env env, + int argc, + const Napi::Value *argv +) { JS_RUN_2("console.log", log); std::vector<napi_value> args; for (int i = 0; i < argc; i++) { @@ -481,9 +492,17 @@ inline void eventEmitAsync( } -inline void inheritEs5(napi_env env, Napi::Function ctor, Napi::Function superCtor) { +inline void inheritEs5( + napi_env env, + Napi::Function ctor, + Napi::Function superCtor +) { - napi_value global, globalObject, setProto, ctorProtoProp, superCtorProtoProp; + napi_value global; + napi_value globalObject; + napi_value setProto; + napi_value ctorProtoProp; + napi_value superCtorProtoProp; napi_value argv[2]; napi_get_global(env, &global); @@ -510,153 +529,170 @@ typedef Napi::Value (*Es5GetterCallback)(const Napi::CallbackInfo& info); typedef void (*Es5SetterCallback)(const Napi::CallbackInfo& info); -#define DECLARE_ES5_CLASS(CLASS, NAME) \ -private: \ - static Napi::FunctionReference _ctorEs5; \ - static const char *_nameEs5; \ - static void _finalizeEs5(napi_env e, void *dest, void* hint); \ - static napi_value _createEs5(napi_env e, napi_callback_info i); \ - inline void super( \ - const Napi::CallbackInfo& info, \ - int argc, \ - const Napi::Value *argv \ - ) { \ - Napi::Function ctor = _ctorEs5.Value(); \ - if (ctor.Has("super_")) { \ - Napi::Function _super = ctor.Get("super_").As<Napi::Function>(); \ - std::vector<napi_value> args; \ - for (int i = 0; i < argc; i++) { \ - args.push_back(argv[i]); \ - } \ - _super.Call(info.This(), args); \ - } \ - } \ - inline void super( \ - const Napi::CallbackInfo& info, \ - int argc = 0, \ - const napi_value *argv = nullptr \ - ) { \ - Napi::Function ctor = _ctorEs5.Value(); \ - if (ctor.Has("super_")) { \ - Napi::Function _super = ctor.Get("super_").As<Napi::Function>(); \ - _super.Call(info.This(), argc, argv); \ - } \ - } \ - inline static Napi::Function wrap(Napi::Env env) { \ - napi_value __initResult; \ - napi_create_function(env, #NAME, 0, _createEs5, nullptr, &__initResult); \ - Napi::Function ctor = Napi::Function(env, __initResult); \ - _ctorEs5 = Napi::Persistent(ctor); \ - _ctorEs5.SuppressDestruct(); \ - return ctor; \ - } \ - inline static Napi::Function wrap( \ - Napi::Env env, \ - Napi::Function superCtor \ - ) { \ - Napi::Function ctor = wrap(env); \ - inheritEs5(env, ctor, superCtor); \ - return ctor; \ - } \ - inline static void method( \ - const char *name, \ - Es5MethodCallback cb \ - ) { \ - Napi::Function proto = _ctorEs5.Value().Get("prototype").As<Napi::Function>(); \ - proto.DefineProperty( \ - Napi::PropertyDescriptor::Function(proto.Env(), proto, name, cb) \ - ); \ - } \ - inline static void accessorR( \ - const char *name, \ - Es5GetterCallback getter \ - ) { \ - Napi::Function proto = _ctorEs5.Value().Get("prototype").As<Napi::Function>(); \ - proto.DefineProperty( \ - Napi::PropertyDescriptor::Accessor(proto.Env(), proto, name, getter) \ - ); \ - } \ - inline static void accessorRw( \ - const char *name, \ - Es5GetterCallback getter, \ - Es5SetterCallback setter \ - ) { \ - Napi::Function proto = _ctorEs5.Value().Get("prototype").As<Napi::Function>(); \ - proto.DefineProperty( \ - Napi::PropertyDescriptor::Accessor( \ - proto.Env(), \ - proto, \ - name, \ - getter, \ - setter \ - ) \ - ); \ - } \ -public: \ - inline static CLASS *unwrap(Napi::Object thatObj) { \ - CLASS *that; \ - napi_unwrap( \ - thatObj.Env(), \ - thatObj.Get(_nameEs5), \ - reinterpret_cast<void**>(&that) \ - ); \ - return that; \ +#define DECLARE_ES5_CLASS(CLASS, NAME) \ +public: \ + inline static CLASS *unwrap(Napi::Object thatObj) { \ + CLASS *that; \ + napi_unwrap( \ + thatObj.Env(), \ + thatObj.Get(_nameEs5), \ + reinterpret_cast<void**>(&that) \ + ); \ + return that; \ + } \ +private: \ + static Napi::FunctionReference _ctorEs5; \ + static const char *_nameEs5; \ + static void _finalizeEs5(napi_env e, void *dest, void* hint); \ + static napi_value _createEs5(napi_env e, napi_callback_info i); \ + inline void super( \ + const Napi::CallbackInfo& info, \ + int argc, \ + const Napi::Value *argv \ + ) { \ + Napi::Function ctor = _ctorEs5.Value(); \ + if (ctor.Has("super_")) { \ + Napi::Function _super = ctor.Get("super_").As<Napi::Function>(); \ + std::vector<napi_value> args; \ + for (int i = 0; i < argc; i++) { \ + args.push_back(argv[i]); \ + } \ + _super.Call(info.This(), args); \ + } \ + } \ + inline void super( \ + const Napi::CallbackInfo& info, \ + int argc = 0, \ + const napi_value *argv = nullptr \ + ) { \ + Napi::Function ctor = _ctorEs5.Value(); \ + if (ctor.Has("super_")) { \ + Napi::Function _super = ctor.Get("super_").As<Napi::Function>(); \ + _super.Call(info.This(), argc, argv); \ + } \ + } \ + inline static Napi::Function wrap(Napi::Env env) { \ + napi_value __initResult; \ + napi_create_function( \ + env, #NAME, 0, _createEs5, nullptr, &__initResult \ + ); \ + Napi::Function ctor = Napi::Function(env, __initResult); \ + _ctorEs5 = Napi::Persistent(ctor); \ + _ctorEs5.SuppressDestruct(); \ + return ctor; \ + } \ + inline static Napi::Function wrap( \ + Napi::Env env, \ + Napi::Function superCtor \ + ) { \ + Napi::Function ctor = wrap(env); \ + inheritEs5(env, ctor, superCtor); \ + return ctor; \ + } \ + inline static void method( \ + const char *name, \ + Es5MethodCallback cb \ + ) { \ + Napi::Function proto = ( \ + _ctorEs5.Value().Get("prototype").As<Napi::Function>() \ + ); \ + proto.DefineProperty( \ + Napi::PropertyDescriptor::Function( \ + proto.Env(), proto, name, cb \ + ) \ + ); \ + } \ + inline static void accessorR( \ + const char *name, \ + Es5GetterCallback getter \ + ) { \ + Napi::Function proto = ( \ + _ctorEs5.Value().Get("prototype").As<Napi::Function>() \ + ); \ + proto.DefineProperty( \ + Napi::PropertyDescriptor::Accessor( \ + proto.Env(), proto, name, getter \ + ) \ + ); \ + } \ + inline static void accessorRw( \ + const char *name, \ + Es5GetterCallback getter, \ + Es5SetterCallback setter \ + ) { \ + Napi::Function proto = ( \ + _ctorEs5.Value().Get("prototype").As<Napi::Function>() \ + ); \ + proto.DefineProperty( \ + Napi::PropertyDescriptor::Accessor( \ + proto.Env(), \ + proto, \ + name, \ + getter, \ + setter \ + ) \ + ); \ } -#define JS_GET_THAT(CLASS) \ - CLASS *that; \ - Napi::Object thatObj = info.This().As<Napi::Object>(); \ - napi_unwrap(info.Env(), thatObj.Get(_nameEs5), reinterpret_cast<void**>(&that)); +#define JS_GET_THAT(CLASS) \ + CLASS *that; \ + Napi::Object thatObj = info.This().As<Napi::Object>(); \ + napi_unwrap( \ + info.Env(), thatObj.Get(_nameEs5), reinterpret_cast<void**>(&that) \ + ); -#define JS_DECLARE_METHOD(CLASS, NAME) \ - inline static Napi::Value __st_##NAME(const Napi::CallbackInfo &info) { \ - JS_GET_THAT(CLASS); \ - return that->__i_##NAME(info); \ - }; \ +#define JS_DECLARE_METHOD(CLASS, NAME) \ + inline static Napi::Value __st_##NAME(const Napi::CallbackInfo &info) { \ + JS_GET_THAT(CLASS); \ + return that->__i_##NAME(info); \ + }; \ Napi::Value __i_##NAME(const Napi::CallbackInfo &info); #define JS_DECLARE_GETTER(CLASS, NAME) JS_DECLARE_METHOD(CLASS, NAME##Getter) -#define JS_DECLARE_SETTER(CLASS, NAME) \ - inline static void __st_##NAME##Setter( \ - const Napi::CallbackInfo &info \ - ) { \ - JS_GET_THAT(CLASS); \ - that->__i_##NAME##Setter(info, info[0]); \ - } \ - void __i_##NAME##Setter(const Napi::CallbackInfo &info, const Napi::Value &value); +#define JS_DECLARE_SETTER(CLASS, NAME) \ + inline static void __st_##NAME##Setter( \ + const Napi::CallbackInfo &info \ + ) { \ + JS_GET_THAT(CLASS); \ + that->__i_##NAME##Setter(info, info[0]); \ + } \ + Napi::Value __i_##NAME##Setter( \ + const Napi::CallbackInfo &info, \ + const Napi::Value &value \ + ); -#define JS_IMPLEMENT_METHOD(CLASS, NAME) \ +#define JS_IMPLEMENT_METHOD(CLASS, NAME) \ Napi::Value CLASS::__i_##NAME(const Napi::CallbackInfo &info) #define JS_IMPLEMENT_GETTER(CLASS, NAME) JS_IMPLEMENT_METHOD(CLASS, NAME##Getter) -#define JS_IMPLEMENT_SETTER(CLASS, NAME) \ - void CLASS::__i_##NAME##Setter( \ - const Napi::CallbackInfo &info, \ - const Napi::Value &value \ +#define JS_IMPLEMENT_SETTER(CLASS, NAME) \ + Napi::Value CLASS::__i_##NAME##Setter( \ + const Napi::CallbackInfo &info, \ + const Napi::Value &value \ ) #define JS_ASSIGN_METHOD(NAME) method(#NAME, __st_##NAME) #define JS_ASSIGN_GETTER(NAME) accessorR(#NAME, __st_##NAME##Getter) -#define JS_ASSIGN_SETTER(NAME) accessorRw(#NAME, __st_##NAME##Getter, __st_##NAME##Setter) +#define JS_ASSIGN_SETTER(NAME) \ + accessorRw(#NAME, __st_##NAME##Getter, __st_##NAME##Setter) -#define IMPLEMENT_ES5_CLASS(CLASS) \ - Napi::FunctionReference CLASS::_ctorEs5; \ - const char *CLASS::_nameEs5 = #CLASS; \ - void CLASS::_finalizeEs5(napi_env e, void *dest, void* hint) { \ - CLASS *instance = reinterpret_cast<CLASS*>(dest); \ - delete instance; \ - } \ - napi_value CLASS::_createEs5(napi_env env, napi_callback_info i) { \ - Napi::CallbackInfo info(env, i); \ - CLASS *instance = new CLASS(info); \ - Napi::Object wrapObj = Napi::Object::New(env); \ - info.This().As<Napi::Object>().Set(_nameEs5, wrapObj); \ - napi_wrap(env, wrapObj, instance, _finalizeEs5, nullptr, nullptr); \ - return info.Env().Undefined(); \ +#define IMPLEMENT_ES5_CLASS(CLASS) \ + Napi::FunctionReference CLASS::_ctorEs5; \ + const char *CLASS::_nameEs5 = #CLASS; \ + void CLASS::_finalizeEs5(napi_env e, void *dest, void* hint) { \ + CLASS *instance = reinterpret_cast<CLASS*>(dest); \ + delete instance; \ + } \ + napi_value CLASS::_createEs5(napi_env env, napi_callback_info i) { \ + Napi::CallbackInfo info(env, i); \ + CLASS *instance = new CLASS(info); \ + Napi::Object wrapObj = Napi::Object::New(env); \ + info.This().As<Napi::Object>().Set(_nameEs5, wrapObj); \ + napi_wrap(env, wrapObj, instance, _finalizeEs5, nullptr, nullptr); \ + return info.Env().Undefined(); \ } - -#endif // _ADDON_TOOLS_HPP_ +#endif // ADDON_TOOLS_HPP