Compare commits

..

178 Commits

Author SHA1 Message Date
Luis Blanco 1ca78906a9 Use cpcpplint util 2023-10-10 19:07:30 +04:00
Luis Blanco 9d68a62da4 Add cpplint badge 2023-10-09 22:46:58 +04:00
Luis Blanco 6c19ce79c3 Fix python version 2 2023-10-09 22:36:38 +04:00
Luis Blanco fc8b31fe36 Fix python version 2023-10-09 22:27:23 +04:00
Luis Blanco c82e029e90 Fix cpplint cmd 2023-10-09 22:24:11 +04:00
Luis Blanco b501c07de1 Fix GH action 2023-10-09 22:21:43 +04:00
Luis Blanco aeb2eae343 Adjust TS comments 2023-10-09 22:15:37 +04:00
Luis Blanco 747e1f6205 Fix linguist attributes 2023-10-07 19:08:39 +04:00
Luis Blanco c9c391ffd0 Use 18.16 and new badges 2023-10-07 19:06:03 +04:00
Luis Blanco d5ffbd6ba8 Fix action name 2023-10-07 18:34:43 +04:00
Luis Blanco 1c0b3afac5 Update deps and remove jest 2023-10-07 18:11:00 +04:00
Luis Blanco afd180d7cf Fix destructive lowercase 2023-09-26 21:32:57 +04:00
Luis Blanco aa063f9eae Update deps 2023-05-08 19:20:24 +04:00
Luis Blanco 105964614f Make install less strict 2023-01-06 20:00:38 +04:00
Luis Blanco 4b31874a56 Use gz 2023-01-04 19:23:43 +04:00
Luis Blanco 3f306a4938 Fix actionPack description 2023-01-04 17:52:04 +04:00
Luis Blanco f9f3ac2a23 Use GZIP 2023-01-04 17:10:00 +04:00
Luis Blanco 9852e1e789 Fix readme 2 2023-01-04 15:31:42 +04:00
Luis Blanco a56e079de0 Fix readme 2023-01-04 15:19:22 +04:00
Luis Blanco abc49171eb Fix publish workflow 2023-01-04 15:08:14 +04:00
Luis Blanco 6bdcf9abe2 Use object assign 2023-01-04 15:07:41 +04:00
Luis Blanco 0564af1e96 Fix version string 2023-01-04 14:50:40 +04:00
Luis Blanco 104fad35ae Remove actionVersion 2023-01-04 14:43:26 +04:00
Luis Blanco 9c76216823 Fix action script 2023-01-04 14:00:32 +04:00
Luis Blanco 955e8b61a8 Adjust actions 2023-01-04 13:36:32 +04:00
Luis Blanco 05e53641c1 Fix requires 2023-01-04 13:22:46 +04:00
Luis Blanco ac0c472be9 Adjust tests 2023-01-04 12:54:39 +04:00
Luis Blanco b05666869a Fix build 2023-01-03 23:45:14 +04:00
Luis Blanco 40e4baeb56 Fix exports 2023-01-03 23:14:04 +04:00
Luis Blanco 3d02cfb49c Add actionVersion test 2023-01-03 22:55:49 +04:00
Luis Blanco fd165d935d Update module structure 2023-01-03 22:20:39 +04:00
Luis Blanco b532ca47bf Update dependencies 2023-01-02 17:09:33 +04:00
Luis Blanco da96b71507 Fix windows compile flags 2022-12-24 21:59:11 +04:00
Luis Blanco 30f5fce2ec Fix platform names 2022-12-24 21:51:35 +04:00
Luis Blanco 6e66f46a4a Allow unknown platforms 2022-12-24 17:20:48 +04:00
Luis Blanco a274cd227e Add aarch64 2022-12-24 17:10:35 +04:00
Luis Blanco d4ed519e71 Remove eslint action matrix 2022-12-10 12:08:42 +04:00
Luis Blanco 3f52e63257 Adjust eslint action 2022-12-10 12:03:00 +04:00
Luis Blanco cd8527cdb1 Fix inline modifier 2022-12-10 11:57:17 +04:00
Luis Blanco 12fe6b4569 Split github action jobs 2022-12-05 23:02:43 +04:00
Luis Blanco 341b5e67a2 Update workflows 2022-12-05 22:54:03 +04:00
Luis Blanco 5d4708dfdf Update info 2022-12-05 22:44:35 +04:00
Luis Blanco eafb17d791 Fix test 2022-12-05 20:03:13 +04:00
Luis Blanco edc530bb5e Export missing test method 2022-12-05 19:55:29 +04:00
Luis Blanco 018b5aca47 Update package lock 2022-12-05 19:52:23 +04:00
Luis Blanco 68731102df Update header 2022-12-05 19:47:08 +04:00
Luis Blanco b77a3cac96 Fix napi error message 2022-11-28 20:24:17 +04:00
Luis Blanco a0a7f6153c Improve download script 2022-09-28 22:37:31 +04:00
Luis Blanco fc183b9793 Merge branch 'master' of https://github.com/node-3d/addon-tools-raub 2022-09-25 12:44:51 +04:00
Luis Blanco 9857670794 Add peer meta 2022-09-25 12:44:26 +04:00
Luis Blanco a7028c0062
remove codeql 2022-09-24 19:44:32 +04:00
Luis Blanco 4ed13bb471
Adjust codeql 2022-09-24 19:38:39 +04:00
Luis Blanco f2f3c440f0
Try codeql 2022-09-24 19:30:21 +04:00
Luis Blanco f9fe9f0124 Adjust readme 2022-09-24 19:11:40 +04:00
Luis Blanco 847a68a2d2 Update deps 2022-09-24 19:02:48 +04:00
Luis Blanco c4d42eea40 Update CI 2021-04-25 11:20:46 +03:00
Luis Blanco 3224521e0c Change CI versions 2021-04-24 09:26:01 +03:00
Luis Blanco cd504c13a3 Remove old actions 2021-04-19 22:52:12 +03:00
Luis Blanco 29681a0b46 Try old actions 2021-04-19 20:38:39 +03:00
Luis Blanco 33b4892dfb Merge CI actions 2021-04-19 20:21:54 +03:00
Luis Blanco 410b704d3d Fix readme 2021-04-19 00:40:17 +03:00
Luis Blanco cca802d932 Fix TS 2021-04-19 00:31:04 +03:00
Luis Blanco 4b98f5f435 Fix lints 2021-04-03 16:07:45 +03:00
Luis Blanco 498de135cb Remove test package 2021-04-03 15:49:45 +03:00
Luis Blanco 3fea905be4 Fix linebreak lint 2021-04-03 15:27:28 +03:00
Luis Blanco 5dd1b40e10 Fix lint ci 2021-04-03 14:13:42 +03:00
Luis Blanco 5f52a14c85 Update workflows 2021-04-03 14:05:05 +03:00
Luis Blanco ae9fd3e8ba Automate NPM publish 2021-03-21 18:23:26 +03:00
Luis Blanco cb8e4841ba Try action Publish 2021-03-21 18:19:00 +03:00
Luis Blanco 0a1b289ce4 Fix scripts 2021-03-21 17:32:38 +03:00
Luis Blanco 773a356788 Fix node version 2021-03-21 17:20:09 +03:00
Luis Blanco 8a2c0187b2 Update deps and tests 2021-03-21 17:11:24 +03:00
Luis Blanco 5af58c65d3 Update deps 2020-08-09 11:32:58 +03:00
Luis Blanco acddbf669d Bump version 2020-04-23 09:35:05 +03:00
Luis Blanco ebdf6d780c
Merge pull request #2 from AlexVestin/patch-1
Use size of the typed array instead of underlying bufffer
2020-04-23 10:31:36 +04:00
AlexVestin d1ee793b75
Use size of the typed array instead of underlying bufffer
If an array is subarrayed from another array they will still have the same underlying buffer giving the wrong size here
2020-04-22 17:25:25 +02:00
Luis Blanco 0057f6744a Update readme 2020-03-29 23:04:00 +03:00
Luis Blanco 6aa6dfe35f Use static cast 2020-03-29 22:49:25 +03:00
Luis Blanco 76c70481e4 Remove redundand tests 2020-03-29 22:41:09 +03:00
Luis Blanco 0ca0012ab9 Move CPPLINT config 2020-03-29 22:38:23 +03:00
Luis Blanco 2402ee198a Split tests 2020-03-29 22:31:04 +03:00
Luis Blanco d68ec8a60f Fix CI 2020-03-29 20:56:26 +03:00
Luis Blanco f48b78cf20 Add hpp tests 2020-03-29 20:33:41 +03:00
Luis Blanco 3a7ed91a68 Fix files 2020-03-23 23:50:36 +03:00
Luis Blanco dcccb8d4c0 Rewrite install 2020-03-23 23:42:13 +03:00
Luis Blanco 9bcce8ede3 Merge branch 'master' of https://github.com/node-3d/addon-tools-raub 2020-03-23 19:46:28 +03:00
Luis Blanco 2c6f74a253 Fix warnings 2020-03-23 19:46:13 +03:00
Luis Blanco 8b07e4aab3 Fix getData 2020-03-23 15:17:24 +03:00
Luis Blanco 1ca8dab472 Reorder getData 2020-03-23 15:09:14 +03:00
Luis Blanco 360867eb0d Update test deps 2020-03-23 15:01:28 +03:00
Luis Blanco 8e64285e8d Use adm-zip 2020-03-23 14:48:26 +03:00
Luis Blanco 8f642c6007 Fix ci 2020-03-21 12:43:12 +03:00
Luis Blanco 0ff55d5010 Fix ci 2020-03-21 12:37:16 +03:00
Luis Blanco 10dd5627d4 Undup macro 2020-03-21 12:31:43 +03:00
Luis Blanco c399f2dfac Use preinstall 2020-03-21 12:14:14 +03:00
Luis Blanco 753e39c6b1 Fix unwrap 2020-03-21 12:09:19 +03:00
Luis Blanco c6435911f6 Add cpp test framework 2020-03-17 10:50:42 +03:00
Luis Blanco 5434ac9ee8 Fix table 2019-11-25 10:28:50 +03:00
Luis Blanco 09c43c8801 Fix file list 2019-11-13 16:02:48 +03:00
Luis Blanco 3870468ac1 Update CI 2019-11-12 22:23:46 +03:00
Luis Blanco 03077d415e Fix readme 2019-11-12 19:15:22 +03:00
Luis Blanco 920be8cbe8 Fix bold 2019-11-12 17:09:57 +03:00
Luis Blanco 63cfadfc0d Try bold 2019-11-12 17:09:14 +03:00
Luis Blanco 7f4dd4f37d Review docs 2019-11-12 17:06:34 +03:00
Luis Blanco 31ede2853f Fix CI 2019-11-11 21:56:52 +03:00
Luis Blanco 90a417bedf wip doc 2019-11-11 17:07:04 +03:00
Luis Blanco 77995f76f6 wip doc 2019-11-10 22:50:09 +03:00
Luis Blanco 46e82f8ea8 wip doc 2019-11-08 23:36:29 +03:00
Luis Blanco 99f06682bf wip docs 2019-11-08 17:06:51 +03:00
Luis Blanco f351c448cb wip doc 2019-11-07 22:14:00 +03:00
Luis Blanco cbae1f5fa7 Merge branch 'master' of https://github.com/node-3d/addon-tools-raub 2019-09-25 14:43:40 +03:00
Luis Blanco 791c1a34d3 Add noexceptions 2019-09-25 14:43:23 +03:00
Luis Blanco a570e12166 Remove extra qualifiers 2019-09-20 19:51:12 +03:00
Luis Blanco efb180be23 Remove extra qualificator 2019-09-20 17:08:27 +03:00
Luis Blanco 7fd868cfde Fix codefactor 2019-09-20 16:10:39 +03:00
Luis Blanco e99a1a1033 Fix codefactor 2019-09-20 16:07:48 +03:00
Luis Blanco aa800818ed wip readme 2019-09-17 16:44:35 +03:00
Luis Blanco b985816b26 Add to files 2019-09-17 15:38:44 +03:00
Luis Blanco 165b3cfbd3 Add cpbin script 2019-09-17 11:17:57 +03:00
Luis Blanco ff5baa1e0a Fix string check 2019-09-08 19:10:28 +03:00
Luis Blanco a05692ebb2 Fix class name string 2019-09-08 16:43:59 +03:00
Luis Blanco 572d89b067 Add unwrap 2019-09-08 00:05:19 +03:00
Luis Blanco fcabcff8cc Fix super collision 2019-09-06 16:28:31 +03:00
Luis Blanco 811bcea608 Add another super signature 2019-09-06 15:37:19 +03:00
Luis Blanco 1e1edf7611 Fix type conflict 2019-09-05 15:43:52 +03:00
Luis Blanco 99322630f5 Fix setter 2019-09-05 12:48:36 +03:00
Luis Blanco b5aee1307f Fix spaces 2019-09-03 22:20:03 +03:00
Luis Blanco 98aaf2d4ba Add ES5 classes 2019-09-03 22:06:57 +03:00
Luis Blanco bb46894d97 Fix emitAsync signature 2019-08-22 23:19:21 +03:00
Luis Blanco 7e7b4dc3e9 Optimize emit signature 2019-08-22 23:14:42 +03:00
Luis Blanco 96b753542f Fix ext 2019-08-19 19:17:16 +03:00
Luis Blanco f0680a40de Fix ext 2019-08-19 18:55:25 +03:00
Luis Blanco 449dc12afd Fix ext 2019-08-18 15:34:07 +03:00
Luis Blanco 2600de3f8f Fix this check 2019-08-18 14:52:39 +03:00
Luis Blanco 5654bbc5d5 Fix args 2019-08-18 14:50:18 +03:00
Luis Blanco 67310fffa0 Add some macros 2019-08-18 11:46:28 +03:00
Luis Blanco 912b12b215 Fix macros 2019-08-11 13:02:11 +03:00
Luis Blanco ddb2217277 Merge branch 'master' of https://github.com/node-3d/addon-tools-raub 2019-08-10 15:48:45 +03:00
Luis Blanco d54af88dd2 Use package files instead of npmignore 2019-08-10 15:48:27 +03:00
Luis Blanco 2ae137d034 Fix getArrayData 2019-08-09 14:37:10 +03:00
Luis Blanco f72cc86fc5 Fix getArrayData 2019-08-09 14:12:07 +03:00
Luis Blanco 670cef5102 Merge branch 'master' of https://github.com/node-3d/addon-tools-raub 2019-08-08 16:32:32 +03:00
Luis Blanco 2fa32d8e5d Fix macros 2019-08-08 16:32:06 +03:00
Luis Blanco b6f7724558 Merge branch 'master' of https://github.com/node-3d/addon-tools-raub 2019-08-07 19:29:14 +03:00
Luis Blanco 3150f55c86 Fix codefactor 2019-08-07 18:15:44 +03:00
Luis Blanco 18d727a499 Remove stub 2019-08-07 11:42:30 +03:00
Luis Blanco 301a280342 Remove stub 2019-08-07 11:42:23 +03:00
Luis Blanco af315ee1fd Update engines 2019-08-07 11:13:50 +03:00
Luis Blanco 43315b4cf0 Relink test 2019-08-07 11:07:42 +03:00
Luis Blanco c7aba01a06 Update ignore 2019-08-07 10:08:36 +03:00
Luis Blanco d75fb96f6b Fix install 2019-08-06 16:51:55 +03:00
Luis Blanco b71173820d Fix install 2019-08-06 16:16:50 +03:00
Luis Blanco 72073bfe79 Fix readme 2019-08-06 15:30:59 +03:00
Luis Blanco 93121bc92a wip readme 2019-08-06 03:37:17 +03:00
Luis Blanco 688b7b08a9 wip readme 2019-08-05 17:14:11 +03:00
Luis Blanco c548b29ecd Fix test 2019-08-05 17:00:41 +03:00
Luis Blanco 95173cd89c wip new arch 2019-08-05 00:47:25 +03:00
Luis Blanco 1fb87351a5 Fix codefactor 2019-03-02 15:16:36 +03:00
Luis Blanco be5e80f2ec Add download 2019-02-20 09:25:13 +03:00
Luis Blanco 9c956b0081 Use NAN 2.12.1 2019-01-11 17:20:21 +03:00
Luis Blanco 04005e1fae Add codefactor 2019-01-10 16:46:32 +03:00
Luis Blanco be27a4226c Set version 4.1.0 2018-12-04 14:20:40 +03:00
Luis Blanco 96d1f1c052 📝 Update readme 2018-12-04 13:49:42 +03:00
Luis Blanco f3496156fa Merge branch 'master' of https://github.com/node-3d/addon-tools-raub 2018-12-03 16:53:16 +03:00
Luis Blanco 0205554b3b 💚 Use default script 2018-12-03 16:51:36 +03:00
Luis Blanco 6ece43b5fd Fix double arg 2018-12-01 12:10:08 +03:00
Luis Blanco 8136b3af00 💚 Add mac 2018-11-30 12:40:55 +03:00
Luis Blanco e341dd8d37 Remove tests preinstall 2018-11-30 11:37:47 +03:00
Luis Blanco b33c19e45a Update tests 2018-11-30 11:26:27 +03:00
Luis Blanco cca0795e2d Fix deprecation warnings 2018-11-29 11:33:36 +03:00
Luis Blanco c4ab0d197c Try new Run #4 2018-11-29 11:03:55 +03:00
Luis Blanco 47209cb113 Try new Run #3 2018-11-29 10:58:07 +03:00
Luis Blanco f09dddceb0 Try new Run #2 2018-11-29 10:52:18 +03:00
Luis Blanco 88ee8720d5 Try new Run 2018-11-29 10:43:00 +03:00
Luis Blanco 8e7a589bd8 Try new syntax 2018-11-29 09:05:14 +03:00
Luis Blanco 003d31356a ⬆️ Upgrade to Nodejs 10 LTS 2018-11-29 08:16:00 +03:00
raub 347d2f5311 💚 Simplify CI 2018-09-11 14:46:01 +03:00
raub 193931995c Going 4.0.0 2018-08-31 16:26:52 +03:00
75 changed files with 5203 additions and 2662 deletions

