diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..c3c66da --- /dev/null +++ b/.babelrc @@ -0,0 +1,36 @@ +{ + "ignore": [ + "./test/dict" + ], + "presets": [ + "@babel/preset-env" + ], + "plugins": [ + "@babel/plugin-proposal-class-properties", + "babel-plugin-dynamic-import-node", + "@babel/plugin-syntax-dynamic-import", + "@babel/plugin-syntax-import-meta", + "@babel/plugin-proposal-json-strings", + [ + "@babel/plugin-proposal-decorators", + { + "legacy": true + } + ], + "@babel/plugin-proposal-function-sent", + "@babel/plugin-proposal-export-namespace-from", + "@babel/plugin-proposal-numeric-separator", + "@babel/plugin-proposal-throw-expressions", + "@babel/plugin-proposal-export-default-from", + "@babel/plugin-proposal-logical-assignment-operators", + "@babel/plugin-proposal-optional-chaining", + [ + "@babel/plugin-proposal-pipeline-operator", + { + "proposal": "minimal" + } + ], + "@babel/plugin-proposal-nullish-coalescing-operator", + "@babel/plugin-proposal-do-expressions" + ] +} diff --git a/.gitignore b/.gitignore index 0db3560..473b32d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ -dict node_modules .idea -*.iml \ No newline at end of file +*.iml +.cache +build +dict +dist diff --git a/.npmignore b/.npmignore index 8a87533..2dafeb1 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,4 @@ node_modules .project .idea -*.iml \ No newline at end of file +*.iml diff --git a/.travis.yml b/.travis.yml index 0a5ead6..2642232 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js node_js: + - '12' + - '11' - '10' - '8' - '6' - - '4' diff --git a/README.md b/README.md index 305c21a..5785d1e 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,22 @@ wordpos [![NPM version](https://img.shields.io/npm/v/wordpos.svg)](https://www.npmjs.com/package/wordpos) [![Build Status](https://img.shields.io/travis/moos/wordpos/master.svg)](https://travis-ci.org/moos/wordpos) -wordpos is a set of *fast* part-of-speech (POS) utilities for Node.js using fast lookup in the WordNet database. +wordpos is a set of *fast* part-of-speech (POS) utilities for Node.js **and** browser using fast lookup in the WordNet database. Version 1.x is a major update with no direct dependence on [natural's](https://github.com/NaturalNode/natural#wordnet) WordNet module, with support for [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), and roughly 5x speed improvement over previous version. -**CAUTION** The WordNet database [wordnet-db](https://github.com/moos/wordnet-db) comprises [155,287 words](https://wordnet.princeton.edu/documentation/wnstats7wn) (3.0 numbers) which uncompress to over **30 MB** of data in several *un*[browserify](https://github.com/substack/node-browserify)-able files. It is *not* meant for the browser environment. +> ~~**CAUTION** The WordNet database [wordnet-db](https://github.com/moos/wordnet-db) comprises [155,287 words](https://wordnet.princeton.edu/documentation/wnstats7wn) (3.0 numbers) which uncompress to over **30 MB** of data in several *un*[browserify](https://github.com/substack/node-browserify)-able files. It is *not* meant for the browser environment.~~ +:zap: v2.x can work in browsers -- to try it out `npm i wordpos@beta` or [see it in action](https://moos.github.io/wordpos). See below for usage. + +## Installation + + npm install -g wordpos + +To run test: (or just: npm test) + + npm install -g mocha + mocha test ## Quick usage @@ -33,7 +43,7 @@ Command-line: (see [CLI](bin) for full command list) ```bash $ wordpos def git git - n: a person who is deemed to be despicable or contemptible; "only a rotter would do that"; "kill the rat"; "throw the bum out"; "you cowardly little pukes!"; "the British call a contemptible person a `git'" + n: a person who is deemed to be despicable or contemptible; "only a rotter would do that"; "kill the rat"; "throw the bum out"; "you cowardly little pukes!"; "the British call a contemptible person a 'git'" $ wordpos def git | wordpos get --adj # Adjective 6: @@ -45,16 +55,8 @@ little British ``` -## Installation - npm install -g wordpos - -To run test: (or just: npm test) - - npm install -g mocha - mocha test - -### Options +## Options ```js WordPOS.defaults = { @@ -68,7 +70,30 @@ WordPOS.defaults = { * if array, stopwords to exclude, eg, ['all','of','this',...] * if false, do not filter any stopwords. */ - stopwords: true + stopwords: true, + + /** + * preload files (in browser only) + * true - preload all POS + * false - do not preload any POS + * 'a' - preload adj + * ['a','v'] - preload adj & verb + * @type {boolean|string|Array} + */ + preload: false, + + /** + * include data files in preload + * @type {boolean} + */ + includeData: false, // WIP + + /** + * set to true to enable debug logging + * @type {boolean} + */ + debug: false + }; ``` To override, pass an options hash to the constructor. With the `profile` option, most callbacks receive a last argument that is the execution time in msec of the call. @@ -224,7 +249,7 @@ wordpos.rand({starsWith: 'zzz'}, console.log) // [] 'zzz' ``` -**Note on performance**: random lookups could involve heavy disk reads. It is better to use the `count` option to get words in batches. This may benefit from the cached reads of similarly keyed entries as well as shared open/close of the index files. +**Note on performance**: (node only) random lookups could involve heavy disk reads. It is better to use the `count` option to get words in batches. This may benefit from the cached reads of similarly keyed entries as well as shared open/close of the index files. Getting random POS (`randNoun()`, etc.) is generally faster than `rand()`, which may look at multiple POS files until `count` requirement is met. @@ -269,8 +294,43 @@ wordpos.isVerb('fish', console.log) ``` Note that callback receives full arguments (including profile, if enabled), while the Promise receives only the result of the call. Also, beware that exceptions in the _callback_ will result in the Promise being _rejected_ and caught by `catch()`, if provided. +## Running inside the browsers -## Fast Index +v2.0 introduces the capability of running wordpos in the browser. The dictionary files are optimized for fast access (lookup by lemma), but they must be fetched, parsed and loaded into browser memory. The files are loaded on-demand (unless the option `preload: true` is given). + +The dict files can be served locally or from CDN (see [samples/cdn](samples/cdn/) for code, or [see it in action](https://moos.github.io/wordpos)). Include the following scripts in your `index.html`: +```html + + +``` +Above assumes wordpos is installed to the directory `./wordpos`. `./wordpos/dict` holds the index and data WordNet files generated for the web in a postinstall script. + +See [samples/self-hosted](samples/self-hosted/). + +To run the samples locally, install [parcel](https://github.com/parcel-bundler/parcel) if you don't already have it (`npm i -g parcel`), then: +```bash +$ npm run start-self +Server running at http://localhost:1234 +... + +$ npm run start-cdn +Server running at http://localhost:1234 +... +``` +and open your browser to that url. + +## Fast Index (node) Version 0.1.4 introduces `fastIndex` option. This uses a secondary index on the index files and is much faster. It is on by default. Secondary index files are generated at install time and placed in the same directory as WNdb.path. Details can be found in tools/stat.js. @@ -287,8 +347,17 @@ For CLI usage and examples, see [bin/README](bin). See [bench/README](bench). + +## TODO +- implement `includeData` option for preload + + ## Changes +**2.0.0** + - Support for running wordpos in browser (no breaking change for node environment) + - Dropped support for node 4.x. + 1.2.0 - Fix `new Buffer()` deprecation warning. - Fix npm audit vulnerabilities @@ -347,4 +416,4 @@ License (The MIT License) -Copyright (c) 2012, 2014, 2016 mooster@42at.com +Copyright (c) 2012-2019 mooster@42at.com diff --git a/docs/cdn/index.html b/docs/cdn/index.html new file mode 100644 index 0000000..1cad5ca --- /dev/null +++ b/docs/cdn/index.html @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + +

CDN WordPOS sample

+ Open console to see results. + +
 
+ + + + + + diff --git a/docs/main.js b/docs/main.js new file mode 100644 index 0000000..5c3349d --- /dev/null +++ b/docs/main.js @@ -0,0 +1,32 @@ +let assertLikely = (r) => { + console.assert(r.def === 'with considerable certainty'); + console.assert(r.pos === 'r'); + console.assert(r.synsetOffset === '00139421'); +}; + +console.group('Likely'); +wordpos.isAdverb('likely').then(res => console.assert(res)); +wordpos.isAdverb('likely', (res, ...profile) => console.log('callback with profile', res, profile)); + +wordpos.getAdverbs('this is is lately a likely tricky business this is') + .then(res => { + let expect = {lately: 1, likely: 1}; + console.log('getAdverbs:', res); + console.assert(res[0] in expect); // NOTE: order is NOT gauranteed! + console.assert(res[1] in expect); + }); + +wordpos.lookupAdverb('likely') + .then(res => { + console.log('lookupAdverb:', res[0]); + assertLikely(res[0]); + }); +// wordpos.lookup('likely').then(res, console.log('lookup ===', res)) + +wordpos.seek('00139421', 'r') + .then(res => { + console.log('seek:', res); + assertLikely(res); + }); + +setTimeout(() => console.groupEnd('Likely'), 1000); diff --git a/docs/self-hosted/index.html b/docs/self-hosted/index.html new file mode 100644 index 0000000..dcdbc03 --- /dev/null +++ b/docs/self-hosted/index.html @@ -0,0 +1,52 @@ + + + + + Wordpos in the browser + + + + + + + + + + + + + +

Self-hosted WordPOS sample

+ Open console to see results. + +
 
+ + + + + + diff --git a/package-lock.json b/package-lock.json index e4a7564..dcf2a0a 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,1208 @@ { "name": "wordpos", - "version": "1.1.6", + "version": "2.0.0-beta", "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.1.2.tgz", + "integrity": "sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.1.2", + "@babel/helpers": "^7.1.2", + "@babel/parser": "^7.1.2", + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.1.2", + "convert-source-map": "^1.1.0", + "debug": "^3.1.0", + "json5": "^0.5.0", + "lodash": "^4.17.10", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.3.tgz", + "integrity": "sha512-ZoCZGcfIJFJuZBqxcY9OjC1KW2lWK64qrX1o4UYL3yshVhwKFYgzpWZ0vvtGMNJdTlvkw0W+HR1VnYN8q3QPFQ==", + "dev": true, + "requires": { + "@babel/types": "^7.1.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", + "dev": true + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz", + "integrity": "sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-define-map": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", + "integrity": "sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz", + "integrity": "sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz", + "integrity": "sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz", + "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz", + "integrity": "sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-wrap-function": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.1.0.tgz", + "integrity": "sha512-R6HU3dete+rwsdAfrOzTlE9Mcpk4RjU3aX3gi9grtmugQY0u79X7eogUvfXA5sI81Mfq1cn6AgxihfN33STjJA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helpers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.1.2.tgz", + "integrity": "sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA==", + "dev": true, + "requires": { + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.1.2" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } + } + }, + "@babel/parser": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.3.tgz", + "integrity": "sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.1.0.tgz", + "integrity": "sha512-Fq803F3Jcxo20MXUSDdmZZXrPe6BWyGcWBPPNB/M7WaUYESKDeKMOGIxEzQOjGSmW/NWb6UaPZrtTB2ekhB/ew==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.0.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.1.0.tgz", + "integrity": "sha512-/PCJWN+CKt5v1xcGn4vnuu13QDoV+P7NcICP44BoonAJoPSGwVkgrXihFIQGiEjjPlUDBIw1cM7wYFLARS2/hw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", + "@babel/plugin-syntax-class-properties": "^7.0.0" + } + }, + "@babel/plugin-proposal-decorators": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.1.2.tgz", + "integrity": "sha512-YooynBO6PmBgHvAd0fl5e5Tq/a0pEC6RqF62ouafme8FzdIVH41Mz/u1dn8fFVm4jzEJ+g/MsOxouwybJPuP8Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/plugin-syntax-decorators": "^7.1.0" + } + }, + "@babel/plugin-proposal-do-expressions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-do-expressions/-/plugin-proposal-do-expressions-7.0.0.tgz", + "integrity": "sha512-fIXsLAsQ5gVhQF44wZ9Yc3EBxaCHzeNjd8z9ivEzKOQyv5VoU1YJQ3AZa0VJgQMX5k/cbXJpNwp2mtg7iSdiGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-do-expressions": "^7.0.0" + } + }, + "@babel/plugin-proposal-export-default-from": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.0.0.tgz", + "integrity": "sha512-cWhkx6SyjZ4caFOanoPmDNgQCuYYTmou4QXy886JsyLTw/vhWQbop2gLKsWyyswrJkKTB7fSNxVYbP/oEsoySA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.0.0.tgz", + "integrity": "sha512-UZuK8lkobh3570vCu0sxDQn+ZlCV6CVLlXe+nNohvPr6/zI5I+j4Ir2fTTCG0ayBQanym0N+29K5+v4c8SATaQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-export-namespace-from": "^7.0.0" + } + }, + "@babel/plugin-proposal-function-sent": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-function-sent/-/plugin-proposal-function-sent-7.1.0.tgz", + "integrity": "sha512-yciM4dketj0pjd1enirzfVWclzSCzjOljHx8E4DJUBq/q65CuaKsX2zhpdImzcn6TtFupzdcuchbqN00IEKDAA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/plugin-syntax-function-sent": "^7.0.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz", + "integrity": "sha512-kfVdUkIAGJIVmHmtS/40i/fg/AGnw/rsZBCaapY5yjeO5RA9m165Xbw9KMOu2nqXP5dTFjEjHdfNdoVcHv133Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.0.0" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.0.0.tgz", + "integrity": "sha512-06osaVN0bKEIXvzScf6qPpbDUEP4sixqVdjwpSPPwEtMyDC+x8PDvcJCww6p6TDOTIHnuUx2Afmguf/RypKDIw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.0.0" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.0.0.tgz", + "integrity": "sha512-QIN3UFo1ul4ruAsjIqK43PeXedo1qY74zeGrODJl1KfCGeMc6qJC4rb5Ylml/smzxibqsDeVZGH+TmWHCldRQQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.0.0.tgz", + "integrity": "sha512-m4iDNpbBv2rTxxgViAeaqLOStc2wrlVAC5ifp6pjBPG29F56LdlPgf5CQYzj99y3kYeKqsyf/dcMx/r+QfwMZg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-numeric-separator": "^7.0.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz", + "integrity": "sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz", + "integrity": "sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.0.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.0.0.tgz", + "integrity": "sha512-7x8HLa71OzNiofbQUVakS0Kmg++6a+cXNfS7QKHbbv03SuSaumJyaWsfNgw+T7aqrJlqurYpZqrkPgXu0iZK0w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0" + } + }, + "@babel/plugin-proposal-pipeline-operator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-pipeline-operator/-/plugin-proposal-pipeline-operator-7.0.0.tgz", + "integrity": "sha512-MN189PDyTMoor/YFh9dk6HpSZLMGHCXRdAhgmzshwcalbgYh5Mkn7Ib17lOo6fmLwHdyQ4GR4yagizfeR2LwQQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-pipeline-operator": "^7.0.0" + } + }, + "@babel/plugin-proposal-throw-expressions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.0.0.tgz", + "integrity": "sha512-CA2EUiwnbXrsdV4hy3jYghm91WaL7zL7xYfu628dyItRr6gylbRxshghGEK/Hhm//rR58N3PBmEeuYqSW57IUQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-throw-expressions": "^7.0.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0.tgz", + "integrity": "sha512-tM3icA6GhC3ch2SkmSxv7J/hCWKISzwycub6eGsDrFDgukD4dZ/I+x81XgW0YslS6mzNuQ1Cbzh5osjIMgepPQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.2.0" + }, + "dependencies": { + "regexpu-core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.2.0.tgz", + "integrity": "sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^7.0.0", + "regjsgen": "^0.4.0", + "regjsparser": "^0.3.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.0.2" + } + }, + "regjsgen": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.4.0.tgz", + "integrity": "sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA==", + "dev": true + }, + "regjsparser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.3.0.tgz", + "integrity": "sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz", + "integrity": "sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0.tgz", + "integrity": "sha512-cR12g0Qzn4sgkjrbrzWy2GE7m9vMl/sFkqZ3gIpAQdrvPDnLM8180i+ANDFIXfjHo9aqp0ccJlQ0QNZcFUbf9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.1.0.tgz", + "integrity": "sha512-uQvRSbgQ0nQg3jsmIixXXDCgSpkBolJ9X7NYThMKCcjvE8dN2uWJUzTUNNAeuKOjARTd+wUQV0ztXpgunZYKzQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-do-expressions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-do-expressions/-/plugin-syntax-do-expressions-7.0.0.tgz", + "integrity": "sha512-ZN5MO2WuYfznTK0/TRlF9qG+pBGV/bY5CRO9/a00XEGvaU31JAewRbYaZrySDw6kwSdtPG76yk9jZdPrEC3jWg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0.tgz", + "integrity": "sha512-Gt9xNyRrCHCiyX/ZxDGOcBnlJl0I3IWicpZRC4CdC0P5a/I07Ya2OAMEBU+J7GmRFVmIetqEYRko6QYRuKOESw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-export-default-from": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.0.0.tgz", + "integrity": "sha512-HNnjg/fFFbnuLAqr/Ocp1Y3GB4AjmXcu1xxn3ql3bS2kGrB/qi+Povshb8i3hOkE5jNozzh8r/0/lq1w8oOWbQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.0.0.tgz", + "integrity": "sha512-l314XT1eMa0MWboSmG4BdKukHfSpSpQRenUoZmEpL6hqc5nc1/ddpLETjPB77gZE1dZ9qxy5D3U3UUjjcX2d4g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-function-sent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-function-sent/-/plugin-syntax-function-sent-7.0.0.tgz", + "integrity": "sha512-j+D8C+clbieA+1UFlRzMkVozWNLB94TCJsUUE7OCyKBRM329ZZXnFPjgm0em5ddLsKV9DNpdtaOZsNZ1J7gHyA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.0.0.tgz", + "integrity": "sha512-FEoGvhXVAiWzpDjyZIlBGzKyNk/lnRPy7aPke3PjVkiAY0QFsvFfkjUg5diRwVfowBA8SJqvFt0ZoXNSjl70hQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz", + "integrity": "sha512-UlSfNydC+XLj4bw7ijpldc1uZ/HB84vw+U6BTuqMdIEmz/LDe63w/GHtpQMdXWdqQZFeAI9PjnHe/vDhwirhKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.0.0.tgz", + "integrity": "sha512-eOcVPYWpdReMfxHZIBRjC5wlB8iU7kM6eQyst0kK6SwUPmpYNKyB4rJdf0HTeUEOSRqdlH6uMiLAzReA0qDGLQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.0.0.tgz", + "integrity": "sha512-oAJmMsAvTSIk9y0sZdU2S/nY44PEUuHN7EzNDMgbuR4e/OwyfR9lSmoBJBZ2lslFZIqhksrTt4i+av7uKfNYDw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.0.0.tgz", + "integrity": "sha512-t9RMUPWsFXVeUZxEOhIDkVqYLi1sWOTjxFBAp8wJtaARilvkGlEQvSObd2W5YKicDktINI9XmdV0sB2FZaLOpw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz", + "integrity": "sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz", + "integrity": "sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.0.0.tgz", + "integrity": "sha512-QXedQsZf8yua1nNrXSePT0TsGSQH9A1iK08m9dhCMdZeJaaxYcQfXdgHWVV6Cp7WE/afPVvSKIsAHK5wP+yxDA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-pipeline-operator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-pipeline-operator/-/plugin-syntax-pipeline-operator-7.0.0.tgz", + "integrity": "sha512-McK1JV4klGq2r0UZ1SLE2u+u37ElArBcPMGl6JizdgEXD3ttp0dpOB5ZpqpeRHkIgnl46th64UHrFDteQ4P5aw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-throw-expressions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.0.0.tgz", + "integrity": "sha512-/5uORdWlPta/ALhI5zKtm0Y9vAYOa7HJMML0OnCGk9XZA4hpGjb0Xjt/OVDCJVawC/4FrlAGCHOaj9BtWeVDvg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0.tgz", + "integrity": "sha512-2EZDBl1WIO/q4DIkIp4s86sdp4ZifL51MoIviLY/gG/mLSuOIEg7J8o6mhbxOTvUJkaN50n+8u41FVsr5KLy/w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.1.0.tgz", + "integrity": "sha512-rNmcmoQ78IrvNCIt/R9U+cixUHeYAzgusTFgIAv+wQb9HJU4szhpDD6e5GCACmj/JP5KxuCwM96bX3L9v4ZN/g==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0.tgz", + "integrity": "sha512-AOBiyUp7vYTqz2Jibe1UaAWL0Hl9JUXEgjFvvvcSc9MVDItv46ViXFw2F7SVt1B5k+KWjl44eeXOAk3UDEaJjQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0.tgz", + "integrity": "sha512-GWEMCrmHQcYWISilUrk9GDqH4enf3UmhOEbNbNrlNAX1ssH3MsS1xLOS6rdjRVPgA7XXVPn87tRkdTEoA/dxEg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.1.0.tgz", + "integrity": "sha512-rNaqoD+4OCBZjM7VaskladgqnZ1LO6o2UxuWSDzljzW21pN1KXkB7BstAVweZdxQkHAujps5QMNOTWesBciKFg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.1.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", + "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", + "dev": true + } + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0.tgz", + "integrity": "sha512-ubouZdChNAv4AAWAgU7QKbB93NU5sHwInEWfp+/OzJKA02E6Woh9RVoX4sZrbRwtybky/d7baTUqwFx+HgbvMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.1.3.tgz", + "integrity": "sha512-Mb9M4DGIOspH1ExHOUnn2UUXFOyVTiX84fXCd+6B5iWrQg/QMeeRmSwpZ9lnjYLSXtZwiw80ytVMr3zue0ucYw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz", + "integrity": "sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + }, + "dependencies": { + "regexpu-core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.2.0.tgz", + "integrity": "sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^7.0.0", + "regjsgen": "^0.4.0", + "regjsparser": "^0.3.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.0.2" + } + }, + "regjsgen": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.4.0.tgz", + "integrity": "sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA==", + "dev": true + }, + "regjsparser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.3.0.tgz", + "integrity": "sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0.tgz", + "integrity": "sha512-w2vfPkMqRkdxx+C71ATLJG30PpwtTpW7DDdLqYt2acXU7YjztzeWW2Jk1T6hKqCLYCcEA5UQM/+xTAm+QCSnuQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.1.0.tgz", + "integrity": "sha512-uZt9kD1Pp/JubkukOGQml9tqAeI8NkE98oZnHZ2qHRElmeKCodbTZgOEUtujSCSLhHSBWbzNiFSDIMC4/RBTLQ==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0.tgz", + "integrity": "sha512-TlxKecN20X2tt2UEr2LNE6aqA0oPeMT1Y3cgz8k4Dn1j5ObT8M3nl9aA37LLklx0PBZKETC9ZAf9n/6SujTuXA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.1.0.tgz", + "integrity": "sha512-VxOa1TMlFMtqPW2IDYZQaHsFrq/dDoIjgN098NowhexhZcz3UGlvPgZXuE1jEvNygyWyxRacqDpCZt+par1FNg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0.tgz", + "integrity": "sha512-1NTDBWkeNXgpUcyoVFxbr9hS57EpZYXpje92zv0SUzjdu3enaRwF/l3cmyRnXLtIdyJASyiS6PtybK+CgKf7jA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.1.0.tgz", + "integrity": "sha512-wt8P+xQ85rrnGNr2x1iV3DW32W8zrB6ctuBkYBbf5/ZzJY99Ob4MFgsZDFgczNU76iy9PWsy4EuxOliDjdKw6A==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.1.0.tgz", + "integrity": "sha512-wtNwtMjn1XGwM0AXPspQgvmE6msSJP15CX2RVfpTSTNPLhKhaOjaIfBaVfj4iUZ/VrFSodcFedwtPg/NxwQlPA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.1.3.tgz", + "integrity": "sha512-PvTxgjxQAq4pvVUZF3mD5gEtVDuId8NtWkJsZLEJZMZAW3TvgQl1pmydLLN1bM8huHFVVU43lf0uvjQj9FRkKw==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.1.0.tgz", + "integrity": "sha512-enrRtn5TfRhMmbRwm7F8qOj0qEYByqUvTttPEGimcBH4CJHphjyK1Vg7sdU7JjeEmgSpM890IT/efS2nMHwYig==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz", + "integrity": "sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.1.0.tgz", + "integrity": "sha512-/O02Je1CRTSk2SSJaq0xjwQ8hG4zhZGNjE8psTsSNPXyLRCODv7/PBozqT5AmQMzp7MI3ndvMhGdqp9c96tTEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.1.0.tgz", + "integrity": "sha512-vHV7oxkEJ8IHxTfRr3hNGzV446GAb+0hgbA7o/0Jd76s+YzccdWuTU296FOCOl/xweU4t/Ya4g41yWz80RFCRw==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "^7.1.0", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz", + "integrity": "sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==", + "dev": true, + "requires": { + "regenerator-transform": "^0.13.3" + }, + "dependencies": { + "regenerator-transform": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz", + "integrity": "sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==", + "dev": true, + "requires": { + "private": "^0.1.6" + } + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz", + "integrity": "sha512-g/99LI4vm5iOf5r1Gdxq5Xmu91zvjhEG5+yZDJW268AZELAu4J1EiFLnkSG3yuUsZyOipVOVUKoGPYwfsTymhw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0.tgz", + "integrity": "sha512-L702YFy2EvirrR4shTj0g2xQp7aNwZoWNCkNu2mcoU0uyzMl0XRwDSwzB/xp6DSUFiBmEXuyAyEN16LsgVqGGQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0.tgz", + "integrity": "sha512-LFUToxiyS/WD+XEWpkx/XJBrUXKewSZpzX68s+yEOtIbdnsRjpryDw9U06gYc6klYEij/+KQVRnD3nz3AoKmjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0.tgz", + "integrity": "sha512-vA6rkTCabRZu7Nbl9DfLZE1imj4tzdWcg5vtdQGvj+OH9itNNB6hxuRMHuIY8SGnEt1T9g5foqs9LnrHzsqEFg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0.tgz", + "integrity": "sha512-1r1X5DO78WnaAIvs5uC48t41LLckxsYklJrZjNKcevyz83sF2l4RHbw29qrCPr/6ksFsdfRpT/ZgxNWHXRnffg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0.tgz", + "integrity": "sha512-uJBrJhBOEa3D033P95nPHu3nbFwFE9ZgXsfEitzoIXIwqAZWk7uXcg06yFKXz9FSxBH5ucgU/cYdX0IV8ldHKw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + }, + "dependencies": { + "regexpu-core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.2.0.tgz", + "integrity": "sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^7.0.0", + "regjsgen": "^0.4.0", + "regjsparser": "^0.3.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.0.2" + } + }, + "regjsgen": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.4.0.tgz", + "integrity": "sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA==", + "dev": true + }, + "regjsparser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.3.0.tgz", + "integrity": "sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "@babel/preset-env": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.1.0.tgz", + "integrity": "sha512-ZLVSynfAoDHB/34A17/JCZbyrzbQj59QC1Anyueb4Bwjh373nVPq5/HMph0z+tCmcDjXDe+DlKQq9ywQuvWrQg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.1.0", + "@babel/plugin-proposal-json-strings": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.0.0", + "@babel/plugin-syntax-async-generators": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.1.0", + "@babel/plugin-transform-block-scoped-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.1.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-dotall-regex": "^7.0.0", + "@babel/plugin-transform-duplicate-keys": "^7.0.0", + "@babel/plugin-transform-exponentiation-operator": "^7.1.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.1.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-amd": "^7.1.0", + "@babel/plugin-transform-modules-commonjs": "^7.1.0", + "@babel/plugin-transform-modules-systemjs": "^7.0.0", + "@babel/plugin-transform-modules-umd": "^7.1.0", + "@babel/plugin-transform-new-target": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.1.0", + "@babel/plugin-transform-parameters": "^7.1.0", + "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "@babel/plugin-transform-typeof-symbol": "^7.0.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "browserslist": "^4.1.0", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.3.0" + }, + "dependencies": { + "browserslist": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.0.tgz", + "integrity": "sha512-j0jLqo+6ZhFWvTjEIcDyR8LIiN8pA3cUrT/SGAs0LPp/cKvkRpCnzuxtnAW+sOPLTic5wfb+TQvRX2RTN2wo4w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000893", + "electron-to-chromium": "^1.3.80", + "node-releases": "^1.0.0-alpha.14" + } + }, + "caniuse-lite": { + "version": "1.0.30000893", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000893.tgz", + "integrity": "sha512-kOddHcTEef+NgN/fs0zmX2brHTNATVOWMEIhlZHCuwQRtXobjSw9pAECc44Op4bTBcavRjkLaPrGomknH7+Jvg==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.80", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.80.tgz", + "integrity": "sha512-WClidEWEUNx7OfwXehB0qaxCuetjbKjev2SmXWgybWPLKAThBiMTF/2Pd8GSUDtoGOavxVzdkKwfFAPRSWlkLw==", + "dev": true + } + } + }, + "@babel/register": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.0.0.tgz", + "integrity": "sha512-f/+CRmaCe7rVEvcvPvxeA8j5aJhHC3aJie7YuqcMDhUOuyWLA7J/aNrTaHIzoWPEhpHA54mec4Mm8fv8KBlv3g==", + "dev": true, + "requires": { + "core-js": "^2.5.7", + "find-cache-dir": "^1.0.0", + "home-or-tmp": "^3.0.0", + "lodash": "^4.17.10", + "mkdirp": "^0.5.1", + "pirates": "^4.0.0", + "source-map-support": "^0.5.9" + } + }, + "@babel/template": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", + "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.1.2", + "@babel/types": "^7.1.2" + } + }, + "@babel/traverse": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.4.tgz", + "integrity": "sha512-my9mdrAIGdDiSVBuMjpn/oXYpva0/EZwWL3sm3Wcy/AVWO2eXnsoZruOT9jOGNRXU8KbCIu5zsKnXcAJ6PcV6Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.1.3", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.1.3", + "@babel/types": "^7.1.3", + "debug": "^3.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "globals": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", + "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz", + "integrity": "sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, + "@types/mz": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/mz/-/mz-0.0.32.tgz", + "integrity": "sha512-cy3yebKhrHuOcrJGkfwNHhpTXQLgmXSv1BX+4p32j+VUQ6aP2eJ5cL7OvGcAQx75fCTFaAIIAKewvqL+iwSd4g==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "10.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.11.7.tgz", + "integrity": "sha512-yOxFfkN9xUFLyvWaeYj90mlqTJ41CsQzWKS3gXdOMOyPVacUsymejKxJ4/pMW7exouubuEeZLJawGgcNGYlTeg==" + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "babel-plugin-dynamic-import-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.2.0.tgz", + "integrity": "sha512-fP899ELUnTaBcIzmrW7nniyqqdYWrWuJUyPWHxFa/c7r7hS6KC8FscNfLlBNIoPSc55kYMGEEKjPjJGCLbE1qA==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -32,6 +1225,12 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, "chai": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", @@ -52,22 +1251,53 @@ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, "commander": { "version": "2.17.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -86,6 +1316,15 @@ "type-detect": "^4.0.0" } }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -98,12 +1337,44 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -124,6 +1395,11 @@ "path-is-absolute": "^1.0.0" } }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -136,12 +1412,24 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "home-or-tmp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-3.0.0.tgz", + "integrity": "sha1-V6j+JM8zzdUkhgoVgh3cJchmcfs=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -158,6 +1446,78 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "js-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.4.tgz", + "integrity": "sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow==", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, "mini-bench": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mini-bench/-/mini-bench-1.0.0.tgz", @@ -175,34 +1535,25 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, - "minipass": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz", - "integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", - "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", - "requires": { - "minipass": "^2.2.1" - } - }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } }, + "mkdirp-promise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", + "integrity": "sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=", + "requires": { + "mkdirp": "*" + } + }, "mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", @@ -236,6 +1587,54 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-releases": { + "version": "1.0.0-alpha.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.0-alpha.14.tgz", + "integrity": "sha512-G8nnF9cP9QPP/jUmYWw/uUUhumHmkm+X/EarCugYFjYm2uXRMFeOD6CVT3RLdoyCvDUNy51nirGfUItKWs/S1g==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -245,22 +1644,143 @@ "wrappy": "1" } }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pirates": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.0.tgz", + "integrity": "sha512-8t5BsXy1LUIjn3WWOlOuFDuKswhQb/tkak641lvBgmPOBUQHXveORtlMCp6OdPV1dtuTaEahKA8VNz6uLfKBtA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz", + "integrity": "sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } }, "supports-color": { "version": "5.4.0", @@ -271,18 +1791,41 @@ "has-flag": "^3.0.0" } }, - "tar": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-3.2.1.tgz", - "integrity": "sha512-ZSzds1E0IqutvMU8HxjMaU8eB7urw2fGwTq88ukDOVuUIh0656l7/P7LiVPxhO5kS4flcRJQk8USG+cghQbTUQ==", + "symlink-dir": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/symlink-dir/-/symlink-dir-1.1.3.tgz", + "integrity": "sha512-klQgTYk7en8A69nAzZjJdaMXbGCmfh0DU+YLaZG/stHNp00VZSS3Pos238Ua7oCKVw57UszViod4D7RVRH6XHg==", "requires": { - "chownr": "^1.0.1", - "minipass": "^2.0.2", - "minizlib": "^1.0.3", - "mkdirp": "^0.5.0", - "yallist": "^3.0.2" + "@types/mz": "0.0.32", + "@types/node": "^10.0.8", + "graceful-fs": "^4.1.11", + "is-windows": "^1.0.0", + "mkdirp-promise": "^5.0.0", + "mz": "^2.4.0" } }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -294,24 +1837,44 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, - "wordnet-db": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/wordnet-db/-/wordnet-db-3.1.6.tgz", - "integrity": "sha1-75kaOOmGq5HhsDai+ZF5jBNiD5g=", + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, "requires": { - "tar": "^3.1" + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" } }, + "unicode-match-property-value-ecmascript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz", + "integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz", + "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==", + "dev": true + }, + "wordnet-db": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/wordnet-db/-/wordnet-db-3.1.11.tgz", + "integrity": "sha512-gU1pmI6SaXA38fA62OoJz1LAYo2B9vnT0lIw6ZCiCel2dlIP8gJ/4EutnkVzenR5QH6X57n/TmWyXPNfiD0uMA==" + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true - }, - "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" } } } diff --git a/package.json b/package.json index 9edf78e..b18c781 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wordpos", - "version": "1.2.0", - "description": "wordpos is a set of part-of-speech utilities for Node.js using the WordNet database.", + "version": "2.0.0-beta.2", + "description": "wordpos is a set of part-of-speech utilities for Node.js & browser using the WordNet database.", "author": "Moos ", "keywords": [ "natural", @@ -14,16 +14,47 @@ ], "homepage": "https://github.com/moos/wordpos", "engines": { - "node": ">=4" + "node": ">=6" }, - "files": ["bench","bin","lib","src","test","tools"], + "files": [ + "bench", + "bin", + "dict", + "dist", + "lib", + "src", + "scripts", + "test", + "!test/dict", + "tools" + ], "bin": "./bin/wordpos-cli.js", "dependencies": { "commander": "^2.0.0", + "symlink-dir": "1.1.3", "underscore": ">=1.3.1", - "wordnet-db": "^3.1.6" + "wordnet-db": "^3.1.11" }, "devDependencies": { + "@babel/core": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-decorators": "^7.0.0", + "@babel/plugin-proposal-do-expressions": "^7.0.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-export-namespace-from": "^7.0.0", + "@babel/plugin-proposal-function-sent": "^7.0.0", + "@babel/plugin-proposal-json-strings": "^7.0.0", + "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0", + "@babel/plugin-proposal-pipeline-operator": "^7.0.0", + "@babel/plugin-proposal-throw-expressions": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-syntax-import-meta": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "@babel/register": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.2.0", "chai": "^4.0.2", "mini-bench": "^1.0.0", "mocha": "^5.2.0" @@ -32,10 +63,23 @@ "type": "git", "url": "git://github.com/moos/wordpos.git" }, - "main": "./src/wordpos.js", + "main": "./src/node/index.js", + "browser": "./src/browser/index.js", "scripts": { - "postinstall": "node tools/stat.js --no-stats index.adv index.adj index.verb index.noun", - "test": "mocha test" + "postinstall": "npm run postinstall-web && npm run postinstall-node", + "postinstall-node": "node tools/stat.js --no-stats index.adv index.adj index.verb index.noun", + "postinstall-web": "node scripts/makeJsonDict.js index data", + "build": "parcel build --detailed-report -d dist -o wordpos.min.js --global WordPOS -t browser src/browser/index.js", + "postbuild": "sed -i 's/ES6_IMPORT/import/' dist/wordpos.min.js", + "test": "npm run test-node && npm run test-browser", + "test-node": "mocha test", + "test-browser": "mocha test/wordpos_test --require @babel/register", + "prestart": "symlink-dir dict samples/self-hosted/dict", + "start": "npm run build && http-server", + "prestart-dev": "rm -rf build && mkdir build && symlink-dir dict build/dict && cp samples/main.js build/main.txt", + "start-dev": "npm run start-self -- -d build", + "start-self": "parcel samples/self-hosted/index.html", + "start-cdn": "parcel samples/cdn/index.html" }, "license": "MIT" } diff --git a/samples/cdn/index.html b/samples/cdn/index.html new file mode 100644 index 0000000..1cad5ca --- /dev/null +++ b/samples/cdn/index.html @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + +

CDN WordPOS sample

+ Open console to see results. + +
 
+ + + + + + diff --git a/samples/main.js b/samples/main.js new file mode 100644 index 0000000..5c3349d --- /dev/null +++ b/samples/main.js @@ -0,0 +1,32 @@ +let assertLikely = (r) => { + console.assert(r.def === 'with considerable certainty'); + console.assert(r.pos === 'r'); + console.assert(r.synsetOffset === '00139421'); +}; + +console.group('Likely'); +wordpos.isAdverb('likely').then(res => console.assert(res)); +wordpos.isAdverb('likely', (res, ...profile) => console.log('callback with profile', res, profile)); + +wordpos.getAdverbs('this is is lately a likely tricky business this is') + .then(res => { + let expect = {lately: 1, likely: 1}; + console.log('getAdverbs:', res); + console.assert(res[0] in expect); // NOTE: order is NOT gauranteed! + console.assert(res[1] in expect); + }); + +wordpos.lookupAdverb('likely') + .then(res => { + console.log('lookupAdverb:', res[0]); + assertLikely(res[0]); + }); +// wordpos.lookup('likely').then(res, console.log('lookup ===', res)) + +wordpos.seek('00139421', 'r') + .then(res => { + console.log('seek:', res); + assertLikely(res); + }); + +setTimeout(() => console.groupEnd('Likely'), 1000); diff --git a/samples/self-hosted/index.html b/samples/self-hosted/index.html new file mode 100644 index 0000000..dcdbc03 --- /dev/null +++ b/samples/self-hosted/index.html @@ -0,0 +1,52 @@ + + + + + Wordpos in the browser + + + + + + + + + + + + + +

Self-hosted WordPOS sample

+ Open console to see results. + +
 
+ + + + + + diff --git a/scripts/makeJsonDict.js b/scripts/makeJsonDict.js new file mode 100644 index 0000000..f80c549 --- /dev/null +++ b/scripts/makeJsonDict.js @@ -0,0 +1,98 @@ +#!/usr/bin/env node + +/** + * takes original WordNet index & data files and converts to + * exported JSON format with lemma as the key. + */ + +let fs = require('fs'); +let path = require('path'); + +let outPath = './dict'; // browser-use files +let testPath = './test/dict'; // mocha files in CJS format + +let posExt = ['adj', 'adv', 'noun', 'verb']; +let dictRoot = require('wordnet-db').path; // source files + +const fileTypes = { + data: true, + index: true +}; +const [,, ...args] = process.argv; + +if (!args.length || args.filter(p => !(p in fileTypes)).length) { + console.log('Converts wordnet-db index & data files to JSON format for use in the browser.'); + console.log('\nUsage: makeJsonDict.js index|data'); + process.exit(1); +} + +function uniq(arr) { + return arr.filter((v, i) => arr.indexOf(v) === i); +} + +console.time('Done'); + +// create out directory +const ensurePath = (path) => { + try { + fs.statSync(path); + } catch (e) { + fs.mkdirSync(path); + } +}; + +ensurePath(outPath); +ensurePath(testPath); + +function processFile(name) { + + // read the file as text + function loadFile(pos) { + console.time(' load'); + let inPath = path.resolve(dictRoot, name + '.' + pos); + let text = fs.readFileSync(inPath, 'utf8'); + console.timeEnd(' load'); + return text; + } + + // convert raw text to JSON and write to file + function processText(pos, text) { + let obj = {}; + let sp = ' '; + console.time(' process'); + text.split('\n').forEach(line => { + if (!line || line[0] === sp) return; + let spi = line.indexOf(sp); + let key = line.substr(0, spi); + line = line.substring(1 + spi, line.lastIndexOf(sp + sp)) + obj[key] = line; + }); + console.timeEnd(' process'); + return obj; + } + + function writeFile(pos, obj) { + console.time(' write'); + let text = JSON.stringify(obj); + fs.writeFileSync(path.resolve(outPath, name + '.' + pos + '.js'), + 'export default ' + text); + + // also write for mocha tests + fs.writeFileSync(path.resolve(testPath, name + '.' + pos + '.js'), + 'module.exports.default = ' + text); + + console.timeEnd(' write'); + } + + posExt.forEach(pos => { + console.log('\n', name, pos, ':'); + let text = loadFile(pos); + let obj = processText(pos, text); + writeFile(pos, obj); + }); +} + +uniq(args).forEach(processFile); + +console.log('\nWritten to', path.resolve(outPath)); +console.timeEnd('Done'); diff --git a/src/browser/baseFile.js b/src/browser/baseFile.js new file mode 100644 index 0000000..abce7c6 --- /dev/null +++ b/src/browser/baseFile.js @@ -0,0 +1,62 @@ +/** + * browser/baseFile.js + * + * Copyright (c) 2012-2019 mooster@42at.com + * https://github.com/moos/wordpos + * + * Released under MIT license + */ + +let isTest = window.__mocha; + +class BaseFile { + + /** + * file contents - in browser it's just a string & not a file! + * @type {Object} + */ + file = {}; + + /** + * constructor + * @param {type} type - 'index' or 'data' + * @param {string} dictPath - path to dict db + * @param {string} posName - one of 'noun', 'verb', 'adj', 'adv' + * @param {object} [options] - @see WordPOS options + */ + + constructor(type, dictPath, posName, options) { + this.type = type; + this.filePath = `${dictPath}/${type}.${posName}.js`; + this.posName = posName; + this.loadError = null; + this.options = Object.assign({}, options); + } + + load() { + if (this.loadError) return Promise.reject(this.loadError); + this.options.debug && console.time('index load ' + this.posName); + + let promise = isTest + ? Promise.resolve(require(this.filePath)) + : ES6_IMPORT(`${this.filePath}`); // prevent parcel from clobbering dynamic import + + this.options.debug && console.timeEnd('index load ' + this.posName) + return promise + .then(exports => { + this.file = exports.default + }) + .catch(err => { + console.error(`Error loading "${this.type}" file ${this.filePath}.`, err); + this.loadError = err; + throw err; + }); + } + + ready(fn, args) { + return this.load().then(() => fn.apply(this, args)); + } +} + +// export default BaseFile; +module.exports = BaseFile; diff --git a/src/browser/dataFile.js b/src/browser/dataFile.js new file mode 100644 index 0000000..ec48a32 --- /dev/null +++ b/src/browser/dataFile.js @@ -0,0 +1,90 @@ +/** + * browser/dataFile.js + * + * Copyright (c) 2012-2019 mooster@42at.com + * https://github.com/moos/wordpos + * + * Portions: Copyright (c) 2011, Chris Umbel + * + * Released under MIT license + */ + +const { lineDataToJSON, LEX_NAMES } = require('../common'); +const { zeroPad } = require('../util'); +const BaseFile = require('./baseFile'); + +/** + * get parsed line from data file + * + * @param {string} offset The offset key + * @return {object} Data record object + * @this DataFile + */ +function seek(offset) { + let str = this.file[offset]; + if (!str) return {}; + // offset was extracted for the key - add it back to line data + return lineDataToJSON(offset + ' ' + str); +} + +/** + * lookup offsets in data file + * + * @param offsets {array} - array of offsets to lookup (obtained from index.find()) + * @param callback{function} (optional) - callback function + * @returns {Promise.[]} array of or single data record + * @this DataFile + */ +function lookup(offsets, callback) { + var results = [], + self = this, + readLine = seek.bind(this), + valid = (item => item.pos), + single = !Array.isArray(offsets); + + if (single) offsets = [offsets]; + return new Promise(function(resolve, reject) { + results = offsets + .map(zeroPad) + .map(readLine) + .filter(valid); + + if (!results.length) { + let err = new RangeError(`No data at offsets ${offsets.join()} in ${self.filePath}.`); + callback && callback(err, single ? {} :[]); + reject(err); + } else { + if (single) results = results[0]; + callback && callback(null, results); + resolve(results); + } + }); +} + +/** + * DataFile class + * + * @param dictPath {string} - path to dict folder + * @param posName {string} - POS name + * @constructor + */ +class DataFile extends BaseFile { + + constructor(dictPath, posName) { + super('data', dictPath, posName); + } + + lookup() { + return this.ready(lookup, arguments); + } +} + +/** + * map of lexFilenum to lex names + * + * @see https://wordnet.princeton.edu/wordnet/man/lexnames.5WN.html + * @type {string[]} + */ +DataFile.LEX_NAMES = LEX_NAMES; + +module.exports = DataFile; diff --git a/src/browser/index.js b/src/browser/index.js new file mode 100644 index 0000000..131a539 --- /dev/null +++ b/src/browser/index.js @@ -0,0 +1,188 @@ +/** +* browser/index.js +* +* Copyright (c) 2012-2019 mooster@42at.com +* https://github.com/moos/wordpos +* +* Released under MIT license +*/ + +const { stopwords, prepText, makeStopwordString } = require('../util'); +const { is, get, getPOS, lookup, seek, lookupPOS } = require('../common'); +const { randX, rand } = require('../rand'); +const IndexFile = require('./indexFile'); +const DataFile = require('./dataFile'); + +const POS = { + n: 'noun', + v: 'verb', + a: 'adj', + r: 'adv' +}; + + +class WordPOS { + + options = {}; + + constructor(config) { + this.options = Object.assign({}, WordPOS.defaults, config); + + this.initFiles(); + if (Array.isArray(this.options.stopwords)) { + this.options.stopwords = makeStopwordString(this.options.stopwords); + } + } + + initFiles() { + const keys = Object.keys(POS); + const loadOne = (Comp, pos) => new Comp(this.options.dictPath, POS[pos], this.options); + const loader = (Comp) => keys.map(loadOne.bind(null, Comp)); + const reducer = (arr) => arr.reduce((coll, item, i) => (coll[keys[i]] = item, coll), {}); + + this.indexFiles = reducer(loader(IndexFile)); + this.dataFiles = reducer(loader(DataFile)); + + if (this.options.preload) { + this.loaded = this.preloadIndexes(this.options.preload); + } + } + + getFilesFor(pos) { + return { + index: this.indexFiles[pos], + data: this.dataFiles[pos] + }; + } + + /** + * loads index files + * + * @param {string|Array} [pos] POS to load (default: all) + * @return {Promise.} + */ + preloadIndexes(pos) { + let file = this.indexFile[pos]; + let load = p => file.load(); + let promise; + + if (!pos || pos === true) { // preload all + promise = Promise.all(Object.keys(POS).map(load)); + } + else if (typeof pos === 'string' && file) { + promise = load(pos); + } + else if (pos instanceof Array) { + promise = pos.forEach(pos => file && load(pos)); + } + + // TODO includeData + + return promise || Promise.reject(new RangeError(`Unknown POS "${pos}" for preload.`)); + } + + parse = prepText; + + seek = seek; + + /** + * isX() - Test if word is given POS + * @see is + */ + isAdjective = is('a'); + isAdverb = is('r'); + isNoun = is('n'); + isVerb = is('v'); + + /** + * getX() - Find all words in string that are given POS + * @see get + */ + getPOS = getPOS; + getAdjectives = get('isAdjective'); + getAdverbs = get('isAdverb'); + getNouns = get('isNoun'); + getVerbs = get('isVerb'); + + /** + * lookupX() - Lookup word definition if already know POS + * @see lookup + */ + lookup = lookupPOS; + lookupAdjective = lookup('a'); + lookupAdverb = lookup('r'); + lookupNoun = lookup('n'); + lookupVerb = lookup('v'); + + /** + * define randX() + * @see makeRandX + */ + rand = rand; + randAdjective = randX('a'); + randAdverb = randX('r'); + randNoun = randX('n'); + randVerb = randX('v'); + +} + +WordPOS.defaults = { + /** + * path to WordNet data (override only if not using wordnet-db) + * @type {string} + */ + dictPath: '', + + /** + * enable profiling, time in msec returned as second argument in callback + * @type {boolean} + */ + profile: false, + + /** + * if true, exclude standard stopwords. + * if array, stopwords to exclude, eg, ['all','of','this',...] + * if false, do not filter any stopwords. + * @type {boolean} + */ + stopwords: true, + + /** + * preload files. + * true - preload all POS + * false - do not preload any POS + * 'a' - preload adj + * ['a','v'] - preload adj & verb + * @type {boolean|string|Array} + */ + preload: false, + + /** + * include data files in preload + * @type {boolean} + */ + includeData: false, + + /** + * set to true to enable debug logging + * @type {boolean} + */ + debug: false + +}; + +/** + * access to WordNet DB + * @type {object} + */ +// WordPOS.WNdb = WNdb; // FIXME + +/** + * access to stopwords + * @type {Array} + */ +WordPOS.stopwords = stopwords; + +// Export as CJS handled by Parcel, otherwise will get WordPOS.default +// if use: export default WordPOS; +module.exports = WordPOS; diff --git a/src/browser/indexFile.js b/src/browser/indexFile.js new file mode 100644 index 0000000..f36e0ad --- /dev/null +++ b/src/browser/indexFile.js @@ -0,0 +1,135 @@ +/** + * browser/indexFile.js + * + * Copyright (c) 2012-2019 mooster@42at.com + * https://github.com/moos/wordpos + * + * Released under MIT license + */ + +const { indexLookup } = require('../common'); +const { sample } = require('../util'); +const BaseFile = require('./baseFile'); +const Trie = require('../../lib/natural/trie/trie'); + +/** + * find a search term in an index file (using fast index) + * + * Calls to same bucket are queued for callback using the piper. + * + * @param search {string} - word to search for + * @param callback {function} - callback receives found line and tokens + * @returns none + * @this IndexFile + */ +function find(search, callback) { + var miss = {status: 'miss'}; + + if (!(search in this.file)) { + callback(miss); + return; + } + + var + line = this.file[search], + tokens = line.split(/\s+/), + result = { + status: 'hit', + key: search, + line: line, + tokens: tokens + }; + + result.tokens.unshift(search); + callback(result); +} + +/** + * Select words at random for POS + * + * @param {string} startsWith - string that results should start with + * @param {integer} count - number of results to return + * @param {Function} callback - receives (results, startsWith) + * @return {Promise} receives results + * @this IndexFile + */ +function rand(startsWith, count, callback) { + const done = (res) => { + callback(res, startsWith || ''); + return Promise.resolve(res); + }; + + const doSample = (values) => { + let res = sample(values, count); + // console.timeEnd('getkeys') + return done(res); + }; + + const time = (label) => { + this.options.debug && console.time(label + ' ' + this.posName); + }; + + const timeEnd = (label) => { + this.options.debug && console.timeEnd(label + ' ' + this.posName); + }; + + if (!startsWith) { + // console.time('getkeys') + return doSample(this.getKeys()); + } + + // calc trie if haven't done so yet + if (!this.trie) { + time('Trie'); + this.trie = new Trie(); + this.trie.addStrings(this.getKeys()); + timeEnd('Trie'); + } + + let keys = []; + time('trie-withprefix'); + keys = this.trie.keysWithPrefix(startsWith); + timeEnd('trie-withprefix'); + + // TODO cache results? + + return keys.length ? doSample(keys) : done([]); +} + +/** + * IndexFile class + */ +class IndexFile extends BaseFile { + + keys = null; + + /** + * @param dictPath {string} - WordNet db dict path + * @param posName {string} - name of index: noun, verb, adj, adv + * @param {object} [options] - @see WordPOS options + * @constructor + */ + constructor(dictPath, posName, options) { + super('index', dictPath, posName, options); + this.options = Object.assign({}, options); + this.posName = posName; + } + + getKeys() { + return this.keys || (this.keys = Object.keys(this.file)); + } + + lookup() { + return this.ready(indexLookup, arguments); + } + + find() { + return this.ready(find, arguments); + } + + rand() { + return this.ready(rand, arguments); + } +} + +module.exports = IndexFile; diff --git a/src/common.js b/src/common.js new file mode 100644 index 0000000..35b45d7 --- /dev/null +++ b/src/common.js @@ -0,0 +1,403 @@ +/** +* common.js +* +* Copyright (c) 2012-2019 mooster@42at.com +* https://github.com/moos/wordpos +* +* Portions: Copyright (c) 2011, Chris Umbel +* +* Released under MIT license +*/ + +var { normalize, nextTick, isString, uniq, sample, diff, flat } = require('./util'); + +function error(err, callback) { + if (isString(err)) err = new RangeError(err); + callback && callback(err, {}); + return Promise.reject(err); +} + +/** + * factory for main lookup function + * + * @param pos {string} - n/v/a/r + * @returns {Function} - lookup function bound to POS + * @this WordPOS + */ +function lookup(pos) { + return function(word, callback) { + var profile = this.options.profile, + start = profile && new Date(), + files = this.getFilesFor(pos), + args = []; + + word = normalize(word); + + // lookup index + return files.index.lookup(word) + .then(function(result) { + if (result) { + // lookup data + return files.data.lookup(result.synsetOffset).then(done); + } else { + // not found in index + return done([]); + } + }) + .catch(done); + + function done(results) { + if (results instanceof Error) { + args.push([], word); + } else { + args.push(results, word); + } + //console.log(3333, args) + profile && args.push(new Date() - start); + nextTick(callback, args); + return results; + } + }; +} + +/** + * find a word and prepare its lexical record + * + * @param word {string} - search word + * @param callback {function} - callback function receives result + * @returns {Promise.} + * @this IndexFile + * + * Credit for this routine to https://github.com/NaturalNode/natural + */ +function indexLookup(word, callback) { + var self = this; + return new Promise(function(resolve, reject){ + self.find(word, function (record) { + var indexRecord = null, + i; + + if (record.status == 'hit') { + var ptrs = [], offsets = []; + let n = parseInt(record.tokens[3]); + + for (i = 0; i < n; i++) { + ptrs.push(record.tokens[i]); + } + + n = parseInt(record.tokens[2]); + for (i = 0; i < n; i++) { + offsets.push(record.tokens[ptrs.length + 6 + i]); + } + + indexRecord = { + lemma : record.tokens[0], + pos : record.tokens[1], + ptrSymbol : ptrs, + senseCnt : parseInt(record.tokens[ptrs.length + 4], 10), + tagsenseCnt : parseInt(record.tokens[ptrs.length + 5], 10), + synsetOffset: offsets + }; + } + callback && callback(indexRecord); + resolve(indexRecord); + }); + }); +} + +/** + * lookup a word in all indexes + * + * @param word {string} - search word + * @param callback {Function} (optional) - callback with (results, word) signature + * @returns {Promise} + * @this WordPOS + */ +function lookupPOS(word, callback) { + var self = this, + results = [], + profile = this.options.profile, + start = profile && new Date(), + methods = ['lookupAdverb', 'lookupAdjective', 'lookupVerb', 'lookupNoun']; + + return Promise + .all(methods.map(exec)) + .then(done) + .catch(error); + + function exec(method) { + return self[ method ] + .call(self, word) + .then(function collect(result){ + results = results.concat(result); + }); + } + + function done() { + var args = [results, word]; + profile && args.push(new Date() - start); + nextTick(callback, args); + return results; + } + + function error(err) { + nextTick(callback, [[], word]); + throw err; + } +} + +/** + * getX() factory function + * + * @param isFn {function} - an isX() function + * @returns {Function} + * @this IndexFile + */ +function get(isFn) { + return function(text, callback, _noprofile) { + var profile = this.options.profile && !_noprofile, + start = profile && new Date(), + words = this.parse(text), + results = [], + self = this, + first = words.shift(); + + // test one first & check for error, otherwise + // map is inoccuous to errors! + return exec(first) + .then(() => Promise.all(words.map(exec))) + .then(done) + .catch(err => { + // done(); // callback signature is same! // FIXME + return Promise.reject(err); + }); + + function exec(word) { + return self[isFn] + .call(self, word, null, /*_noprofile*/ true) + .then(function collect(result) { + result && results.push(word); + }); + } + + function done(){ + var args = [results]; + profile && args.push(new Date() - start); + nextTick(callback, args); + return results; + } + }; +} + +/** + * getPOS() - Find all POS for all words in given string + * + * @param text {string} - words to lookup for POS + * @param callback {function} (optional) - receives object with words broken into POS or 'rest', ie, + * Object: {nouns:[], verbs:[], adjectives:[], adverbs:[], rest:[]} + * @return Promise - resolve function receives data object + */ +function getPOS(text, callback) { + var self = this, + data = {nouns:[], verbs:[], adjectives:[], adverbs:[], rest:[]}, + profile = this.options.profile, + start = profile && new Date(), + words = this.parse(text), + methods = ['getAdverbs', 'getAdjectives', 'getVerbs', 'getNouns']; + + return Promise + .all(methods.map(exec)) + .then(done) + .catch(error); + + function exec(method) { + return self[ method ] + .call(self, text, null, true) + .then(function collect(results) { + // getAdjectives --> adjectives + var pos = method.replace('get','').toLowerCase(); + data[ pos ] = results; + }); + } + + function done() { + var args = [data]; + var matches = uniq(flat(Object.values(data))); + data.rest = diff(words, matches); + + profile && args.push(new Date() - start); + nextTick(callback, args); + return data; + } + + function error(err) { + nextTick(callback, []); + throw err; + } +} + +/** + * isX() factory function + * + * @param pos {string} - n/v/a/r + * @returns {Function} + * @this WordPOS + */ +function is(pos){ + return function(word, callback, _noprofile) { + // disable profiling when isX() used internally + var profile = this.options.profile && !_noprofile, + start = profile && new Date(), + args = [], + index = this.getFilesFor(pos).index; + word = normalize(word); + + return index + .lookup(word) + .then(function(record) { + var result = !!record; + args.push(result, word); + profile && args.push(new Date() - start); + nextTick(callback, args); + return result; + }); + }; +} + +/** + * parse a single data file line, returning data object + * + * @param line {string} - a single line from WordNet data file + * @returns {object} + * + * Credit for this routine to https://github.com/NaturalNode/natural + */ +function lineDataToJSON(line, location) { + var data = line.split('| '), + tokens = data[0].split(/\s+/), + ptrs = [], + wCnt = parseInt(tokens[3], 16), + synonyms = [], + i; + + for(i = 0; i < wCnt; i++) { + synonyms.push(tokens[4 + i * 2]); + } + + var ptrOffset = (wCnt - 1) * 2 + 6; + let n = parseInt(tokens[ptrOffset], 10); + for(i = 0; i < n; i++) { + ptrs.push({ + pointerSymbol: tokens[ptrOffset + 1 + i * 4], + synsetOffset: tokens[ptrOffset + 2 + i * 4], + pos: tokens[ptrOffset + 3 + i * 4], + sourceTarget: tokens[ptrOffset + 4 + i * 4] + }); + } + + // break "gloss" into definition vs. examples + var glossArray = data[1].split('; '); + var definition = glossArray[0]; + var examples = glossArray.slice(1); + var lexFilenum = parseInt(tokens[1], 10); + + for (var k = 0; k < examples.length; k++) { + examples[k] = examples[k].replace(/\"/g,'').replace(/\s\s+/g,''); + } + + return { + synsetOffset: tokens[0], + lexFilenum: lexFilenum, + lexName: LEX_NAMES[ lexFilenum ], + pos: tokens[2], + wCnt: wCnt, + lemma: tokens[4], + synonyms: synonyms, + lexId: tokens[5], + ptrs: ptrs, + gloss: data[1], + def: definition, + exp: examples + }; +} + + +/** + * seek - get record at offset for pos + * + * @param offset {number} - synset offset + * @param pos {string} - POS a/r/n/v + * @param callback {function} - optional callback + * @returns Promise + * @this WordPOS + */ +function seek(offset, pos, callback){ + var offsetTmp = Number(offset); + if (isNaN(offsetTmp) || offsetTmp <= 0) return error('Offset must be valid positive number: ' + offset, callback); + + var data = this.getFilesFor(pos).data; + if (!data) return error('Incorrect POS - 2nd argument must be a, r, n or v.', callback); + + return data.lookup(offset, callback); +} + +const LEX_NAMES = [ + 'adj.all', + 'adj.pert', + 'adv.all', + 'noun.Tops', + 'noun.act', + 'noun.animal', + 'noun.artifact', + 'noun.attribute', + 'noun.body', + 'noun.cognition', + 'noun.communication', + 'noun.event', + 'noun.feeling', + 'noun.food', + 'noun.group', + 'noun.location', + 'noun.motive', + 'noun.object', + 'noun.person', + 'noun.phenomenon', + 'noun.plant', + 'noun.possession', + 'noun.process', + 'noun.quantity', + 'noun.relation', + 'noun.shape', + 'noun.state', + 'noun.substance', + 'noun.time', + 'verb.body', + 'verb.change', + 'verb.cognition', + 'verb.communication', + 'verb.competition', + 'verb.consumption', + 'verb.contact', + 'verb.creation', + 'verb.emotion', + 'verb.motion', + 'verb.perception', + 'verb.possession', + 'verb.social', + 'verb.stative', + 'verb.weather', + 'adj.ppl' +]; + +module.exports= { + indexLookup, + is, + get, + seek, + getPOS, + + lineDataToJSON, + LEX_NAMES, + lookup, + lookupPOS +} diff --git a/src/dataFile.js b/src/node/dataFile.js similarity index 62% rename from src/dataFile.js rename to src/node/dataFile.js index 3d115c2..b5d722b 100644 --- a/src/dataFile.js +++ b/src/node/dataFile.js @@ -1,7 +1,7 @@ /*! * dataFile.js * - * Copyright (c) 2012-2018 mooster@42at.com + * Copyright (c) 2012-2019 mooster@42at.com * https://github.com/moos/wordpos * * Portions: Copyright (c) 2011, Chris Umbel @@ -11,7 +11,12 @@ var fs = require('fs'), path = require('path'), - _ = require('underscore'); + _ = require('underscore'), + { zeroPad } = require('../util'), + { + lineDataToJSON, + LEX_NAMES + } = require('../common'); /** * sanity check read data - line must start with zero-padded location @@ -20,67 +25,7 @@ var fs = require('fs'), * @return {boolean} true if line data is good */ function dataCheck(line, location) { - var pad = '00000000', // 8 zeros - padded = String(pad + location).slice( - pad.length); - return line.indexOf(padded) === 0; -} - -/** - * parse a single data file line, returning data object - * - * @param line {string} - a single line from WordNet data file - * @returns {object} - * - * Credit for this routine to https://github.com/NaturalNode/natural - */ -function lineDataToJSON(line, location) { - if (!dataCheck(line, location)) return new Error('Bad data at location ' + location); - - var data = line.split('| '), - tokens = data[0].split(/\s+/), - ptrs = [], - wCnt = parseInt(tokens[3], 16), - synonyms = [], - i; - - for(i = 0; i < wCnt; i++) { - synonyms.push(tokens[4 + i * 2]); - } - - var ptrOffset = (wCnt - 1) * 2 + 6; - for(i = 0; i < parseInt(tokens[ptrOffset], 10); i++) { - ptrs.push({ - pointerSymbol: tokens[ptrOffset + 1 + i * 4], - synsetOffset: parseInt(tokens[ptrOffset + 2 + i * 4], 10), - pos: tokens[ptrOffset + 3 + i * 4], - sourceTarget: tokens[ptrOffset + 4 + i * 4] - }); - } - - // break "gloss" into definition vs. examples - var glossArray = data[1].split("; "); - var definition = glossArray[0]; - var examples = glossArray.slice(1); - var lexFilenum = parseInt(tokens[1], 10); - - for (var k = 0; k < examples.length; k++) { - examples[k] = examples[k].replace(/\"/g,'').replace(/\s\s+/g,''); - } - - return { - synsetOffset: parseInt(tokens[0], 10), - lexFilenum: lexFilenum, - lexName: DataFile.LEX_NAMES[ lexFilenum ], - pos: tokens[2], - wCnt: wCnt, - lemma: tokens[4], - synonyms: synonyms, - lexId: tokens[5], - ptrs: ptrs, - gloss: data[1], - def: definition, - exp: examples - }; + return line.indexOf(zeroPad(location)) === 0; } /** @@ -98,6 +43,7 @@ function readLocation(location, callback) { len = file.nominalLineLength, buffer = new Buffer.alloc(len); + location = Number(location); readChunk(location, function(err, count) { if (err) { //console.log(err); @@ -105,11 +51,13 @@ function readLocation(location, callback) { return; } //console.log(' read %d bytes at <%d>', count, location); + if (!dataCheck(str, location)) return callback(new RangeError('No data at offset ' + location)); + callback(null, lineDataToJSON(str, location)); }); function readChunk(pos, cb) { - var nonDataErr = new Error('no data at offset ' + pos); + var nonDataErr = new RangeError('No data at offset ' + pos); fs.read(file.fd, buffer, 0, len, pos, function (err, count) { if (!count) return cb(nonDataErr, count); @@ -213,7 +161,6 @@ function promisifyInto(collect) { } } - /** * DataFile class * @@ -258,55 +205,8 @@ DataFile.MAX_LINE_LENGTH = { /** * map of lexFilenum to lex names * - * @see https://wordnet.princeton.edu/wordnet/man/lexnames.5WN.html * @type {string[]} */ -DataFile.LEX_NAMES = [ - 'adj.all', - 'adj.pert', - 'adv.all', - 'noun.Tops', - 'noun.act', - 'noun.animal', - 'noun.artifact', - 'noun.attribute', - 'noun.body', - 'noun.cognition', - 'noun.communication', - 'noun.event', - 'noun.feeling', - 'noun.food', - 'noun.group', - 'noun.location', - 'noun.motive', - 'noun.object', - 'noun.person', - 'noun.phenomenon', - 'noun.plant', - 'noun.possession', - 'noun.process', - 'noun.quantity', - 'noun.relation', - 'noun.shape', - 'noun.state', - 'noun.substance', - 'noun.time', - 'verb.body', - 'verb.change', - 'verb.cognition', - 'verb.communication', - 'verb.competition', - 'verb.consumption', - 'verb.contact', - 'verb.creation', - 'verb.emotion', - 'verb.motion', - 'verb.perception', - 'verb.possession', - 'verb.social', - 'verb.stative', - 'verb.weather', - 'adj.ppl' -]; +DataFile.LEX_NAMES = LEX_NAMES; module.exports = DataFile; diff --git a/src/node/index.js b/src/node/index.js new file mode 100644 index 0000000..eb724bd --- /dev/null +++ b/src/node/index.js @@ -0,0 +1,190 @@ +/*! +* node/index.js +* +* Node.js part-of-speech utilities using WordNet database. +* +* Copyright (c) 2012-2019 mooster@42at.com +* https://github.com/moos/wordpos +* +* Released under MIT license +*/ + +var + _ = require('underscore')._, + util = require('util'), + stopwordsStr, + WNdb = require('wordnet-db'), + DataFile = require('./dataFile'), + IndexFile = require('./indexFile'), + { + nextTick, + normalize, + tokenizer, + prepText, + makeStopwordString, + stopwords + } = require('../util'), + { + is, + get, + getPOS, + seek, + lookup, + lookupPOS + } = require('../common'); + +stopwordsStr = makeStopwordString(stopwords); + +/** + * @class WordPOS + * @param options {object} -- @see WordPOS.defaults + * @constructor + */ +var WordPOS = function(options) { + var dictPath; + + this.options = _.defaults({}, _.isObject(options) && options || {}, { + dictPath: WNdb.path + }, WordPOS.defaults); + + dictPath = this.options.dictPath; + + this.nounIndex = new IndexFile(dictPath, 'noun'); + this.verbIndex = new IndexFile(dictPath, 'verb'); + this.adjIndex = new IndexFile(dictPath, 'adj'); + this.advIndex = new IndexFile(dictPath, 'adv'); + + this.nounData = new DataFile(dictPath, 'noun'); + this.verbData = new DataFile(dictPath, 'verb'); + this.adjData = new DataFile(dictPath, 'adj'); + this.advData = new DataFile(dictPath, 'adv'); + + // define randX() functions + require('./rand').init(this); + + if (_.isArray(this.options.stopwords)) { + this.options.stopwords = makeStopwordString(this.options.stopwords); + } +}; + + +WordPOS.defaults = { + /** + * path to WordNet data (override only if not using wordnet-db) + */ + dictPath: '', + + /** + * enable profiling, time in msec returned as second argument in callback + */ + profile: false, + + /** + * if true, exclude standard stopwords. + * if array, stopwords to exclude, eg, ['all','of','this',...] + * if false, do not filter any stopwords. + */ + stopwords: true +}; + +var wordposProto = WordPOS.prototype; + +/** + * lookup a word in all indexes + * + * @param word {string} - search word + * @param callback {Function} (optional) - callback with (results, word) signature + * @returns {Promise} + */ +wordposProto.lookup = lookupPOS; + +/** + * getPOS() - Find all POS for all words in given string + * + * @param text {string} - words to lookup for POS + * @param callback {function} (optional) - receives object with words broken into POS or 'rest', ie, + * Object: {nouns:[], verbs:[], adjectives:[], adverbs:[], rest:[]} + * @return Promise - resolve function receives data object + */ +wordposProto.getPOS = getPOS; + +/** + * get index and data files for given pos + * + * @param pos {string} - n/v/a/r + * @returns {object} - keys {index, data} + */ +wordposProto.getFilesFor = function (pos) { + switch(pos) { + case 'n': + return {index: this.nounIndex, data: this.nounData}; + case 'v': + return {index: this.verbIndex, data: this.verbData}; + case 'a': case 's': + return {index: this.adjIndex, data: this.adjData}; + case 'r': + return {index: this.advIndex, data: this.advData}; + } + return {}; +}; + + +/** + * lookupX() - Lookup word definition if already know POS + * @see lookup + */ +wordposProto.lookupAdjective = lookup('a'); +wordposProto.lookupAdverb = lookup('r'); +wordposProto.lookupNoun = lookup('n'); +wordposProto.lookupVerb = lookup('v'); + +/** + * isX() - Test if word is given POS + * @see is + */ +wordposProto.isAdjective = is('a'); +wordposProto.isAdverb = is('r'); +wordposProto.isNoun = is('n'); +wordposProto.isVerb = is('v'); + +/** + * getX() - Find all words in string that are given POS + * @see get + */ +wordposProto.getAdjectives = get('isAdjective'); +wordposProto.getAdverbs = get('isAdverb'); +wordposProto.getNouns = get('isNoun'); +wordposProto.getVerbs = get('isVerb'); + +/** + * parse - get deduped, less stopwords + * + * @param text {string|array} - string of words to parse. If array is given, it is left in tact. + * @returns {array} + */ +wordposProto.parse = prepText; + +/** + * seek - get record at offset for pos + * + * @param offset {number} - synset offset + * @param pos {string} - POS a/r/n/v + * @param callback {function} - optional callback + * @returns Promise + */ +wordposProto.seek = seek; + +/** + * access to WordNet DB + * @type {object} + */ +WordPOS.WNdb = WNdb; + +/** + * access to stopwords + * @type {Array} + */ +WordPOS.stopwords = stopwords; + + +module.exports = WordPOS; diff --git a/src/indexFile.js b/src/node/indexFile.js similarity index 80% rename from src/indexFile.js rename to src/node/indexFile.js index acc4a46..74a4c3e 100644 --- a/src/indexFile.js +++ b/src/node/indexFile.js @@ -1,9 +1,9 @@ /*! - * indexFile.js + * node/indexFile.js * * implements fast index lookup of WordNet's index files * - * Copyright (c) 2012-2018 mooster@42at.com + * Copyright (c) 2012-2019 mooster@42at.com * https://github.com/moos/wordpos * * Portions: Copyright (c) 2011, Chris Umbel @@ -16,6 +16,7 @@ var _ = require('underscore')._, path = require('path'), fs = require('fs'), piper = require('./piper'), + { indexLookup } = require('../common'), KEY_LENGTH = 3; @@ -133,49 +134,6 @@ function find(search, callback) { } } -/** - * find a word and prepare its lexical record - * - * @param word {string} - search word - * @param callback {function} - callback function receives result - * @returns none - * - * Credit for this routine to https://github.com/NaturalNode/natural - */ -function lookup(word, callback) { - var self = this; - - return new Promise(function(resolve, reject){ - self.find(word, function (record) { - var indexRecord = null, - i; - - if (record.status == 'hit') { - var ptrs = [], offsets = []; - - for (i = 0; i < parseInt(record.tokens[3]); i++) - ptrs.push(record.tokens[i]); - - for (i = 0; i < parseInt(record.tokens[2]); i++) - offsets.push(parseInt(record.tokens[ptrs.length + 6 + i], 10)); - - indexRecord = { - lemma : record.tokens[0], - pos : record.tokens[1], - ptrSymbol : ptrs, - senseCnt : parseInt(record.tokens[ptrs.length + 4], 10), - tagsenseCnt : parseInt(record.tokens[ptrs.length + 5], 10), - synsetOffset: offsets - }; - } - - callback && callback(indexRecord); - resolve(indexRecord); - }); - }); -} - - /** * loads fast index data and return fast index find function * @@ -216,7 +174,7 @@ var IndexFile = function(dictPath, name) { initIndex(this); }; -IndexFile.prototype.lookup = lookup; +IndexFile.prototype.lookup = indexLookup; IndexFile.prototype.find = find; /** diff --git a/src/piper.js b/src/node/piper.js similarity index 98% rename from src/piper.js rename to src/node/piper.js index c0985de..fc13be9 100644 --- a/src/piper.js +++ b/src/node/piper.js @@ -4,7 +4,7 @@ * executes multiple async i/o tasks and pools similar callbacks, * calling i/o open/close when all incoming tasks are done. * - * Copyright (c) 2012-2016 mooster@42at.com + * Copyright (c) 2012-2019 mooster@42at.com * https://github.com/moos/wordpos * * Released under MIT license @@ -79,4 +79,3 @@ piper.wrapper = function(self, task /*, result...*/){ module.exports = piper; - diff --git a/src/node/rand.js b/src/node/rand.js new file mode 100644 index 0000000..62f9ca0 --- /dev/null +++ b/src/node/rand.js @@ -0,0 +1,164 @@ +/*! + * node/rand.js + * + * define rand() and randX() functions on wordpos + * + * Copyright (c) 2012-2019 mooster@42at.com + * https://github.com/moos/wordpos + * + * Released under MIT license + */ + +var _ = require('underscore')._, + { randX, rand } = require('../rand'), + Trie = require('../../lib/natural/trie/trie'), + IndexFile = require(`./indexFile`), + KEY_LENGTH = 3; + +/** + * rand function (bound to index) + * + * @param startsWith {string} - get random word(s) that start with this, or '' + * @param num {number} - number of words to return + * @param callback {function} - callback function, receives words array and startsWith + * @returns Promise + * @this IndexFile + */ +function randomizer(startsWith, num, callback){ + var self = this, + nextKey = null, + trie = this.fastIndex.trie, + key, keys; + + return new Promise(function(resolve, reject) { + // console.log('-- ', startsWith, num, self.fastIndex.indexKeys.length); + if (startsWith) { + key = startsWith.slice(0, KEY_LENGTH); + + /** + * if key is 'a' or 'ab' (<3 chars), search for ALL keys starting with that. + */ + if (key.length < KEY_LENGTH) { + + // calc trie if haven't done so yet + if (!trie) { + // console.time('trie'); + trie = new Trie(); + trie.addStrings(self.fastIndex.indexKeys); + self.fastIndex.trie = trie; + //console.log(' +++ Trie calc '); + // console.timeEnd('trie') + } + + try { + // trie throws if not found!!!!! + keys = trie.keysWithPrefix(startsWith); + } catch (e) { + keys = []; + } + + // read all keys then select random word. + // May be large disk read! + key = keys[0]; + nextKey = _.last(keys); + } + + if (!key || !(key in self.fastIndex.offsets)) { + callback && callback([], startsWith); + resolve([]); + } + + } else { + // no startWith given - random select among keys + keys = _.sample(self.fastIndex.indexKeys, num); + + // if num > 1, run each key independently and collect results + if (num > 1) { + var results = [], ii = 0; + _(keys).each(function (startsWith) { + self.rand(startsWith, 1, function (result) { + results.push(result[0]); + if (++ii == num) { + callback && callback(results, ''); + resolve(results); + } + }); + }); + return; + } + key = keys; + } + + // prepare the piper + var args = [key, nextKey, self], + task = 'rand:' + key + nextKey, + context = [startsWith, num, callback]; // last arg MUST be callback + + // pay the piper + self.piper(task, IndexFile.readIndexBetweenKeys, args, context, collector); + + function collector(key, nextKey, index, startsWith, num, callback, buffer) { + var lines = buffer.toString().split('\n'), + matches = lines.map(function (line) { + return line.substring(0, line.indexOf(' ')); + }); + //console.log(' got lines for key ', key, lines.length); + + // we got bunch of matches for key - now search within for startsWith + if (startsWith !== key) { + // binary search for startsWith within set of matches + var ind = _.sortedIndex(matches, startsWith); + if (ind >= lines.length || matches[ind].indexOf(startsWith) === -1) { + callback && callback([], startsWith); + resolve([]); + return; + } + + var trie = new Trie(); + trie.addStrings(matches); + //console.log('Trie > ', trie.matchesWithPrefix( startsWith )); + matches = trie.keysWithPrefix(startsWith); + } + + var words = _.sample(matches, num); + callback && callback(words, startsWith); + resolve(words); + } + + }); // Promise +} + +/** + * bind rand() to index + * + * @param index {object} - the IndexFile instance + * @returns {function} - bound rand function for index + */ +function randomify(index){ + if (!index.fastIndex) throw new Error('rand requires fastIndex'); + index.rand = _.bind(randomizer, index); +} + + +module.exports = { + + init: function(wordposProto) { + randomify(wordposProto.nounIndex); + randomify(wordposProto.verbIndex); + randomify(wordposProto.adjIndex); + randomify(wordposProto.advIndex); + + /** + * define rand() (all POS) + */ + wordposProto.rand = rand; + + /** + * define randX() + */ + wordposProto.randAdjective = randX('a'); + wordposProto.randAdverb = randX('r'); + wordposProto.randNoun = randX('n'); + wordposProto.randVerb = randX('v'); + } +}; diff --git a/src/rand.js b/src/rand.js index 17808c8..c39ee03 100644 --- a/src/rand.js +++ b/src/rand.js @@ -1,28 +1,22 @@ -/*! - * rand.js - * - * define rand() and randX() functions on wordpos - * - * Copyright (c) 2012-2016 mooster@42at.com - * https://github.com/moos/wordpos - * - * Released under MIT license - */ - -var _ = require('underscore')._, - util = require('util'), - Trie = require('../lib/natural/trie/trie'), - IndexFile = require('./indexFile'), - KEY_LENGTH = 3; +/** +* rand.js +* +* Copyright (c) 2012-2019 mooster@42at.com +* https://github.com/moos/wordpos +* +* Released under MIT license +*/ +var { uniq, sample } = require('./util'); /** * factory function for randX() * * @param pos {string} - a,r,n,v * @returns {Function} - rand function bound to an index file + * @this WordPOS */ -function makeRandX(pos){ +function randX(pos){ return function(opts, callback, _noprofile) { // disable profiling when isX() used internally var profile = this.options.profile && !_noprofile, @@ -44,154 +38,40 @@ function makeRandX(pos){ }; } -/** - * rand function (bound to index) - * - * @param startsWith {string} - get random word(s) that start with this, or '' - * @param num {number} - number of words to return - * @param callback {function} - callback function, receives words array and startsWith - * @returns Promise - */ -function rand(startsWith, num, callback){ - var self = this, - nextKey = null, - trie = this.fastIndex.trie, - key, keys; - - return new Promise(function(resolve, reject) { - - //console.log('-- ', startsWith, num, self.fastIndex.indexKeys.length); - if (startsWith) { - key = startsWith.slice(0, KEY_LENGTH); - - /** - * if key is 'a' or 'ab' (<3 chars), search for ALL keys starting with that. - */ - if (key.length < KEY_LENGTH) { - - // calc trie if haven't done so yet - if (!trie) { - trie = new Trie(); - trie.addStrings(self.fastIndex.indexKeys); - self.fastIndex.trie = trie; - //console.log(' +++ Trie calc '); - } - - try { - // trie throws if not found!!!!! - keys = trie.keysWithPrefix(startsWith); - } catch (e) { - keys = []; - } - - // read all keys then select random word. - // May be large disk read! - key = keys[0]; - nextKey = _.last(keys); - } - - if (!key || !(key in self.fastIndex.offsets)) { - callback && callback([], startsWith); - resolve([]); - } - - } else { - // no startWith given - random select among keys - keys = _.sample(self.fastIndex.indexKeys, num); - - // if num > 1, run each key independently and collect results - if (num > 1) { - var results = [], ii = 0; - _(keys).each(function (startsWith) { - self.rand(startsWith, 1, function (result) { - results.push(result[0]); - if (++ii == num) { - callback && callback(results, ''); - resolve(results); - } - }); - }); - return; - } - key = keys; - } - - // prepare the piper - var args = [key, nextKey, self], - task = 'rand:' + key + nextKey, - context = [startsWith, num, callback]; // last arg MUST be callback - - // pay the piper - self.piper(task, IndexFile.readIndexBetweenKeys, args, context, collector); - - function collector(key, nextKey, index, startsWith, num, callback, buffer) { - var lines = buffer.toString().split('\n'), - matches = lines.map(function (line) { - return line.substring(0, line.indexOf(' ')); - }); - //console.log(' got lines for key ', key, lines.length); - - // we got bunch of matches for key - now search within for startsWith - if (startsWith !== key) { - // binary search for startsWith within set of matches - var ind = _.sortedIndex(matches, startsWith); - if (ind >= lines.length || matches[ind].indexOf(startsWith) === -1) { - callback && callback([], startsWith); - resolve([]); - return; - } - - var trie = new Trie(); - trie.addStrings(matches); - //console.log('Trie > ', trie.matchesWithPrefix( startsWith )); - matches = trie.keysWithPrefix(startsWith); - } - - var words = _.sample(matches, num); - callback && callback(words, startsWith); - resolve(words); - } - - }); // Promise -} - -// relative weight of each POS word count (DB 3.1 numbers) -var POS_factor = { - Noun: 26, - Verb: 3, - Adjective: 5, - Adverb: 1, - Total: 37 -}; /** * rand() - for all Index files - * @returns Promise + * + * @param [opts] {object} options + * @param opts.startsWith {string} string random words should start with + * @param opts.count {integer} number of random words to return + * @param callback {function} - callback receives (results, startsWith, profile) + * @returns {Promise} receives results + * @this WordPOS */ -function randAll(opts, callback) { - +function rand(opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } else { - opts = _.clone(opts || {}); + opts = Object.assign({ + startsWith: '', + count: 1 + }, opts); } var profile = this.options.profile, start = profile && new Date(), results = [], - startsWith = opts && opts.startsWith || '', - count = opts && opts.count || 1, - args = [null, startsWith], + count = opts.count, + args = [null, opts.startsWith], parts = 'Noun Verb Adjective Adverb'.split(' '), self = this; - - return new Promise(function(resolve, reject) { // select at random a POS to look at - var doParts = _.sample(parts, parts.length); + var doParts = sample(parts, parts.length); tryPart(); function tryPart() { @@ -207,7 +87,7 @@ function randAll(opts, callback) { function partCallback(result) { if (result) { - results = _.uniq(results.concat(result)); // make sure it's unique! + results = uniq(results.concat(result)); // make sure it's unique! } if (results.length < count && doParts.length) { @@ -215,7 +95,7 @@ function randAll(opts, callback) { } // final random and trim excess - results = _.sample(results, count); + results = sample(results, count); done(); } @@ -229,39 +109,17 @@ function randAll(opts, callback) { }); // Promise } -/** - * bind rand() to index - * - * @param index {object} - the IndexFile instance - * @returns {function} - bound rand function for index - */ -function randomify(index){ - if (!index.fastIndex) throw 'rand requires fastIndex'; - return _.bind(rand, index); -} - - -module.exports = { - - init: function(wordposProto) { - wordposProto.nounIndex.rand = randomify(wordposProto.nounIndex); - wordposProto.verbIndex.rand = randomify(wordposProto.verbIndex); - wordposProto.adjIndex.rand = randomify(wordposProto.adjIndex); - wordposProto.advIndex.rand = randomify(wordposProto.advIndex); - - /** - * define rand() - */ - wordposProto.rand = randAll; - - /** - * define randX() - */ - wordposProto.randAdjective = makeRandX('a'); - wordposProto.randAdverb = makeRandX('r'); - wordposProto.randNoun = makeRandX('n'); - wordposProto.randVerb = makeRandX('v'); - } +// relative weight of each POS word count (DB 3.1 numbers) +const POS_factor = { + Noun: 26, + Verb: 3, + Adjective: 5, + Adverb: 1, + Total: 37 }; +module.exports = { + randX, + rand +}; diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..2b72dfd --- /dev/null +++ b/src/util.js @@ -0,0 +1,117 @@ +/** +* util.js +* +* Copyright (c) 2012-2019 mooster@42at.com +* https://github.com/moos/wordpos +* +* Released under MIT license +*/ + +let stopwords = require('../lib/natural/util/stopwords').words; +let stopwordsStr = makeStopwordString(stopwords); + +function makeStopwordString(stopwords) { + return ' ' + stopwords.join(' ') + ' '; +} + +// setImmediate executes callback AFTER promise handlers. +// Without it, exceptions in callback may be caught by Promise. +function nextTick(fn, args) { + if (fn) { + fn.apply(null, args); + } +} + +// offsets must be zero-padded to 8 chars +function zeroPad(str) { + var pad = '00000000'; // 8 zeros + return String(pad + str).slice(-pad.length); +} + +function normalize(word) { + return word.toLowerCase().replace(/\s+/g, '_'); +} + +function isStopword(stopwordsStr, word) { + return stopwordsStr.indexOf(' '+word+' ') >= 0; +} + +function tokenizer(str) { + return str.split(/\W+/); +} + +function uniq(arr) { + return arr.filter((v, i) => arr.indexOf(v) === i); +} + +function diff(arr, subArr) { + return arr.filter(x => !subArr.includes(x)); +} + +// flatten an array - 1-deep only! +function flat(arr) { + return [].concat.apply([], arr); +} + +// get random sample from array (note: count << array.length) +// https://stackoverflow.com/a/37834217 +function sample(array, count) { + var indices = []; + var result = new Array(count); + for (let i = 0; i < count; i++ ) { + let j = Math.floor(Math.random() * (array.length - i) + i); + let val = array[indices[j] === undefined ? j : indices[j]]; + if (val === undefined) { + result.length = i; + break; + } + result[i] = val; + indices[j] = indices[i] === undefined ? i : indices[i]; + } + return result; +} + +function isString(s) { + return typeof s === 'string'; +} + +function reject(arr, predicate) { + return arr.filter(item => !predicate(item)) +} + +function prepText(text) { + if (Array.isArray(text)) return text; + var deduped = uniq(tokenizer(text)); + if (!this.options.stopwords) return deduped; + return reject(deduped, isStopword.bind(null, + isString(this.options.stopwords) ? this.options.stopwords : stopwordsStr + )); +} + +// node <= 6 polyfill +// @see https://github.com/tc39/proposal-object-values-entries/blob/master/polyfill.js +const reduce = Function.bind.call(Function.call, Array.prototype.reduce); +const isEnumerable = Function.bind.call(Function.call, Object.prototype.propertyIsEnumerable); +const concat = Function.bind.call(Function.call, Array.prototype.concat); +const keys = Reflect.ownKeys; +if (!Object.values) { + Object.values = function values(O) { + return reduce(keys(O), (v, k) => concat(v, typeof k === 'string' && isEnumerable(O, k) ? [O[k]] : []), []); + }; +} + + +module.exports = { + isString, + zeroPad, + stopwords, + nextTick, + normalize, + tokenizer, + prepText, + makeStopwordString, + uniq, + diff, + flat, + sample +}; diff --git a/src/wordpos.js b/src/wordpos.js index b5bb23a..e1a974e 100644 --- a/src/wordpos.js +++ b/src/wordpos.js @@ -3,401 +3,14 @@ * * Node.js part-of-speech utilities using WordNet database. * -* Copyright (c) 2012-2016 mooster@42at.com +* Copyright (c) 2012-2019 mooster@42at.com * https://github.com/moos/wordpos * * Released under MIT license */ -var _ = require('underscore')._, - util = require('util'), - stopwords = require('../lib/natural/util/stopwords').words, - stopwordsStr = makeStopwordString(stopwords), - WNdb = require('wordnet-db'), - DataFile = require('./dataFile'), - IndexFile = require('./indexFile'); - - -function normalize(word) { - return word.toLowerCase().replace(/\s+/g, '_'); +if (process.browser) { + module.exports = require('./browser'); +} else { + module.exports = require('./node'); } - -function makeStopwordString(stopwords) { - return ' '+ stopwords.join(' ') +' '; -} - -function isStopword(stopwords, word) { - return stopwords.indexOf(' '+word+' ') >= 0; -} - -function tokenizer(str) { - return str.split(/\W+/); //_.without(results,'',' ') -} - -function prepText(text) { - if (_.isArray(text)) return text; - var deduped = _.uniq(tokenizer(text)); - if (!this.options.stopwords) return deduped; - return _.reject(deduped, _.bind(isStopword, null, - _.isString(this.options.stopwords) ? this.options.stopwords : stopwordsStr - )); -} - -/** - * factory for main lookup function - * - * @param pos {string} - n/v/a/r - * @returns {Function} - lookup function bound to POS - */ -function lookup(pos) { - return function(word, callback) { - var profile = this.options.profile, - start = profile && new Date(), - files = this.getFilesFor(pos), - args = []; - - word = normalize(word); - - // lookup index - return files.index.lookup(word) - .then(function(result) { - if (result) { - // lookup data - return files.data.lookup(result.synsetOffset).then(done); - } else { - // not found in index - return done([]); - } - }) - .catch(done); - - function done(results) { - if (results instanceof Error) { - args.push([], word); - } else { - args.push(results, word); - } - //console.log(3333, args) - profile && args.push(new Date() - start); - nextTick(callback, args); - return results; - } - }; -} - -/** - * isX() factory function - * - * @param pos {string} - n/v/a/r - * @returns {Function} - */ -function is(pos){ - return function(word, callback, _noprofile) { - // disable profiling when isX() used internally - var profile = this.options.profile && !_noprofile, - start = profile && new Date(), - args = [], - index = this.getFilesFor(pos).index; - word = normalize(word); - - return index - .lookup(word) - .then(function(record) { - var result = !!record; - args.push(result, word); - profile && args.push(new Date() - start); - nextTick(callback, args); - return result; - }); - }; -} - - -/** - * getX() factory function - * - * @param isFn {function} - an isX() function - * @returns {Function} - */ -function get(isFn) { - return function(text, callback, _noprofile) { - var profile = this.options.profile && !_noprofile, - start = profile && new Date(), - words = this.parse(text), - results = [], - self = this; - - //if (!n) return (process.nextTick(done),0); - return Promise - .all(words.map(exec)) - .then(done); - - function exec(word) { - return self[isFn] - .call(self, word, null, /*_noprofile*/ true) - .then(function collect(result) { - result && results.push(word); - }); - } - - function done(){ - var args = [results]; - profile && args.push(new Date() - start); - nextTick(callback, args); - return results; - } - }; -} - -// setImmediate executes callback AFTER promise handlers. -// Without it, exceptions in callback may be caught by Promise. -function nextTick(fn, args) { - if (fn) { - fn.apply(null, args); - } -} - - -/** - * @class WordPOS - * @param options {object} -- @see WordPOS.defaults - * @constructor - */ -var WordPOS = function(options) { - var dictPath; - - this.options = _.defaults({}, _.isObject(options) && options || {}, { - dictPath: WNdb.path - }, WordPOS.defaults); - - dictPath = this.options.dictPath; - - this.nounIndex = new IndexFile(dictPath, 'noun'); - this.verbIndex = new IndexFile(dictPath, 'verb'); - this.adjIndex = new IndexFile(dictPath, 'adj'); - this.advIndex = new IndexFile(dictPath, 'adv'); - - this.nounData = new DataFile(dictPath, 'noun'); - this.verbData = new DataFile(dictPath, 'verb'); - this.adjData = new DataFile(dictPath, 'adj'); - this.advData = new DataFile(dictPath, 'adv'); - - // define randX() functions - require('./rand').init(this); - - if (_.isArray(this.options.stopwords)) { - this.options.stopwords = makeStopwordString(this.options.stopwords); - } -}; - - -WordPOS.defaults = { - /** - * path to WordNet data (override only if not using wordnet-db) - */ - dictPath: '', - - /** - * enable profiling, time in msec returned as second argument in callback - */ - profile: false, - - /** - * if true, exclude standard stopwords. - * if array, stopwords to exclude, eg, ['all','of','this',...] - * if false, do not filter any stopwords. - */ - stopwords: true -}; - -var wordposProto = WordPOS.prototype; - -/** - * lookup a word in all indexes - * - * @param word {string} - search word - * @param callback {Function} (optional) - callback with (results, word) signature - * @returns {Promise} - */ -wordposProto.lookup = function(word, callback) { - var self = this, - results = [], - profile = this.options.profile, - start = profile && new Date(), - methods = ['lookupAdverb', 'lookupAdjective', 'lookupVerb', 'lookupNoun']; - - return Promise - .all(methods.map(exec)) - .then(done) - .catch(error); - - function exec(method) { - return self[ method ] - .call(self, word) - .then(function collect(result){ - results = results.concat(result); - }); - } - - function done() { - var args = [results, word]; - profile && args.push(new Date() - start); - nextTick(callback, args); - return results; - } - - function error(err) { - nextTick(callback, [[], word]); - throw err; - } -}; - - -/** - * getPOS() - Find all POS for all words in given string - * - * @param text {string} - words to lookup for POS - * @param callback {function} (optional) - receives object with words broken into POS or 'rest', ie, - * Object: {nouns:[], verbs:[], adjectives:[], adverbs:[], rest:[]} - * @return Promise - resolve function receives data object - */ -wordposProto.getPOS = function(text, callback) { - var self = this, - data = {nouns:[], verbs:[], adjectives:[], adverbs:[], rest:[]}, - profile = this.options.profile, - start = profile && new Date(), - words = this.parse(text), - methods = ['getAdverbs', 'getAdjectives', 'getVerbs', 'getNouns']; - - return Promise - .all(methods.map(exec)) - .then(done) - .catch(error); - - function exec(method) { - return self[ method ] - .call(self, text, null, true) - .then(function collect(results) { - // getAdjectives --> adjectives - var pos = method.replace('get','').toLowerCase(); - data[ pos ] = results; - }); - } - - function done() { - var matches = _(data).chain() - .values() - .flatten() - .uniq() - .value(), - args = [data]; - - data.rest = _(words).difference(matches); - - profile && args.push(new Date() - start); - nextTick(callback, args); - return data; - } - - function error(err) { - nextTick(callback, []); - throw err; - } -}; - -/** - * get index and data files for given pos - * - * @param pos {string} - n/v/a/r - * @returns {object} - keys {index, data} - */ -wordposProto.getFilesFor = function (pos) { - switch(pos) { - case 'n': - return {index: this.nounIndex, data: this.nounData}; - case 'v': - return {index: this.verbIndex, data: this.verbData}; - case 'a': case 's': - return {index: this.adjIndex, data: this.adjData}; - case 'r': - return {index: this.advIndex, data: this.advData}; - } - return {}; -}; - - -/** - * lookupX() - Lookup word definition if already know POS - * @see lookup - */ -wordposProto.lookupAdjective = lookup('a'); -wordposProto.lookupAdverb = lookup('r'); -wordposProto.lookupNoun = lookup('n'); -wordposProto.lookupVerb = lookup('v'); - -/** - * isX() - Test if word is given POS - * @see is - */ -wordposProto.isAdjective = is('a'); -wordposProto.isAdverb = is('r'); -wordposProto.isNoun = is('n'); -wordposProto.isVerb = is('v'); - -/** - * getX() - Find all words in string that are given POS - * @see get - */ -wordposProto.getAdjectives = get('isAdjective'); -wordposProto.getAdverbs = get('isAdverb'); -wordposProto.getNouns = get('isNoun'); -wordposProto.getVerbs = get('isVerb'); - -/** - * parse - get deduped, less stopwords - * - * @param text {string|array} - string of words to parse. If array is given, it is left in tact. - * @returns {array} - */ -wordposProto.parse = prepText; - - -/** - * seek - get record at offset for pos - * - * @param offset {number} - synset offset - * @param pos {string} - POS a/r/n/v - * @param callback {function} - optional callback - * @returns Promise - */ -wordposProto.seek = function(offset, pos, callback){ - offset = Number(offset); - if (_.isNaN(offset) || offset <= 0) return error('offset must be valid positive number.'); - - var data = this.getFilesFor(pos).data; - if (!data) return error('Incorrect POS - 2nd argument must be a, r, n or v.'); - - return data.lookup(offset, callback); - - function error(msg) { - var err = new Error(msg); - callback && callback(err, {}); - return Promise.reject(err); - } -}; - - -/** - * access to WordNet DB - * @type {object} - */ -WordPOS.WNdb = WNdb; - -/** - * access to stopwords - * @type {Array} - */ -WordPOS.stopwords = stopwords; - - -module.exports = WordPOS; diff --git a/test/validate_test.js b/test/validate_test.js index 52a6065..5bccb3d 100644 --- a/test/validate_test.js +++ b/test/validate_test.js @@ -50,4 +50,4 @@ function callback(error, stdout, stderr) { console.log(stdout); console.error(stderr); gDone(); -} \ No newline at end of file +} diff --git a/test/wordpos_test.js b/test/wordpos_test.js index 79b6a2b..21be423 100644 --- a/test/wordpos_test.js +++ b/test/wordpos_test.js @@ -1,7 +1,7 @@ /** * wordpos_test.js * - * test file for main wordpos functionality + * test file for main wordpos functionality (both node and browser) * * Usage: * npm install mocha -g @@ -11,21 +11,43 @@ * * npm test * - * Copyright (c) 2012-2016 mooster@42at.com + * Copyright (c) 2012-2019 mooster@42at.com * https://github.com/moos/wordpos * * Released under MIT license */ -//import {describe, it} from 'mocha/lib/mocha.js'; + // used in src code to signal test mode +global.window = global.window || {}; +global.window.__mocha = true; var chai = require('chai'), _ = require('underscore'), assert = chai.assert, + browser = process.browser = process.argv.includes('@babel/register'), WordPOS = require('../src/wordpos'), - wordpos = new WordPOS({profile: false}); + path = require('path'), + dictPath = browser ? path.resolve('./test/dict') : undefined, + wordpos = new WordPOS({ + profile: false, + dictPath: dictPath, + // debug: true + }); + +const assertNoData = (err) => { + assert(err instanceof RangeError); + assert(/No data at offset/.test(err.message)); +}; + +const assertOffsetErr = (err) => { + assert(err instanceof RangeError); + assert.equal(err.message, 'Offset must be valid positive number: foobar'); +}; + + +console.log('Running', browser ? 'browser' : 'node', 'tests'); chai.config.showDiff = true; var str = "The angry bear chased the frightened little squirrel", @@ -40,25 +62,23 @@ var str = "The angry bear chased the frightened little squirrel", offset = 1285602; - describe('lookup', function() { - it('with callback', function (done) { - wordpos.lookup('hegemony', function (result) { + + it('with callback', function () { + return wordpos.lookup('hegemony', function (result) { assert.equal(result.length, 1); assert.equal(result[0].pos, 'n'); assert.equal(result[0].lemma, 'hegemony'); assert.equal(result[0].synonyms.length, 1); - done(); }); }); - it('with Promise', function (done) { - wordpos.lookup('hegemony').then(function (result) { + it('with Promise', function () { + return wordpos.lookup('hegemony').then(function (result) { assert.equal(result.length, 1); assert.equal(result[0].pos, 'n'); assert.equal(result[0].lemma, 'hegemony'); assert.equal(result[0].synonyms.length, 1); - done(); }); }); }); @@ -83,42 +103,38 @@ describe('options passed to constructor', function() { describe('getX()...', function() { - it('should get all POS', function(done) { - wordpos.getPOS(str, function(result) { + + it('should get all POS', function() { + return wordpos.getPOS(str, function(result) { assert.sameMembers(result.nouns, expected.nouns); assert.sameMembers(result.verbs, expected.verbs); assert.sameMembers(result.adjectives, expected.adjectives); assert.sameMembers(result.adverbs, expected.adverbs); assert.sameMembers(result.rest, expected.rest); - done(); }); }); - it('should get nouns', function(done) { - wordpos.getNouns(str, function(result) { + it('should get nouns', function() { + return wordpos.getNouns(str, function(result) { assert.sameMembers(result, expected.nouns); - done(); }); }); - it('should get verbs', function(done) { - wordpos.getVerbs(str, function(result) { + it('should get verbs', function() { + return wordpos.getVerbs(str, function(result) { assert.sameMembers(result, expected.verbs); - done(); }); }); - it('should get adjectives', function(done) { - wordpos.getAdjectives(str, function(result) { + it('should get adjectives', function() { + return wordpos.getAdjectives(str, function(result) { assert.sameMembers(result, expected.adjectives); - done(); }); }); - it('should get adverbs', function(done) { - wordpos.getAdverbs(str, function(result) { + it('should get adverbs', function() { + return wordpos.getAdverbs(str, function(result) { assert.sameMembers(result, expected.adverbs); - done(); }); }); }); @@ -223,7 +239,7 @@ describe('lookupX()...', function() { describe('profile option', function() { - var wp = new WordPOS({profile : true}); + var wp = new WordPOS({profile : true, dictPath: dictPath}); it('should return time argument for isX()', function(done){ wp.isNoun(garble, function(result, word, time) { @@ -248,7 +264,7 @@ describe('profile option', function() { }); it('should disable stopword filtering', function(done){ - var wp = new WordPOS({stopwords : false}), + var wp = new WordPOS({stopwords : false, dictPath: dictPath}), strWithStopwords = 'about after all'; // 3 adjective stopwords wp.getAdjectives(strWithStopwords, function(result){ assert.equal(result.length, 3); @@ -257,7 +273,7 @@ describe('profile option', function() { }); it('should use custom stopwords', function(done){ - var wp = new WordPOS({stopwords : ['all']}), + var wp = new WordPOS({stopwords : ['all'], dictPath: dictPath}), strWithStopwords = 'about after all'; // 3 adjective stopwords // 'all' should be filtered wp.getAdjectives(strWithStopwords, function(result){ @@ -269,7 +285,7 @@ describe('profile option', function() { describe('nested callbacks on same index key', function() { - var wp = new WordPOS(), + var wp = new WordPOS({dictPath: dictPath}), word1 = 'head', word2 = word1 + 'er'; @@ -288,128 +304,86 @@ describe('nested callbacks on same index key', function() { describe('rand()...', function() { - it('should get random word', function(done) { - wordpos.rand(function(result) { + it('should get random word', function() { + return wordpos.rand(function(result) { assert.equal(result.length, 1); - done(); }); }); - it('should get N random words', function(done) { - wordpos.rand({count: 3}, function(result) { + it('should get N random words', function() { + return wordpos.rand({count: 3}, function(result) { assert.equal(result.length, 3); - done(); }); }); - it('should get random word starting with', function(done) { - wordpos.rand({startsWith: 'foo'}, function(result, startsWith) { + it('should get random word starting with', function() { + return wordpos.rand({startsWith: 'foo'}, function(result, startsWith) { assert.equal(result[0].indexOf('foo'), 0); assert.equal(startsWith, 'foo'); - done(); }); }); - it('should get nothing starting with not found', function(done) { - wordpos.rand({startsWith: 'zzzz'}, function(result) { + it('should get nothing starting with not found', function() { + return wordpos.rand({startsWith: 'zzzz'}, function(result) { assert.equal(result.length, 0); - done(); }); }); }); describe('randX()...', function() { - it('should get random noun', function(done) { - wordpos.randNoun(function(result) { - assert.equal(result.length, 1); - done(); - }); - }); + let assertOneResult = (res) => { + assert.equal(res.length, 1); + }; - it('should get random verb', function(done) { - wordpos.randVerb(function(result) { - assert.equal(result.length, 1); - done(); - }); - }); - - it('should get random adjective', function(done) { - wordpos.randAdjective(function(result) { - assert.equal(result.length, 1); - done(); - }); - }); - - it('should get random adverb', function(done) { - wordpos.randAdverb(function(result) { - assert.equal(result.length, 1); - done(); - }); - }); + it('should get random noun', () => wordpos.randNoun(assertOneResult)); + it('should get random verb', () => wordpos.randVerb(assertOneResult)); + it('should get random adjective', () => wordpos.randAdjective(assertOneResult)); + it('should get random adverb', () => wordpos.randAdverb(assertOneResult)); // not found - it('should NOT get random noun starting with', function(done) { - wordpos.randNoun({startsWith: 'zzzz'},function(result, startsWith) { - assert.equal(result.length, 0); - done(); - }); - }); + it('should NOT get random noun starting with', () => + wordpos.randNoun({startsWith: 'zzzz'}, (result, startsWith) => + assert.equal(result.length, 0) + ) + ); }); describe('seek()...', function() { - it('should seek offset', function(done) { - wordpos.seek(offset, 'a', function(err, result) { + it('should seek offset', function() { + return wordpos.seek(offset, 'a', function(err, result) { assert.equal(result.synsetOffset, offset); assert.equal(result.pos, 's'); assert.equal(result.lemma, 'amazing'); - done(); }); }); - it('should handle bad offset', function(done) { - wordpos.seek('foobar', 'a', function(err, result){ - assert(err instanceof Error); - assert.equal(err.message, 'offset must be valid positive number.'); - done(); - }).catch(_.noop); // UnhandledPromiseRejectionWarning + it('should handle bad offset', function() { + return wordpos.seek('foobar', 'a', assertOffsetErr).catch(assertOffsetErr); }); - it('should handle wrong offset', function(done) { - var bad_offset = offset + 1; - wordpos.seek(bad_offset, 'a', function(err, result) { - assert(err instanceof Error); - assert.equal(err.message, 'Bad data at location ' + bad_offset); - assert.deepEqual(result, {}); - done(); - }).catch(_.noop); // UnhandledPromiseRejectionWarning; + it('should handle wrong offset', function() { + const bad_offset = offset + 1; + return wordpos.seek(bad_offset, 'a', assertNoData).catch(assertNoData); }); - it('should handle very large offset', function(done) { - var bad_offset = offset + 100000000; - wordpos.seek(bad_offset, 'a', function(err, result) { - assert(err instanceof Error); - assert.equal(err.message, 'no data at offset ' + bad_offset); - assert.deepEqual(result, {}); - done(); - }).catch(_.noop); // UnhandledPromiseRejectionWarning; + it('should handle very large offset', function() { + const bad_offset = offset + 999999999; + return wordpos.seek(bad_offset, 'a', assertNoData).catch(assertNoData); }); - it('should handle bad POS', function(done) { - wordpos.seek(offset, 'g', function(err, result) { + it('should handle bad POS', function() { + const assertErr = err => { assert(err instanceof Error); assert(/Incorrect POS/.test(err.message)); - done(); - }).catch(_.noop); // UnhandledPromiseRejectionWarning; + }; + return wordpos.seek(offset, 'g', assertErr).catch(assertErr); }); - it('should handle wrong POS', function(done) { - wordpos.seek(offset, 'v', function(err, result){ - assert.equal(err.message, 'Bad data at location ' + offset); - }).catch(_.noop); // UnhandledPromiseRejectionWarning; - done(); + it('should handle wrong POS', function() { + return wordpos.seek(offset, 'v', assertNoData).catch(assertNoData); }); }); @@ -489,17 +463,11 @@ describe('Promise pattern', function() { }); it('seek() - wrong offset', function () { - return wordpos.seek(offset + 1, 'a').catch(function (err) { - assert(err instanceof Error); - assert.equal(err.message, 'Bad data at location ' + (offset+1)); - }); + return wordpos.seek(offset + 1, 'a').catch(assertNoData); }); it('seek() - bad offset', function () { - return wordpos.seek('foobar', 'a').catch(function (err) { - assert(err instanceof Error); - assert.equal(err.message, 'offset must be valid positive number.'); - }); + return wordpos.seek('foobar', 'a').catch(assertOffsetErr); }); -}); \ No newline at end of file +}); diff --git a/tools/stat.js b/tools/stat.js index e045ae7..ddd79f1 100644 --- a/tools/stat.js +++ b/tools/stat.js @@ -46,7 +46,7 @@ * Released under MIT license */ var - WNdb = require('../src/wordpos').WNdb, + WNdb = require('wordnet-db'), util = require('util'), BufferedReader = require ('./buffered-reader'), _ = require('underscore')._,