Compare commits

..

No commits in common. "master" and "v4.2.0" have entirely different histories.

74 changed files with 2673 additions and 5204 deletions

116
.eslintrc Normal file
View File

@ -0,0 +1,116 @@
{
"root": true,
"env": {
"node" : true,
"es6" : true,
"mocha" : true
},
"globals": {
"expect" : true,
"chai" : true,
"sinon" : true
},
"extends": ["eslint:recommended"],
"parserOptions": {
"ecmaVersion": 8,
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"rules": {
"arrow-parens": ["error", "as-needed"],
"no-trailing-spaces": [
"error",
{
"skipBlankLines": true
}
],
"indent": [
"error",
"tab",
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"max-len": ["error", 110],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"no-multiple-empty-lines": ["error", { "max": 3, "maxEOF": 1, "maxBOF": 1 }],
"keyword-spacing": ["error", { "before": true, "after": true }],
"space-before-blocks": ["error"],
"space-before-function-paren": ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}],
"space-infix-ops": ["error"],
"space-unary-ops": [
"error", {
"words": true,
"nonwords": false,
"overrides": {
"!": true
}
}
],
"spaced-comment": [0],
"camelcase": ["error"],
"no-tabs": [0],
"comma-dangle": [0],
"global-require": [0],
"func-names": [0],
"no-param-reassign": [0],
"no-underscore-dangle": [0],
"no-restricted-syntax": [
"error",
{
"selector": "LabeledStatement",
"message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand."
},
{
"selector": "WithStatement",
"message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize."
}
],
"no-mixed-operators": [0],
"no-plusplus": [0],
"comma-spacing": [0],
"default-case": [0],
"no-shadow": [0],
"no-console": [0],
"key-spacing": [0],
"no-return-assign": [0],
"consistent-return": [0],
"class-methods-use-this": [0],
"no-multi-spaces": [
"error",
{
"exceptions": {
"VariableDeclarator": true,
"Property": true,
"ImportDeclaration": true
}
}
],
"array-callback-return": [0],
"no-use-before-define": [
"error",
{
"functions": false,
"classes": true,
"variables": true
}
],
"padded-blocks": [0],
"space-in-parens": [0],
"valid-jsdoc": [0],
"no-unused-expressions": [0],
"import/no-dynamic-require": [0]
}
}

View File

@ -1,75 +0,0 @@
{
"ignorePatterns": [
"src/**"
],
"extends": [
"eslint:recommended",
"plugin:node/recommended"
],
"parserOptions": {
"ecmaVersion": 2022
},
"env": {
"node": true,
"es6": true
},
"rules": {
"arrow-parens": ["error", "always"],
"no-trailing-spaces": [
"error",
{
"skipBlankLines": true
}
],
"indent": [
"error",
"tab",
{
"SwitchCase": 1
}
],
"operator-linebreak": [
"error",
"after",
{
"overrides": {
"?": "before",
":": "before"
}
}
],
"max-len": ["error", 110],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"no-multiple-empty-lines": ["error", { "max": 3, "maxEOF": 1, "maxBOF": 1 }],
"keyword-spacing": ["error", { "before": true, "after": true }],
"space-before-blocks": ["error"],
"space-before-function-paren": ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}],
"camelcase": ["error"],
"no-tabs": [0],
"global-require": [0],
"no-underscore-dangle": [0],
"no-plusplus": [0],
"no-shadow": [0],
"node/no-unpublished-require": [0],
"no-process-exit": [0],
"linebreak-style": [0],
"node/no-missing-require": [0],
"no-console": [0],
"node/no-unsupported-features/es-builtins": 0,
"node/no-unsupported-features/node-builtins": 0,
"func-names": [
"error",
"never",
{
"generators": "never"
}
]
}
}

1
.gitattributes vendored
View File

@ -1 +0,0 @@
test-addon/binding.gyp linguist-vendored

View File

@ -1,47 +0,0 @@
name: Cpplint
defaults:
run:
shell: bash
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
eslint:
name: Cpplint
runs-on: ubuntu-20.04
steps:
- name: Fetch Repository
uses: actions/checkout@v3
with:
persist-credentials: false
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.16.0
cache: 'npm'
- name: Install Modules
run: npm ci
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install Cpplint
run: pip install cpplint
- name: Run Cpplint
run: |
node -e "require('.').cpcpplint()"
cpplint --recursive ./test-addon
cpplint --recursive ./include

View File

@ -1,36 +0,0 @@
name: ESLint
defaults:
run:
shell: bash
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
eslint:
name: ESLint
runs-on: ubuntu-20.04
steps:
- name: Fetch Repository
uses: actions/checkout@v3
with:
persist-credentials: false
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.16.0
cache: 'npm'
- name: Install Modules
run: npm ci
- name: Run ESLint
run: npm run eslint

View File

@ -1,46 +0,0 @@
name: Publish to NPM
defaults:
run:
shell: bash
on:
workflow_dispatch
jobs:
Publish:
if: contains('["raub"]', github.actor)
runs-on: ubuntu-latest
steps:
- name: Fetch Repository
uses: actions/checkout@v3
with:
persist-credentials: false
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.16.0
cache: 'npm'
- name: Get Package Version
id: package-version
run: node -p "'version='+require('./package').version" >> $GITHUB_OUTPUT
- name: Publish
run: |
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
npm publish --ignore-scripts
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.package-version.outputs.version }}
name: Release ${{ steps.package-version.outputs.version }}
body: Published at ${{ github.sha }}

View File

@ -1,43 +0,0 @@
name: Test
defaults:
run:
shell: bash
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
unit-tests:
name: Unit Tests
strategy:
matrix:
os: [ubuntu-20.04, windows-2022, macos-11, [self-hosted, linux, ARM64]]
runs-on: ${{ matrix.os }}
steps:
- name: Fetch Repository
uses: actions/checkout@v3
with:
persist-credentials: false
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.16.0
cache: 'npm'
- name: Install Modules
run: npm ci
- name: Build Sample Addon
run: npm run build-test
- name: Run Unit Tests
run: npm run test-ci

10
.gitignore vendored
View File

@ -1,9 +1,11 @@
.cproject
.idea .idea
.lock-wscript .cproject
.DS_Store
.project .project
.lock-wscript
build*/
.DS_Store .DS_Store
Debug/
node_modules/ node_modules/
test-addon/build/ package-lock.json
binary/
*.log *.log

15
.npmignore Normal file
View File

@ -0,0 +1,15 @@
*.log
.cproject
.eslintrc
.gitignore
.idea
.lock-wscript
.project
binary/
build*/
CPPLINT.cfg
Debug/
examples/
package-lock.json
test/
qt/

19
.travis.yml Normal file
View File

@ -0,0 +1,19 @@
language: node_js
node_js:
- "10.13.0"
matrix:
include:
- name: "Linux"
os: linux
dist: xenial
sudo: false
- name: "MacOS"
os: osx
install:
- cd test
- npm i

View File

@ -1,17 +0,0 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**",
"${LocalAppData}/node-gyp/Cache/16.17.0/include/node",
"${LocalAppData}/node-gyp/Cache/18.16.0/include/node"
],
"windowsSdkVersion": "10.0.19041.0",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "windows-msvc-x64"
}
],
"version": 4
}

View File

@ -1,18 +1,15 @@
set noparent set noparent
linelength=110 linelength=110
filter=-build/header_guard
filter=-build/include
filter=-build/include_order
filter=-build/include_what_you_use
filter=-build/namespaces
filter=-legal/copyright filter=-legal/copyright
filter=-readability/todo filter=-build/include_order
filter=-runtime/indentation_namespace filter=-build/header_guard
filter=-build/namespaces
filter=-build/include_what_you_use
filter=-whitespace/blank_line filter=-whitespace/blank_line
filter=-whitespace/braces
filter=-whitespace/comments filter=-whitespace/comments
filter=-whitespace/tab
filter=-whitespace/end_of_line filter=-whitespace/end_of_line
filter=-whitespace/indent filter=-whitespace/indent
filter=-whitespace/operators filter=-whitespace/operators
filter=-whitespace/parens filter=-whitespace/parens
filter=-whitespace/tab filter=-readability/todo

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2023 Luis Blanco Copyright (c) 2018 Luis Blanco
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

802
README.md
View File