116
.eslintrc
View File

@ -1,116 +0,0 @@
{
"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]
}
}

75
.eslintrc.json Normal file
View File

@ -0,0 +1,75 @@
{
"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 Normal file
View File

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

47
.github/workflows/cpplint.yml vendored Normal file
View File

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

36
.github/workflows/eslint.yml vendored Normal file
View File

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

46
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,46 @@
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 }}

43
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,43 @@
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,11 +1,9 @@
.idea
.cproject .cproject
.project .idea
.lock-wscript .lock-wscript
build*/
.DS_Store .DS_Store
Debug/ .project
.DS_Store
node_modules/ node_modules/
package-lock.json test-addon/build/
binary/
*.log *.log

View File

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

View File

@ -1,39 +0,0 @@
sudo: false
language: node_js
node_js:
- "8.11.1"
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.5
packages:
- clang-3.5
before_install:
- if [[ $(uname -s) == 'Linux' ]]; then
export CXX="clang++-3.5";
export CC="clang-3.5";
fi;
- npm install -g npm@5.6.0
install:
- cd examples/addon && npm i --clang=1
- cd ../deps && npm i --clang=1
- cd ../.. && npm i --clang=1
- cd test && npm i --clang=1
- cd ..
script:
- cd examples/addon && node .
- cd ../deps && node .
- cd ../.. && node .
- cd test && npm test
- cd ..

17
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
"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,15 +0,0 @@
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

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2018 Luis Blanco Copyright (c) 2023 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

799
README.md
View File

@ -2,799 +2,74 @@
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://nodei.co/npm/addon-tools-raub.png?compact=true) [![NPM](https://badge.fury.io/js/addon-tools-raub.svg)](https://badge.fury.io/js/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)
![Build Status](https://api.travis-ci.org/node-3d/addon-tools-raub.svg?branch=master) [![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)
[![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)
## 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 x32/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-linux32
* 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 an Addon Tools compliant binary dependency module.
* Assume `MY_ADDON` is the name of this addon.
* 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(\'node-deps-EXT_LIB-raub\').include()")',
'EXT_LIB_bin' : '<!(node -e "require(\'node-deps-EXT_LIB-raub\').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
There is a C++ header file, `addon-tools.hpp`, shipped with this package. It Macro shortcuts for C++ addons using **NAPI**.
introduces several useful macros and utilities. Also it includes Nan automatically, See [docs inside the folder](/include).
so that you can replace:
Example of an addon method definition:
``` ```
// #include <v8.h> // already in node.h // hpp:
// #include <node.h> // already in nan.h #include <addon-tools.hpp>
#include <nan.h> DBG_EXPORT JS_METHOD(doSomething);
``` // cpp:
DBG_EXPORT JS_METHOD(doSomething) { NAPI_ENV;
with LET_INT32_ARG(0, param0);
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
Exports: JavaScript helpers for Node.js addon development. The short list of helpers:
* `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.
``` ```
'variables': { 'getBin', 'getPlatform', 'getInclude', 'getPaths',
'mkdir' : '<!(node -e "require(\'addon-tools-raub\').mkdir()")', 'install', 'cpbin', 'download', 'read', 'write', 'copy', 'exists',
}, 'mkdir', 'stat', 'isDir', 'isFile', 'dirUp', 'ensuredir', 'copysafe',
... 'readdir', 'subdirs', 'subfiles', 'traverse', 'copyall',
'action' : ['<(mkdir)', '-p', 'binary'], 'rmdir', 'rm', 'WritableBuffer', 'actionPack',
``` ```
### rm See the [TypeScript definitions](/index.d.ts) with comments.
Disregard `del` and `rd` on Windows command line. Now the same command can
be used on all platforms to remove single and multiple files and directories. ### Example for an ADDON's **index.js**:
``` ```
'variables': { const { getBin } = require('addon-tools-raub');
'rm' : '<!(node -e "require(\'addon-tools-raub\').rm()")', const core = require(`./${getBin()}/ADDON`); // uses the platform-specific ADDON.node
'rem' : '<!(node -e "require(\'.\').rem()")',
},
...
'action' : ['<(rm)', '-rf', '<@(rem)'],
``` ```
### cp
For Windows the `/y` flag was embedded. ### Example for **binding.gyp**:
```
'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 -e "require(\'addon-tools-raub\').include()")', '<!@(node -p "require(\'addon-tools-raub\').getInclude()")',
], ],
``` ```
Then include the **event-emitter.hpp**, it also includes **addon-tools.hpp**. > NOTE: the optional `node-addon-api` dependency is used by the `getInclude()` helper. If not found,
Inherit from `EventEmitter`, it already inherits from `Nan::ObjectWrap`: the **napi.h** include path won't be a part of the returned string.
### Example of `cpbin` in **package.json :: scripts**:
``` ```
#include <event-emitter.hpp> "build": "cd src && node-gyp rebuild -j max --silent && node -e \"require('addon-tools-raub').cpbin('segfault')\" && cd ..",
"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.

View File

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

View File

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

View File

@ -1,6 +0,0 @@
for %%x in (%*) do (
if not %%x=="-rf" if not %%x=="-r" if not %%x=="-f" if exist %%~x\ rd /s /q %%x
if not %%x=="-rf" if not %%x=="-r" if not %%x=="-f" if not exist %%~x\ if exist %%x del /f /q %%x
)

View File

@ -1,116 +0,0 @@
{
"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,12 +0,0 @@
.idea
.cproject
.project
.lock-wscript
build*/
bin-*/
.DS_Store
Debug/
node_modules/
package-lock.json
binary/
*.log

View File

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

View File

@ -1,91 +0,0 @@
{
'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/*.*'
] } ],
],
}],
},
]
}

View File

@ -1,17 +0,0 @@
'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

@ -1,23 +0,0 @@
#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

@ -1,105 +0,0 @@
#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

@ -1,39 +0,0 @@
#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_

View File

@ -1,65 +0,0 @@
'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

@ -1,9 +0,0 @@
{
"name": "example",
"version": "0.0.0",
"private": true,
"main": "index.js",
"dependencies": {
"addon-tools-raub": "file:../../"
}
}

View File

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

View File

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

View File

@ -1,21 +0,0 @@
{
'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

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

View File

@ -1,9 +0,0 @@
{
"name": "example",
"version": "0.0.0",
"private": true,
"main": "index.js",
"dependencies": {
"addon-tools-raub": "file:../../"
}
}

240
include/README.md Normal file
View File

@ -0,0 +1,240 @@
# 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,308 +1,669 @@
#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>
#include <nan.h> #ifdef _WIN32
#define strcasestr(s, t) strstr(strupr(s), strupr(t))
#endif
#define NAN_HS Nan::HandleScope scope; #ifdef _WIN32
#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 RET_VALUE(VAL) info.GetReturnValue().Set(VAL); #define JS_UNDEFINED env.Undefined()
#define RET_UNDEFINED RET_VALUE(Nan::Undefined()); #define JS_NULL env.Null()
#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))
typedef v8::Local<v8::Value> V8_VAR_VAL; #define JS_THROW(VAL) \
typedef v8::Local<v8::Object> V8_VAR_OBJ; Napi::Error::New(env, VAL).ThrowAsJavaScriptException();
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)) { \
return Nan::ThrowTypeError("Expected at least " #N " arguments"); JS_THROW("Expected at least " #N " arguments"); \
RET_UNDEFINED; \
}
#define IS_ARG_EMPTY(I) (info[I]->IsNull() || info[I]->IsUndefined()) #define IS_EMPTY(VAL) (VAL.IsNull() || VAL.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) { \
return Nan::ThrowTypeError("Argument " #I " must be " T); JS_THROW("Argument " #I " must be of type `" 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)) { \
return Nan::ThrowTypeError("Argument " #I " must be " T " or null"); JS_THROW( \
"Argument " #I \
" must be of type `" T \
"` or be `null`/`undefined`" \
); \
RET_UNDEFINED; \
}
#define REQ_UTF8_ARG(I, VAR) \ #define REQ_STR_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsString(), "string"); \ CHECK_REQ_ARG(I, IsString(), "String"); \
Nan::Utf8String VAR(info[I]); std::string VAR = info[I].ToString().Utf8Value();
#define LET_UTF8_ARG(I, VAR) \ #define USE_STR_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsString(), "string"); \ CHECK_LET_ARG(I, IsString(), "String"); \
Nan::Utf8String VAR(JS_STR("")); std::string VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToString().Utf8Value();
#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, IsInt32(), "int32"); \ CHECK_REQ_ARG(I, IsNumber(), "Int32"); \
int VAR = info[I]->Int32Value(); int VAR = info[I].ToNumber().Int32Value();
#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]->Int32Value();
#define REQ_UINT32_ARG(I, VAR) \ #define REQ_UINT32_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsUint32(), "uint32"); \ CHECK_REQ_ARG(I, IsNumber(), "Uint32"); \
unsigned int VAR = info[I]->Uint32Value(); unsigned int VAR = info[I].ToNumber().Uint32Value();
#define LET_UINT32_ARG(I, VAR) \ #define USE_UINT32_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsUint32(), "uint32"); \ CHECK_LET_ARG(I, IsNumber(), "Uint32"); \
unsigned int VAR = IS_ARG_EMPTY(I) ? 0 : info[I]->Uint32Value(); unsigned int VAR = IS_ARG_EMPTY(I) \
? (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]->BooleanValue(); bool VAR = info[I].ToBoolean().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]->BooleanValue();
#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]->IntegerValue()); size_t VAR = static_cast<size_t>(info[I].ToNumber().DoubleValue());
#define LET_OFFS_ARG(I, VAR) \ #define USE_OFFS_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "number"); \ CHECK_LET_ARG(I, IsNumber(), "Number"); \
size_t VAR = IS_ARG_EMPTY(I) ? 0 : static_cast<size_t>(info[I]->IntegerValue()); size_t VAR = IS_ARG_EMPTY(I) \
? (DEF) \
: 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]->NumberValue(); double VAR = info[I].ToNumber().DoubleValue();
#define LET_DOUBLE_ARG(I, VAR) \ #define USE_DOUBLE_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "number"); \ CHECK_LET_ARG(I, IsNumber(), "Number"); \
double VAR = IS_ARG_EMPTY(I) ? 0.0 : info[I]->NumberValue(); double VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().DoubleValue();
#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 = static_cast<float>(info[I]->NumberValue()); float VAR = info[I].ToNumber().FloatValue();
#define LET_FLOAT_ARG(I, VAR) \ #define USE_FLOAT_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "number"); \ CHECK_LET_ARG(I, IsNumber(), "Number"); \
float VAR = IS_ARG_EMPTY(I) ? 0.f : static_cast<float>(info[I]->NumberValue()); float VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().FloatValue();
#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(), "void*"); \ CHECK_REQ_ARG(I, IsExternal(), "Pointer"); \
V8_VAR_EXT VAR = V8_VAR_EXT::Cast(info[I]); void *VAR = info[I].As< Napi::External<void> >().Data();
#define LET_EXT_ARG(I, VAR) \ #define USE_EXT_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsExternal(), "number"); \ CHECK_LET_ARG(I, IsExternal(), "Pointer"); \
V8_VAR_EXT VAR = IS_ARG_EMPTY(I) ? JS_EXT(nullptr) : V8_VAR_EXT::Cast(info[I]); void *VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As< Napi::External<void> >().Data();
#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"); \
V8_VAR_FUNC VAR = V8_VAR_FUNC::Cast(info[I]); Napi::Function VAR = info[I].As<Napi::Function>();
#define REQ_OBJ_ARG(I, VAR) \ #define REQ_OBJ_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsObject(), "object"); \ CHECK_REQ_ARG(I, IsObject(), "Object"); \
V8_VAR_OBJ VAR = V8_VAR_OBJ::Cast(info[I]); Napi::Object VAR = info[I].As<Napi::Object>();
#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) \
REQ_OBJ_ARG(I, _obj_##VAR); \ CHECK_REQ_ARG(I, IsArrayBuffer(), "ArrayBuffer"); \
if( ! _obj_##VAR->IsArrayBufferView() ) \ Napi::ArrayBuffer VAR = info[I].As<Napi::ArrayBuffer>();
return Nan::ThrowTypeError("Argument " #I " must be an array buffer");\
V8_VAR_ABV VAR = V8_VAR_ABV::Cast(_obj_##VAR);
#define SET_PROP(OBJ, KEY, VAL) OBJ->Set(JS_STR(KEY), VAL); #define REQ_BUF_ARG(I, VAR) \
#define SET_I(ARR, I, VAL) ARR->Set(I, VAL); CHECK_REQ_ARG(I, IsBuffer(), "Buffer"); \
Napi::Buffer<uint8_t> VAR = info[I].As< Napi::Buffer<uint8_t> >();
#define CTOR_CHECK(T) \ #define REQ_ARRAY_ARG(I, VAR) \
if ( ! info.IsConstructCall() ) \ CHECK_REQ_ARG(I, IsArray(), "Array"); \
return Nan::ThrowTypeError(T " must be called with the 'new' keyword."); Napi::Array VAR = info[I].As<Napi::Array>();
#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) { \
return Nan::ThrowTypeError("Value must be " T); JS_THROW("Value must be " T); \
RET_UNDEFINED; \
}
#define ACCESSOR_RW(OBJ, NAME) \ #define JS_METHOD(NAME) Napi::Value NAME(const Napi::CallbackInfo &info)
Nan::SetAccessor(OBJ, JS_STR(#NAME), NAME ## Getter, NAME ## Setter);
#define ACCESSOR_R(OBJ, NAME) \ #define ACCESSOR_RW(CLASS, NAME) \
Nan::SetAccessor(OBJ, JS_STR(#NAME), NAME ## Getter); InstanceAccessor(#NAME, &CLASS::NAME ## Getter, &CLASS::NAME ## Setter)
#define ACCESSOR_R(CLASS, NAME) \
InstanceAccessor(#NAME, &CLASS::NAME ## Getter, nullptr)
#define SETTER_UTF8_ARG \ #define ACCESSOR_M(CLASS, NAME) \
SETTER_CHECK(IsString(), "string"); \ InstanceMethod(#NAME, &CLASS::NAME)
Nan::Utf8String v(value);
#define SETTER_STR_ARG SETTER_UTF8_ARG #define THIS_OBJ(VAR) \
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(IsInt32(), "int32"); \ SETTER_CHECK(IsNumber(), "Int32"); \
int v = value->Int32Value(); int v = value.ToNumber().Int32Value();
#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->BooleanValue(); bool v = value.ToBoolean().Value();
#define SETTER_UINT32_ARG \ #define SETTER_UINT32_ARG \
SETTER_CHECK(IsUint32(), "uint32"); \ SETTER_CHECK(IsNumber(), "Uint32"); \
unsigned int v = value->Uint32Value(); unsigned int v = value.ToNumber().Uint32Value();
#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->IntegerValue()); size_t v = static_cast<size_t>(value.ToNumber().DoubleValue());
#define SETTER_DOUBLE_ARG \ #define SETTER_DOUBLE_ARG \
SETTER_CHECK(IsNumber(), "number"); \ SETTER_CHECK(IsNumber(), "Number"); \
double v = value->NumberValue(); double v = value.ToNumber().DoubleValue();
#define SETTER_FLOAT_ARG \ #define SETTER_FLOAT_ARG \
SETTER_CHECK(IsNumber(), "number"); \ SETTER_CHECK(IsNumber(), "Number"); \
float v = static_cast<float>(value->NumberValue()); float v = value.ToNumber().FloatValue();
#define SETTER_EXT_ARG \ #define SETTER_EXT_ARG \
SETTER_CHECK(IsExternal(), "void*"); \ SETTER_CHECK(IsExternal(), "Pointer"); \
V8_VAR_EXT v = V8_VAR_EXT::Cast(value); Napi::External v = value.As<Napi::External>();
#define SETTER_FUN_ARG \ #define SETTER_FUN_ARG \
SETTER_CHECK(IsFunction(), "function"); \ SETTER_CHECK(IsFunction(), "Function"); \
V8_VAR_FUNC v = V8_VAR_FUNC::Cast(value); Napi::Function v = value.As<Napi::Function>()
#define SETTER_OBJ_ARG \ #define SETTER_OBJ_ARG \
SETTER_CHECK(IsObject(), "object"); \ SETTER_CHECK(IsObject(), "Object"); \
V8_VAR_OBJ v = V8_VAR_OBJ::Cast(value); Napi::Object v = value.As<Napi::Object>()
#define SETTER_ARRV_ARG \ #define SETTER_ARRV_ARG \
SETTER_CHECK(IsObject(), "object"); \ SETTER_CHECK(IsArrayBuffer(), "TypedArray"); \
V8_VAR_OBJ _obj_v = V8_VAR_OBJ::Cast(value); \ Napi::ArrayBuffer v = value.As<Napi::ArrayBuffer>();
if( ! _obj_v->IsArrayBufferView() ) \
return Nan::ThrowTypeError("The value must be an array buffer"); \
V8_VAR_ABV v = V8_VAR_ABV::Cast(_obj_v);
#define GET_AND_THROW_LAST_ERROR() \
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)
template<typename Type> #define NAPI_CALL(the_call) \
inline Type* getArrayData(V8_VAR_OBJ obj, int *num = nullptr) { do { \
if ((the_call) != napi_ok) { \
GET_AND_THROW_LAST_ERROR(); \
RET_UNDEFINED; \
} \
} while (0)
Type *data = nullptr; #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();
Napi::ArrayBuffer arr = ta.ArrayBuffer();
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;
}
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->IsArrayBufferView() ) { if (!obj.IsBuffer()) {
Nan::ThrowError("Argument must be a TypedArray."); JS_THROW("Argument must be of type `Buffer`.");
return data; return out;
} }
V8_VAR_ABV arr = V8_VAR_ABV::Cast(obj); Napi::Buffer<uint8_t> arr = obj.As< Napi::Buffer<uint8_t> >();
if (num) { if (num) {
*num = arr->ByteLength() / sizeof(Type); *num = arr.Length() / sizeof(Type);
} }
data = reinterpret_cast<Type*>(arr->Buffer()->GetContents().Data()); out = arr.Data();
return data;
return out;
} }
inline void *getData(V8_VAR_OBJ obj) { inline void *getData(Napi::Env env, Napi::Object obj) {
void *out = nullptr;
void *pixels = nullptr; if (obj.IsTypedArray() || obj.IsArrayBuffer()) {
out = getArrayData<uint8_t>(env, obj);
if (obj->IsArrayBufferView()) { } else if (obj.IsBuffer()) {
pixels = getArrayData<unsigned char>(obj); out = getBufferData<uint8_t>(env, obj);
} else if (obj->Has(JS_STR("data"))) { } else if (obj.Has("data")) {
V8_VAR_VAL data = Nan::Get(obj, JS_STR("data")).ToLocalChecked(); Napi::Object data = obj.Get("data").As<Napi::Object>();
if ( ! data->IsNullOrUndefined() ) { if (data.IsTypedArray() || data.IsArrayBuffer()) {
pixels = node::Buffer::Data(data); out = getArrayData<uint8_t>(env, data);
} else if (data.IsBuffer()) {
out = getBufferData<uint8_t>(env, data);
} }
} }
return pixels; return out;
} }
inline void consoleLog(int argc, V8_VAR_VAL *argv) { inline Napi::Value consoleLog(
Napi::Env env,
V8_VAR_STR code = JS_STR("((...args) => console.log(...args))"); int argc,
V8_VAR_FUNC log = V8_VAR_FUNC::Cast(v8::Script::Compile(code)->Run()); const Napi::Value *argv
) {
Nan::Callback logCb(log); JS_RUN("console.log", log);
Nan::AsyncResource async("consoleLog()"); std::vector<napi_value> args;
for (int i = 0; i < argc; i++) {
logCb.Call(argc, argv, &async); args.push_back(napi_value(argv[i]));
}
log.As<Napi::Function>().Call(args);
RET_UNDEFINED;
} }
inline void consoleLog(const std::string &message) { inline Napi::Value consoleLog(Napi::Env env, const std::string &message) {
Napi::Value arg = JS_STR(message);
V8_VAR_VAL arg = JS_STR(message); consoleLog(env, 1, &arg);
consoleLog(1, &arg); RET_UNDEFINED;
} }
#endif // _ADDON_TOOLS_HPP_ inline void eventEmit(
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

103
include/class-wrapping.md Normal file
View File

@ -0,0 +1,103 @@
# 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.

View File

@ -1,712 +0,0 @@
#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_VAR_STR stack = V8_VAR_STR::Cast(v8::Script::Compile(code)->Run());
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_VAR_FUNC decor = V8_VAR_FUNC::Cast(v8::Script::Compile(code)->Run());
Nan::Callback decorCb(decor);
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_

65
include/index.js Normal file
View File

@ -0,0 +1,65 @@
'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,
};

41
include/index.test.js Normal file
View File

@ -0,0 +1,41 @@
'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');
});
});
});