@ -2,74 +2,802 @@
This is a part of [Node3D](https://github.com/node-3d) project. This is a part of [Node3D](https://github.com/node-3d) project.
[![NPM](https://badge.fury.io/js/addon-tools-raub.svg)](https://badge.fury.io/js/addon-tools-raub) [![NPM](https://nodei.co/npm/addon-tools-raub.png?compact=true)](https://www.npmjs.com/package/addon-tools-raub)
[![ESLint](https://github.com/node-3d/addon-tools-raub/actions/workflows/eslint.yml/badge.svg)](https://github.com/node-3d/addon-tools-raub/actions/workflows/eslint.yml)
[![Test](https://github.com/node-3d/addon-tools-raub/actions/workflows/test.yml/badge.svg)](https://github.com/node-3d/addon-tools-raub/actions/workflows/test.yml) [![Build Status](https://api.travis-ci.com/node-3d/addon-tools-raub.svg?branch=master)](https://travis-ci.com/node-3d/addon-tools-raub)
[![Cpplint](https://github.com/node-3d/addon-tools-raub/actions/workflows/cpplint.yml/badge.svg)](https://github.com/node-3d/addon-tools-raub/actions/workflows/cpplint.yml) [![CodeFactor](https://www.codefactor.io/repository/github/node-3d/addon-tools-raub/badge)](https://www.codefactor.io/repository/github/node-3d/addon-tools-raub)
> npm i -s addon-tools-raub
## Synopsis
Helpers for Node.js addons and dependency packages:
* `consoleLog()` C++ implementation.
* `EventEmitter` C++ implementation.
* C++ macros and shortcuts.
* Crossplatform commands for GYP: `cp`, `rm`, `mkdir`.
* Regarded platforms: win x32/x64, linux x64, mac x64.
Useful links: [V8 Ref](https://v8.paulfryzel.com/docs/master/),
[Nan Docs](https://github.com/nodejs/nan#api),
[GYP Docs](https://gyp.gsrc.io/docs/UserDocumentation.md).
---
## Contents
[Snippets](#snippets)
[include/addon-tools.hpp](#includeaddon-toolshpp)
[index.js](#indexjs)
[Crossplatform commands](#crossplatform-commands)
[Class EventEmitter](#class-eventemitter)
[Function consoleLog](#function-consolelog)
---
## Snippets
### binding.gyp
<details>
<summary>Crossplatform commands</summary>
```
'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()")',
},
```
On both Windows and Unix those are the console commands for various
file system operations. No need for GYP conditions, yay!
</details>
<details>
<summary>Addon binary directory</summary>
```
'variables': {
'binary' : '<!(node -e "require(\'addon-tools-raub\').bin()")',
},
```
In some cases, you'd like to have your addon installed for multiple architectures
simultaneously. For example, when using NVM to fluently switch environments.
Because the target directory is different for each arch, you only have to do
`npm rebuild` after the first switch.
</details>
<details>
<summary>Include directories</summary>
```
'include_dirs': [
'<!@(node -e "require(\'addon-tools-raub\').include()")',
],
```
Those are the directory paths to C++ include files for Addon Tools and Nan
(which is preinstalled with Addon Tools)
</details>
<details>
<summary>Remove intermediates</summary>
```
[ 'OS=="linux"', { 'action' : [
'<(rm)',
'<(module_root_dir)/build/Release/obj.target/addon/cpp/addon.o',
'<(module_root_dir)/build/Release/addon.node'
] } ],
[ 'OS=="mac"', { 'action' : [
'<(rm)',
'<(module_root_dir)/build/Release/obj.target/addon/cpp/addon.o',
'<(module_root_dir)/build/Release/addon.node'
] } ],
[ 'OS=="win"', { 'action' : [
'<(rm)',
'<(module_root_dir)/build/Release/addon.*',
'<(module_root_dir)/build/Release/obj/addon/*.*'
] } ],
```
Build-files can be removed in a separate build-step with `<(rm)`. Those are
usually PDB and OBJ files, which are rather big. However, in case of a hardcore
debug session you might want to comment this out.
</details>
### Binary dependency package
If you design a module with binary dependencies for several platforms, **Addon Tools**
would encourage you to abide by the following rules:
* Your binary directories are:
* bin-win32
* bin-win64
* bin-linux64
* bin-mac64
* 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);
```
* Your whole **binding.gyp**:
<details>
<summary>Show binding.gyp</summary>
```
{
'variables': {
'rm' : '<!(node -e "require(\'addon-tools-raub\').rm()")',
'rem' : '<!(node -e "require(\'.\').rem()")',
'XALL%': 'false',
},
'targets': [
{
'target_name' : 'remove_extras',
'type' : 'none',
'conditions' : [['XALL=="false"', {'actions': [
{
'action_name' : 'Unnecessary binaries removed.',
'inputs' : [],
'outputs' : ['build'],
'action' : ['<(rm)', '-rf', '<@(rem)'],
}
]}]],
}
]
}
```
Notice the `XALL` variable here. If the package is installed with `npm i`, then
quite expectedly all but the required arch directories are removed. But with
`npm i --XALL` you can keep all the binaries. It might be useful when debugging
multiple archs and switching Node.js versions with
[NVM](https://github.com/creationix/nvm).
</details>
### Compiled addon
It is easy to build a C++ addon with **Addon Tools**. To have a full picture, you
can view the
[official example](https://github.com/node-3d/addon-tools-raub/tree/master/examples/addon).
The main file for an addon is **binding.gyp**. Here's a snippet with most of the features.
<details>
<summary>binding.gyp</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` directory.
``` ```
npm i -s addon-tools-raub {
'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',
],
}
],
[
'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'],
}],
},
{
'target_name' : 'remove_extras',
'type' : 'none',
'dependencies' : ['copy_binary'],
'actions' : [{
'action_name' : 'Build intermediates removed.',
'inputs' : [],
'outputs' : ['cpp'],
'conditions' : [
[ 'OS=="linux"', { 'action' : [
'rm',
'<(module_root_dir)/build/Release/obj.target/MY_ADDON/cpp/MY_ADDON.o',
'<(module_root_dir)/build/Release/obj.target/MY_ADDON.node',
'<(module_root_dir)/build/Release/MY_ADDON.node'
] } ],
[ 'OS=="mac"', { 'action' : [
'rm',
'<(module_root_dir)/build/Release/obj.target/MY_ADDON/cpp/MY_ADDON.o',
'<(module_root_dir)/build/Release/MY_ADDON.node'
] } ],
[ 'OS=="win"', { 'action' : [
'<(_del)',
'<(module_root_dir)/build/Release/MY_ADDON.*',
'<(module_root_dir)/build/Release/obj/MY_ADDON/*.*'
] } ],
],
}],
},
]
}
``` ```
Then require the built module like this:
```
const { binPath } = require('addon-tools-raub');
const core = require(`./${binPath}/MY_ADDON`);
```
</details>
---
## include/addon-tools.hpp ## include/addon-tools.hpp
Macro shortcuts for C++ addons using **NAPI**. There is a C++ header file, `addon-tools.hpp`, shipped with this package. It
See [docs inside the folder](/include). introduces several useful macros and utilities. Also it includes Nan automatically,
so that you can replace:
Example of an addon method definition:
``` ```
// hpp: // #include <v8.h> // already in node.h
#include <addon-tools.hpp> // #include <node.h> // already in nan.h
DBG_EXPORT JS_METHOD(doSomething); #include <nan.h>
// cpp: ```
DBG_EXPORT JS_METHOD(doSomething) { NAPI_ENV;
LET_INT32_ARG(0, param0); with
std::cout << "param0: " << param0 << std::endl;
RET_UNDEFINED; ```
#include <addon-tools.hpp> // or event-emitter.hpp
```
In gyp, the include directory should be set for your addon to know where to get it.
As it was mentioned above, this can be done automatically. Also an actual path to the
directory is exported from the module and is accessible like this:
```
require('addon-tools-raub').include() // implicit console.log()
require('addon-tools-raub').includePath // just a string
```
Currently, there are following helpers in **addon-tools.hpp**:
<details>
<summary>Handle scope</summary>
* `NAN_HS` - creates a HandleScope. Also, you do not need them within `NAN_METHOD`,
`NAN_SETTER`, and `NAN_GETTER`, as it is stated in
[Nan doc](https://github.com/nodejs/nan/blob/master/doc/methods.md#api_nan_method).
So it is most likely to be used in parts of code called from C++ land.
```
void windowFocusCB(GLFWwindow *window, int focused) { NAN_HS;
...
} }
...
glfwSetWindowFocusCallback(window, windowFocusCB);
``` ```
</details>
<details>
<summary>Method return</summary>
* `RET_VALUE(VAL)` - set method return value, where `VAL` is `v8::Local<v8::Value>`.
* `RET_UNDEFINED` - set method return value as `undefined`.
* `RET_STR(VAL)` - set method return value, where `VAL` is `const char *`.
* `RET_UTF8(VAL)` - set method return value, where `VAL` is `const char *`.
* `RET_INT(VAL)` - set method return value, where `VAL` is `int32`.
* `RET_INT32(VAL)` - set method return value, where `VAL` is `int32`.
* `RET_UINT32(VAL)` - set method return value, where `VAL` is `uint32`.
* `RET_NUM(VAL)` - set method return value, where `VAL` is `double`.
* `RET_OFFS(VAL)` - set method return value, where `VAL` is `size_t`.
* `RET_FLOAT(VAL)` - set method return value, where `VAL` is `float`.
* `RET_DOUBLE(VAL)` - set method return value, where `VAL` is `double`.
* `RET_EXT(VAL)` - set method return value, where `VAL` is `void *`.
* `RET_BOOL(VAL)` - set method return value, where `VAL` is `bool`.
* `RET_FUN(VAL)` - set method return value, where `VAL` is `Nan::Persistent<v8::Function>`.
* `RET_OBJ(VAL)` - set method return value, where `VAL` is `Nan::Persistent<v8::Object>`.
</details>
<details>
<summary>Shortcut types</summary>
* `V8_VAR_VAL` = `v8::Local<v8::Value>`
* `V8_VAR_OBJ` = `v8::Local<v8::Object>`
* `V8_VAR_ARR` = `v8::Local<v8::Array>`
* `V8_VAR_STR` = `v8::Local<v8::String>`
* `V8_VAR_FUNC` = `v8::Local<v8::Function>`
* `V8_VAR_FT` = `v8::Local<v8::FunctionTemplate>`
* `V8_VAR_OT` = `v8::Local<v8::ObjectTemplate>`
* `V8_STORE_FT` = `Nan::Persistent<v8::FunctionTemplate>`
* `V8_STORE_FUNC` = `Nan::Persistent<v8::Function>`
* `V8_STORE_OBJ` = `Nan::Persistent<v8::Object>`
* `V8_STORE_VAL` = `Nan::Persistent<v8::Value>`
</details>
<details>
<summary>New JS value</summary>
* `JS_STR(...)` - create a string value
* `JS_UTF8(...)` - same as JS_STR
* `JS_INT(val)` - create an integer value
* `JS_INT32(val)` - same as `JS_INT`
* `JS_UINT32(val)` - same as `JS_INT`
* `JS_NUM(val)` - create a numeric value
* `JS_OFFS(val)` - same as `JS_NUM`, but has a cast designed to avoid `size_t -> double` warning
* `JS_FLOAT(val)` - same as `JS_NUM`
* `JS_DOUBLE(val)` - same as `JS_NUM`
* `JS_EXT(val)` - create an external (pointer) value
* `JS_BOOL(val)` - create a boolean value
* `JS_FUN(val)` - get a function from persistent `Nan::Persistent<v8::Function>`.
* `JS_OBJ(val)` - get an object from persistent `Nan::Persistent<v8::Object>`.
</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
[v8::Value](https://v8docs.nodesource.com/node-0.8/dc/d0a/classv8_1_1_value.html)
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>
Two types of argument retrieval are supported: `REQ_` and `LET_`. The difference
is that `LET_` allows the argument to be empty, using some zero-default in this case.
`I` is the index of argument as in `info[I]`,
starting from `0`. `VAR` is the name of the variable to be created.
* `REQ_UTF8_ARG(I, VAR)` - require `I`'th argument to be a `string`. Stored at `Nan::Utf8String VAR`.
* `LET_UTF8_ARG(I, VAR)` - let optional `I`'th argument to be a `string`, the default is `""`. Stored at `Nan::Utf8String VAR`.
* `REQ_STR_ARG(I, VAR)` - require `I`'th argument to be a `string`. Stored at `Nan::Utf8String VAR`.
* `LET_STR_ARG(I, VAR)` - let optional `I`'th argument to be a `string`, the default is `""`. Stored at `Nan::Utf8String VAR`.
* `REQ_INT32_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `int VAR`.
* `LET_INT32_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0`. Stored at `int VAR`.
* `REQ_INT32_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `int VAR`.
* `LET_INT32_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0`. Stored at `int VAR`.
* `REQ_UINT32_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `unsigned VAR`.
* `LET_UINT32_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0`. Stored at `unsigned VAR`.
* `REQ_BOOL_ARG(I, VAR)` - require `I`'th argument to be a `boolean`. Stored at `bool VAR`.
* `LET_BOOL_ARG(I, VAR)` - let optional `I`'th argument to be a `boolean`, the default is `false`. Stored at `Nan::Utf8String VAR`.
* `REQ_OFFS_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `size_t VAR`.
* `LET_OFFS_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0`. Stored at `Nan::Utf8String VAR`.
* `REQ_DOUBLE_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `double VAR`.
* `LET_DOUBLE_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0.0`. Stored at `Nan::Utf8String VAR`.
* `REQ_FLOAT_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `float VAR`.
* `LET_FLOAT_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0.0f`. Stored at `Nan::Utf8String VAR`.
* `REQ_EXT_ARG(I, VAR)` - require `I`'th argument to be an `external`. Stored at `Local<External> VAR`.
* `LET_EXT_ARG(I, VAR)` - let optional `I`'th argument to be an `external`, the default is `nullptr`. Stored at `Nan::Utf8String VAR`.
* `REQ_FUN_ARG(I, VAR)` - require `I`'th argument to be a `function`. Stored at `Local<Function> VAR`.
* `REQ_OBJ_ARG(I, VAR)` - require `I`'th argument to be an `object`. Stored at `Local<Object> VAR`.
* `REQ_ARRV_ARG(I, VAR)` - require `I`'th argument to be a `TypedArray`. Stored at `Local<ArrayBufferView> VAR`.
```
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;
...
```
> Note: The conversion from `Nan::Utf8String` to `std::string` (via `char *`)
is possible with unary `*` operator.
</details>
<details>
<summary>Set properties</summary>
Set-helpers for string and numeric keys. String keys are converted to JS strings
automatically.
* `SET_PROP(OBJ, KEY, VAL)`
* `SET_I(ARR, I, VAL)`
</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(OBJ, NAME)` - add read and write accessors of NAME for OBJ.
* `ACCESSOR_R(OBJ, NAME)` - read-only property.
```
void MyClass::init(Handle<Object> target) {
...
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
ACCESSOR_RW(proto, message);
...
}
NAN_GETTER(MyClass::messageGetter) { ...
NAN_SETTER(MyClass::messageSetter) { ...
```
</details>
<details>
<summary>Setter argument</summary>
Useful addition to NAN_SETTER macro. Works similar to method arguments. But there
is always only one required argument stored in `v`.
* `SETTER_UTF8_ARG` - require the value to be a `string`. Stored at `Nan::Utf8String v`.
* `SETTER_STR_ARG` - require the value to be a `string`. Stored at `Nan::Utf8String v`.
* `SETTER_INT32_ARG` - require the value to be a `number`. Stored at `int v`.
* `SETTER_INT_ARG` - require the value to be a `number`. Stored at `int v`.
* `SETTER_UINT32_ARG` - require the value to be a `number`. Stored at `unsigned v`.
* `SETTER_BOOL_ARG` - require the value to be a `boolean`. Stored at `bool v`.
* `SETTER_OFFS_ARG` - require the value to be a `number`. Stored at `size_t v`.
* `SETTER_DOUBLE_ARG` - require the value to be a `number`. Stored at `double v`.
* `SETTER_FLOAT_ARG` - require the value to be a `number`. Stored at `float v`.
* `SETTER_EXT_ARG` - require the value to be an `external`. Stored at `Local<External> v`.
* `SETTER_FUN_ARG` - require the value to be a `function`. Stored at `Local<Function> v`.
* `SETTER_OBJ_ARG` - require the value to be an `object`. Stored at `Local<Object> v`.
* `SETTER_ARRV_ARG` - require the value to be a `TypedArray`. Stored at `Local<ArrayBufferView> v`.
```
NAN_SETTER(MyClass::messageSetter) { SETTER_UTF8_ARG;
// Variable created: Nan::Utf8String 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, checked with `IsArrayBufferView()`.
Returns `NULL` 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>
---
## index.js ## index.js
JavaScript helpers for Node.js addon development. The short list of helpers: Exports:
* `paths(dir)` - function. Returns a set of platform dependent paths depending on
input `dir`.
* `bin()` - prints platform binary directory absolute path.
* `rem()` - prints a space-separated list of binary paths to be cleaned on this platform.
* `include()` - prints include directory for this `dir`.
* `binPath` - platform binary directory absolute path.
* `remPath` - a space-separated list of binary paths to be cleaned on this platform.
* `includePath` - include directory for this `dir`.
* `root()` - prints where `'addon-tools-raub'` module is situated.
* `include()` - prints both `'addon-tools-raub'` and `'nan'` include paths. Use with
`node -e` through list context command expansion `<!@(...)`
* `rm()` - prints the location of `'_rm.bat'` file on Windows and plain `rm` on Unix.
* `cp()` - prints the location of `'_cp.bat'` file on Windows and plain `cp` on Unix.
* `mkdir()` - prints the location of `'_mkdir.bat'` file on Windows and plain `mkdir` on Unix.
* `bin()` - prints platform binary directory name.
* `binPath` - platform binary directory name.
* `rootPath` - where `'addon-tools-raub'` module is situated.
* `includePath` - both `'addon-tools-raub'` and `'nan'` include paths.
* `rmPath` - the location of `'_rm.bat'` file on Windows and plain `rm` on Unix.
* `cpPath` - the location of `'_cp.bat'` file on Windows and plain `cp` on Unix.
* `mkdirPath` - the location of `'_mkdir.bat'` file on Windows and plain `mkdir` on Unix.
---
## Crossplatform commands
Because of the differences between Windows and Unix command shells, often a whole
lot of conditions have to be introduced in **binding.gyp** file. Now some of
them can be easily omitted with the new crossplatform commands, supplied by this
package.
This comes especially handy together with GYP's executable list expansion. For
example a list of files to be removed for cleaning. Or a list of unnecessary
binaries to be removed upon installation of a binary-dependency package.
### 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.
``` ```
'getBin', 'getPlatform', 'getInclude', 'getPaths', 'variables': {
'install', 'cpbin', 'download', 'read', 'write', 'copy', 'exists', 'mkdir' : '<!(node -e "require(\'addon-tools-raub\').mkdir()")',
'mkdir', 'stat', 'isDir', 'isFile', 'dirUp', 'ensuredir', 'copysafe', },
'readdir', 'subdirs', 'subfiles', 'traverse', 'copyall', ...
'rmdir', 'rm', 'WritableBuffer', 'actionPack', 'action' : ['<(mkdir)', '-p', 'binary'],
``` ```
See the [TypeScript definitions](/index.d.ts) with comments. ### rm
Disregard `del` and `rd` on Windows command line. Now the same command can
### Example for an ADDON's **index.js**: be used on all platforms to remove single and multiple files and directories.
``` ```
const { getBin } = require('addon-tools-raub'); 'variables': {
const core = require(`./${getBin()}/ADDON`); // uses the platform-specific ADDON.node 'rm' : '<!(node -e "require(\'addon-tools-raub\').rm()")',
'rem' : '<!(node -e "require(\'.\').rem()")',
},
...
'action' : ['<(rm)', '-rf', '<@(rem)'],
``` ```
### cp
### Example for **binding.gyp**: For Windows the `/y` flag was embedded.
```
'variables': {
'cp' : '<!(node -e "require(\'addon-tools-raub\').cp()")',
},
...
'action' : ['<(cp)', 'a', 'b'],
```
---
## Class EventEmitter
A C++ implementation of [Events API](https://nodejs.org/api/events.html).
> Note: This implementation has some minor deviations from the above standard.
Specifically there is no static `EventEmitter.defaultMaxListeners` property.
However the dynamic one persists and is infinite (`0`) by default.
Also
[EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
is implemented. Not in full detail, but should be fine for callers.
An example can be found in **examples/node-addon** directory.
There is `Example` class, implemented in **cpp/example.cpp**, that inherits
EventEmitter behavior and is exported to JS.
For the C++ side `EventEmitter` has following public methods:
* `void emit(const std::string &name, int argc = 0, v8::Local<v8::Value> *argv = NULL)` -
emits an event with the given `name` and, optionally, some additional arguments where
`argc` is the number of arguments and `argv` is a pointer to the arguments array.
* `void on(const std::string &name, V8_VAR_FUNC cb)` -
subscribes `cb` to receive `name` events from this emitter, basically
`emitter.on(name, cb)`.
* `void destroy()` - destroys the object, i.e. deactivates it and frees
resources. This is what also called inside
`~EventEmitter()`, but only the first call is effective anyway.
Be sure to add the include directory in **binding.gyp**:
``` ```
'include_dirs': [ 'include_dirs': [
'<!@(node -p "require(\'addon-tools-raub\').getInclude()")', '<!@(node -e "require(\'addon-tools-raub\').include()")',
], ],
``` ```
> NOTE: the optional `node-addon-api` dependency is used by the `getInclude()` helper. If not found, Then include the **event-emitter.hpp**, it also includes **addon-tools.hpp**.
the **napi.h** include path won't be a part of the returned string. Inherit from `EventEmitter`, it already inherits from `Nan::ObjectWrap`:
### Example of `cpbin` in **package.json :: scripts**:
``` ```
"build": "cd src && node-gyp rebuild -j max --silent && node -e \"require('addon-tools-raub').cpbin('segfault')\" && cd ..", #include <event-emitter.hpp>
"build-only": "cd src && node-gyp build -j max --silent && node -e \"require('addon-tools-raub').cpbin('segfault')\" && cd ..",
class Example : public EventEmitter {
...
}
``` ```
> Note: Do not forget to call `EventEmitter::init()` once, in the module `init()`.
<details>
<summary>V8 Inheritance</summary>
Now that everything is in place, consider providing **V8** with JS inheritance info:
```
void Example::init(Handle<Object> target) {
Local<FunctionTemplate> proto = Nan::New<FunctionTemplate>(newCtor);
// -------------------------- HERE!
// class Example extends EventEmitter
Local<FunctionTemplate> parent = Nan::New(EventEmitter::_prototype);
proto->Inherit(parent);
// --------------------------
proto->InstanceTemplate()->SetInternalFieldCount(1);
proto->SetClassName(JS_STR("Example"));
Local<Function> ctor = Nan::GetFunction(proto).ToLocalChecked();
_constructor.Reset(ctor);
Nan::Set(target, JS_STR("Example"), ctor);
}
```
</details>
---
## 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 V8 `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: Don't do it in GC-accessible code: sometimes it works, sometimes it crashes.

1
_cp.bat Normal file
View File

@ -0,0 +1 @@
copy /y %1 %2

5
_mkdir.bat Normal file
View File

@ -0,0 +1,5 @@
for %%x in (%*) do (
if not %%x=="-p" if not exist %%x md %%x
)

6
_rm.bat Normal file
View File

@ -0,0 +1,6 @@
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
)

116
examples/addon/.eslintrc Normal file
View File

@ -0,0 +1,116 @@
{
"root": true,
"env": {
"node" : true,
"es6" : true,
"mocha" : true
},
"globals": {
"expect" : true,
"chai" : true,
"sinon" : true
},
"extends": ["eslint:recommended"],
"parserOptions": {
"ecmaVersion": 8,
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"rules": {
"arrow-parens": ["error", "as-needed"],
"no-trailing-spaces": [
"error",
{
"skipBlankLines": true
}
],
"indent": [
"error",
"tab",
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"max-len": ["error", 110],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"no-multiple-empty-lines": ["error", { "max": 3, "maxEOF": 1, "maxBOF": 1 }],
"keyword-spacing": ["error", { "before": true, "after": true }],
"space-before-blocks": ["error"],
"space-before-function-paren": ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}],
"space-infix-ops": ["error"],
"space-unary-ops": [
"error", {
"words": true,
"nonwords": false,
"overrides": {
"!": true
}
}
],
"spaced-comment": [0],
"camelcase": ["error"],
"no-tabs": [0],
"comma-dangle": [0],
"global-require": [0],
"func-names": [0],
"no-param-reassign": [0],
"no-underscore-dangle": [0],
"no-restricted-syntax": [
"error",
{
"selector": "LabeledStatement",
"message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand."
},
{
"selector": "WithStatement",
"message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize."
}
],
"no-mixed-operators": [0],
"no-plusplus": [0],
"comma-spacing": [0],
"default-case": [0],
"no-shadow": [0],
"no-console": [0],
"key-spacing": [0],
"no-return-assign": [0],
"consistent-return": [0],
"class-methods-use-this": [0],
"no-multi-spaces": [
"error",
{
"exceptions": {
"VariableDeclarator": true,
"Property": true,
"ImportDeclaration": true
}
}
],
"array-callback-return": [0],
"no-use-before-define": [
"error",
{
"functions": false,
"classes": true,
"variables": true
}
],
"padded-blocks": [0],
"space-in-parens": [0],
"valid-jsdoc": [0],
"no-unused-expressions": [0],
"import/no-dynamic-require": [0]
}
}

12
examples/addon/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.idea
.cproject
.project
.lock-wscript
build*/
bin-*/
.DS_Store
Debug/
node_modules/
package-lock.json
binary/
*.log

16
examples/addon/.npmignore Normal file
View File

@ -0,0 +1,16 @@
*.log
.cproject
.eslintrc
.gitignore
.idea
.lock-wscript
.project
binary/
bin-*/
build*/
CPPLINT.cfg
Debug/
examples/
package-lock.json
test/
qt/

View File

@ -0,0 +1,15 @@
set noparent
linelength=110
filter=-legal/copyright
filter=-build/include_order
filter=-build/header_guard
filter=-build/namespaces
filter=-build/include_what_you_use
filter=-whitespace/blank_line
filter=-whitespace/comments
filter=-whitespace/tab
filter=-whitespace/end_of_line
filter=-whitespace/indent
filter=-whitespace/operators
filter=-whitespace/parens
filter=-readability/todo

View File

@ -0,0 +1,91 @@
{
'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()")',
},
'targets': [
{
'target_name': 'addon',
'sources': [
'cpp/bindings.cpp',
'cpp/example.cpp',
],
'include_dirs': [
'<!@(node -e "require(\'addon-tools-raub\').include()")',
],
'conditions' : [
[
'OS=="win"',
{
'msvs_settings' : {
'VCCLCompilerTool' : {
'AdditionalOptions' : [
'/O2','/Oy', # Comment this for debugging
# '/Z7', # Unomment this for debugging
'/GL','/GF','/Gm-', '/Fm-',
'/EHsc','/MT','/GS','/Gy','/GR-','/Gd',
]
},
'VCLinkerTool' : {
'AdditionalOptions' : ['/RELEASE','/OPT:REF','/OPT:ICF','/LTCG']
},
},
},
],
],
},
{
'target_name' : 'make_directory',
'type' : 'none',
'dependencies' : ['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/addon.node', '<(binary)/addon.node'],
}],
},
{
'target_name' : 'remove_extras',
'type' : 'none',
'dependencies' : ['copy_binary'],
'actions' : [{
'action_name' : 'Build intermediates removed.',
'inputs' : [],
'outputs' : ['cpp'],
'conditions' : [
[ 'OS=="linux"', { 'action' : [
'rm',
'<(module_root_dir)/build/Release/obj.target/addon/cpp/bindings.o',
'<(module_root_dir)/build/Release/obj.target/addon/cpp/example.o',
'<(module_root_dir)/build/Release/addon.node'
] } ],
[ 'OS=="mac"', { 'action' : [
'rm',
'<(module_root_dir)/build/Release/obj.target/addon/cpp/bindings.o',
'<(module_root_dir)/build/Release/obj.target/addon/cpp/example.o',
'<(module_root_dir)/build/Release/addon.node'
] } ],
[ 'OS=="win"', { 'action' : [
'<(rm)',
'<(module_root_dir)/build/Release/addon.*',
'<(module_root_dir)/build/Release/obj/addon/*.*'
] } ],
],
}],
},
]
}

17
examples/addon/core.js Normal file
View File

@ -0,0 +1,17 @@
'use strict';
const util = require('util');
const { binPath } = require('addon-tools-raub');
const core = require(`./${binPath}/addon`);
const { Example } = core;
Example.prototype[util.inspect.custom] = function () {
return `Example { listeners: [${this.eventNames()}] }`;
};
module.exports = core;

View File

@ -0,0 +1,23 @@
#include <cstdlib>
#include <event-emitter.hpp>
#include "example.hpp"
extern "C" {
void init(V8_VAR_OBJ target) {
EventEmitter::init(target);
Example::init(target);
}
NODE_MODULE(example, init);
} // extern "C"

View File

@ -0,0 +1,105 @@
#include <cstdlib>
#include "example.hpp"
using namespace v8;
using namespace node;
using namespace std;
// ------ Aux macros
#define THIS_EXAMPLE \
Example *example = ObjectWrap::Unwrap<Example>(info.This());
#define THIS_CHECK \
if (example->_isDestroyed) return;
// ------ Constructor and Destructor
Example::Example() : EventEmitter() {
_isDestroyed = false;
}
Example::~Example() {
_destroy();
}
NAN_METHOD(Example::cppOn) { THIS_EXAMPLE; THIS_CHECK;
REQ_STR_ARG(0, name);
REQ_FUN_ARG(1, cb);
example->on(*name, cb);
}
// ------ System methods and props for ObjectWrap
V8_STORE_FT Example::_protoExample;
V8_STORE_FUNC Example::_ctorExample;
void Example::init(V8_VAR_OBJ target) {
V8_VAR_FT proto = Nan::New<FunctionTemplate>(newCtor);
// class AudioBufferSourceNode inherits AudioScheduledSourceNode
V8_VAR_FT parent = Nan::New(EventEmitter::_protoEventEmitter);
proto->Inherit(parent);
proto->InstanceTemplate()->SetInternalFieldCount(1);
proto->SetClassName(JS_STR("Example"));
// -------- dynamic
Nan::SetPrototypeMethod(proto, "destroy", destroy);
Nan::SetPrototypeMethod(proto, "cppOn", cppOn);
// -------- static
V8_VAR_FUNC ctor = Nan::GetFunction(proto).ToLocalChecked();
_protoExample.Reset(proto);
_ctorExample.Reset(ctor);
Nan::Set(target, JS_STR("Example"), ctor);
}
NAN_METHOD(Example::newCtor) {
CTOR_CHECK("EventEmitter");
Example *example = new Example();
example->Wrap(info.This());
RET_VALUE(info.This());
}
void Example::_destroy() { DES_CHECK;
_isDestroyed = true;
EventEmitter::_destroy();
}
NAN_METHOD(Example::destroy) { THIS_EXAMPLE; THIS_CHECK;
example->emit("destroy");
example->_destroy();
}

View File

@ -0,0 +1,39 @@
#ifndef _EXAMPLE_HPP_
#define _EXAMPLE_HPP_
#include <event-emitter.hpp>
class Example : public EventEmitter {
public:
~Example();
static void init(V8_VAR_OBJ target);
protected:
Example();
void _destroy();
static V8_STORE_FT _protoExample;
static V8_STORE_FUNC _ctorExample;
bool _isDestroyed;
private:
static NAN_METHOD(newCtor);
static NAN_METHOD(destroy);
static NAN_METHOD(cppOn);
};
#endif // _EXAMPLE_HPP_

65
examples/addon/index.js Normal file
View File

@ -0,0 +1,65 @@
'use strict';
const { Example, EventEmitter } = require('./core');
console.log('Example', Example);
const example = new Example();
console.log('example 0', example, 'instanceof EventEmitter', example instanceof EventEmitter);
console.log('static listenerCount', EventEmitter.listenerCount);
console.log('listenerCount', example.listenerCount);
console.log('addListener', example.addListener);
console.log('emit', example.emit);
console.log('eventNames', example.eventNames);
console.log('getMaxListeners', example.getMaxListeners);
console.log('listeners', example.listeners);
console.log('on', example.on);
console.log('once', example.once);
console.log('prependListener', example.prependListener);
console.log('prependOnceListener', example.prependOnceListener);
console.log('removeAllListeners', example.removeAllListeners);
console.log('removeListener', example.removeListener);
console.log('setMaxListeners', example.setMaxListeners);
console.log('rawListeners', example.rawListeners);
console.log('destroy', example.destroy);
example.on('evt1', (arg1, arg2) => {
console.log('EVT1', arg1, arg2, example.eventNames());
});
example.once('evt2', (arg1, arg2) => {
console.log('EVT2', arg1, arg2, example.eventNames());
});
example.emit('evt1', 111, '221');
example.emit('evt1', 112, '222');
console.log('example.eventNames 1', example.eventNames());
example.emit('evt2', 111, '221');
console.log('example.eventNames 2', example.eventNames());
example.emit('evt2', 112, '222');
console.log('example 1', example);
example.setMaxListeners(2);
example.on('max1', () => {});
example.on('max1', () => {});
example.on('max1', () => {});
example.on('cpp-on', (arg1, arg2) => {
console.log('CPP_ON', arg1, arg2, example.eventNames());
});
example.emit('cpp-on', 555, 'abc');
module.exports = Example;

View File

@ -0,0 +1,9 @@
{
"name": "example",
"version": "0.0.0",
"private": true,
"main": "index.js",
"dependencies": {
"addon-tools-raub": "https://github.com/node-3d/addon-tools-raub.git"
}
}

9
examples/deps/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
node_modules
*.log
build*
.DS_Store
*.pro.user
*.exp
*.pdb
*.ilk
package-lock.json

11
examples/deps/.npmignore Normal file
View File

@ -0,0 +1,11 @@
node_modules
*.log
build*
.DS_Store
*.pro.user
*.exp
*.pdb
*.ilk
.gitignore
package-lock.json
test

View File

View File

View File

View File

21
examples/deps/binding.gyp Normal file
View File

@ -0,0 +1,21 @@
{
'variables': {
'rm' : '<!(node -e "require(\'addon-tools-raub\').rm()")',
'rem' : '<!(node -e "require(\'.\').rem()")',
'XALL%': 'false',
},
'targets': [
{
'target_name' : 'remove_extras',
'type' : 'none',
'conditions' : [['XALL=="false"', {'actions': [
{
'action_name' : 'Unnecessary binaries removed.',
'inputs' : [],
'outputs' : ['build'],
'action' : ['<(rm)', '-rf', '<@(rem)'],
}
]}]],
}
]
}

View File

3
examples/deps/index.js Normal file
View File

@ -0,0 +1,3 @@
'use strict';
module.exports = require('addon-tools-raub').paths(__dirname);

View File

@ -0,0 +1,9 @@
{
"name": "example",
"version": "0.0.0",
"private": true,
"main": "index.js",
"dependencies": {
"addon-tools-raub": "https://github.com/node-3d/addon-tools-raub.git"
}
}

View File

@ -1,240 +0,0 @@
# 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**
implicitly, so you can replace:
```
#include <napi.h>
```
with
```
#include <addon-tools.hpp>
```
In **GYP**, the include directory should be set for your addon.
An actual path to the directory is exported from the module
and is accessible with:
```
require('addon-tools-raub').getInclude() // a string
```
For more examples, see [code snippets here](snippets.md).
### ES5 Classes
The standard class-defining (i.e. exporting a JS class from the C++ side) tools
from **NAPI** are a bit repetitive and excessive. So instead, Addon Tools
comes with a set of helpers for old-school class definition.
Think of it as ES5 classes. Just a function, spawning instances that have their
constructor and prototype set accordingly. Such classes may be further extended
and/or manipulated from JS-land. See the [class-wrapping doc here](class-wrapping.md).
### Method Helpers
Usually all the helpers work within the context of a method. In this case we
have `Napi::CallbackInfo info` passed as an argument. And we can return `undefined`
in case a problem has occured. So most of these macros are only usable
within `Napi::Value`-returning functions.
```
#define NAPI_ENV Napi::Env env = info.Env();
#define NAPI_HS Napi::HandleScope scope(env);
```
Other global helpers:
* `DBG_EXPORT`- set symbol visibility (mainly for callstack traces). On Windows, that is
equal to exporting a symbol: `__declspec(dllexport)`. On Unix it does nothing.
<details>
<summary><b>Return value</b></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 of numeric type.
* `RET_EXT(VAL)` - return `Napi::External`, expected `VAL` is a pointer.
* `RET_BOOL(VAL)` - return `Napi::Boolean`, expected `VAL` is convertible to bool.
* `RET_ARRAY_STR(VAL)` - return `Napi::Array`, expected `VAL` is `std::vector<std::string>`.
</details>
<details>
<summary><b>New JS value</b></summary>
* `JS_UNDEFINED` - an `undefined` value.
* `JS_NULL` - a `null` value.
* `JS_STR(VAL)` - create a `Napi::String`, expected `VAL` is `const char *`.
* `JS_NUM(VAL)` - create a `Napi::Number`, expected `VAL` is of numeric type.
* `JS_EXT(VAL)` - create a `Napi::External`, expected `VAL` is a pointer.
* `JS_BOOL(VAL)` - create a `Napi::Boolean`, expected `VAL` is convertible to bool.
* `JS_OBJECT` - a new empty `Object` instance.
* `JS_ARRAY` - a new empty `Array` instance.
</details>
<details>
<summary><b>Method check</b></summary>
These checks throw JS `TypeError` if not passed. `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)
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.
* `SETTER_CHECK(C, T)` - check if setter `value` is approved by `C` check.
* `DES_CHECK` - for void-returning methods, check if the instance wasn't
destroyed by `destroy()`.
* `THIS_CHECK` - check if the instance wasn't
destroyed by `destroy()`, and then fetch `env`.
</details>
<details>
<summary><b>Method arguments</b></summary>
Following macros convert JS arguments into C++ variables.
Three types of argument retrieval are supported:
* `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.
* `SOFT_` - 2 params, is `LET_` without type and arity checks.
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:
| Macro | JS type | C++ type | Default |
| :--- | :---: | :---: | :---: |
| `REQ_STR_ARG` | `string` | `std::string` | - |
| `USE_STR_ARG` | `string` | `std::string` | - |
| `LET_STR_ARG` | `string` | `std::string` | `""` |
| `REQ_INT32_ARG` | `number` | `int32_t` | - |
| `USE_INT32_ARG` | `number` | `int32_t` | - |
| `LET_INT32_ARG` | `number` | `int32_t` | `0` |
| `REQ_INT_ARG` | `number` | `int32_t` | - |
| `USE_INT_ARG` | `number` | `int32_t` | - |
| `LET_INT_ARG` | `number` | `int32_t` | `0` |
| `REQ_UINT32_ARG` | `number` | `uint32_t` | - |
| `USE_UINT32_ARG` | `number` | `uint32_t` | - |
| `LET_UINT32_ARG` | `number` | `uint32_t` | `0` |
| `REQ_UINT_ARG` | `number` | `uint32_t` | - |
| `USE_UINT_ARG` | `number` | `uint32_t` | - |
| `LET_UINT_ARG` | `number` | `uint32_t` | `0` |
| `REQ_BOOL_ARG` | `Boolean` | `bool` | - |
| `USE_BOOL_ARG` | `Boolean` | `bool` | - |
| `LET_BOOL_ARG` | `Boolean` | `bool` | `false` |
| `SOFT_BOOL_ARG` | `Boolean` | `bool` | `false` |
| `REQ_OFFS_ARG` | `number` | `size_t` | - |
| `USE_OFFS_ARG` | `number` | `size_t` | - |
| `LET_OFFS_ARG` | `number` | `size_t` | `0` |
| `REQ_DOUBLE_ARG` | `number` | `double` | - |
| `USE_DOUBLE_ARG` | `number` | `double` | - |
| `LET_DOUBLE_ARG` | `number` | `double` | `0.0` |
| `REQ_FLOAT_ARG` | `number` | `float` | - |
| `USE_FLOAT_ARG` | `number` | `float` | - |
| `LET_FLOAT_ARG` | `number` | `float` | `0.f` |
| `REQ_EXT_ARG` | `native` | `void*` | - |
| `USE_EXT_ARG` | `native` | `void*` | - |
| `LET_EXT_ARG` | `native` | `void*` | `nullptr` |
| `REQ_OBJ_ARG` | `object` | `Napi::Object` | - |
| `USE_OBJ_ARG` | `object` | `Napi::Object` | - |
| `LET_OBJ_ARG` | `object` | `Napi::Object` | `{}` |
| `REQ_ARRAY_ARG` | `object` | `Napi::Array` | - |
| `USE_ARRAY_ARG` | `object` | `Napi::Array` | - |
| `LET_ARRAY_ARG` | `object` | `Napi::Array` | `[]` |
| `LET_ARRAY_STR_ARG` | `object` | `std::vector<std::string>` | `std::vector<std::string>()` |
| `REQ_FUN_ARG` | `function` | `Napi::Function` | - |
| `REQ_ARRV_ARG` | `ArrayBuffer` | `Napi::ArrayBuffer` | - |
| `REQ_BUF_ARG` | `Buffer` | `Napi::Buffer<uint8_t>` | - |
```
JS_METHOD(test) {
REQ_UINT32_ARG(0, width); // uint32_t width
REQ_UINT32_ARG(1, height); // uint32_t height
LET_FLOAT_ARG(2, z); // float z
// An error is thrown if width or height are not passed as numbers.
// Argument z can be undefined, null, or number; error otherwise.
...
```
</details>
<details>
<summary><b>Setter argument</b></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_IMPLEMENT_SETTER(MyClass, x) { THIS_CHECK; SETTER_STR_ARG;
// Variable created: std::string v;
...
```
See also: [Class Wrapping](class-wrapping.md)
</details>
<details>
<summary><b>JS Data to C++ Data</b></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.
* `T *getBufferData(value, num = NULL)` - extracts Buffer data from
the given JS value. Checks with `IsBuffer()`.
Returns `nullptr` for empty JS values. For unacceptable values throws TypeError.
* `void *getData(value)` - if `value` is a `TypedArray|Buffer`,
calls `getArrayData` or `getArrayData` on it. Otherwise, if
`value.data` is a `TypedArray|Buffer`,
calls `getArrayData` or `getArrayData` on it.
Returns `nullptr` in other cases.
</details>

View File

@ -1,669 +1,317 @@
#ifndef ADDON_TOOLS_HPP #ifndef _ADDON_TOOLS_HPP_
#define ADDON_TOOLS_HPP #define _ADDON_TOOLS_HPP_
#define NODE_ADDON_API_DISABLE_DEPRECATED
#define NAPI_DISABLE_CPP_EXCEPTIONS
#include <napi.h>
#ifdef _WIN32 #include <nan.h>
#define strcasestr(s, t) strstr(strupr(s), strupr(t))
#endif
#ifdef _WIN32 #define NAN_HS Nan::HandleScope scope;
#define DBG_EXPORT __declspec(dllexport)
#else
#define DBG_EXPORT
#endif
#define NAPI_ENV Napi::Env env = info.Env();
#define NAPI_HS Napi::HandleScope scope(env);
#define JS_UNDEFINED env.Undefined() #define RET_VALUE(VAL) info.GetReturnValue().Set(VAL);
#define JS_NULL env.Null() #define RET_UNDEFINED RET_VALUE(Nan::Undefined());
#define JS_STR(VAL) Napi::String::New(env, VAL)
#define JS_NUM(VAL) Napi::Number::New(env, static_cast<double>(VAL))
#define JS_EXT(VAL) Napi::External<void>::New(env, static_cast<void*>(VAL))
#define JS_BOOL(VAL) Napi::Boolean::New(env, static_cast<bool>(VAL))
#define JS_OBJECT Napi::Object::New(env)
#define JS_ARRAY Napi::Array::New(env)
#define RET_VALUE(VAL) return VAL;
#define RET_UNDEFINED RET_VALUE(JS_UNDEFINED)
#define RET_NULL RET_VALUE(JS_NULL)
#define RET_STR(VAL) RET_VALUE(JS_STR(VAL))
#define RET_NUM(VAL) RET_VALUE(JS_NUM(VAL))
#define RET_EXT(VAL) RET_VALUE(JS_EXT(VAL))
#define RET_BOOL(VAL) RET_VALUE(JS_BOOL(VAL))
#define JS_THROW(VAL) \ typedef v8::Local<v8::Value> V8_VAR_VAL;
Napi::Error::New(env, VAL).ThrowAsJavaScriptException(); typedef v8::Local<v8::Object> V8_VAR_OBJ;
typedef v8::Local<v8::Array> V8_VAR_ARR;
typedef v8::Local<v8::ArrayBufferView> V8_VAR_ABV;
typedef v8::Local<v8::String> V8_VAR_STR;
typedef v8::Local<v8::Function> V8_VAR_FUNC;
typedef v8::Local<v8::External> V8_VAR_EXT;
typedef v8::Local<v8::FunctionTemplate> V8_VAR_FT;
typedef v8::Local<v8::ObjectTemplate> V8_VAR_OT;
typedef Nan::Persistent<v8::FunctionTemplate> V8_STORE_FT;
typedef Nan::Persistent<v8::Function> V8_STORE_FUNC;
typedef Nan::Persistent<v8::Object> V8_STORE_OBJ;
typedef Nan::Persistent<v8::Value> V8_STORE_VAL;
#define JS_STR(...) Nan::New<v8::String>(__VA_ARGS__).ToLocalChecked()
#define JS_UTF8(...) Nan::New<v8::String>(__VA_ARGS__).ToLocalChecked()
#define JS_INT(val) Nan::New<v8::Integer>(val)
#define JS_INT32(val) Nan::New<v8::Integer>(val)
#define JS_UINT32(val) Nan::New<v8::Integer>(val)
#define JS_NUM(val) Nan::New<v8::Number>(val)
#define JS_OFFS(val) Nan::New<v8::Number>(static_cast<double>(val))
#define JS_FLOAT(val) Nan::New<v8::Number>(val)
#define JS_DOUBLE(val) Nan::New<v8::Number>(val)
#define JS_EXT(val) Nan::New<v8::External>(reinterpret_cast<void*>(val))
#define JS_BOOL(val) (val) ? Nan::True() : Nan::False()
#define JS_FUN(val) Nan::New<v8::Function>(val)
#define JS_OBJ(val) Nan::New<v8::Object>(val)
#define RET_STR(...) RET_VALUE(JS_STR(__VA_ARGS__))
#define RET_UTF8(...) RET_VALUE(JS_UTF8(__VA_ARGS__))
#define RET_INT(val) RET_VALUE(JS_INT(val))
#define RET_INT32(val) RET_VALUE(JS_INT32(val))
#define RET_UINT32(val) RET_VALUE(JS_UINT32(val))
#define RET_NUM(val) RET_VALUE(JS_NUM(val))
#define RET_OFFS(val) RET_VALUE(JS_OFFS(val))
#define RET_FLOAT(val) RET_VALUE(JS_FLOAT(val))
#define RET_DOUBLE(val) RET_VALUE(JS_DOUBLE(val))
#define RET_EXT(val) RET_VALUE(JS_EXT(val))
#define RET_BOOL(val) RET_VALUE(JS_BOOL(val))
#define RET_FUN(val) RET_VALUE(JS_FUN(val))
#define RET_OBJ(val) RET_VALUE(JS_OBJ(val))
#define REQ_ARGS(N) \ #define REQ_ARGS(N) \
if (info.Length() < (N)) { \ if (info.Length() < (N)) \
JS_THROW("Expected at least " #N " arguments"); \ return Nan::ThrowTypeError("Expected at least " #N " arguments");
RET_UNDEFINED; \
}
#define IS_EMPTY(VAL) (VAL.IsNull() || VAL.IsUndefined()) #define IS_ARG_EMPTY(I) (info[I]->IsNull() || info[I]->IsUndefined())
#define IS_ARG_EMPTY(I) IS_EMPTY(info[I])
#define CHECK_REQ_ARG(I, C, T) \ #define CHECK_REQ_ARG(I, C, T) \
if (info.Length() <= (I) || !info[I].C) { \ if (info.Length() <= (I) || ! info[I]->C) \
JS_THROW("Argument " #I " must be of type `" T "`"); \ return Nan::ThrowTypeError("Argument " #I " must be " T);
RET_UNDEFINED; \
}
#define CHECK_LET_ARG(I, C, T) \ #define CHECK_LET_ARG(I, C, T) \
if (!(IS_ARG_EMPTY(I) || info[I].C)) { \ if ( ! (IS_ARG_EMPTY(I) || info[I]->C) ) \
JS_THROW( \ return Nan::ThrowTypeError("Argument " #I " must be " T " or null");
"Argument " #I \
" must be of type `" T \
"` or be `null`/`undefined`" \
); \
RET_UNDEFINED; \
}
#define REQ_STR_ARG(I, VAR) \ #define REQ_UTF8_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsString(), "String"); \ CHECK_REQ_ARG(I, IsString(), "string"); \
std::string VAR = info[I].ToString().Utf8Value(); Nan::Utf8String VAR(info[I]);
#define USE_STR_ARG(I, VAR, DEF) \ #define LET_UTF8_ARG(I, VAR) \
CHECK_LET_ARG(I, IsString(), "String"); \ CHECK_LET_ARG(I, IsString(), "string"); \
std::string VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToString().Utf8Value(); Nan::Utf8String VAR(JS_STR(""));
#define LET_STR_ARG(I, VAR) USE_STR_ARG(I, VAR, "")
#define REQ_STR_ARG(I, VAR) REQ_UTF8_ARG(I, VAR)
#define LET_STR_ARG(I, VAR) LET_UTF8_ARG(I, VAR)
#define REQ_INT32_ARG(I, VAR) \ #define REQ_INT32_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Int32"); \ CHECK_REQ_ARG(I, IsInt32(), "int32"); \
int VAR = info[I].ToNumber().Int32Value(); int VAR = info[I].As<v8::Int32>()->Value();
#define USE_INT32_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Int32"); \
int VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().Int32Value();
#define LET_INT32_ARG(I, VAR) USE_INT32_ARG(I, VAR, 0)
#define REQ_INT_ARG(I, VAR) REQ_INT32_ARG(I, VAR)
#define USE_INT_ARG(I, VAR, DEF) USE_INT32_ARG(I, VAR, DEF)
#define LET_INT_ARG(I, VAR) LET_INT32_ARG(I, VAR)
#define LET_INT32_ARG(I, VAR) \
CHECK_LET_ARG(I, IsInt32(), "int32"); \
int VAR = IS_ARG_EMPTY(I) ? 0 : info[I].As<v8::Int32>()->Value();
#define REQ_UINT32_ARG(I, VAR) \ #define REQ_UINT32_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Uint32"); \ CHECK_REQ_ARG(I, IsUint32(), "uint32"); \
unsigned int VAR = info[I].ToNumber().Uint32Value(); unsigned int VAR = info[I].As<v8::Uint32>()->Value();
#define USE_UINT32_ARG(I, VAR, DEF) \ #define LET_UINT32_ARG(I, VAR) \
CHECK_LET_ARG(I, IsNumber(), "Uint32"); \ CHECK_LET_ARG(I, IsUint32(), "uint32"); \
unsigned int VAR = IS_ARG_EMPTY(I) \ unsigned int VAR = IS_ARG_EMPTY(I) ? 0 : info[I].As<v8::Uint32>()->Value();
? (DEF) \
: info[I].ToNumber().Uint32Value();
#define LET_UINT32_ARG(I, VAR) USE_UINT32_ARG(I, VAR, 0)
#define REQ_UINT_ARG(I, VAR) REQ_UINT32_ARG(I, VAR)
#define USE_UINT_ARG(I, VAR, DEF) USE_UINT32_ARG(I, VAR, DEF)
#define LET_UINT_ARG(I, VAR) LET_UINT32_ARG(I, VAR)
#define REQ_INT_ARG(I, VAR) LET_INT32_ARG(I, VAR)
#define LET_INT_ARG(I, VAR) REQ_UINT32_ARG(I, VAR)
#define REQ_BOOL_ARG(I, VAR) \ #define REQ_BOOL_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsBoolean(), "Bool"); \ CHECK_REQ_ARG(I, IsBoolean(), "bool"); \
bool VAR = info[I].ToBoolean().Value(); bool VAR = info[I].As<v8::Boolean>()->Value();
#define USE_BOOL_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsBoolean(), "Bool"); \
bool VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToBoolean().Value();
#define LET_BOOL_ARG(I, VAR) USE_BOOL_ARG(I, VAR, false)
#define SOFT_BOOL_ARG(I, VAR) \
bool VAR = (info.Length() >= (I) && info[I].ToBoolean().Value()) || false;
#define LET_BOOL_ARG(I, VAR) \
CHECK_LET_ARG(I, IsBoolean(), "bool"); \
bool VAR = IS_ARG_EMPTY(I) ? false : info[I].As<v8::Boolean>()->Value();
#define REQ_OFFS_ARG(I, VAR) \ #define REQ_OFFS_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Number"); \ CHECK_REQ_ARG(I, IsNumber(), "number"); \
size_t VAR = static_cast<size_t>(info[I].ToNumber().DoubleValue()); size_t VAR = static_cast<size_t>(info[I].As<v8::Integer>()->Value());
#define USE_OFFS_ARG(I, VAR, DEF) \ #define LET_OFFS_ARG(I, VAR) \
CHECK_LET_ARG(I, IsNumber(), "Number"); \ CHECK_LET_ARG(I, IsNumber(), "number"); \
size_t VAR = IS_ARG_EMPTY(I) \ size_t VAR = IS_ARG_EMPTY(I) ? 0 : static_cast<size_t>( \
? (DEF) \ info[I].As<v8::Integer>()->Value() \
: static_cast<size_t>(info[I].ToNumber().DoubleValue()); );
#define LET_OFFS_ARG(I, VAR) USE_OFFS_ARG(I, VAR, 0)
#define REQ_DOUBLE_ARG(I, VAR) \ #define REQ_DOUBLE_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Number"); \ CHECK_REQ_ARG(I, IsNumber(), "number"); \
double VAR = info[I].ToNumber().DoubleValue(); double VAR = info[I].As<v8::Number>()->Value();
#define USE_DOUBLE_ARG(I, VAR, DEF) \ #define LET_DOUBLE_ARG(I, VAR) \
CHECK_LET_ARG(I, IsNumber(), "Number"); \ CHECK_LET_ARG(I, IsNumber(), "number"); \
double VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().DoubleValue(); double VAR = IS_ARG_EMPTY(I) ? 0.0 : info[I].As<v8::Number>()->Value();
#define LET_DOUBLE_ARG(I, VAR) USE_DOUBLE_ARG(I, VAR, 0.0)
#define REQ_FLOAT_ARG(I, VAR) \ #define REQ_FLOAT_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Number"); \ CHECK_REQ_ARG(I, IsNumber(), "number"); \
float VAR = info[I].ToNumber().FloatValue(); float VAR = static_cast<float>(info[I].As<v8::Number>()->Value());
#define USE_FLOAT_ARG(I, VAR, DEF) \ #define LET_FLOAT_ARG(I, VAR) \
CHECK_LET_ARG(I, IsNumber(), "Number"); \ CHECK_LET_ARG(I, IsNumber(), "number"); \
float VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().FloatValue(); float VAR = IS_ARG_EMPTY(I) ? 0.f : static_cast<float>( \
info[I].As<v8::Number>()->Value() \
#define LET_FLOAT_ARG(I, VAR) USE_FLOAT_ARG(I, VAR, 0.f) );
#define REQ_EXT_ARG(I, VAR) \ #define REQ_EXT_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsExternal(), "Pointer"); \ CHECK_REQ_ARG(I, IsExternal(), "void*"); \
void *VAR = info[I].As< Napi::External<void> >().Data(); V8_VAR_EXT VAR = V8_VAR_EXT::Cast(info[I]);
#define USE_EXT_ARG(I, VAR, DEF) \ #define LET_EXT_ARG(I, VAR) \
CHECK_LET_ARG(I, IsExternal(), "Pointer"); \ CHECK_LET_ARG(I, IsExternal(), "number"); \
void *VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As< Napi::External<void> >().Data(); V8_VAR_EXT VAR = IS_ARG_EMPTY(I) ? JS_EXT(nullptr) : V8_VAR_EXT::Cast(info[I]);
#define LET_EXT_ARG(I, VAR) USE_EXT_ARG(I, VAR, nullptr)
#define REQ_FUN_ARG(I, VAR) \ #define REQ_FUN_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsFunction(), "Function"); \ CHECK_REQ_ARG(I, IsFunction(), "function"); \
Napi::Function VAR = info[I].As<Napi::Function>(); V8_VAR_FUNC VAR = V8_VAR_FUNC::Cast(info[I]);
#define REQ_OBJ_ARG(I, VAR) \ #define REQ_OBJ_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsObject(), "Object"); \ CHECK_REQ_ARG(I, IsObject(), "object"); \
Napi::Object VAR = info[I].As<Napi::Object>(); V8_VAR_OBJ VAR = V8_VAR_OBJ::Cast(info[I]);
#define USE_OBJ_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsObject(), "Object"); \
Napi::Object VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As<Napi::Object>();
#define LET_OBJ_ARG(I, VAR) USE_OBJ_ARG(I, VAR, Napi::Object::New(env))
#define REQ_ARRV_ARG(I, VAR) \ #define REQ_ARRV_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsArrayBuffer(), "ArrayBuffer"); \ REQ_OBJ_ARG(I, _obj_##VAR); \
Napi::ArrayBuffer VAR = info[I].As<Napi::ArrayBuffer>(); if( ! _obj_##VAR->IsArrayBufferView() ) \
return Nan::ThrowTypeError("Argument " #I " must be an array buffer");\
V8_VAR_ABV VAR = V8_VAR_ABV::Cast(_obj_##VAR);
#define REQ_BUF_ARG(I, VAR) \ #define SET_PROP(OBJ, KEY, VAL) OBJ->Set(JS_STR(KEY), VAL);
CHECK_REQ_ARG(I, IsBuffer(), "Buffer"); \ #define SET_I(ARR, I, VAL) ARR->Set(I, VAL);
Napi::Buffer<uint8_t> VAR = info[I].As< Napi::Buffer<uint8_t> >();
#define REQ_ARRAY_ARG(I, VAR) \ #define CTOR_CHECK(T) \
CHECK_REQ_ARG(I, IsArray(), "Array"); \ if ( ! info.IsConstructCall() ) \
Napi::Array VAR = info[I].As<Napi::Array>(); return Nan::ThrowTypeError(T " must be called with the 'new' keyword.");
#define USE_ARRAY_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsArray(), "Array"); \
Napi::Array VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As<Napi::Array>();
#define LET_ARRAY_ARG(I, VAR) USE_ARRAY_ARG(I, VAR, Napi::Array::New(env))
inline std::vector<std::string> arrayStrToVec(const Napi::Array &arr) {
uint32_t count = arr.Length();
std::vector<std::string> result(count);
for (uint32_t i = 0; i < count; i++) {
Napi::Value item = arr[i];
if (item.IsString()) {
result[i] = item.ToString().Utf8Value();
}
}
return result;
}
inline Napi::Array stringsToArray(Napi::Env env, const char **strings, size_t count) {
Napi::Array arr = JS_ARRAY;
for (size_t i = 0; i < count; i++) {
arr.Set(i, strings[i]);
}
return arr;
}
inline Napi::Array vecStrToArray(Napi::Env env, const std::vector<std::string> &strings) {
Napi::Array arr = JS_ARRAY;
size_t count = strings.size();
for (size_t i = 0; i < count; i++) {
arr.Set(i, strings[i]);
}
return arr;
}
#define LET_ARRAY_STR_ARG(I, VAR) \
USE_ARRAY_ARG(I, __ARRAY_ ## VAR, Napi::Array::New(env)); \
std::vector<std::string> VAR = arrayStrToVec(__ARRAY_ ## VAR);
#define RET_ARRAY_STR(VAL) RET_VALUE(vecStrToArray(env, VAL))
#define REQ_TYPED_ARRAY_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsTypedArray(), "TypedArray"); \
Napi::TypedArray VAR = info[I].As<Napi::TypedArray>();
#define DES_CHECK \ #define DES_CHECK \
if (_isDestroyed) return; if (_isDestroyed) return;
#define THIS_CHECK \
NAPI_ENV; \
if (_isDestroyed) RET_UNDEFINED;
#define CACHE_CAS(CACHE, V) \
if (CACHE == V) { \
RET_UNDEFINED; \
} \
CACHE = V;
#define SETTER_CHECK(C, T) \ #define SETTER_CHECK(C, T) \
if (!value.C) { \ if ( ! value->C ) \
JS_THROW("Value must be " T); \ return Nan::ThrowTypeError("Value must be " T);
RET_UNDEFINED; \
}
#define JS_METHOD(NAME) Napi::Value NAME(const Napi::CallbackInfo &info) #define ACCESSOR_RW(OBJ, NAME) \
Nan::SetAccessor(OBJ, JS_STR(#NAME), NAME ## Getter, NAME ## Setter);
#define ACCESSOR_RW(CLASS, NAME) \ #define ACCESSOR_R(OBJ, NAME) \
InstanceAccessor(#NAME, &CLASS::NAME ## Getter, &CLASS::NAME ## Setter) Nan::SetAccessor(OBJ, JS_STR(#NAME), NAME ## Getter);
#define ACCESSOR_R(CLASS, NAME) \
InstanceAccessor(#NAME, &CLASS::NAME ## Getter, nullptr)
#define ACCESSOR_M(CLASS, NAME) \ #define SETTER_UTF8_ARG \
InstanceMethod(#NAME, &CLASS::NAME) SETTER_CHECK(IsString(), "string"); \
Nan::Utf8String v(value);
#define THIS_OBJ(VAR) \ #define SETTER_STR_ARG SETTER_UTF8_ARG
Napi::Object VAR = info.This().As<Napi::Object>();
#define SETTER_STR_ARG \
SETTER_CHECK(IsString(), "String"); \
std::string v = value.ToString().Utf8Value();
#define SETTER_INT32_ARG \ #define SETTER_INT32_ARG \
SETTER_CHECK(IsNumber(), "Int32"); \ SETTER_CHECK(IsInt32(), "int32"); \
int v = value.ToNumber().Int32Value(); int v = value.As<v8::Int32>()->Value();
#define SETTER_INT_ARG SETTER_INT32_ARG #define SETTER_INT_ARG SETTER_INT32_ARG
#define SETTER_BOOL_ARG \ #define SETTER_BOOL_ARG \
SETTER_CHECK(IsBoolean(), "Bool"); \ SETTER_CHECK(IsBoolean(), "bool"); \
bool v = value.ToBoolean().Value(); bool v = value.As<v8::Boolean>()->Value();
#define SETTER_UINT32_ARG \ #define SETTER_UINT32_ARG \
SETTER_CHECK(IsNumber(), "Uint32"); \ SETTER_CHECK(IsUint32(), "uint32"); \
unsigned int v = value.ToNumber().Uint32Value(); unsigned int v = value.As<v8::Uint32>()->Value();
#define SETTER_UINT_ARG SETTER_UINT32_ARG
#define SETTER_OFFS_ARG \ #define SETTER_OFFS_ARG \
SETTER_CHECK(IsNumber(), "Number"); \ SETTER_CHECK(IsNumber(), "number"); \
size_t v = static_cast<size_t>(value.ToNumber().DoubleValue()); size_t v = static_cast<size_t>(value.As<v8::Integer>()->Value());
#define SETTER_DOUBLE_ARG \ #define SETTER_DOUBLE_ARG \
SETTER_CHECK(IsNumber(), "Number"); \ SETTER_CHECK(IsNumber(), "number"); \
double v = value.ToNumber().DoubleValue(); double v = value.As<v8::Number>()->Value();
#define SETTER_FLOAT_ARG \ #define SETTER_FLOAT_ARG \
SETTER_CHECK(IsNumber(), "Number"); \ SETTER_CHECK(IsNumber(), "number"); \
float v = value.ToNumber().FloatValue(); float v = static_cast<float>(value.As<v8::Number>()->Value());
#define SETTER_EXT_ARG \ #define SETTER_EXT_ARG \
SETTER_CHECK(IsExternal(), "Pointer"); \ SETTER_CHECK(IsExternal(), "void*"); \
Napi::External v = value.As<Napi::External>(); V8_VAR_EXT v = V8_VAR_EXT::Cast(value);
#define SETTER_FUN_ARG \ #define SETTER_FUN_ARG \
SETTER_CHECK(IsFunction(), "Function"); \ SETTER_CHECK(IsFunction(), "function"); \
Napi::Function v = value.As<Napi::Function>() V8_VAR_FUNC v = V8_VAR_FUNC::Cast(value);
#define SETTER_OBJ_ARG \ #define SETTER_OBJ_ARG \
SETTER_CHECK(IsObject(), "Object"); \ SETTER_CHECK(IsObject(), "object"); \
Napi::Object v = value.As<Napi::Object>() V8_VAR_OBJ v = V8_VAR_OBJ::Cast(value);
#define SETTER_ARRV_ARG \ #define SETTER_ARRV_ARG \
SETTER_CHECK(IsArrayBuffer(), "TypedArray"); \ SETTER_CHECK(IsObject(), "object"); \
Napi::ArrayBuffer v = value.As<Napi::ArrayBuffer>(); V8_VAR_OBJ _obj_v = V8_VAR_OBJ::Cast(value); \
if( ! _obj_v->IsArrayBufferView() ) \
return Nan::ThrowTypeError("The value must be an array buffer"); \
#define GET_AND_THROW_LAST_ERROR() \ V8_VAR_ABV v = V8_VAR_ABV::Cast(_obj_v);
do { \
const napi_extended_error_info *error_info; \
napi_get_last_error_info((env), &error_info); \
bool is_pending; \
napi_is_exception_pending((env), &is_pending); \
/* If an exception is already pending, don't rethrow it */ \
if (!is_pending) { \
const char* error_message = error_info->error_message != NULL \
? error_info->error_message \
: "empty error message"; \
JS_THROW(error_message); \
} \
} while (0)
#define NAPI_CALL(the_call) \
do { \
if ((the_call) != napi_ok) { \
GET_AND_THROW_LAST_ERROR(); \
RET_UNDEFINED; \
} \
} while (0)
#define JS_RUN(code, VAR) \
napi_value __RESULT_ ## VAR; \
NAPI_CALL( \
napi_run_script(env, napi_value(JS_STR(code)), &__RESULT_ ## VAR) \
); \
Napi::Value VAR(env, __RESULT_ ## VAR);
template<typename Type = uint8_t>
inline Type* getArrayData(
Napi::Env env,
Napi::Object obj,
int *num = nullptr
) {
Type *out = nullptr;
if (obj.IsTypedArray()) {
Napi::TypedArray ta = obj.As<Napi::TypedArray>();
size_t offset = ta.ByteOffset(); template<typename Type>
Napi::ArrayBuffer arr = ta.ArrayBuffer(); inline Type* getArrayData(V8_VAR_OBJ obj, int *num = nullptr) {
if (num) {
*num = ta.ByteLength() / sizeof(Type);
}
uint8_t *base = reinterpret_cast<uint8_t *>(arr.Data());
out = reinterpret_cast<Type *>(base + offset);
} else if (obj.IsArrayBuffer()) {
Napi::ArrayBuffer arr = obj.As<Napi::ArrayBuffer>();
if (num) {
*num = arr.ByteLength() / sizeof(Type);
}
out = reinterpret_cast<Type *>(arr.Data());
} else {
if (num) {
*num = 0;
}
JS_THROW("Argument must be of type `TypedArray`.");
}
return out; Type *data = nullptr;
}
template<typename Type = uint8_t>
inline Type* getBufferData(
Napi::Env env,
Napi::Object obj,
int *num = nullptr
) {
Type *out = nullptr;
if (num) { if (num) {
*num = 0; *num = 0;
} }
if (!obj.IsBuffer()) { if ( ! obj->IsArrayBufferView() ) {
JS_THROW("Argument must be of type `Buffer`."); Nan::ThrowError("Argument must be a TypedArray.");
return out; return data;
} }
Napi::Buffer<uint8_t> arr = obj.As< Napi::Buffer<uint8_t> >(); V8_VAR_ABV arr = V8_VAR_ABV::Cast(obj);
if (num) { if (num) {
*num = arr.Length() / sizeof(Type); *num = arr->ByteLength() / sizeof(Type);
} }
out = arr.Data(); data = reinterpret_cast<Type*>(arr->Buffer()->GetContents().Data());
return data;
return out;
} }
inline void *getData(Napi::Env env, Napi::Object obj) { inline void *getData(V8_VAR_OBJ obj) {
void *out = nullptr;
if (obj.IsTypedArray() || obj.IsArrayBuffer()) { void *pixels = nullptr;
out = getArrayData<uint8_t>(env, obj);
} else if (obj.IsBuffer()) { if (obj->IsArrayBufferView()) {
out = getBufferData<uint8_t>(env, obj); pixels = getArrayData<unsigned char>(obj);
} else if (obj.Has("data")) { } else if (obj->Has(JS_STR("data"))) {
Napi::Object data = obj.Get("data").As<Napi::Object>(); V8_VAR_VAL data = Nan::Get(obj, JS_STR("data")).ToLocalChecked();
if (data.IsTypedArray() || data.IsArrayBuffer()) { if ( ! data->IsNullOrUndefined() ) {
out = getArrayData<uint8_t>(env, data); pixels = node::Buffer::Data(data);
} else if (data.IsBuffer()) {
out = getBufferData<uint8_t>(env, data);
} }
} }
return out; return pixels;
} }
inline Napi::Value consoleLog( inline void consoleLog(int argc, V8_VAR_VAL *argv) {
Napi::Env env,
int argc, V8_VAR_STR code = JS_STR("((...args) => console.log(...args))");
const Napi::Value *argv
) { v8::Local<v8::Value> log = v8::Script::Compile(
JS_RUN("console.log", log); Nan::GetCurrentContext(), code
std::vector<napi_value> args; ).ToLocalChecked()->Run(
for (int i = 0; i < argc; i++) { Nan::GetCurrentContext()
args.push_back(napi_value(argv[i])); ).ToLocalChecked();
} Nan::Callback logCb(Nan::To<v8::Function>(log).ToLocalChecked());
log.As<Napi::Function>().Call(args);
RET_UNDEFINED; Nan::AsyncResource async("consoleLog()");
logCb.Call(argc, argv, &async);
} }
inline Napi::Value consoleLog(Napi::Env env, const std::string &message) { inline void consoleLog(const std::string &message) {
Napi::Value arg = JS_STR(message);
consoleLog(env, 1, &arg); V8_VAR_VAL arg = JS_STR(message);
RET_UNDEFINED; consoleLog(1, &arg);
} }
inline void eventEmit( #endif // _ADDON_TOOLS_HPP_
Napi::Object that,
const std::string &name,
int argc = 0,
const Napi::Value *argv = nullptr,
napi_async_context context = nullptr
) {
if (!that.Has("emit")) {
return;
}
Napi::Env env = that.Env();
Napi::String eventName = JS_STR(name);
Napi::Function thatEmit = that.Get("emit").As<Napi::Function>();
std::vector<napi_value> args;
args.push_back(napi_value(eventName));
for (int i = 0; i < argc; i++) {
args.push_back(napi_value(argv[i]));
}
if (context) {
thatEmit.MakeCallback(that, args, context);
} else {
thatEmit.Call(that, args);
}
}
typedef Napi::Value (*Es5MethodCallback)(const Napi::CallbackInfo& info);
typedef Napi::Value (*Es5GetterCallback)(const Napi::CallbackInfo& info);
typedef void (*Es5SetterCallback)(const Napi::CallbackInfo& info);
#define DECLARE_ES5_CLASS(CLASS, NAME) \
public: \
inline static CLASS *unwrap(Napi::Object thatObj) { \
CLASS *that; \
napi_status ns = napi_unwrap( \
thatObj.Env(), \
thatObj.Get(_nameEs5), \
reinterpret_cast<void**>(&that) \
); \
if (ns != napi_ok) { \
return nullptr; \
} \
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 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 = CLASS::unwrap(info.This().As<Napi::Object>());
#define JS_DECLARE_METHOD(CLASS, NAME) \
inline static JS_METHOD(__st_##NAME) { \
JS_GET_THAT(CLASS); \
return that->__i_##NAME(info); \
}; \
JS_METHOD(__i_##NAME);
#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]); \
} \
Napi::Value __i_##NAME##Setter( \
const Napi::CallbackInfo &info, \
const Napi::Value &value \
);
#define JS_IMPLEMENT_METHOD(CLASS, NAME) \
JS_METHOD(CLASS::__i_##NAME)
#define JS_IMPLEMENT_GETTER(CLASS, NAME) \
JS_IMPLEMENT_METHOD(CLASS, NAME##Getter)
#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 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

View File

@ -1,103 +0,0 @@
# Es5 class wrapping
This wrapping implementation diverges from standard ES6 style wrapping.
It also uses composition rather than inheritance, so it is easily pluggable.
* For **NAPI** addons, `super()` can be called from C++ side.
* Constructor is callable with `ClassName.call(obj, ...args)`.
* Multiple C++ objects can be attached to a single JS object
if it is necessary in an inheritance scenario.
* On JS side `util.inherits`
[is used](https://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor),
and on C++ side there is `inheritEs5` function.
## Class Declaration
```
class ClassName {
DECLARE_ES5_CLASS(ClassName, JSClassName);
public:
static void init(Napi::Env env, Napi::Object exports);
explicit ClassName(const Napi::CallbackInfo& info);
~ClassName();
void _destroy();
private:
JS_DECLARE_GETTER(ClassName, isDestroyed);
JS_DECLARE_METHOD(ClassName, ClassName, destroy);
bool _isDestroyed;
};
```
* `DECLARE_ES5_CLASS` - adds utility declarations, the first argument
must be this class name, and the second argument will become the
name (arbitrary) of this function (constructor) in JS.
* `init` - can be used to initialize this class and export it.
* `JS_DECLARE_METHOD` - declares a method, the first argument is this class,
the second is the name of the method to be created.
* `JS_DECLARE_GETTER` - declares a getter, the first argument is this class,
the second is the name of the getter to be created.
* `JS_DECLARE_SETTER` - declares a setter, the first argument is this class,
the second is the name of the setter to be created.
## Class Implementation
```
IMPLEMENT_ES5_CLASS(ClassName);
// Fill the properties and export the class
void ClassName::init(Napi::Env env, Napi::Object exports) {
Napi::Function ctor = wrap(env);
JS_ASSIGN_METHOD(destroy);
JS_ASSIGN_GETTER(isDestroyed);
// ...
exports.Set("JSClassName", ctor);
}
ClassName::ClassName(const Napi::CallbackInfo &info) { NAPI_ENV;
super(info);
_isDestroyed = false;
// ...
}
ClassName::~ClassName() {
_destroy();
}
void ClassName::_destroy() { DES_CHECK;
// ...
_isDestroyed = true;
}
JS_IMPLEMENT_METHOD(ClassName, destroy) { THIS_CHECK;
emit("destroy");
_destroy();
RET_UNDEFINED;
}
JS_IMPLEMENT_GETTER(ClassName, isDestroyed) { THIS_CHECK;
RET_BOOL(_isDestroyed);
}
```
* `IMPLEMENT_ES5_CLASS` - implements some utility functions for class wrapping.
* `JS_ASSIGN_METHOD` - in `init()`, assigns the given method to this class.
* `JS_ASSIGN_GETTER` - in `init()`, assigns the given getter to this class.
* `JS_ASSIGN_SETTER` - in `init()`, assigns both getter and setter to this class.
It also takes only one argument because both have the same name.
* `JS_IMPLEMENT_METHOD` - implements a method, the first argument is this class,
the second is the name of the method being implemented.
* `JS_IMPLEMENT_GETTER` - implements a getter, the first argument is this class,
the second is the name of the getter being implemented.
* `JS_IMPLEMENT_SETTER` - implements a setter, the first argument is this class,
the second is the name of the setter being implemented.

723
include/event-emitter.hpp Normal file
View File

@ -0,0 +1,723 @@
#ifndef _EVENT_EMITTER_
#define _EVENT_EMITTER_
#include <addon-tools.hpp>
#include <string>
#include <map>
#include <deque>
#define THIS_EVENT_EMITTER \
EventEmitter *eventEmitter = ObjectWrap::Unwrap<EventEmitter>(info.This());
#define EVENT_EMITTER_THIS_CHECK \
if (eventEmitter->_isDestroyed) return;
// This template class provides static-member initialization in-header
template <typename T>
class StaticHolder {
protected:
static V8_STORE_FT _protoEventEmitter;
static V8_STORE_FUNC _ctorEventEmitter;
};
template <typename T> V8_STORE_FT StaticHolder<T>::_protoEventEmitter;
template <typename T> V8_STORE_FUNC StaticHolder<T>::_ctorEventEmitter;
class EventEmitter : public StaticHolder<int>, public Nan::ObjectWrap {
typedef Nan::CopyablePersistentTraits<v8::Function>::CopyablePersistent FN_TYPE;
typedef std::deque<FN_TYPE> VEC_TYPE;
typedef std::map<std::string, VEC_TYPE> MAP_TYPE;
typedef std::map<int, FN_TYPE> FNMAP_TYPE;
typedef VEC_TYPE::iterator IT_TYPE;
typedef MAP_TYPE::iterator MAP_IT_TYPE;
typedef FNMAP_TYPE::iterator FNMAP_IT_TYPE;
public:
// Public V8 init
static void init(V8_VAR_OBJ target) {
V8_VAR_FT proto = Nan::New<v8::FunctionTemplate>(newCtor);
proto->InstanceTemplate()->SetInternalFieldCount(1);
proto->SetClassName(JS_STR("EventEmitter"));
// Accessors
V8_VAR_OT obj = proto->PrototypeTemplate();
ACCESSOR_R(obj, isDestroyed);
// -------- dynamic
Nan::SetPrototypeMethod(proto, "listenerCount", jsListenerCount);
Nan::SetPrototypeMethod(proto, "addEventListener", jsAddListener);
Nan::SetPrototypeMethod(proto, "addListener", jsAddListener);
Nan::SetPrototypeMethod(proto, "dispatchEvent", jsDispatchEvent);
Nan::SetPrototypeMethod(proto, "emit", jsEmit);
Nan::SetPrototypeMethod(proto, "eventNames", jsEventNames);
Nan::SetPrototypeMethod(proto, "getMaxListeners", jsGetMaxListeners);
Nan::SetPrototypeMethod(proto, "listeners", jsListeners);
Nan::SetPrototypeMethod(proto, "off", jsRemoveListener);
Nan::SetPrototypeMethod(proto, "on", jsAddListener);
Nan::SetPrototypeMethod(proto, "once", jsAddListener);
Nan::SetPrototypeMethod(proto, "prependListener", jsPrependListener);
Nan::SetPrototypeMethod(proto, "prependOnceListener", jsPrependOnceListener);
Nan::SetPrototypeMethod(proto, "removeAllListeners", jsRemoveAllListeners);
Nan::SetPrototypeMethod(proto, "removeEventListener", jsRemoveListener);
Nan::SetPrototypeMethod(proto, "removeListener", jsRemoveListener);
Nan::SetPrototypeMethod(proto, "setMaxListeners", jsSetMaxListeners);
Nan::SetPrototypeMethod(proto, "rawListeners", jsRawListeners);
Nan::SetPrototypeMethod(proto, "destroy", jsDestroy);
// -------- static
V8_VAR_FUNC ctor = Nan::GetFunction(proto).ToLocalChecked();
V8_VAR_OBJ ctorObj = V8_VAR_OBJ::Cast(ctor);
Nan::SetMethod(ctorObj, "listenerCount", jsStaticListenerCount);
_ctorEventEmitter.Reset(ctor);
_protoEventEmitter.Reset(proto);
Nan::Set(target, JS_STR("EventEmitter"), ctor);
}
// C++ side emit() method
void emit(const std::string &name, int argc = 0, V8_VAR_VAL *argv = NULL) {
// Important! As actual get map[key] produces a new (empty) map entry
if ( _listeners.find(name) == _listeners.end() ) {
return;
}
// A copy is intended, because handlers can call removeListener (and they DO)
VEC_TYPE list = _listeners[name];
if (list.empty()) {
return;
}
for (IT_TYPE it = list.begin(); it != list.end(); ++it) {
Nan::Callback callback(Nan::New(*it));
if ( ! callback.IsEmpty() ) {
Nan::AsyncResource async("EventEmitter::cpp_emit()");
callback.Call(handle(), argc, argv, &async);
}
}
}
// C++ side on() method
void on(const std::string &name, V8_VAR_FUNC cb) {
V8_VAR_OBJ me = handle();
V8_VAR_VAL onVal = Nan::Get(me, JS_STR("on")).ToLocalChecked();
V8_VAR_FUNC onFunc = V8_VAR_FUNC::Cast(onVal);
Nan::Callback onCb(onFunc);
V8_VAR_VAL argv[] = { JS_STR(name.c_str()), cb };
Nan::AsyncResource async("EventEmitter::cpp_on()");
onCb.Call(me, 2, argv, &async);
}
void _destroy() { DES_CHECK;
_isDestroyed = true;
_listeners.clear();
_raw.clear();
_wrappedIds.clear();
_rawIds.clear();
}
~EventEmitter () { _destroy(); }
protected:
EventEmitter () {
_isDestroyed = false;
_maxListeners = 0;
_freeId = 0;
}
private:
static NAN_METHOD(newCtor) {
CTOR_CHECK("EventEmitter");
EventEmitter *eventEmitter = new EventEmitter();
eventEmitter->Wrap(info.This());
RET_VALUE(info.This());
}
static NAN_GETTER(isDestroyedGetter) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
RET_BOOL(eventEmitter->_isDestroyed);
}
// Deprecated static method
static NAN_METHOD(jsStaticListenerCount) {
REQ_OBJ_ARG(0, obj);
EventEmitter *eventEmitter = ObjectWrap::Unwrap<EventEmitter>(obj);
REQ_UTF8_ARG(1, name);
const VEC_TYPE &list = eventEmitter->_listeners[*name];
RET_INT(static_cast<int>(list.size()));
}
static NAN_METHOD(jsAddListener) {
_wrapListener(info);
RET_VALUE(info.This());
}
static NAN_METHOD(jsDispatchEvent) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
REQ_OBJ_ARG(0, event);
if ( ! event->Has(JS_STR("type")) ) {
return Nan::ThrowError("Event must have the `type` property.");
}
Nan::Utf8String name(event->Get(JS_STR("type")));
V8_VAR_VAL args = event;
eventEmitter->emit(*name, 1, &args);
RET_BOOL(true);
}
static NAN_METHOD(jsEmit) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
REQ_UTF8_ARG(0, name);
int length = info.Length();
std::vector< V8_VAR_VAL > args;
for (int i = 1; i < length; i++) {
args.push_back(info[i]);
}
eventEmitter->emit(*name, length - 1, &args[0]);
if ( eventEmitter->_listeners.find(*name) == eventEmitter->_listeners.end() ) {
RET_BOOL(false);
} else {
RET_BOOL(true);
}
}
static NAN_METHOD(jsEventNames) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
V8_VAR_ARR jsNames = Nan::New<v8::Array>(eventEmitter->_raw.size());
if (eventEmitter->_raw.empty()) {
RET_VALUE(jsNames);
return;
}
int i = 0;
for (MAP_IT_TYPE it = eventEmitter->_raw.begin(); it != eventEmitter->_raw.end(); ++it, i++) {
jsNames->Set(JS_INT(i), JS_STR(it->first));
}
RET_VALUE(jsNames);
}
static NAN_METHOD(jsGetMaxListeners) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
RET_INT(eventEmitter->_maxListeners);
}
static NAN_METHOD(jsListenerCount) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
REQ_UTF8_ARG(0, name);
const VEC_TYPE &list = eventEmitter->_listeners[*name];
RET_INT(static_cast<int>(list.size()));
}
static NAN_METHOD(jsListeners) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
REQ_UTF8_ARG(0, name);
VEC_TYPE &list = eventEmitter->_listeners[*name];
V8_VAR_ARR jsListeners = Nan::New<v8::Array>(list.size());
if (list.empty()) {
RET_VALUE(jsListeners);
return;
}
int i = 0;
for (IT_TYPE it = list.begin(); it != list.end(); ++it, i++) {
jsListeners->Set(JS_INT(i), Nan::New(*it));
}
RET_VALUE(jsListeners);
}
static inline void _reportListeners(
const std::string &name,
int numListeners,
int maxListeners
) {
std::string msg = "EventEmitter Warning: too many listeners (";
msg += std::to_string(numListeners);
msg += " > ";
msg += std::to_string(maxListeners);
msg += ") on '";
msg += name;
msg += "' event, possible memory leak.\n";
// Some JS magic to retrieve the call stack
V8_VAR_STR code = JS_STR(
"(new Error()).stack.split('\\n').slice(2).join('\\n')"
);
v8::Local<v8::Value> stack = v8::Script::Compile(
Nan::GetCurrentContext(), code
).ToLocalChecked()->Run(
Nan::GetCurrentContext()
).ToLocalChecked();
Nan::Utf8String stackStr(stack);
msg += *stackStr;
consoleLog(msg);
}
static inline void _addListener(
const Nan::FunctionCallbackInfo<v8::Value> &info,
const std::string &name,
V8_STORE_FUNC *cb,
bool isFront
) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
V8_VAR_VAL args[] = { info[0], info[1] };
eventEmitter->emit("newListener", 2, args);
if (isFront) {
eventEmitter->_listeners[name].push_front(*cb);
eventEmitter->_raw[name].push_front(*cb);
} else {
eventEmitter->_listeners[name].push_back(*cb);
eventEmitter->_raw[name].push_back(*cb);
}
int count = eventEmitter->_raw[name].size();
if (eventEmitter->_maxListeners > 0 && count > eventEmitter->_maxListeners) {
_reportListeners(name, count, eventEmitter->_maxListeners);
}
}
static inline void _wrapListener(
const Nan::FunctionCallbackInfo<v8::Value> &info,
bool isFront = false
) {
REQ_UTF8_ARG(0, name);
REQ_FUN_ARG(1, cb);
V8_STORE_FUNC persistentCb;
persistentCb.Reset(cb);
_addListener(info, *name, &persistentCb, isFront);
}
static inline void _addOnceListener(
const Nan::FunctionCallbackInfo<v8::Value> &info,
const std::string &name,
V8_STORE_FUNC *raw,
V8_STORE_FUNC *cb,
bool isFront
) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
V8_VAR_VAL args[] = { info[0], info[1] };
eventEmitter->emit("newListener", 2, args);
if (isFront) {
eventEmitter->_listeners[name].push_front(*cb);
eventEmitter->_raw[name].push_front(*raw);
} else {
eventEmitter->_listeners[name].push_back(*cb);
eventEmitter->_raw[name].push_back(*raw);
}
int nextId = eventEmitter->_freeId++;
eventEmitter->_wrappedIds[nextId] = *cb;
eventEmitter->_rawIds[nextId] = *raw;
int count = eventEmitter->_raw[name].size();
if (eventEmitter->_maxListeners > 0 && count > eventEmitter->_maxListeners) {
_reportListeners(name, count, eventEmitter->_maxListeners);
}
}
static inline void _wrapOnceListener(
const Nan::FunctionCallbackInfo<v8::Value> &info,
bool isFront = false
) {
REQ_UTF8_ARG(0, name);
REQ_FUN_ARG(1, raw);
V8_VAR_STR code = JS_STR(R"(
((emitter, name, cb) => (...args) => {
cb(...args);
emitter.removeListener(name, cb);
})
)");
v8::Local<v8::Value> decor = v8::Script::Compile(
Nan::GetCurrentContext(), code
).ToLocalChecked()->Run(
Nan::GetCurrentContext()
).ToLocalChecked();
Nan::Callback decorCb(Nan::To<v8::Function>(decor).ToLocalChecked());
V8_VAR_VAL argv[] = { info.This(), info[0], raw };
Nan::AsyncResource async("EventEmitter::js_once()");
V8_VAR_VAL wrapValue = decorCb.Call(3, argv, &async).ToLocalChecked();
V8_VAR_FUNC wrap = V8_VAR_FUNC::Cast(wrapValue);
V8_STORE_FUNC persistentWrap;
persistentWrap.Reset(wrap);
V8_STORE_FUNC persistentRaw;
persistentRaw.Reset(raw);
_addOnceListener(info, *name, &persistentRaw, &persistentWrap, isFront);
}
static NAN_METHOD(jsOnce) {
_wrapOnceListener(info);
RET_VALUE(info.This());
}
static NAN_METHOD(jsPrependListener) {
_wrapListener(info, true);
RET_VALUE(info.This());
}
static NAN_METHOD(jsPrependOnceListener) {
_wrapOnceListener(info, true);
RET_VALUE(info.This());
}
static NAN_METHOD(jsRemoveAllListeners) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
RET_VALUE(info.This());
if (info.Length() == 0) {
MAP_TYPE tmpMap = eventEmitter->_raw;
eventEmitter->_listeners.clear();
eventEmitter->_raw.clear();
eventEmitter->_wrappedIds.clear();
eventEmitter->_rawIds.clear();
for (MAP_IT_TYPE itMap = tmpMap.begin(); itMap != tmpMap.end(); ++itMap) {
const std::string &current = itMap->first;
VEC_TYPE &list = itMap->second;
for (IT_TYPE it = list.begin(); it != list.end(); ++it) {
V8_VAR_VAL args[] = { JS_STR(current.c_str()), Nan::New(*it) };
eventEmitter->emit("removeListener", 2, args);
}
}
return;
}
REQ_UTF8_ARG(0, n);
std::string name = std::string(*n);
VEC_TYPE &list = eventEmitter->_raw[name];
if (list.empty()) {
return;
}
if (eventEmitter->_rawIds.size()) {
std::vector<int> removes;
for (IT_TYPE it = list.begin(); it != list.end(); ++it) {
FN_TYPE fn = *it;
for (FNMAP_IT_TYPE itRaw = eventEmitter->_rawIds.begin(); itRaw != eventEmitter->_rawIds.end(); ++itRaw) {
if (fn == itRaw->second) {
removes.push_back(itRaw->first);
}
}
}
if (removes.size()) {
for (std::vector<int>::const_iterator it = removes.begin(); it != removes.end(); ++it) {
eventEmitter->_wrappedIds.erase(*it);
eventEmitter->_rawIds.erase(*it);
}
}
}
VEC_TYPE tmpVec = eventEmitter->_raw[name];
eventEmitter->_listeners[name].clear();
eventEmitter->_raw[name].clear();
for (IT_TYPE it = tmpVec.begin(); it != tmpVec.end(); ++it) {
V8_VAR_VAL args[] = { JS_STR(name.c_str()), Nan::New(*it) };
eventEmitter->emit("removeListener", 2, args);
}
}
static NAN_METHOD(jsRemoveListener) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
RET_VALUE(info.This());
REQ_UTF8_ARG(0, n);
REQ_FUN_ARG(1, raw);
V8_STORE_FUNC persistentRaw;
persistentRaw.Reset(raw);
std::string name = std::string(*n);
VEC_TYPE &rawList = eventEmitter->_raw[name];
if (rawList.empty()) {
return;
}
V8_VAR_VAL args[] = { info[0], info[1] };
for (IT_TYPE it = rawList.begin(); it != rawList.end(); ++it) {
if (*it == persistentRaw) {
rawList.erase(it);
if (rawList.empty()) {
eventEmitter->_raw.erase(name);
}
break;
}
}
VEC_TYPE &wrapList = eventEmitter->_listeners[name];
if (eventEmitter->_wrappedIds.size() == 0) {
for (IT_TYPE it = wrapList.begin(); it != wrapList.end(); ++it) {
if (*it == persistentRaw) {
wrapList.erase(it);
if (wrapList.empty()) {
eventEmitter->_listeners.erase(name);
}
break;
}
}
eventEmitter->emit("removeListener", 2, args);
return;
}
for (FNMAP_IT_TYPE itRaw = eventEmitter->_rawIds.begin(); itRaw != eventEmitter->_rawIds.end(); ++itRaw) {
if (persistentRaw == itRaw->second) {
FN_TYPE fn = eventEmitter->_wrappedIds[itRaw->first];
for (IT_TYPE it = wrapList.begin(); it != wrapList.end(); ++it) {
if (*it == fn) {
wrapList.erase(it);
if (wrapList.empty()) {
eventEmitter->_listeners.erase(name);
}
break;
}
}
eventEmitter->_wrappedIds.erase(itRaw->first);
eventEmitter->_rawIds.erase(itRaw->first);
break;
}
}
eventEmitter->emit("removeListener", 2, args);
}
static NAN_METHOD(jsSetMaxListeners) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
REQ_INT32_ARG(0, value);
eventEmitter->_maxListeners = value;
RET_VALUE(info.This());
}
static NAN_METHOD(jsRawListeners) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
REQ_UTF8_ARG(0, name);
VEC_TYPE &list = eventEmitter->_raw[*name];
V8_VAR_ARR jsListeners = Nan::New<v8::Array>(list.size());
if (list.empty()) {
RET_VALUE(jsListeners);
return;
}
int i = 0;
for (IT_TYPE it = list.begin(); it != list.end(); ++it, i++) {
jsListeners->Set(JS_INT(i), Nan::New(*it));
}
RET_VALUE(jsListeners);
}
static NAN_METHOD(jsDestroy) { THIS_EVENT_EMITTER; EVENT_EMITTER_THIS_CHECK;
eventEmitter->emit("destroy");
eventEmitter->_destroy();
}
private:
bool _isDestroyed;
int _maxListeners;
MAP_TYPE _listeners;
MAP_TYPE _raw;
int _freeId;
FNMAP_TYPE _wrappedIds;
FNMAP_TYPE _rawIds;
};
#endif // _EVENT_EMITTER_

View File

@ -1,65 +0,0 @@
'use strict';
const path = require('node:path');
const nameWindows = 'windows';
const platformAndArch = `${process.platform}-${process.arch}`;
const platformNames = {
'win32-x64': nameWindows,
'linux-x64': 'linux',
'darwin-x64': 'osx',
'linux-arm64': 'aarch64',
};
const platformName = platformNames[platformAndArch] || platformAndArch;
const isWindows = platformName === nameWindows;
const getPaths = (dir) => {
dir = dir.replace(/\\/g, '/');
const bin = `${dir}/bin-${platformName}`;
const include = `${dir}/include`;
if (isWindows) {
process.env.path = `${bin};${process.env.path ? `${process.env.path}` : ''}`;
}
return { bin, include };
};
const getBin = () => {
return `bin-${platformName}`;
};
const getPlatform = () => {
return platformName;
};
const getInclude = () => {
let napi = null;
try {
napi = require('node-addon-api');
} catch (ex) {
// do nothing
}
const rootPath = path.resolve(`${__dirname}/..`).replace(/\\/g, '/');
const napiInclude = napi ? napi.include_dir.replace(/\\/g, '/') : '';
const thisInclude = `${rootPath}/include`;
const includePath = `${napiInclude} ${thisInclude}`;
return includePath;
};
module.exports = {
getPaths,
getBin,
getPlatform,
getInclude,
};

View File

@ -1,41 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const tools = require('.');
describe('AT / include', () => {
const stringMethods = ['getBin', 'getPlatform', 'getInclude'];
stringMethods.forEach((name) => {
describe(`#${name}()`, () => {
it('is a function', () => {
assert.strictEqual(typeof tools[name], 'function');
});
it('returns an object', () => {
assert.strictEqual(typeof tools[name](), 'string');
});
});
});
describe('#getPaths()', () => {
it('is a function', () => {
assert.strictEqual(typeof tools.getPaths, 'function');
});
it('returns an object', () => {
assert.strictEqual(typeof tools.getPaths(__dirname), 'object');
});
it('has "include" string', () => {
assert.strictEqual(typeof tools.getPaths(__dirname).include, 'string');
});
it('has "bin" string', () => {
assert.strictEqual(typeof tools.getPaths(__dirname).include, 'string');
});
});
});

View File

@ -1,81 +0,0 @@
# Snippets
## C++ Addon building
### 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 `/${getPlatform()}.gz`
In **package.json**:
```
"scripts": {
"postinstall": "node install",
},
"dependencies": {
"addon-tools-raub": "^7.0.0",
},
"devDependencies": {
"node-addon-api": "^5.0.0"
}
```
Create the **install.js** file, see `install` in [index.d.ts](/index.d.ts).
**Addon Tools** will unpack (using **tar**) 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 into the `index.js` without changes.
```
module.exports = require('addon-tools-raub').getPaths(__dirname);
```
* For a compiled addon:
Require the `ADDON.node` in your **index.js** from the platform-specific directory.
```
const { getBin } = require('addon-tools-raub');
const core = require(`./${getBin()}/ADDON`);
```
Publishing binaries is done by attaching a GZIPped platform folder to a 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
**install.js**.
> 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 GZIPped set of files/folders.
### GYP Variables
```
'variables': {
'bin': '<!(node -p "require(\'addon-tools-raub\').getBin()")',
'DEPS_include': '<!(node -p "require(\'DEPS\').getInclude()")',
'DEPS_bin': '<!(node -p "require(\'DEPS\').getBin()")',
},
```
* `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\').getInclude()")',
'<(DEPS_include)',
],
```
See example of a working [**binding.gyp** here](/test-addon/binding.gyp)

361
index.d.ts vendored
View File

@ -1,361 +0,0 @@
declare module "addon-tools-raub" {
type Stats = import('node:fs').Stats;
type Writable = import('node:stream').Writable;
type Readable = import('node:stream').Readable;
type WritableOptions = import('node:stream').WritableOptions;
/**
* Get the internal paths for an addon
*
* Returns a set of platform dependent paths depending on the input dir
*/
export const getPaths: (dir: string) => Readonly<{
/**
* Path to binaries
*
* Platform binary directory absolute path for this `dir`
*/
bin: string;
/**
* Path to include
*
* Include directory for this `dir`
*/
include: string;
}>;
type TPlatformName = 'windows' | 'linux' | 'osx' | 'aarch64';
type TPlatformDir = `bin-${TPlatformName}`;
/**
* Get the platform-specific binary directory name
*/
export const getBin: () => TPlatformDir;
/**
* Get the platform identifier
*/
export const getPlatform: () => TPlatformName;
/**
* Get the include directories for **binding.gyp**
*
* Both 'addon-tools-raub' and 'node-addon-api' include paths.
* In binding.gyp: `'<!@(node -p "require(\'addon-tools-raub\').getInclude()")'`
*/
export const getInclude: () => string;
/**
* Install binaries
*
* Downloads and unpacks the platform specific binary for the calling package.
* To use it, create a new script for your package, which may as well be named
* **install.js**, with the following content:
*
* ```
* 'use strict';
* const { install } = require('addon-tools-raub');
* const prefix = 'https://github.com/USER/ADDON-NAME/releases/download';
* const tag = '1.0.0';
* install(`${prefix}/${tag}`);
* ```
*
* * `prefix` - the constant base part of the download url.
* * `tag` - the version-dependent part of the url.
*
* ```
* "scripts": {
* "postinstall": "node install"
* },
* ```
*/
export const install: (folder: string) => Promise<boolean>;
/**
* Copy binary
*
* Copies the addon binary from `src/build/Release` to the platform-specific folder.
*
* ```
* "scripts": {
* * "build": "node-gyp rebuild && node -e \"require('addon-tools-raub').cpbin('ADDON')\""
* },
* ```
*
* Here ADDON should be replaced with the name of your addon, without `.node` extension.
*/
export const cpbin: (name: string) => Promise<void>;
/**
* Packs binaries into GZIP
*
* Example of `actionPack` usage in **Github Actions**:
*
* ```
* - name: Pack Files
* id: pack-files
* run: node -e "require('addon-tools-raub').actionPack()" >> $GITHUB_OUTPUT
* - name: Store Binaries
* uses: softprops/action-gh-release@v1
* with:
* files: ${{ steps.pack-files.outputs.pack }}
* ```
*/
export const actionPack: () => Promise<void>;
/**
* Download to memory
*
* Accepts an **URL**, and returns an in-memory file Buffer,
* when the file is loaded. Use for small files, as the whole
* file will be loaded into memory at once.
*
* ```
* download(srcUrl).then(data => useData(data), err => emit('error', err));
* ```
* or
* ```
* const data = await download(srcUrl);
* useData(data);
* ```
*/
export const download: (url: string) => Promise<Buffer>;
/**
* (async) Read a file
*
* Reads a whole file to string, NOT A Buffer
*/
type ComposeFnParam = (source: any) => void;
/**
* WritableBuffer
*
* A [Writable](https://nodejs.org/api/stream.html#stream_writable_streams)
* stream buffer, that is stored in-memory and can be fully
* obtained when writing was finished. It is equivalent to stream-writing
* a temporary file and then reading it into a `Buffer`.
*/
export class WritableBuffer implements Writable {
constructor();
/**
* Get the downloaded data
* Use `stream.get()` to obtain the data when writing was finished
*/
get(): Buffer;
// ----------- implements Writable
readonly writable: boolean;
readonly writableEnded: boolean;
readonly writableFinished: boolean;
readonly writableHighWaterMark: number;
readonly writableLength: number;
readonly writableObjectMode: boolean;
readonly writableCorked: number;
destroyed: boolean;
readonly closed: boolean;
readonly errored: Error | null;
readonly writableNeedDrain: boolean;
constructor(opts?: WritableOptions);
_write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
_writev?(
chunks: Array<{
chunk: any;
encoding: BufferEncoding;
}>,
callback: (error?: Error | null) => void,
): void;
_construct?(callback: (error?: Error | null) => void): void;
_destroy(error: Error | null, callback: (error?: Error | null) => void): void;
_final(callback: (error?: Error | null) => void): void;
write(chunk: any, callback?: (error: Error | null | undefined) => void): boolean;
write(chunk: any, encoding: BufferEncoding, callback?: (error: Error | null | undefined) => void): boolean;
setDefaultEncoding(encoding: BufferEncoding): this;
end(cb?: () => void): this;
end(chunk: any, cb?: () => void): this;
end(chunk: any, encoding: BufferEncoding, cb?: () => void): this;
cork(): void;
uncork(): void;
destroy(error?: Error): this;
addListener(event: "close", listener: () => void): this;
addListener(event: "drain", listener: () => void): this;
addListener(event: "error", listener: (err: Error) => void): this;
addListener(event: "finish", listener: () => void): this;
addListener(event: "pipe", listener: (src: Readable) => void): this;
addListener(event: "unpipe", listener: (src: Readable) => void): this;
addListener(event: string | symbol, listener: (...args: any[]) => void): this;
emit(event: "close"): boolean;
emit(event: "drain"): boolean;
emit(event: "error", err: Error): boolean;
emit(event: "finish"): boolean;
emit(event: "pipe", src: Readable): boolean;
emit(event: "unpipe", src: Readable): boolean;
emit(event: string | symbol, ...args: any[]): boolean;
on(event: "close", listener: () => void): this;
on(event: "drain", listener: () => void): this;
on(event: "error", listener: (err: Error) => void): this;
on(event: "finish", listener: () => void): this;
on(event: "pipe", listener: (src: Readable) => void): this;
on(event: "unpipe", listener: (src: Readable) => void): this;
on(event: string | symbol, listener: (...args: any[]) => void): this;
once(event: "close", listener: () => void): this;
once(event: "drain", listener: () => void): this;
once(event: "error", listener: (err: Error) => void): this;
once(event: "finish", listener: () => void): this;
once(event: "pipe", listener: (src: Readable) => void): this;
once(event: "unpipe", listener: (src: Readable) => void): this;
once(event: string | symbol, listener: (...args: any[]) => void): this;
prependListener(event: "close", listener: () => void): this;
prependListener(event: "drain", listener: () => void): this;
prependListener(event: "error", listener: (err: Error) => void): this;
prependListener(event: "finish", listener: () => void): this;
prependListener(event: "pipe", listener: (src: Readable) => void): this;
prependListener(event: "unpipe", listener: (src: Readable) => void): this;
prependListener(event: string | symbol, listener: (...args: any[]) => void): this;
prependOnceListener(event: "close", listener: () => void): this;
prependOnceListener(event: "drain", listener: () => void): this;
prependOnceListener(event: "error", listener: (err: Error) => void): this;
prependOnceListener(event: "finish", listener: () => void): this;
prependOnceListener(event: "pipe", listener: (src: Readable) => void): this;
prependOnceListener(event: "unpipe", listener: (src: Readable) => void): this;
prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this;
removeListener(event: "close", listener: () => void): this;
removeListener(event: "drain", listener: () => void): this;
removeListener(event: "error", listener: (err: Error) => void): this;
removeListener(event: "finish", listener: () => void): this;
removeListener(event: "pipe", listener: (src: Readable) => void): this;
removeListener(event: "unpipe", listener: (src: Readable) => void): this;
removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
pipe<T extends NodeJS.WritableStream>(
destination: T,
options?: {
end?: boolean | undefined;
},
): T;
compose<T extends NodeJS.ReadableStream>(
stream: T | ComposeFnParam | Iterable<T> | AsyncIterable<T>,
options?: { signal: AbortSignal },
): T;
off(eventName: string | symbol, listener: (...args: any[]) => void): this;
removeAllListeners(event?: string | symbol): this;
setMaxListeners(n: number): this;
getMaxListeners(): number;
listeners(eventName: string | symbol): Function[];
rawListeners(eventName: string | symbol): Function[];
emit(eventName: string | symbol, ...args: any[]): boolean;
listenerCount(eventName: string | symbol, listener?: Function): number;
eventNames(): Array<string | symbol>;
}
export const read: (name: string) => Promise<string>;
/**
* (async) Write a file
*/
export const write: (name: string, text: string) => Promise<void>;
/**
* (async) Copy a file
*/
export const copy: (src: string, dest: string) => Promise<void>;
/**
* (async) Check if a file/folder exists
*/
export const exists: (name: string) => Promise<boolean>;
/**
* (async) Create an empty folder
*/
export const mkdir: (name: string) => Promise<void>;
/**
* (async) Get status on a file
*/
export const stat: (name: string) => Promise<Stats>;
/**
* (async) Check if the path is a folder
*/
export const isDir: (name: string) => Promise<boolean>;
/**
* (async) Check if the path is a file
*/
export const isFile: (name: string) => Promise<boolean>;
/**
* Cut the path one folder up
*/
export const dirUp: (dir: string) => string;
/**
* (async) Create a directory
*
* Like `mkdir -p`, makes sure a directory exists
*/
export const ensuredir: (dir: string) => Promise<void>;
/**
* (async) Copy a file
*
* Copy a file, `dest` folder is created if needed
*/
export const copysafe: (src: string, dest: string) => Promise<void>;
/**
* (async) Read a directory
*
* Get file/folder names of the 1st level
*/
export const readdir: (src: string, dest: string) => Promise<ReadonlyArray<string>>;
/**
* (async) List subdirectories
*
* Get folder paths (concatenated with input) of the 1st level
*/
export const subdirs: (name: string) => Promise<ReadonlyArray<string>>;
/**
* (async) List nested files
*
* Get file paths (concatenated with input) of the 1st level
*/
export const subfiles: (name: string) => Promise<ReadonlyArray<string>>;
/**
* (async) Get all nested files recursively
*
* Folder paths are omitted by default.
* Order is: shallow-to-deep, each subdirectory lists dirs-then-files.
*/
export const traverse: (name: string, showDirs?: boolean) => Promise<ReadonlyArray<string>>;
/**
* (async) Copy a directory
*
* Copy a folder with all the contained files
*/
export const copyall: (src: string, dest: string) => Promise<void>;
/**
* (async) Remove a directory
*
* Like `rm -rf`, removes everything recursively
*/
export const rmdir: (name: string) => Promise<void>;
/**
* (async) Remove a file
*
* Must be a file, not a folder. Just `fs.unlink`.
*/
export const rm: (name: string) => Promise<void>;
}

View File

@ -1,3 +1,95 @@
'use strict'; 'use strict';
module.exports = Object.assign({}, require('./include'), require('./utils')); const path = require('path');
const rootPath = __dirname.replace(/\\/g, '/');
const nanInclude = path.dirname(require.resolve('nan')).replace(/\\/g, '/');
const thisInclude = `${rootPath}/include`;
const isWindows = process.platform === 'win32';
const names = ['win32', 'win64', 'linux32', 'linux64', 'mac64'];
const prefixName = name => `bin-${name}`;
const getPlatformDir = platform => {
switch (platform) {
case 'win32' : return process.arch === 'x64' ? 'win64' : 'win32';
case 'linux' :
if (process.arch === 'x32') {
throw new Error('Linux x32 not supported since 4.0.0.');
}
return 'linux64';
case 'darwin' :
if (process.arch === 'x32') {
throw new Error('Mac x32 not supported.');
}
return 'mac64';
default : throw new Error(`Platform "${platform}" is not supported.`);
}
};
const currentDir = prefixName(getPlatformDir(process.platform));
const remDirs = names.map(prefixName).filter(n => n !== currentDir);
const paths = dir => {
dir = dir.replace(/\\/g, '/');
const binPath = `${dir}/${currentDir}`;
if (isWindows) {
process.env.path = `${binPath};${process.env.path ? `${process.env.path}` : ''}`;
}
const remPath = remDirs.map(k => `${dir}/${k}`).join(' ');
const includePath = `${dir}/include`;
return {
binPath,
remPath,
includePath,
bin() { console.log(binPath); },
rem() { console.log(remPath); },
include() { console.log(includePath); },
};
};
const includePath = `${nanInclude} ${thisInclude}`;
const binPath = currentDir;
const mkdirPath = isWindows ? `${rootPath}/_mkdir.bat` : 'mkdir';
const rmPath = isWindows ? `${rootPath}/_rm.bat` : 'rm';
const cpPath = isWindows ? `${rootPath}/_cp.bat` : 'cp';
module.exports = {
paths,
binPath,
rootPath,
includePath,
mkdirPath,
rmPath,
cpPath,
bin() { return console.log(binPath); },
root() { return console.log(rootPath); },
include() { console.log(includePath); },
mkdir() { return console.log(mkdirPath); },
rm() { return console.log(rmPath); },
cp() { return console.log(cpPath); },
};

1309
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,60 +1,39 @@
{ {
"author": "Luis Blanco <luisblanco1337@gmail.com>", "author": "Luis Blanco <luisblanco1337@gmail.com>",
"name": "addon-tools-raub", "name": "addon-tools-raub",
"version": "7.4.0", "version": "4.2.0",
"description": "Helpers for Node.js addons and dependency packages", "description": "Helpers for Node.js addons and dependency packages",
"license": "MIT", "license": "MIT",
"main": "index.js", "main": "index.js",
"keywords": [ "keywords": [
"support",
"headers", "headers",
"include", "include",
"eventemitter",
"events", "events",
"utils", "utils",
"c++", "c++",
"addon", "addon",
"bindings", "bindings",
"native", "native",
"napi",
"gyp" "gyp"
], ],
"files": [
"include",
"utils.js",
"utils.d.ts",
"index.js",
"index.d.ts",
"utils",
"LICENSE",
"package.json",
"README.md"
],
"engines": { "engines": {
"node": ">=18.16.0", "node": ">=10.13.0",
"npm": ">=9.5.1" "npm": ">=6.4.1"
},
"scripts": {
"eslint": "eslint .",
"test": "node --test --watch .",
"test-ci": "node --test",
"build-test": "cd test-addon && node-gyp rebuild -j max --silent && cd .."
}, },
"maintainers": [
{
"name": "Luis Blanco",
"email": "luisblanco1337@gmail.com",
"skype": "rauber666"
}
],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/node-3d/addon-tools-raub.git" "url": "https://github.com/node-3d/addon-tools-raub.git"
}, },
"peerDependencies": { "dependencies": {
"node-addon-api": "^7.0.0" "nan": "2.12.1"
},
"peerDependenciesMeta": {
"node-addon-api": {
"optional": true
}
},
"devDependencies": {
"@types/node": "^20.8.3",
"eslint": "^8.51.0",
"eslint-plugin-node": "^11.1.0",
"node-addon-api": "^7.0.0",
"typescript": "^5.2.2"
} }
} }

View File

@ -1,37 +0,0 @@
{
'targets': [{
'target_name': 'test',
'sources': [
'test.cpp',
],
'cflags_cc': ['-std=c++17', '-fno-exceptions'],
'include_dirs': [
'<!@(node -p "require(\'..\').getInclude()")',
],
'conditions': [
['OS=="linux"', {
'defines': ['__linux__'],
}],
['OS=="mac"', {
'MACOSX_DEPLOYMENT_TARGET': '10.9',
'defines': ['__APPLE__'],
'CLANG_CXX_LIBRARY': 'libc++',
'OTHER_CFLAGS': ['-std=c++17', '-fno-exceptions'],
}],
['OS=="win"', {
'defines' : ['WIN32_LEAN_AND_MEAN', 'VC_EXTRALEAN', '_WIN32', '_HAS_EXCEPTIONS=0'],
'msvs_settings' : {
'VCCLCompilerTool' : {
'AdditionalOptions' : [
'/O2','/Oy','/GL','/GF','/Gm-', '/std:c++17',
'/EHa-s-c-','/MT','/GS','/Gy','/GR-','/Gd',
],
},
'VCLinkerTool' : {
'AdditionalOptions' : ['/DEBUG:NONE', '/LTCG', '/OPT:NOREF'],
},
},
}],
],
}],
}

View File

@ -1,45 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const arrayArgLetMsg = { message: 'Argument 0 must be of type `Array` or be `null`/`undefined`' };
describe('AT / HPP / LET_ARRAY_ARG', () => {
it('exports letArrayStrArg', () => {
assert.strictEqual(typeof test.letArrayStrArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letArrayStrArg('1'), arrayArgLetMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.letArrayStrArg(1), arrayArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letArrayStrArg(true), arrayArgLetMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.letArrayStrArg(test.retExt()), arrayArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letArrayStrArg({}), arrayArgLetMsg);
});
it('accepts an empty arg', () => {
assert.ok(Array.isArray(test.letArrayStrArg()));
});
it('accepts undefined', () => {
assert.ok(Array.isArray(test.letArrayStrArg(undefined)));
});
it('accepts null', () => {
assert.ok(Array.isArray(test.letArrayStrArg(null)));
});
it('accepts an array', () => {
assert.ok(Array.isArray(test.letArrayStrArg([])));
});
it('returns same array', () => {
assert.deepStrictEqual(test.letArrayStrArg(['a', 'b']),['a', 'b']);
});
});

View File

@ -1,109 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const arrayArgMsg = { message: 'Argument 0 must be of type `Array`' };
const arrayArgLetMsg = { message: 'Argument 0 must be of type `Array` or be `null`/`undefined`' };
describe('AT / HPP / REQ_ARRAY_ARG', () => {
it('exports reqArrayArg', () => {
assert.strictEqual(typeof test.reqArrayArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqArrayArg(), arrayArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqArrayArg(undefined), arrayArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqArrayArg(null), arrayArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqArrayArg('1'), arrayArgMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.reqArrayArg(1), arrayArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqArrayArg(true), arrayArgMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.reqArrayArg(test.retExt()), arrayArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqArrayArg({}), arrayArgMsg);
});
it('accepts an array', () => {
assert.ok(Array.isArray(test.reqArrayArg([])));
});
});
describe('addon-tools.hpp: LET_ARRAY_ARG', () => {
it('exports letArrayArg', () => {
assert.strictEqual(typeof test.letArrayArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letArrayArg('1'), arrayArgLetMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.letArrayArg(1), arrayArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letArrayArg(true), arrayArgLetMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.letArrayArg(test.retExt()), arrayArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letArrayArg({}), arrayArgLetMsg);
});
it('accepts an empty arg', () => {
assert.ok(Array.isArray(test.letArrayArg()));
});
it('accepts undefined', () => {
assert.ok(Array.isArray(test.letArrayArg(undefined)));
});
it('accepts null', () => {
assert.ok(Array.isArray(test.letArrayArg(null)));
});
it('accepts an array', () => {
assert.ok(Array.isArray(test.letArrayArg([])));
});
});
describe('addon-tools.hpp: USE_ARRAY_ARG', () => {
it('exports useArrayArg', () => {
assert.strictEqual(typeof test.useArrayArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.useArrayArg('1'), arrayArgLetMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.useArrayArg(1), arrayArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.useArrayArg(true), arrayArgLetMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.useArrayArg(test.retExt()), arrayArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.useArrayArg({}), arrayArgLetMsg);
});
it('accepts an empty arg', () => {
assert.ok(Array.isArray(test.useArrayArg()));
});
it('accepts undefined', () => {
assert.ok(Array.isArray(test.useArrayArg(undefined)));
});
it('accepts null', () => {
assert.ok(Array.isArray(test.useArrayArg(null)));
});
it('accepts an array', () => {
assert.ok(Array.isArray(test.useArrayArg([])));
});
});

View File

@ -1,100 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const boolArgMsg = { message: 'Argument 0 must be of type `Bool`' };
const boolArgLetMsg = { message: 'Argument 0 must be of type `Bool` or be `null`/`undefined`' };
describe('AT / HPP / REQ_BOOL_ARG', () => {
it('exports reqBoolArg', () => {
assert.strictEqual(typeof test.reqBoolArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqBoolArg(), boolArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqBoolArg(undefined), boolArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqBoolArg(null), boolArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqBoolArg('1'), boolArgMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.reqBoolArg(1), boolArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqBoolArg({}), boolArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqBoolArg([]), boolArgMsg);
});
it('accepts a boolean', () => {
assert.ok(test.reqBoolArg(true));
});
});
describe('addon-tools.hpp: LET_BOOL_ARG', () => {
it('exports letBoolArg', () => {
assert.strictEqual(typeof test.letBoolArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letBoolArg('1'), boolArgLetMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.letBoolArg(1), boolArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letBoolArg({}), boolArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.letBoolArg([]), boolArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.letBoolArg(), false);
});
it('accepts undefined', () => {
assert.strictEqual(test.letBoolArg(undefined), false);
});
it('accepts null', () => {
assert.strictEqual(test.letBoolArg(null), false);
});
it('accepts a boolean', () => {
assert.ok(test.letBoolArg(true));
});
});
describe('addon-tools.hpp: USE_BOOL_ARG', () => {
it('exports useBoolArg', () => {
assert.strictEqual(typeof test.useBoolArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.useBoolArg('1'), boolArgLetMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.useBoolArg(1), boolArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.useBoolArg({}), boolArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.useBoolArg([]), boolArgLetMsg);
});
it('accepts an empty arg', () => {
assert.ok(test.useBoolArg());
});
it('accepts undefined', () => {
assert.ok(test.useBoolArg(undefined));
});
it('accepts null', () => {
assert.ok(test.useBoolArg(null));
});
it('accepts a boolean', () => {
assert.ok(test.useBoolArg(true));
});
});

View File

@ -1,100 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const numArgMsg = { message: 'Argument 0 must be of type `Number`' };
const numArgLetMsg = { message: 'Argument 0 must be of type `Number` or be `null`/`undefined`' };
describe('AT / HPP / REQ_DOUBLE_ARG', () => {
it('exports reqDoubleArg', () => {
assert.strictEqual(typeof test.reqDoubleArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqDoubleArg(), numArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqDoubleArg(undefined), numArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqDoubleArg(null), numArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqDoubleArg('1'), numArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqDoubleArg(true), numArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqDoubleArg({}), numArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqDoubleArg([]), numArgMsg);
});
it('accepts a number', () => {
assert.strictEqual(test.reqDoubleArg(55), 55);
});
});
describe('addon-tools.hpp: LET_DOUBLE_ARG', () => {
it('exports letDoubleArg', () => {
assert.strictEqual(typeof test.letDoubleArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letDoubleArg('1'), numArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letDoubleArg(true), numArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letDoubleArg({}), numArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.letDoubleArg([]), numArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.letDoubleArg(), 0);
});
it('accepts undefined', () => {
assert.strictEqual(test.letDoubleArg(undefined), 0);
});
it('accepts null', () => {
assert.strictEqual(test.letDoubleArg(null), 0);
});
it('accepts a number', () => {
assert.strictEqual(test.letDoubleArg(55), 55);
});
});
describe('addon-tools.hpp: USE_DOUBLE_ARG', () => {
it('exports useDoubleArg', () => {
assert.strictEqual(typeof test.useDoubleArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.useDoubleArg('1'), numArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.useDoubleArg(true), numArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.useDoubleArg({}), numArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.useDoubleArg([]), numArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.useDoubleArg(), 10);
});
it('accepts undefined', () => {
assert.strictEqual(test.useDoubleArg(undefined), 10);
});
it('accepts null', () => {
assert.strictEqual(test.useDoubleArg(null), 10);
});
it('accepts a number', () => {
assert.strictEqual(test.useDoubleArg(55), 55);
});
});

View File

@ -1,109 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const extArgMsg = { message: 'Argument 0 must be of type `Pointer`' };
const extArgLetMsg = { message: 'Argument 0 must be of type `Pointer` or be `null`/`undefined`' };
describe('AT / HPP / REQ_EXT_ARG', () => {
it('exports reqExtArg', () => {
assert.strictEqual(typeof test.reqExtArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqExtArg(), extArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqExtArg(undefined), extArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqExtArg(null), extArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqExtArg('1'), extArgMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.reqExtArg(1), extArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqExtArg(true), extArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqExtArg({}), extArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqExtArg([]), extArgMsg);
});
it('accepts a pointer', () => {
assert.strictEqual(typeof test.reqExtArg(test.retExt()), 'object');
});
});
describe('addon-tools.hpp: LET_EXT_ARG', () => {
it('exports letExtArg', () => {
assert.strictEqual(typeof test.letExtArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letExtArg('1'), extArgLetMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.letExtArg(1), extArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letExtArg(true), extArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letExtArg({}), extArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.letExtArg([]), extArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(typeof test.letExtArg(), 'object');
});
it('accepts undefined', () => {
assert.strictEqual(typeof test.letExtArg(undefined), 'object');
});
it('accepts null', () => {
assert.strictEqual(typeof test.letExtArg(null), 'object');
});
it('accepts a pointer', () => {
assert.strictEqual(typeof test.reqExtArg(test.retExt()), 'object');
});
});
describe('addon-tools.hpp: USE_EXT_ARG', () => {
it('exports useExtArg', () => {
assert.strictEqual(typeof test.useExtArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.useExtArg('1'), extArgLetMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.useExtArg(1), extArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.useExtArg(true), extArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.useExtArg({}), extArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.useExtArg([]), extArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(typeof test.useExtArg(), 'object');
});
it('accepts undefined', () => {
assert.strictEqual(typeof test.useExtArg(undefined), 'object');
});
it('accepts null', () => {
assert.strictEqual(typeof test.useExtArg(null), 'object');
});
it('accepts a number', () => {
assert.strictEqual(typeof test.useExtArg(test.retExt()), 'object');
});
});

View File

@ -1,100 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const numArgMsg = { message: 'Argument 0 must be of type `Number`' };
const numArgLetMsg = { message: 'Argument 0 must be of type `Number` or be `null`/`undefined`' };
describe('AT / HPP / REQ_FLOAT_ARG', () => {
it('exports reqFloatArg', () => {
assert.strictEqual(typeof test.reqFloatArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqFloatArg(), numArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqFloatArg(undefined), numArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqFloatArg(null), numArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqFloatArg('1'), numArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqFloatArg(true), numArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqFloatArg({}), numArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqFloatArg([]), numArgMsg);
});
it('accepts a number', () => {
assert.strictEqual(test.reqFloatArg(55), 55);
});
});
describe('addon-tools.hpp: LET_FLOAT_ARG', () => {
it('exports letFloatArg', () => {
assert.strictEqual(typeof test.letFloatArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letFloatArg('1'), numArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letFloatArg(true), numArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letFloatArg({}), numArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.letFloatArg([]), numArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.letFloatArg(), 0);
});
it('accepts undefined', () => {
assert.strictEqual(test.letFloatArg(undefined), 0);
});
it('accepts null', () => {
assert.strictEqual(test.letFloatArg(null), 0);
});
it('accepts a number', () => {
assert.strictEqual(test.letFloatArg(55), 55);
});
});
describe('addon-tools.hpp: USE_FLOAT_ARG', () => {
it('exports useFloatArg', () => {
assert.strictEqual(typeof test.useFloatArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.useFloatArg('1'), numArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.useFloatArg(true), numArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.useFloatArg({}), numArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.useFloatArg([]), numArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.useFloatArg(), 10);
});
it('accepts undefined', () => {
assert.strictEqual(test.useFloatArg(undefined), 10);
});
it('accepts null', () => {
assert.strictEqual(test.useFloatArg(null), 10);
});
it('accepts a number', () => {
assert.strictEqual(test.useFloatArg(55), 55);
});
});

View File

@ -1,100 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const intArgMsg = { message: 'Argument 0 must be of type `Int32`' };
const intArgLetMsg = { message: 'Argument 0 must be of type `Int32` or be `null`/`undefined`' };
describe('AT / HPP / REQ_INT_ARG, REQ_INT32_ARG', () => {
it('exports reqIntArg', () => {
assert.strictEqual(typeof test.reqIntArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqIntArg(), intArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqIntArg(undefined), intArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqIntArg(null), intArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqIntArg('1'), intArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqIntArg(true), intArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqIntArg({}), intArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqIntArg([]), intArgMsg);
});
it('accepts a number', () => {
assert.strictEqual(test.reqIntArg(55), 55);
});
});
describe('addon-tools.hpp: LET_INT_ARG / LET_INT32_ARG', () => {
it('exports letIntArg', () => {
assert.strictEqual(typeof test.letIntArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letIntArg('1'), intArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letIntArg(true), intArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letIntArg({}), intArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.letIntArg([]), intArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.letIntArg(), 0);
});
it('accepts undefined', () => {
assert.strictEqual(test.letIntArg(undefined), 0);
});
it('accepts null', () => {
assert.strictEqual(test.letIntArg(null), 0);
});
it('accepts a number', () => {
assert.strictEqual(test.letIntArg(55), 55);
});
});
describe('addon-tools.hpp: USE_INT_ARG / USE_INT32_ARG', () => {
it('exports useIntArg', () => {
assert.strictEqual(typeof test.useIntArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.useIntArg('1'), intArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.useIntArg(true), intArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.useIntArg({}), intArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.useIntArg([]), intArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.useIntArg(), 10);
});
it('accepts undefined', () => {
assert.strictEqual(test.useIntArg(undefined), 10);
});
it('accepts null', () => {
assert.strictEqual(test.useIntArg(null), 10);
});
it('accepts a number', () => {
assert.strictEqual(test.useIntArg(55), 55);
});
});

View File

@ -1,109 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const objArgMsg = { message: 'Argument 0 must be of type `Object`' };
const objArgLetMsg = { message: 'Argument 0 must be of type `Object` or be `null`/`undefined`' };
describe('AT / HPP / REQ_OBJ_ARG', () => {
it('exports reqObjArg', () => {
assert.strictEqual(typeof test.reqObjArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqObjArg(), objArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqObjArg(undefined), objArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqObjArg(null), objArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqObjArg('1'), objArgMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.reqObjArg(1), objArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqObjArg(true), objArgMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.reqObjArg(test.retExt()), objArgMsg);
});
it('accepts an object', () => {
assert.strictEqual(typeof test.reqObjArg({}), 'object');
});
it('accepts an array', () => {
assert.ok(Array.isArray(test.reqObjArg([])));
});
});
describe('addon-tools.hpp: LET_OBJ_ARG', () => {
it('exports letObjArg', () => {
assert.strictEqual(typeof test.letObjArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letObjArg('1'), objArgLetMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.letObjArg(1), objArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letObjArg(true), objArgLetMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.letObjArg(test.retExt()), objArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(typeof test.letObjArg(), 'object');
});
it('accepts undefined', () => {
assert.strictEqual(typeof test.letObjArg(undefined), 'object');
});
it('accepts null', () => {
assert.strictEqual(typeof test.letObjArg(null), 'object');
});
it('accepts an object', () => {
assert.strictEqual(typeof test.letObjArg({}), 'object');
});
it('accepts an array', () => {
assert.ok(Array.isArray(test.letObjArg([])));
});
});
describe('addon-tools.hpp: USE_OBJ_ARG', () => {
it('exports useObjArg', () => {
assert.strictEqual(typeof test.useObjArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.useObjArg('1'), objArgLetMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.useObjArg(1), objArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.useObjArg(true), objArgLetMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.useObjArg(test.retExt()), objArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(typeof test.useObjArg(), 'object');
});
it('accepts undefined', () => {
assert.strictEqual(typeof test.useObjArg(undefined), 'object');
});
it('accepts null', () => {
assert.strictEqual(typeof test.useObjArg(null), 'object');
});
it('accepts an object', () => {
assert.strictEqual(typeof test.useObjArg({}), 'object');
});
it('accepts an array', () => {
assert.ok(Array.isArray(test.useObjArg([])));
});
});

View File

@ -1,100 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const numArgMsg = { message: 'Argument 0 must be of type `Number`' };
const numArgLetMsg = { message: 'Argument 0 must be of type `Number` or be `null`/`undefined`' };
describe('AT / HPP / REQ_OFFS_ARG', () => {
it('exports reqOffsArg', () => {
assert.strictEqual(typeof test.reqOffsArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqOffsArg(), numArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqOffsArg(undefined), numArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqOffsArg(null), numArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqOffsArg('1'), numArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqOffsArg(true), numArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqOffsArg({}), numArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqOffsArg([]), numArgMsg);
});
it('accepts a number', () => {
assert.strictEqual(test.reqOffsArg(55), 55);
});
});
describe('addon-tools.hpp: LET_OFFS_ARG', () => {
it('exports letOffsArg', () => {
assert.strictEqual(typeof test.letOffsArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letOffsArg('1'), numArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letOffsArg(true), numArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letOffsArg({}), numArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.letOffsArg([]), numArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.letOffsArg(), 0);
});
it('accepts undefined', () => {
assert.strictEqual(test.letOffsArg(undefined), 0);
});
it('accepts null', () => {
assert.strictEqual(test.letOffsArg(null), 0);
});
it('accepts a number', () => {
assert.strictEqual(test.letOffsArg(55), 55);
});
});
describe('addon-tools.hpp: USE_OFFS_ARG', () => {
it('exports useOffsArg', () => {
assert.strictEqual(typeof test.useOffsArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.useOffsArg('1'), numArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.useOffsArg(true), numArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.useOffsArg({}), numArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.useOffsArg([]), numArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.useOffsArg(), 10);
});
it('accepts undefined', () => {
assert.strictEqual(test.useOffsArg(undefined), 10);
});
it('accepts null', () => {
assert.strictEqual(test.useOffsArg(null), 10);
});
it('accepts a number', () => {
assert.strictEqual(test.useOffsArg(55), 55);
});
});

View File

@ -1,100 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const strArgMsg = { message: 'Argument 0 must be of type `String`' };
const strArgLetMsg = { message: 'Argument 0 must be of type `String` or be `null`/`undefined`' };
describe('AT / HPP / REQ_STR_ARG', () => {
it('exports reqStrArg', () => {
assert.strictEqual(typeof test.reqStrArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqStrArg(), strArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqStrArg(undefined), strArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqStrArg(null), strArgMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.reqStrArg(1), strArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqStrArg(true), strArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqStrArg({}), strArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqStrArg([]), strArgMsg);
});
it('accepts a string', () => {
assert.strictEqual(test.reqStrArg('1abc'), '1abc');
});
});
describe('addon-tools.hpp: LET_STR_ARG', () => {
it('exports letStrArg', () => {
assert.strictEqual(typeof test.letStrArg, 'function');
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.letStrArg(1), strArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letStrArg(true), strArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letStrArg({}), strArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.letStrArg([]), strArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.letStrArg(), '');
});
it('accepts undefined', () => {
assert.strictEqual(test.letStrArg(undefined), '');
});
it('accepts null', () => {
assert.strictEqual(test.letStrArg(null), '');
});
it('accepts a string', () => {
assert.strictEqual(test.letStrArg('1abc'), '1abc');
});
});
describe('addon-tools.hpp: USE_STR_ARG', () => {
it('exports useStrArg', () => {
assert.strictEqual(typeof test.useStrArg, 'function');
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.useStrArg(1), strArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.useStrArg(true), strArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.useStrArg({}), strArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.useStrArg([]), strArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.useStrArg(), 'default');
});
it('accepts undefined', () => {
assert.strictEqual(test.useStrArg(undefined), 'default');
});
it('accepts null', () => {
assert.strictEqual(test.useStrArg(null), 'default');
});
it('accepts a string', () => {
assert.strictEqual(test.useStrArg('1abc'), '1abc');
});
});

View File

@ -1,100 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
const uintArgMsg = { message: 'Argument 0 must be of type `Uint32`' };
const uintArgLetMsg = { message: 'Argument 0 must be of type `Uint32` or be `null`/`undefined`' };
describe('AT / HPP / REQ_UINT_ARG, REQ_UINT32_ARG', () => {
it('exports reqUintArg', () => {
assert.strictEqual(typeof test.reqUintArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqUintArg(), uintArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqUintArg(undefined), uintArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqUintArg(null), uintArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqUintArg('1'), uintArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqUintArg(true), uintArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqUintArg({}), uintArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqUintArg([]), uintArgMsg);
});
it('accepts a number', () => {
assert.strictEqual(test.reqUintArg(55), 55);
});
});
describe('addon-tools.hpp: LET_UINT_ARG / LET_UINT32_ARG', () => {
it('exports letUintArg', () => {
assert.strictEqual(typeof test.letUintArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.letUintArg('1'), uintArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.letUintArg(true), uintArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.letUintArg({}), uintArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.letUintArg([]), uintArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.letUintArg(), 0);
});
it('accepts undefined', () => {
assert.strictEqual(test.letUintArg(undefined), 0);
});
it('accepts null', () => {
assert.strictEqual(test.letUintArg(null), 0);
});
it('accepts a number', () => {
assert.strictEqual(test.letUintArg(55), 55);
});
});
describe('addon-tools.hpp: USE_UINT_ARG / USE_UINT32_ARG', () => {
it('exports useUintArg', () => {
assert.strictEqual(typeof test.useUintArg, 'function');
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.useUintArg('1'), uintArgLetMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.useUintArg(true), uintArgLetMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.useUintArg({}), uintArgLetMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.useUintArg([]), uintArgLetMsg);
});
it('accepts an empty arg', () => {
assert.strictEqual(test.useUintArg(), 10);
});
it('accepts undefined', () => {
assert.strictEqual(test.useUintArg(undefined), 10);
});
it('accepts null', () => {
assert.strictEqual(test.useUintArg(null), 10);
});
it('accepts a number', () => {
assert.strictEqual(test.useUintArg(55), 55);
});
});

View File

@ -1,211 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const test = require('./build/Release/test.node');
describe('AT / HPP / Function arguments', () => {
const arg3Msg = { message: 'Expected at least 3 arguments' };
describe('REQ_ARGS', () => {
it('exports reqArgs3', () => {
assert.strictEqual(typeof test.reqArgs3, 'function');
});
it('throws if no args passed', () => {
assert.throws(() => test.reqArgs3(), arg3Msg);
});
it('throws if 1 arg passed', () => {
assert.throws(() => test.reqArgs3(1), arg3Msg);
});
it('returns true if 3 args passed', () => {
assert.ok(test.reqArgs3(1, 2, 3));
});
it('returns true if 5 args passed', () => {
assert.ok(test.reqArgs3(1, 2, 3, 4, 5));
});
});
describe('IS_ARG_EMPTY', () => {
it('exports isArg0Empty', () => {
assert.strictEqual(typeof test.isArg0Empty, 'function');
});
it('returns true for absent arg', () => {
assert.ok(test.isArg0Empty());
});
it('returns true for undefined arg', () => {
assert.ok(test.isArg0Empty(undefined));
});
it('returns true for null arg', () => {
assert.ok(test.isArg0Empty(null));
});
it('returns false for non-empty value', () => {
assert.strictEqual(test.isArg0Empty(1), false);
});
});
// ------------------------------ FUN_ARG
const funArgMsg = { message: 'Argument 0 must be of type `Function`' };
describe('REQ_FUN_ARG', () => {
it('exports reqFunArg', () => {
assert.strictEqual(typeof test.reqFunArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqFunArg(), funArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqFunArg(undefined), funArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqFunArg(null), funArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqFunArg('1'), funArgMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.reqFunArg(1), funArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqFunArg(true), funArgMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.reqFunArg(test.retExt()), funArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqFunArg({}), funArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqFunArg([]), funArgMsg);
});
it('accepts a function', () => {
assert.strictEqual(typeof test.reqFunArg(() => {}), 'function');
});
});
// ------------------------------ ARRV_ARG
const arrvArgMsg = { message: 'Argument 0 must be of type `ArrayBuffer`' };
describe('REQ_ARRV_ARG', () => {
it('exports reqArrvArg', () => {
assert.strictEqual(typeof test.reqArrvArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqArrvArg(), arrvArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqArrvArg(undefined), arrvArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqArrvArg(null), arrvArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqArrvArg('1'), arrvArgMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.reqArrvArg(1), arrvArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqArrvArg(true), arrvArgMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.reqArrvArg(test.retExt()), arrvArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqArrvArg({}), arrvArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqArrvArg([]), arrvArgMsg);
});
it('accepts an array buffer', () => {
const { buffer } = new Uint8Array([1, 2, 3]);
assert.strictEqual(test.reqArrvArg(buffer), buffer);
});
});
// ------------------------------ BUF_ARG
const bufArgMsg = { message: 'Argument 0 must be of type `Buffer`' };
describe('REQ_BUF_ARG', () => {
it('exports reqBufArg', () => {
assert.strictEqual(typeof test.reqBufArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqBufArg(), bufArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqBufArg(undefined), bufArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqBufArg(null), bufArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqBufArg('1'), bufArgMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.reqBufArg(1), bufArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqBufArg(true), bufArgMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.reqBufArg(test.retExt()), bufArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqBufArg({}), bufArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqBufArg([]), bufArgMsg);
});
it('accepts a buffer', () => {
const buffer = Buffer.from([1, 2, 3]);
assert.strictEqual(test.reqBufArg(buffer), buffer);
});
});
// ------------------------------ TYPED_ARRAY_ARG
const typedArgMsg = { message: 'Argument 0 must be of type `TypedArray`' };
describe('REQ_TYPED_ARRAY_ARG', () => {
it('exports reqTypedArg', () => {
assert.strictEqual(typeof test.reqTypedArg, 'function');
});
it('throws if arg was not passed', () => {
assert.throws(() => test.reqTypedArg(), typedArgMsg);
});
it('throws if arg was passed undefined', () => {
assert.throws(() => test.reqTypedArg(undefined), typedArgMsg);
});
it('throws if arg was passed null', () => {
assert.throws(() => test.reqTypedArg(null), typedArgMsg);
});
it('throws if arg was passed a string', () => {
assert.throws(() => test.reqTypedArg('1'), typedArgMsg);
});
it('throws if arg was passed a number', () => {
assert.throws(() => test.reqTypedArg(1), typedArgMsg);
});
it('throws if arg was passed a boolean', () => {
assert.throws(() => test.reqTypedArg(true), typedArgMsg);
});
it('throws if arg was passed a pointer', () => {
assert.throws(() => test.reqTypedArg(test.retExt()), typedArgMsg);
});
it('throws if arg was passed an object', () => {
assert.throws(() => test.reqTypedArg({}), typedArgMsg);
});
it('throws if arg was passed an array', () => {
assert.throws(() => test.reqTypedArg([]), typedArgMsg);
});
it('accepts a typed array', () => {
const typed = new Uint8Array([1, 2, 3]);
assert.strictEqual(test.reqTypedArg(typed), typed);
});
});
});

View File

@ -1,302 +0,0 @@
#include <addon-tools.hpp>
JS_METHOD(empty) { NAPI_ENV;
NAPI_HS;
RET_UNDEFINED;
}
JS_METHOD(throwing) { NAPI_ENV;
JS_THROW("Some error");
RET_UNDEFINED;
}
JS_METHOD(retUndefined) { NAPI_ENV;
RET_UNDEFINED;
}
JS_METHOD(retNull) { NAPI_ENV;
RET_NULL;
}
JS_METHOD(retStr) { NAPI_ENV;
RET_STR("abcdef");
}
JS_METHOD(retNum) { NAPI_ENV;
RET_NUM(12345);
}
JS_METHOD(retExt) { NAPI_ENV;
RET_EXT(nullptr);
}
JS_METHOD(retBool) { NAPI_ENV;
RET_BOOL(true);
}
JS_METHOD(retObject) { NAPI_ENV;
RET_VALUE(JS_OBJECT);
}
JS_METHOD(retArray) { NAPI_ENV;
RET_VALUE(JS_ARRAY);
}
JS_METHOD(reqArgs3) { NAPI_ENV;
REQ_ARGS(3);
RET_BOOL(true);
}
JS_METHOD(isArg0Empty) { NAPI_ENV;
RET_BOOL(IS_ARG_EMPTY(0));
}
JS_METHOD(reqStrArg) { NAPI_ENV;
REQ_STR_ARG(0, arg);
RET_STR(arg.c_str());
}
JS_METHOD(useStrArg) { NAPI_ENV;
USE_STR_ARG(0, arg, "default");
RET_STR(arg.c_str());
}
JS_METHOD(letStrArg) { NAPI_ENV;
LET_STR_ARG(0, arg);
RET_STR(arg.c_str());
}
JS_METHOD(reqIntArg) { NAPI_ENV;
REQ_INT_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(useIntArg) { NAPI_ENV;
USE_INT_ARG(0, arg, 10);
RET_NUM(arg);
}
JS_METHOD(letIntArg) { NAPI_ENV;
LET_INT_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(reqUintArg) { NAPI_ENV;
REQ_UINT_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(useUintArg) { NAPI_ENV;
USE_UINT_ARG(0, arg, 10);
RET_NUM(arg);
}
JS_METHOD(letUintArg) { NAPI_ENV;
LET_UINT_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(reqBoolArg) { NAPI_ENV;
REQ_BOOL_ARG(0, arg);
RET_BOOL(arg);
}
JS_METHOD(useBoolArg) { NAPI_ENV;
USE_BOOL_ARG(0, arg, true);
RET_BOOL(arg);
}
JS_METHOD(letBoolArg) { NAPI_ENV;
LET_BOOL_ARG(0, arg);
RET_BOOL(arg);
}
JS_METHOD(reqOffsArg) { NAPI_ENV;
REQ_OFFS_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(useOffsArg) { NAPI_ENV;
USE_OFFS_ARG(0, arg, 10);
RET_NUM(arg);
}
JS_METHOD(letOffsArg) { NAPI_ENV;
LET_OFFS_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(reqDoubleArg) { NAPI_ENV;
REQ_DOUBLE_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(useDoubleArg) { NAPI_ENV;
USE_DOUBLE_ARG(0, arg, 10);
RET_NUM(arg);
}
JS_METHOD(letDoubleArg) { NAPI_ENV;
LET_DOUBLE_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(reqFloatArg) { NAPI_ENV;
REQ_FLOAT_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(useFloatArg) { NAPI_ENV;
USE_FLOAT_ARG(0, arg, 10);
RET_NUM(arg);
}
JS_METHOD(letFloatArg) { NAPI_ENV;
LET_FLOAT_ARG(0, arg);
RET_NUM(arg);
}
JS_METHOD(reqExtArg) { NAPI_ENV;
REQ_EXT_ARG(0, arg);
RET_EXT(arg);
}
JS_METHOD(useExtArg) { NAPI_ENV;
USE_EXT_ARG(0, arg, nullptr);
RET_EXT(arg);
}
JS_METHOD(letExtArg) { NAPI_ENV;
LET_EXT_ARG(0, arg);
RET_EXT(arg);
}
JS_METHOD(reqObjArg) { NAPI_ENV;
REQ_OBJ_ARG(0, arg);
RET_VALUE(arg);
}
JS_METHOD(useObjArg) { NAPI_ENV;
USE_OBJ_ARG(0, arg, JS_OBJECT);
RET_VALUE(arg);
}
JS_METHOD(letObjArg) { NAPI_ENV;
LET_OBJ_ARG(0, arg);
RET_VALUE(arg);
}
JS_METHOD(reqArrayArg) { NAPI_ENV;
REQ_ARRAY_ARG(0, arg);
RET_VALUE(arg);
}
JS_METHOD(useArrayArg) { NAPI_ENV;
USE_ARRAY_ARG(0, arg, JS_ARRAY);
RET_VALUE(arg);
}
JS_METHOD(letArrayArg) { NAPI_ENV;
LET_ARRAY_ARG(0, arg);
RET_VALUE(arg);
}
JS_METHOD(letArrayStrArg) { NAPI_ENV;
LET_ARRAY_STR_ARG(0, arg);
RET_ARRAY_STR(arg);
}
JS_METHOD(reqFunArg) { NAPI_ENV;
REQ_FUN_ARG(0, arg);
RET_VALUE(arg);
}
JS_METHOD(reqArrvArg) { NAPI_ENV;
REQ_ARRV_ARG(0, arg);
RET_VALUE(arg);
}
JS_METHOD(reqBufArg) { NAPI_ENV;
REQ_BUF_ARG(0, arg);
RET_VALUE(arg);
}
JS_METHOD(reqTypedArg) { NAPI_ENV;
REQ_TYPED_ARRAY_ARG(0, arg);
RET_VALUE(arg);
}
#define TEST_SET_METHOD(name) \
exports.DefineProperty( \
Napi::PropertyDescriptor::Function(env, exports, #name, name) \
);
Napi::Object init(Napi::Env env, Napi::Object exports) {
TEST_SET_METHOD(empty);
TEST_SET_METHOD(throwing);
TEST_SET_METHOD(retUndefined);
TEST_SET_METHOD(retNull);
TEST_SET_METHOD(retStr);
TEST_SET_METHOD(retNum);
TEST_SET_METHOD(retExt);
TEST_SET_METHOD(retBool);
TEST_SET_METHOD(retObject);
TEST_SET_METHOD(retArray);
TEST_SET_METHOD(reqArgs3);
TEST_SET_METHOD(isArg0Empty);
TEST_SET_METHOD(reqStrArg);
TEST_SET_METHOD(useStrArg);
TEST_SET_METHOD(letStrArg);
TEST_SET_METHOD(reqIntArg);
TEST_SET_METHOD(useIntArg);
TEST_SET_METHOD(letIntArg);
TEST_SET_METHOD(reqUintArg);
TEST_SET_METHOD(useUintArg);
TEST_SET_METHOD(letUintArg);
TEST_SET_METHOD(reqBoolArg);
TEST_SET_METHOD(useBoolArg);
TEST_SET_METHOD(letBoolArg);
TEST_SET_METHOD(reqOffsArg);
TEST_SET_METHOD(useOffsArg);
TEST_SET_METHOD(letOffsArg);
TEST_SET_METHOD(reqDoubleArg);
TEST_SET_METHOD(useDoubleArg);
TEST_SET_METHOD(letDoubleArg);
TEST_SET_METHOD(reqFloatArg);
TEST_SET_METHOD(useFloatArg);
TEST_SET_METHOD(letFloatArg);
TEST_SET_METHOD(reqExtArg);
TEST_SET_METHOD(useExtArg);
TEST_SET_METHOD(letExtArg);
TEST_SET_METHOD(reqObjArg);
TEST_SET_METHOD(useObjArg);
TEST_SET_METHOD(letObjArg);
TEST_SET_METHOD(reqArrayArg);
TEST_SET_METHOD(useArrayArg);
TEST_SET_METHOD(letArrayArg);
TEST_SET_METHOD(letArrayStrArg);
TEST_SET_METHOD(reqFunArg);
TEST_SET_METHOD(reqArrvArg);
TEST_SET_METHOD(reqBufArg);
TEST_SET_METHOD(reqTypedArg);
return exports;
}
NODE_API_MODULE(test, init)

16
test/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "test",
"version": "0.0.0",
"private": true,
"main": "mocha",
"scripts": {
"test": "mocha",
"start": "mocha"
},
"dependencies": {
"addon-tools-raub": "https://github.com/node-3d/addon-tools-raub.git",
"chai": "^4.2.0",
"mocha": "^5.2.0",
"sinon": "^7.1.1"
}
}

128
test/test.js Normal file
View File

@ -0,0 +1,128 @@
'use strict';
const path = require('path');
const fs = require('fs');
const { expect } = require('chai');
const { stub, spy } = require('sinon');
const tools = require('addon-tools-raub');
const toolsDir = path.dirname(require.resolve('addon-tools-raub')).replace(/\\/g, '/');
const allMethods = ['paths', 'bin', 'root', 'include', 'mkdir', 'rm', 'cp'];
const ownMethods = allMethods.slice(1);
const cmdMethods = allMethods.slice(3);
const pathsMethods = ['bin', 'rem', 'include'];
describe('Tools', () => {
let log;
let stubbed;
beforeEach(() => {
log = spy();
stubbed = stub(console, 'log').callsFake(log);
});
afterEach(() => stubbed.restore());
describe('Own Methods', () => {
ownMethods.forEach(
m => it(`#${m}() is available`, () => {
expect(tools).to.respondTo(m);
})
);
ownMethods.forEach(
m => it(`#${m}() writes stdout`, () => {
tools[m]();
expect(log.getCall(0), 'called').to.exist;
expect(log.getCall(0).args[0], 'has args').to.exist;
expect(log.getCall(0).args[0], 'writes string').to.be.a('string');
})
);
it('#bin() is correct', () => {
tools.bin();
expect(log.getCall(0).args[0]).to.equal(path.basename(tools.paths(__dirname).binPath));
});
it('#root() is correct', () => {
tools.root();
expect(log.getCall(0).args[0]).to.equal(toolsDir);
});
it('#include() is correct', async () => {
tools.include();
const dirs = log.getCall(0).args[0].split(' ');
const stats = await Promise.all(dirs.map(dir => new Promise(
res => fs.stat(
dir,
(err, stat) => err ? res(false) : res(stat.isDirectory())
)
)));
dirs.forEach((dir, i) => expect(stats[i], dir).to.be.true);
});
});
describe('Cmd Methods', () => {
cmdMethods.forEach(
m => it(`#${m}() is available`, () => {
expect(tools).to.respondTo(m);
})
);
cmdMethods.forEach(
m => it(`#${m}() writes stdout`, () => {
tools[m]();
expect(log.getCall(0), 'called').to.exist;
expect(log.getCall(0).args[0], 'has args').to.exist;
expect(log.getCall(0).args[0], 'writes string').to.be.a('string');
})
);
});
describe('Paths', () => {
it('#paths() returns an object', () => {
expect(tools.paths(__dirname)).to.be.an('object');
});
pathsMethods.forEach(
m => it(`#${m}() is available`, () => {
const paths = tools.paths(__dirname);
expect(paths).to.respondTo(m);
})
);
pathsMethods.forEach(
m => it(`#${m}() writes stdout`, () => {
const paths = tools.paths(__dirname);
paths[m]();
expect(log.getCall(0), 'called').to.exist;
expect(log.getCall(0).args[0], 'has args').to.exist;
expect(log.getCall(0).args[0], 'writes string').to.be.a('string');
})
);
});
});

View File

@ -1,20 +0,0 @@
'use strict';
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
const { getPlatform, getBin } = require('../include');
const actionPack = async () => {
try {
await exec(`cd ${getBin()} && tar -czf ../${getPlatform()}.gz *`);
console.log(`pack=${getPlatform()}.gz`);
} catch (error) {
console.error(error);
process.exit(-1);
}
};
module.exports = { actionPack };

View File

@ -1,31 +0,0 @@
'use strict';
const { copy, exists, mkdir, rm } = require('./files');
const { getBin } = require('../include');
const cpbin = async (name) => {
const srcDir = process.cwd().replace(/\\/g, '/');
if (!await exists(`${srcDir}/build/Release/${name}.node`) ) {
console.error(`Error. File "${srcDir}/build/Release/${name}.node" not found.`);
}
const binAbs = `${srcDir}/../${getBin()}`;
if (!await exists(binAbs)) {
await mkdir(binAbs);
}
const destAbs = `${binAbs}/${name}.node`;
if (await exists(destAbs)) {
await rm(destAbs);
}
await copy(`${srcDir}/build/Release/${name}.node`, destAbs);
console.log(`The binary "${name}.node" was copied to "${getBin()}".`);
};
module.exports = { cpbin };

View File

@ -1,24 +0,0 @@
'use strict';
const { copy, exists } = require('./files');
const cpcpplint = async () => {
const cpplintDest = `${process.cwd()}/CPPLINT.cfg`.replace(/\\/g, '/');
const cpplintSrc = `${__dirname}/CPPLINT.cfg`.replace(/\\/g, '/');
if (!await exists(cpplintSrc) ) {
console.error('Error. File "CPPLINT.cfg" not found.');
return;
}
if (await exists(cpplintDest) ) {
console.warn('Warning. Dest "CPPLINT.cfg" exists and will be overwritten.');
}
await copy(cpplintSrc, cpplintDest);
console.log(`"CPPLINT.cfg" was copied to "${cpplintDest}".`);
};
module.exports = { cpcpplint };

View File

@ -1,46 +0,0 @@
'use strict';
const https = require('node:https');
const http = require('node:http');
const { WritableBuffer } = require('./writable-buffer');
const protocols = { http, https };
const downloadRecursive = async (url, count = 1) => {
const stream = new WritableBuffer();
const proto = protocols[url.match(/^https?/i)[0].toLowerCase()];
const response = await new Promise((res, rej) => {
const request = proto.get(url, (response) => res(response));
request.on('error', (err) => rej(err));
});
// Handle redirects
if ([301, 302, 303, 307].includes(response.statusCode)) {
if (count < 5) {
return downloadRecursive(response.headers.location, count + 1);
}
console.log(url);
throw new Error('Error: Too many redirects.');
}
// Handle bad status
if (response.statusCode !== 200) {
console.log(url);
throw new Error(`Response status was ${response.statusCode}`);
}
response.pipe(stream);
return new Promise((res, rej) => {
response.on('error', (err) => rej(err));
response.on('end', () => res(stream.get()));
});
};
const download = (url) => downloadRecursive(url);
module.exports = { download };

View File

@ -1,196 +0,0 @@
'use strict';
const fs = require('node:fs');
// (async) Reads a whole file to string, NOT A Buffer
const read = (name) => new Promise(
(res, rej) => fs.readFile(
name,
(err, data) => (err ? rej(err) : res(data.toString()))
)
);
// (async) Write a file
const write = (name, text) => new Promise(
(res, rej) => fs.writeFile(name, text, (err) => (err ? rej(err) : res()))
);
// (async) Copy a file
const copy = async (src, dest) => {
try {
await new Promise(
(res, rej) => fs.copyFile(src, dest, (err) => (err ? rej(err) : res()))
);
} catch (e) {
if (e.code !== 'EBUSY') {
console.warn('WARNING\n', e);
}
}
};
// (async) Check if a file/folder exists
const exists = (name) => new Promise(
(res) => fs.access(
name,
fs.constants.F_OK,
(err) => res(err ? false : true)
)
);
// (async) Create an empty folder
const mkdir = async (name) => {
if (await exists(name)) {
return;
}
return new Promise(
(res, rej) => fs.mkdir(name, (err) => (err ? rej(err) : res()))
);
};
// (async) Get status on a file
const stat = (name) => new Promise(
(res, rej) => fs.stat(name, (err, stats) => (err ? rej(err) : res(stats)))
);
// (async) Check if the path is a folder
const isDir = async (name) => (await stat(name)).isDirectory();
// (async) Check if the path is a file
const isFile = async (name) => (await stat(name)).isFile();
// Cut the path one folder up
const dirUp = (dir) => dir.replace(/\\/g, '/').split('/').slice(0, -1).join('/');
// (async) Like `mkdir -p`, makes sure a directory exists
const ensuredir = async (dir) => {
if (await exists(dir) && await isDir(dir)) {
return;
}
await ensuredir(dirUp(dir));
await mkdir(dir);
};
// (async) Copy a file, `dest` folder is created if needed
const copysafe = async (src, dest) => {
await ensuredir(dirUp(dest));
await copy(src, dest);
};
// (async) Get file/folder names of the 1st level
const readdir = (name) => new Promise(
(res, rej) => fs.readdir(
name,
(err, dirents) => (err ? rej(err) : res(dirents))
)
);
// (async) Get folder paths (concatenated with input) of the 1st level
const subdirs = async (name) => {
const all = await readdir(name);
const mapped = await Promise.all(all.map((d) => isDir(`${name}/${d}`)));
return all.filter((_, i) => mapped[i]);
};
// (async) Get file paths (concatenated with input) of the 1st level
const subfiles = async (name) => {
const all = await readdir(name);
const mapped = await Promise.all(all.map((d) => isFile(`${name}/${d}`)));
return all.filter((_, i) => mapped[i]).map((f) => `${name}/${f}`);
};
// (async) Get all nested files recursively
// Folder paths are omitted by default
// Order is: shallow-to-deep, each subdirectory lists dirs-then-files.
const traverse = async (name, showDirs = false) => {
const dirs = [];
const stack = [name];
while (stack.length) {
const dir = stack.pop();
dirs.push(dir);
(await subdirs(dir)).forEach((d) => stack.push(`${dir}/${d}`));
}
return (showDirs ? dirs : []).concat(
...(await Promise.all(dirs.map(subfiles)))
);
};
// (async) Copy a folder with all the contained files
const copyall = async (src, dest) => {
const files = (await traverse(src, true)).reverse();
while (files.length) {
const target = files.pop();
const dir = await isDir(target);
if (dir) {
await mkdir(target.replace(src, dest));
} else {
await copy(target, target.replace(src, dest));
}
}
};
// (async) Like `rm -rf`, removes everything recursively
const rmdir = async (name) => {
if (!await exists(name)) {
return;
}
const paths = await traverse(name, true);
while (paths.length) {
const target = paths.pop();
const dir = await isDir(target);
await new Promise(
(res, rej) => fs[dir ? 'rmdir' : 'unlink'](
target,
(err) => (err ? rej(err) : res())
)
);
}
};
// (async) Remove a file. Must be a file, not a folder. Just `fs.unlink`.
const rm = async (name) => {
if (!await exists(name)) {
return;
}
await new Promise(
(res, rej) => fs.unlink(name, (err) => (err ? rej(err) : res()))
);
};
module.exports = {
read,
write,
copy,
exists,
mkdir,
stat,
isDir,
isFile,
dirUp,
ensuredir,
copysafe,
readdir,
subdirs,
subfiles,
traverse,
copyall,
rmdir,
rm,
};

View File

@ -1,13 +0,0 @@
'use strict';
module.exports = Object.assign(
{},
require('./action-pack'),
require('./cpbin'),
require('./cpcpplint'),
require('./download'),
require('./files'),
require('./install'),
require('./writable-buffer'),
);

View File

@ -1,72 +0,0 @@
'use strict';
const fs = require('node:fs');
const https = require('node:https');
const http = require('node:http');
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
const { getBin, getPlatform } = require('../include');
const { mkdir, rmdir, rm } = require('./files');
const protocols = { http, https };
const installRecursive = async (url, count = 1) => {
try {
const proto = protocols[url.match(/^https?/)[0]];
const response = await new Promise((res, rej) => {
const request = proto.get(url, (response) => res(response));
request.on('error', (err) => rej(err));
});
response.on('error', (err) => { throw err; });
// Handle redirects
if ([301, 302, 303, 307].includes(response.statusCode)) {
if (count < 5) {
return installRecursive(response.headers.location, count + 1);
}
console.warn(url);
throw new Error('Error: Too many redirects.');
}
// Handle bad status
if (response.statusCode !== 200) {
console.warn(url);
throw new Error(`Response status was ${response.statusCode}`);
}
await rmdir(getBin());
await mkdir(getBin());
const packPath = `${getBin()}/${getPlatform()}.gz`;
await new Promise((res, rej) => {
const packWriter = fs.createWriteStream(packPath);
packWriter.on('error', (err) => rej(err));
packWriter.on('finish', () => res());
response.pipe(packWriter);
});
const { stderr } = await exec(`tar -xzf ${packPath} --directory ${getBin()}`);
if (stderr) {
console.warn(stderr);
}
await rm(packPath);
return true;
} catch (error) {
console.error(error.message);
return false;
}
};
const install = async (folder) => {
const url = `${folder}/${getPlatform()}.gz`;
return installRecursive(url);
};
module.exports = { install };

View File

@ -1,22 +0,0 @@
'use strict';
const assert = require('node:assert').strict;
const { describe, it } = require('node:test');
const utils = require('.');
describe('AT / utils', () => {
const methods = [
'install', 'cpbin', 'download', 'read', 'write', 'copy', 'exists',
'mkdir', 'stat', 'isDir', 'isFile', 'dirUp', 'ensuredir', 'copysafe',
'readdir', 'subdirs', 'subfiles', 'traverse', 'copyall',
'rmdir', 'rm', 'WritableBuffer', 'actionPack',
];
methods.forEach((name) => {
it(`exports the "${name}" function`, () => {
assert.strictEqual(typeof utils[name], 'function');
});
});
});

View File

@ -1,56 +0,0 @@
'use strict';
const { Writable } = require('node:stream');
const CHUNK_SIZE = 1024;
const INITIAL_SIZE = 8 * CHUNK_SIZE;
const INCREMENT_SIZE = 8 * CHUNK_SIZE;
class WritableBuffer extends Writable {
constructor() {
super();
this._buffer = Buffer.alloc(INITIAL_SIZE);
this._size = 0;
}
get() {
if (!this._size) {
return null;
}
const data = Buffer.alloc(this._size);
this._buffer.copy(data, 0, 0, this._size);
return data;
}
_increaseAsNeeded(incomingSize) {
if ((this._buffer.length - this._size) >= incomingSize) {
return;
}
const freeSpace = this._buffer.length - this._size;
const factor = Math.ceil((incomingSize - freeSpace) / INCREMENT_SIZE);
const newBuffer = Buffer.alloc(this._buffer.length + (INCREMENT_SIZE * factor));
this._buffer.copy(newBuffer, 0, 0, this._size);
this._buffer = newBuffer;
}
_write(chunk, encoding, callback) {
this._increaseAsNeeded(chunk.length);
chunk.copy(this._buffer, this._size, 0);
this._size += chunk.length;
callback();
}
}
module.exports = { WritableBuffer };