81
include/snippets.md Normal file
View File

@ -0,0 +1,81 @@
# 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 Normal file
View File

@ -0,0 +1,361 @@
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,87 +1,3 @@
'use strict'; 'use strict';
const path = require('path'); module.exports = Object.assign({}, require('./include'), require('./utils'));
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' : return process.arch === 'x64' ? 'linux64' : 'linux32';
case 'darwin' : 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 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,60 @@
{ {
"author": "Luis Blanco <luisblanco1337@gmail.com>", "author": "Luis Blanco <luisblanco1337@gmail.com>",
"name": "addon-tools-raub", "name": "addon-tools-raub",
"version": "3.0.0", "version": "7.4.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"
], ],
"engines": { "files": [
"node": ">=8.11.2", "include",
"npm": ">=5.6.0" "utils.js",
}, "utils.d.ts",
"maintainers": [ "index.js",
{ "index.d.ts",
"name": "Luis Blanco", "utils",
"email": "luisblanco1337@gmail.com", "LICENSE",
"skype": "rauber666" "package.json",
} "README.md"
], ],
"engines": {
"node": ">=18.16.0",
"npm": ">=9.5.1"
},
"scripts": {
"eslint": "eslint .",
"test": "node --test --watch .",
"test-ci": "node --test",
"build-test": "cd test-addon && node-gyp rebuild -j max --silent && cd .."
},
"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"
}, },
"dependencies": { "peerDependencies": {
"nan": "~2.10.0" "node-addon-api": "^7.0.0"
},
"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"
} }
} }

37
test-addon/binding.gyp vendored Normal file
View File

@ -0,0 +1,37 @@
{
'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

@ -0,0 +1,45 @@
'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

@ -0,0 +1,109 @@
'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

@ -0,0 +1,100 @@
'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

@ -0,0 +1,100 @@
'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

@ -0,0 +1,109 @@
'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

@ -0,0 +1,100 @@
'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

@ -0,0 +1,100 @@
'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

@ -0,0 +1,109 @@
'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

@ -0,0 +1,100 @@
'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

@ -0,0 +1,100 @@
'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

@ -0,0 +1,100 @@
'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);
});
});

211
test-addon/hpp-arg.test.js Normal file
View File

@ -0,0 +1,211 @@
'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);
});
});
});

302
test-addon/test.cpp Normal file
View File

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

View File

@ -1,17 +0,0 @@
{
"name": "test",
"version": "0.0.0",
"private": true,
"main": "mocha",
"scripts": {
"test": "mocha",
"start": "mocha",
"preinstall": "cd .. && npm i"
},
"dependencies": {
"addon-tools-raub": "file:../",
"chai": "^4.1.2",
"mocha": "^5.0.0",
"sinon": "^4.2.2"
}
}

View File

@ -1,128 +0,0 @@
'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,15 +1,18 @@
set noparent set noparent
linelength=110 linelength=110
filter=-legal/copyright
filter=-build/include_order
filter=-build/header_guard filter=-build/header_guard
filter=-build/namespaces filter=-build/include
filter=-build/include_order
filter=-build/include_what_you_use filter=-build/include_what_you_use
filter=-build/namespaces
filter=-legal/copyright
filter=-readability/todo
filter=-runtime/indentation_namespace
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=-readability/todo filter=-whitespace/tab

20
utils/action-pack.js Normal file
View File

@ -0,0 +1,20 @@
'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 };

31
utils/cpbin.js Normal file
View File

@ -0,0 +1,31 @@
'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 };

24
utils/cpcpplint.js Normal file
View File

@ -0,0 +1,24 @@
'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 };

46
utils/download.js Normal file
View File

@ -0,0 +1,46 @@
'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 };

196
utils/files.js Normal file
View File

@ -0,0 +1,196 @@
'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,
};

13
utils/index.js Normal file
View File

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

72
utils/install.js Normal file
View File

@ -0,0 +1,72 @@
'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 };

22
utils/utils.test.js Normal file
View File

@ -0,0 +1,22 @@
'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');
});
});
});

56
utils/writable-buffer.js Normal file
View File

@ -0,0 +1,56 @@
'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 };