init
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Evgeny Poberezkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
# fast-deep-equal
|
||||
The fastest deep equal with ES6 Map, Set and Typed arrays support.
|
||||
|
||||
[](https://travis-ci.org/epoberezkin/fast-deep-equal)
|
||||
[](https://www.npmjs.com/package/fast-deep-equal)
|
||||
[](https://coveralls.io/github/epoberezkin/fast-deep-equal?branch=master)
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install fast-deep-equal
|
||||
```
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- ES5 compatible
|
||||
- works in node.js (8+) and browsers (IE9+)
|
||||
- checks equality of Date and RegExp objects by value.
|
||||
|
||||
ES6 equal (`require('fast-deep-equal/es6')`) also supports:
|
||||
- Maps
|
||||
- Sets
|
||||
- Typed arrays
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
var equal = require('fast-deep-equal');
|
||||
console.log(equal({foo: 'bar'}, {foo: 'bar'})); // true
|
||||
```
|
||||
|
||||
To support ES6 Maps, Sets and Typed arrays equality use:
|
||||
|
||||
```javascript
|
||||
var equal = require('fast-deep-equal/es6');
|
||||
console.log(equal(Int16Array([1, 2]), Int16Array([1, 2]))); // true
|
||||
```
|
||||
|
||||
To use with React (avoiding the traversal of React elements' _owner
|
||||
property that contains circular references and is not needed when
|
||||
comparing the elements - borrowed from [react-fast-compare](https://github.com/FormidableLabs/react-fast-compare)):
|
||||
|
||||
```javascript
|
||||
var equal = require('fast-deep-equal/react');
|
||||
var equal = require('fast-deep-equal/es6/react');
|
||||
```
|
||||
|
||||
|
||||
## Performance benchmark
|
||||
|
||||
Node.js v12.6.0:
|
||||
|
||||
```
|
||||
fast-deep-equal x 261,950 ops/sec ±0.52% (89 runs sampled)
|
||||
fast-deep-equal/es6 x 212,991 ops/sec ±0.34% (92 runs sampled)
|
||||
fast-equals x 230,957 ops/sec ±0.83% (85 runs sampled)
|
||||
nano-equal x 187,995 ops/sec ±0.53% (88 runs sampled)
|
||||
shallow-equal-fuzzy x 138,302 ops/sec ±0.49% (90 runs sampled)
|
||||
underscore.isEqual x 74,423 ops/sec ±0.38% (89 runs sampled)
|
||||
lodash.isEqual x 36,637 ops/sec ±0.72% (90 runs sampled)
|
||||
deep-equal x 2,310 ops/sec ±0.37% (90 runs sampled)
|
||||
deep-eql x 35,312 ops/sec ±0.67% (91 runs sampled)
|
||||
ramda.equals x 12,054 ops/sec ±0.40% (91 runs sampled)
|
||||
util.isDeepStrictEqual x 46,440 ops/sec ±0.43% (90 runs sampled)
|
||||
assert.deepStrictEqual x 456 ops/sec ±0.71% (88 runs sampled)
|
||||
|
||||
The fastest is fast-deep-equal
|
||||
```
|
||||
|
||||
To run benchmark (requires node.js 6+):
|
||||
|
||||
```bash
|
||||
npm run benchmark
|
||||
```
|
||||
|
||||
__Please note__: this benchmark runs against the available test cases. To choose the most performant library for your application, it is recommended to benchmark against your data and to NOT expect this benchmark to reflect the performance difference in your application.
|
||||
|
||||
|
||||
## Enterprise support
|
||||
|
||||
fast-deep-equal package is a part of [Tidelift enterprise subscription](https://tidelift.com/subscription/pkg/npm-fast-deep-equal?utm_source=npm-fast-deep-equal&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) - it provides a centralised commercial support to open-source software users, in addition to the support provided by software maintainers.
|
||||
|
||||
|
||||
## Security contact
|
||||
|
||||
To report a security vulnerability, please use the
|
||||
[Tidelift security contact](https://tidelift.com/security).
|
||||
Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerability via GitHub issues.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/epoberezkin/fast-deep-equal/blob/master/LICENSE)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
declare const equal: (a: any, b: any) => boolean;
|
||||
export = equal;
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
// do not edit .js files directly - edit src/index.jst
|
||||
|
||||
|
||||
var envHasBigInt64Array = typeof BigInt64Array !== 'undefined';
|
||||
|
||||
|
||||
module.exports = function equal(a, b) {
|
||||
if (a === b) return true;
|
||||
|
||||
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
||||
if (a.constructor !== b.constructor) return false;
|
||||
|
||||
var length, i, keys;
|
||||
if (Array.isArray(a)) {
|
||||
length = a.length;
|
||||
if (length != b.length) return false;
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!equal(a[i], b[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if ((a instanceof Map) && (b instanceof Map)) {
|
||||
if (a.size !== b.size) return false;
|
||||
for (i of a.entries())
|
||||
if (!b.has(i[0])) return false;
|
||||
for (i of a.entries())
|
||||
if (!equal(i[1], b.get(i[0]))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((a instanceof Set) && (b instanceof Set)) {
|
||||
if (a.size !== b.size) return false;
|
||||
for (i of a.entries())
|
||||
if (!b.has(i[0])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
|
||||
length = a.length;
|
||||
if (length != b.length) return false;
|
||||
for (i = length; i-- !== 0;)
|
||||
if (a[i] !== b[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
||||
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
||||
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
||||
|
||||
keys = Object.keys(a);
|
||||
length = keys.length;
|
||||
if (length !== Object.keys(b).length) return false;
|
||||
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
|
||||
|
||||
for (i = length; i-- !== 0;) {
|
||||
var key = keys[i];
|
||||
|
||||
if (!equal(a[key], b[key])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// true if both NaN, false otherwise
|
||||
return a!==a && b!==b;
|
||||
};
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
declare const equal: (a: any, b: any) => boolean;
|
||||
export = equal;
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
'use strict';
|
||||
|
||||
// do not edit .js files directly - edit src/index.jst
|
||||
|
||||
|
||||
var envHasBigInt64Array = typeof BigInt64Array !== 'undefined';
|
||||
|
||||
|
||||
module.exports = function equal(a, b) {
|
||||
if (a === b) return true;
|
||||
|
||||
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
||||
if (a.constructor !== b.constructor) return false;
|
||||
|
||||
var length, i, keys;
|
||||
if (Array.isArray(a)) {
|
||||
length = a.length;
|
||||
if (length != b.length) return false;
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!equal(a[i], b[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if ((a instanceof Map) && (b instanceof Map)) {
|
||||
if (a.size !== b.size) return false;
|
||||
for (i of a.entries())
|
||||
if (!b.has(i[0])) return false;
|
||||
for (i of a.entries())
|
||||
if (!equal(i[1], b.get(i[0]))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((a instanceof Set) && (b instanceof Set)) {
|
||||
if (a.size !== b.size) return false;
|
||||
for (i of a.entries())
|
||||
if (!b.has(i[0])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
|
||||
length = a.length;
|
||||
if (length != b.length) return false;
|
||||
for (i = length; i-- !== 0;)
|
||||
if (a[i] !== b[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
||||
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
||||
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
||||
|
||||
keys = Object.keys(a);
|
||||
length = keys.length;
|
||||
if (length !== Object.keys(b).length) return false;
|
||||
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
|
||||
|
||||
for (i = length; i-- !== 0;) {
|
||||
var key = keys[i];
|
||||
|
||||
if (key === '_owner' && a.$$typeof) {
|
||||
// React-specific: avoid traversing React elements' _owner.
|
||||
// _owner contains circular references
|
||||
// and is not needed when comparing the actual elements (and not their owners)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!equal(a[key], b[key])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// true if both NaN, false otherwise
|
||||
return a!==a && b!==b;
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
declare module 'fast-deep-equal' {
|
||||
const equal: (a: any, b: any) => boolean;
|
||||
export = equal;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
// do not edit .js files directly - edit src/index.jst
|
||||
|
||||
|
||||
|
||||
module.exports = function equal(a, b) {
|
||||
if (a === b) return true;
|
||||
|
||||
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
||||
if (a.constructor !== b.constructor) return false;
|
||||
|
||||
var length, i, keys;
|
||||
if (Array.isArray(a)) {
|
||||
length = a.length;
|
||||
if (length != b.length) return false;
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!equal(a[i], b[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
||||
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
||||
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
||||
|
||||
keys = Object.keys(a);
|
||||
length = keys.length;
|
||||
if (length !== Object.keys(b).length) return false;
|
||||
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
|
||||
|
||||
for (i = length; i-- !== 0;) {
|
||||
var key = keys[i];
|
||||
|
||||
if (!equal(a[key], b[key])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// true if both NaN, false otherwise
|
||||
return a!==a && b!==b;
|
||||
};
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "fast-deep-equal",
|
||||
"version": "3.1.3",
|
||||
"description": "Fast deep equal",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"eslint": "eslint *.js benchmark/*.js spec/*.js",
|
||||
"build": "node build",
|
||||
"benchmark": "npm i && npm run build && cd ./benchmark && npm i && node ./",
|
||||
"test-spec": "mocha spec/*.spec.js -R spec",
|
||||
"test-cov": "nyc npm run test-spec",
|
||||
"test-ts": "tsc --target ES5 --noImplicitAny index.d.ts",
|
||||
"test": "npm run build && npm run eslint && npm run test-ts && npm run test-cov",
|
||||
"prepublish": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/epoberezkin/fast-deep-equal.git"
|
||||
},
|
||||
"keywords": [
|
||||
"fast",
|
||||
"equal",
|
||||
"deep-equal"
|
||||
],
|
||||
"author": "Evgeny Poberezkin",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/epoberezkin/fast-deep-equal/issues"
|
||||
},
|
||||
"homepage": "https://github.com/epoberezkin/fast-deep-equal#readme",
|
||||
"devDependencies": {
|
||||
"coveralls": "^3.1.0",
|
||||
"dot": "^1.1.2",
|
||||
"eslint": "^7.2.0",
|
||||
"mocha": "^7.2.0",
|
||||
"nyc": "^15.1.0",
|
||||
"pre-commit": "^1.2.2",
|
||||
"react": "^16.12.0",
|
||||
"react-test-renderer": "^16.12.0",
|
||||
"sinon": "^9.0.2",
|
||||
"typescript": "^3.9.5"
|
||||
},
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"**/spec/**",
|
||||
"node_modules"
|
||||
],
|
||||
"reporter": [
|
||||
"lcov",
|
||||
"text-summary"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"react.js",
|
||||
"react.d.ts",
|
||||
"es6/"
|
||||
],
|
||||
"types": "index.d.ts"
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
declare const equal: (a: any, b: any) => boolean;
|
||||
export = equal;
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
// do not edit .js files directly - edit src/index.jst
|
||||
|
||||
|
||||
|
||||
module.exports = function equal(a, b) {
|
||||
if (a === b) return true;
|
||||
|
||||
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
||||
if (a.constructor !== b.constructor) return false;
|
||||
|
||||
var length, i, keys;
|
||||
if (Array.isArray(a)) {
|
||||
length = a.length;
|
||||
if (length != b.length) return false;
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!equal(a[i], b[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
||||
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
||||
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
||||
|
||||
keys = Object.keys(a);
|
||||
length = keys.length;
|
||||
if (length !== Object.keys(b).length) return false;
|
||||
|
||||
for (i = length; i-- !== 0;)
|
||||
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
|
||||
|
||||
for (i = length; i-- !== 0;) {
|
||||
var key = keys[i];
|
||||
|
||||
if (key === '_owner' && a.$$typeof) {
|
||||
// React-specific: avoid traversing React elements' _owner.
|
||||
// _owner contains circular references
|
||||
// and is not needed when comparing the actual elements (and not their owners)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!equal(a[key], b[key])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// true if both NaN, false otherwise
|
||||
return a!==a && b!==b;
|
||||
};
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
language: node_js
|
||||
node_js: '0.10'
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Fast Diff [](https://travis-ci.org/jhchen/fast-diff)
|
||||
|
||||
This is a simplified import of the excellent [diff-match-patch](https://code.google.com/p/google-diff-match-patch/) library by [Neil Fraser](https://neil.fraser.name/) into the Node.js environment. The match and patch parts are removed, as well as all the extra diff options. What remains is incredibly fast diffing between two strings.
|
||||
|
||||
The diff function is an implementation of ["An O(ND) Difference Algorithm and its Variations" (Myers, 1986)](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.4.6927&rep=rep1&type=pdf) with the suggested divide and conquer strategy along with several [optimizations](http://neil.fraser.name/news/2007/10/09/) Neil added.
|
||||
|
||||
```js
|
||||
var diff = require('fast-diff');
|
||||
|
||||
var good = 'Good dog';
|
||||
var bad = 'Bad dog';
|
||||
|
||||
var result = diff(good, bad);
|
||||
// [[-1, "Goo"], [1, "Ba"], [0, "d dog"]]
|
||||
|
||||
// Respect suggested edit location (cursor position), added in v1.1
|
||||
diff('aaa', 'aaaa', 1)
|
||||
// [[0, "a"], [1, "a"], [0, "aa"]]
|
||||
|
||||
// For convenience
|
||||
diff.INSERT === 1;
|
||||
diff.EQUAL === 0;
|
||||
diff.DELETE === -1;
|
||||
```
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
declare function diff(
|
||||
text1: string,
|
||||
text2: string,
|
||||
cursorPos?: number | diff.CursorInfo
|
||||
): diff.Diff[];
|
||||
|
||||
declare namespace diff {
|
||||
type Diff = [-1 | 0 | 1, string];
|
||||
|
||||
const DELETE: -1;
|
||||
const INSERT: 1;
|
||||
const EQUAL: 0;
|
||||
|
||||
interface CursorInfo {
|
||||
oldRange: { index: number; length: number };
|
||||
newRange: { index: number; length: number };
|
||||
}
|
||||
}
|
||||
|
||||
export = diff;
|
||||
+774
@@ -0,0 +1,774 @@
|
||||
/**
|
||||
* This library modifies the diff-patch-match library by Neil Fraser
|
||||
* by removing the patch and match functionality and certain advanced
|
||||
* options in the diff function. The original license is as follows:
|
||||
*
|
||||
* ===
|
||||
*
|
||||
* Diff Match and Patch
|
||||
*
|
||||
* Copyright 2006 Google Inc.
|
||||
* http://code.google.com/p/google-diff-match-patch/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* The data structure representing a diff is an array of tuples:
|
||||
* [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
|
||||
* which means: delete 'Hello', add 'Goodbye' and keep ' world.'
|
||||
*/
|
||||
var DIFF_DELETE = -1;
|
||||
var DIFF_INSERT = 1;
|
||||
var DIFF_EQUAL = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Find the differences between two texts. Simplifies the problem by stripping
|
||||
* any common prefix or suffix off the texts before diffing.
|
||||
* @param {string} text1 Old string to be diffed.
|
||||
* @param {string} text2 New string to be diffed.
|
||||
* @param {Int|Object} [cursor_pos] Edit position in text1 or object with more info
|
||||
* @return {Array} Array of diff tuples.
|
||||
*/
|
||||
function diff_main(text1, text2, cursor_pos, _fix_unicode) {
|
||||
// Check for equality
|
||||
if (text1 === text2) {
|
||||
if (text1) {
|
||||
return [[DIFF_EQUAL, text1]];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
if (cursor_pos != null) {
|
||||
var editdiff = find_cursor_edit_diff(text1, text2, cursor_pos);
|
||||
if (editdiff) {
|
||||
return editdiff;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim off common prefix (speedup).
|
||||
var commonlength = diff_commonPrefix(text1, text2);
|
||||
var commonprefix = text1.substring(0, commonlength);
|
||||
text1 = text1.substring(commonlength);
|
||||
text2 = text2.substring(commonlength);
|
||||
|
||||
// Trim off common suffix (speedup).
|
||||
commonlength = diff_commonSuffix(text1, text2);
|
||||
var commonsuffix = text1.substring(text1.length - commonlength);
|
||||
text1 = text1.substring(0, text1.length - commonlength);
|
||||
text2 = text2.substring(0, text2.length - commonlength);
|
||||
|
||||
// Compute the diff on the middle block.
|
||||
var diffs = diff_compute_(text1, text2);
|
||||
|
||||
// Restore the prefix and suffix.
|
||||
if (commonprefix) {
|
||||
diffs.unshift([DIFF_EQUAL, commonprefix]);
|
||||
}
|
||||
if (commonsuffix) {
|
||||
diffs.push([DIFF_EQUAL, commonsuffix]);
|
||||
}
|
||||
diff_cleanupMerge(diffs, _fix_unicode);
|
||||
return diffs;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Find the differences between two texts. Assumes that the texts do not
|
||||
* have any common prefix or suffix.
|
||||
* @param {string} text1 Old string to be diffed.
|
||||
* @param {string} text2 New string to be diffed.
|
||||
* @return {Array} Array of diff tuples.
|
||||
*/
|
||||
function diff_compute_(text1, text2) {
|
||||
var diffs;
|
||||
|
||||
if (!text1) {
|
||||
// Just add some text (speedup).
|
||||
return [[DIFF_INSERT, text2]];
|
||||
}
|
||||
|
||||
if (!text2) {
|
||||
// Just delete some text (speedup).
|
||||
return [[DIFF_DELETE, text1]];
|
||||
}
|
||||
|
||||
var longtext = text1.length > text2.length ? text1 : text2;
|
||||
var shorttext = text1.length > text2.length ? text2 : text1;
|
||||
var i = longtext.indexOf(shorttext);
|
||||
if (i !== -1) {
|
||||
// Shorter text is inside the longer text (speedup).
|
||||
diffs = [
|
||||
[DIFF_INSERT, longtext.substring(0, i)],
|
||||
[DIFF_EQUAL, shorttext],
|
||||
[DIFF_INSERT, longtext.substring(i + shorttext.length)]
|
||||
];
|
||||
// Swap insertions for deletions if diff is reversed.
|
||||
if (text1.length > text2.length) {
|
||||
diffs[0][0] = diffs[2][0] = DIFF_DELETE;
|
||||
}
|
||||
return diffs;
|
||||
}
|
||||
|
||||
if (shorttext.length === 1) {
|
||||
// Single character string.
|
||||
// After the previous speedup, the character can't be an equality.
|
||||
return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
|
||||
}
|
||||
|
||||
// Check to see if the problem can be split in two.
|
||||
var hm = diff_halfMatch_(text1, text2);
|
||||
if (hm) {
|
||||
// A half-match was found, sort out the return data.
|
||||
var text1_a = hm[0];
|
||||
var text1_b = hm[1];
|
||||
var text2_a = hm[2];
|
||||
var text2_b = hm[3];
|
||||
var mid_common = hm[4];
|
||||
// Send both pairs off for separate processing.
|
||||
var diffs_a = diff_main(text1_a, text2_a);
|
||||
var diffs_b = diff_main(text1_b, text2_b);
|
||||
// Merge the results.
|
||||
return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b);
|
||||
}
|
||||
|
||||
return diff_bisect_(text1, text2);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Find the 'middle snake' of a diff, split the problem in two
|
||||
* and return the recursively constructed diff.
|
||||
* See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
|
||||
* @param {string} text1 Old string to be diffed.
|
||||
* @param {string} text2 New string to be diffed.
|
||||
* @return {Array} Array of diff tuples.
|
||||
* @private
|
||||
*/
|
||||
function diff_bisect_(text1, text2) {
|
||||
// Cache the text lengths to prevent multiple calls.
|
||||
var text1_length = text1.length;
|
||||
var text2_length = text2.length;
|
||||
var max_d = Math.ceil((text1_length + text2_length) / 2);
|
||||
var v_offset = max_d;
|
||||
var v_length = 2 * max_d;
|
||||
var v1 = new Array(v_length);
|
||||
var v2 = new Array(v_length);
|
||||
// Setting all elements to -1 is faster in Chrome & Firefox than mixing
|
||||
// integers and undefined.
|
||||
for (var x = 0; x < v_length; x++) {
|
||||
v1[x] = -1;
|
||||
v2[x] = -1;
|
||||
}
|
||||
v1[v_offset + 1] = 0;
|
||||
v2[v_offset + 1] = 0;
|
||||
var delta = text1_length - text2_length;
|
||||
// If the total number of characters is odd, then the front path will collide
|
||||
// with the reverse path.
|
||||
var front = (delta % 2 !== 0);
|
||||
// Offsets for start and end of k loop.
|
||||
// Prevents mapping of space beyond the grid.
|
||||
var k1start = 0;
|
||||
var k1end = 0;
|
||||
var k2start = 0;
|
||||
var k2end = 0;
|
||||
for (var d = 0; d < max_d; d++) {
|
||||
// Walk the front path one step.
|
||||
for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
|
||||
var k1_offset = v_offset + k1;
|
||||
var x1;
|
||||
if (k1 === -d || (k1 !== d && v1[k1_offset - 1] < v1[k1_offset + 1])) {
|
||||
x1 = v1[k1_offset + 1];
|
||||
} else {
|
||||
x1 = v1[k1_offset - 1] + 1;
|
||||
}
|
||||
var y1 = x1 - k1;
|
||||
while (
|
||||
x1 < text1_length && y1 < text2_length &&
|
||||
text1.charAt(x1) === text2.charAt(y1)
|
||||
) {
|
||||
x1++;
|
||||
y1++;
|
||||
}
|
||||
v1[k1_offset] = x1;
|
||||
if (x1 > text1_length) {
|
||||
// Ran off the right of the graph.
|
||||
k1end += 2;
|
||||
} else if (y1 > text2_length) {
|
||||
// Ran off the bottom of the graph.
|
||||
k1start += 2;
|
||||
} else if (front) {
|
||||
var k2_offset = v_offset + delta - k1;
|
||||
if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] !== -1) {
|
||||
// Mirror x2 onto top-left coordinate system.
|
||||
var x2 = text1_length - v2[k2_offset];
|
||||
if (x1 >= x2) {
|
||||
// Overlap detected.
|
||||
return diff_bisectSplit_(text1, text2, x1, y1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Walk the reverse path one step.
|
||||
for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
|
||||
var k2_offset = v_offset + k2;
|
||||
var x2;
|
||||
if (k2 === -d || (k2 !== d && v2[k2_offset - 1] < v2[k2_offset + 1])) {
|
||||
x2 = v2[k2_offset + 1];
|
||||
} else {
|
||||
x2 = v2[k2_offset - 1] + 1;
|
||||
}
|
||||
var y2 = x2 - k2;
|
||||
while (
|
||||
x2 < text1_length && y2 < text2_length &&
|
||||
text1.charAt(text1_length - x2 - 1) === text2.charAt(text2_length - y2 - 1)
|
||||
) {
|
||||
x2++;
|
||||
y2++;
|
||||
}
|
||||
v2[k2_offset] = x2;
|
||||
if (x2 > text1_length) {
|
||||
// Ran off the left of the graph.
|
||||
k2end += 2;
|
||||
} else if (y2 > text2_length) {
|
||||
// Ran off the top of the graph.
|
||||
k2start += 2;
|
||||
} else if (!front) {
|
||||
var k1_offset = v_offset + delta - k2;
|
||||
if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] !== -1) {
|
||||
var x1 = v1[k1_offset];
|
||||
var y1 = v_offset + x1 - k1_offset;
|
||||
// Mirror x2 onto top-left coordinate system.
|
||||
x2 = text1_length - x2;
|
||||
if (x1 >= x2) {
|
||||
// Overlap detected.
|
||||
return diff_bisectSplit_(text1, text2, x1, y1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Diff took too long and hit the deadline or
|
||||
// number of diffs equals number of characters, no commonality at all.
|
||||
return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Given the location of the 'middle snake', split the diff in two parts
|
||||
* and recurse.
|
||||
* @param {string} text1 Old string to be diffed.
|
||||
* @param {string} text2 New string to be diffed.
|
||||
* @param {number} x Index of split point in text1.
|
||||
* @param {number} y Index of split point in text2.
|
||||
* @return {Array} Array of diff tuples.
|
||||
*/
|
||||
function diff_bisectSplit_(text1, text2, x, y) {
|
||||
var text1a = text1.substring(0, x);
|
||||
var text2a = text2.substring(0, y);
|
||||
var text1b = text1.substring(x);
|
||||
var text2b = text2.substring(y);
|
||||
|
||||
// Compute both diffs serially.
|
||||
var diffs = diff_main(text1a, text2a);
|
||||
var diffsb = diff_main(text1b, text2b);
|
||||
|
||||
return diffs.concat(diffsb);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine the common prefix of two strings.
|
||||
* @param {string} text1 First string.
|
||||
* @param {string} text2 Second string.
|
||||
* @return {number} The number of characters common to the start of each
|
||||
* string.
|
||||
*/
|
||||
function diff_commonPrefix(text1, text2) {
|
||||
// Quick check for common null cases.
|
||||
if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) {
|
||||
return 0;
|
||||
}
|
||||
// Binary search.
|
||||
// Performance analysis: http://neil.fraser.name/news/2007/10/09/
|
||||
var pointermin = 0;
|
||||
var pointermax = Math.min(text1.length, text2.length);
|
||||
var pointermid = pointermax;
|
||||
var pointerstart = 0;
|
||||
while (pointermin < pointermid) {
|
||||
if (
|
||||
text1.substring(pointerstart, pointermid) ==
|
||||
text2.substring(pointerstart, pointermid)
|
||||
) {
|
||||
pointermin = pointermid;
|
||||
pointerstart = pointermin;
|
||||
} else {
|
||||
pointermax = pointermid;
|
||||
}
|
||||
pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
|
||||
}
|
||||
|
||||
if (is_surrogate_pair_start(text1.charCodeAt(pointermid - 1))) {
|
||||
pointermid--;
|
||||
}
|
||||
|
||||
return pointermid;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine the common suffix of two strings.
|
||||
* @param {string} text1 First string.
|
||||
* @param {string} text2 Second string.
|
||||
* @return {number} The number of characters common to the end of each string.
|
||||
*/
|
||||
function diff_commonSuffix(text1, text2) {
|
||||
// Quick check for common null cases.
|
||||
if (!text1 || !text2 || text1.slice(-1) !== text2.slice(-1)) {
|
||||
return 0;
|
||||
}
|
||||
// Binary search.
|
||||
// Performance analysis: http://neil.fraser.name/news/2007/10/09/
|
||||
var pointermin = 0;
|
||||
var pointermax = Math.min(text1.length, text2.length);
|
||||
var pointermid = pointermax;
|
||||
var pointerend = 0;
|
||||
while (pointermin < pointermid) {
|
||||
if (
|
||||
text1.substring(text1.length - pointermid, text1.length - pointerend) ==
|
||||
text2.substring(text2.length - pointermid, text2.length - pointerend)
|
||||
) {
|
||||
pointermin = pointermid;
|
||||
pointerend = pointermin;
|
||||
} else {
|
||||
pointermax = pointermid;
|
||||
}
|
||||
pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
|
||||
}
|
||||
|
||||
if (is_surrogate_pair_end(text1.charCodeAt(text1.length - pointermid))) {
|
||||
pointermid--;
|
||||
}
|
||||
|
||||
return pointermid;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Do the two texts share a substring which is at least half the length of the
|
||||
* longer text?
|
||||
* This speedup can produce non-minimal diffs.
|
||||
* @param {string} text1 First string.
|
||||
* @param {string} text2 Second string.
|
||||
* @return {Array.<string>} Five element Array, containing the prefix of
|
||||
* text1, the suffix of text1, the prefix of text2, the suffix of
|
||||
* text2 and the common middle. Or null if there was no match.
|
||||
*/
|
||||
function diff_halfMatch_(text1, text2) {
|
||||
var longtext = text1.length > text2.length ? text1 : text2;
|
||||
var shorttext = text1.length > text2.length ? text2 : text1;
|
||||
if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
|
||||
return null; // Pointless.
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a substring of shorttext exist within longtext such that the substring
|
||||
* is at least half the length of longtext?
|
||||
* Closure, but does not reference any external variables.
|
||||
* @param {string} longtext Longer string.
|
||||
* @param {string} shorttext Shorter string.
|
||||
* @param {number} i Start index of quarter length substring within longtext.
|
||||
* @return {Array.<string>} Five element Array, containing the prefix of
|
||||
* longtext, the suffix of longtext, the prefix of shorttext, the suffix
|
||||
* of shorttext and the common middle. Or null if there was no match.
|
||||
* @private
|
||||
*/
|
||||
function diff_halfMatchI_(longtext, shorttext, i) {
|
||||
// Start with a 1/4 length substring at position i as a seed.
|
||||
var seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
|
||||
var j = -1;
|
||||
var best_common = '';
|
||||
var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b;
|
||||
while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
|
||||
var prefixLength = diff_commonPrefix(
|
||||
longtext.substring(i), shorttext.substring(j));
|
||||
var suffixLength = diff_commonSuffix(
|
||||
longtext.substring(0, i), shorttext.substring(0, j));
|
||||
if (best_common.length < suffixLength + prefixLength) {
|
||||
best_common = shorttext.substring(
|
||||
j - suffixLength, j) + shorttext.substring(j, j + prefixLength);
|
||||
best_longtext_a = longtext.substring(0, i - suffixLength);
|
||||
best_longtext_b = longtext.substring(i + prefixLength);
|
||||
best_shorttext_a = shorttext.substring(0, j - suffixLength);
|
||||
best_shorttext_b = shorttext.substring(j + prefixLength);
|
||||
}
|
||||
}
|
||||
if (best_common.length * 2 >= longtext.length) {
|
||||
return [
|
||||
best_longtext_a, best_longtext_b,
|
||||
best_shorttext_a, best_shorttext_b, best_common
|
||||
];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// First check if the second quarter is the seed for a half-match.
|
||||
var hm1 = diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 4));
|
||||
// Check again based on the third quarter.
|
||||
var hm2 = diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 2));
|
||||
var hm;
|
||||
if (!hm1 && !hm2) {
|
||||
return null;
|
||||
} else if (!hm2) {
|
||||
hm = hm1;
|
||||
} else if (!hm1) {
|
||||
hm = hm2;
|
||||
} else {
|
||||
// Both matched. Select the longest.
|
||||
hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
|
||||
}
|
||||
|
||||
// A half-match was found, sort out the return data.
|
||||
var text1_a, text1_b, text2_a, text2_b;
|
||||
if (text1.length > text2.length) {
|
||||
text1_a = hm[0];
|
||||
text1_b = hm[1];
|
||||
text2_a = hm[2];
|
||||
text2_b = hm[3];
|
||||
} else {
|
||||
text2_a = hm[0];
|
||||
text2_b = hm[1];
|
||||
text1_a = hm[2];
|
||||
text1_b = hm[3];
|
||||
}
|
||||
var mid_common = hm[4];
|
||||
return [text1_a, text1_b, text2_a, text2_b, mid_common];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reorder and merge like edit sections. Merge equalities.
|
||||
* Any edit section can move as long as it doesn't cross an equality.
|
||||
* @param {Array} diffs Array of diff tuples.
|
||||
* @param {boolean} fix_unicode Whether to normalize to a unicode-correct diff
|
||||
*/
|
||||
function diff_cleanupMerge(diffs, fix_unicode) {
|
||||
diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end.
|
||||
var pointer = 0;
|
||||
var count_delete = 0;
|
||||
var count_insert = 0;
|
||||
var text_delete = '';
|
||||
var text_insert = '';
|
||||
var commonlength;
|
||||
while (pointer < diffs.length) {
|
||||
if (pointer < diffs.length - 1 && !diffs[pointer][1]) {
|
||||
diffs.splice(pointer, 1);
|
||||
continue;
|
||||
}
|
||||
switch (diffs[pointer][0]) {
|
||||
case DIFF_INSERT:
|
||||
|
||||
count_insert++;
|
||||
text_insert += diffs[pointer][1];
|
||||
pointer++;
|
||||
break;
|
||||
case DIFF_DELETE:
|
||||
count_delete++;
|
||||
text_delete += diffs[pointer][1];
|
||||
pointer++;
|
||||
break;
|
||||
case DIFF_EQUAL:
|
||||
var previous_equality = pointer - count_insert - count_delete - 1;
|
||||
if (fix_unicode) {
|
||||
// prevent splitting of unicode surrogate pairs. when fix_unicode is true,
|
||||
// we assume that the old and new text in the diff are complete and correct
|
||||
// unicode-encoded JS strings, but the tuple boundaries may fall between
|
||||
// surrogate pairs. we fix this by shaving off stray surrogates from the end
|
||||
// of the previous equality and the beginning of this equality. this may create
|
||||
// empty equalities or a common prefix or suffix. for example, if AB and AC are
|
||||
// emojis, `[[0, 'A'], [-1, 'BA'], [0, 'C']]` would turn into deleting 'ABAC' and
|
||||
// inserting 'AC', and then the common suffix 'AC' will be eliminated. in this
|
||||
// particular case, both equalities go away, we absorb any previous inequalities,
|
||||
// and we keep scanning for the next equality before rewriting the tuples.
|
||||
if (previous_equality >= 0 && ends_with_pair_start(diffs[previous_equality][1])) {
|
||||
var stray = diffs[previous_equality][1].slice(-1);
|
||||
diffs[previous_equality][1] = diffs[previous_equality][1].slice(0, -1);
|
||||
text_delete = stray + text_delete;
|
||||
text_insert = stray + text_insert;
|
||||
if (!diffs[previous_equality][1]) {
|
||||
// emptied out previous equality, so delete it and include previous delete/insert
|
||||
diffs.splice(previous_equality, 1);
|
||||
pointer--;
|
||||
var k = previous_equality - 1;
|
||||
if (diffs[k] && diffs[k][0] === DIFF_INSERT) {
|
||||
count_insert++;
|
||||
text_insert = diffs[k][1] + text_insert;
|
||||
k--;
|
||||
}
|
||||
if (diffs[k] && diffs[k][0] === DIFF_DELETE) {
|
||||
count_delete++;
|
||||
text_delete = diffs[k][1] + text_delete;
|
||||
k--;
|
||||
}
|
||||
previous_equality = k;
|
||||
}
|
||||
}
|
||||
if (starts_with_pair_end(diffs[pointer][1])) {
|
||||
var stray = diffs[pointer][1].charAt(0);
|
||||
diffs[pointer][1] = diffs[pointer][1].slice(1);
|
||||
text_delete += stray;
|
||||
text_insert += stray;
|
||||
}
|
||||
}
|
||||
if (pointer < diffs.length - 1 && !diffs[pointer][1]) {
|
||||
// for empty equality not at end, wait for next equality
|
||||
diffs.splice(pointer, 1);
|
||||
break;
|
||||
}
|
||||
if (text_delete.length > 0 || text_insert.length > 0) {
|
||||
// note that diff_commonPrefix and diff_commonSuffix are unicode-aware
|
||||
if (text_delete.length > 0 && text_insert.length > 0) {
|
||||
// Factor out any common prefixes.
|
||||
commonlength = diff_commonPrefix(text_insert, text_delete);
|
||||
if (commonlength !== 0) {
|
||||
if (previous_equality >= 0) {
|
||||
diffs[previous_equality][1] += text_insert.substring(0, commonlength);
|
||||
} else {
|
||||
diffs.splice(0, 0, [DIFF_EQUAL, text_insert.substring(0, commonlength)]);
|
||||
pointer++;
|
||||
}
|
||||
text_insert = text_insert.substring(commonlength);
|
||||
text_delete = text_delete.substring(commonlength);
|
||||
}
|
||||
// Factor out any common suffixes.
|
||||
commonlength = diff_commonSuffix(text_insert, text_delete);
|
||||
if (commonlength !== 0) {
|
||||
diffs[pointer][1] =
|
||||
text_insert.substring(text_insert.length - commonlength) + diffs[pointer][1];
|
||||
text_insert = text_insert.substring(0, text_insert.length - commonlength);
|
||||
text_delete = text_delete.substring(0, text_delete.length - commonlength);
|
||||
}
|
||||
}
|
||||
// Delete the offending records and add the merged ones.
|
||||
var n = count_insert + count_delete;
|
||||
if (text_delete.length === 0 && text_insert.length === 0) {
|
||||
diffs.splice(pointer - n, n);
|
||||
pointer = pointer - n;
|
||||
} else if (text_delete.length === 0) {
|
||||
diffs.splice(pointer - n, n, [DIFF_INSERT, text_insert]);
|
||||
pointer = pointer - n + 1;
|
||||
} else if (text_insert.length === 0) {
|
||||
diffs.splice(pointer - n, n, [DIFF_DELETE, text_delete]);
|
||||
pointer = pointer - n + 1;
|
||||
} else {
|
||||
diffs.splice(pointer - n, n, [DIFF_DELETE, text_delete], [DIFF_INSERT, text_insert]);
|
||||
pointer = pointer - n + 2;
|
||||
}
|
||||
}
|
||||
if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
|
||||
// Merge this equality with the previous one.
|
||||
diffs[pointer - 1][1] += diffs[pointer][1];
|
||||
diffs.splice(pointer, 1);
|
||||
} else {
|
||||
pointer++;
|
||||
}
|
||||
count_insert = 0;
|
||||
count_delete = 0;
|
||||
text_delete = '';
|
||||
text_insert = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (diffs[diffs.length - 1][1] === '') {
|
||||
diffs.pop(); // Remove the dummy entry at the end.
|
||||
}
|
||||
|
||||
// Second pass: look for single edits surrounded on both sides by equalities
|
||||
// which can be shifted sideways to eliminate an equality.
|
||||
// e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
|
||||
var changes = false;
|
||||
pointer = 1;
|
||||
// Intentionally ignore the first and last element (don't need checking).
|
||||
while (pointer < diffs.length - 1) {
|
||||
if (diffs[pointer - 1][0] === DIFF_EQUAL &&
|
||||
diffs[pointer + 1][0] === DIFF_EQUAL) {
|
||||
// This is a single edit surrounded by equalities.
|
||||
if (diffs[pointer][1].substring(diffs[pointer][1].length -
|
||||
diffs[pointer - 1][1].length) === diffs[pointer - 1][1]) {
|
||||
// Shift the edit over the previous equality.
|
||||
diffs[pointer][1] = diffs[pointer - 1][1] +
|
||||
diffs[pointer][1].substring(0, diffs[pointer][1].length -
|
||||
diffs[pointer - 1][1].length);
|
||||
diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
|
||||
diffs.splice(pointer - 1, 1);
|
||||
changes = true;
|
||||
} else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) ==
|
||||
diffs[pointer + 1][1]) {
|
||||
// Shift the edit over the next equality.
|
||||
diffs[pointer - 1][1] += diffs[pointer + 1][1];
|
||||
diffs[pointer][1] =
|
||||
diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
|
||||
diffs[pointer + 1][1];
|
||||
diffs.splice(pointer + 1, 1);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
pointer++;
|
||||
}
|
||||
// If shifts were made, the diff needs reordering and another shift sweep.
|
||||
if (changes) {
|
||||
diff_cleanupMerge(diffs, fix_unicode);
|
||||
}
|
||||
};
|
||||
|
||||
function is_surrogate_pair_start(charCode) {
|
||||
return charCode >= 0xD800 && charCode <= 0xDBFF;
|
||||
}
|
||||
|
||||
function is_surrogate_pair_end(charCode) {
|
||||
return charCode >= 0xDC00 && charCode <= 0xDFFF;
|
||||
}
|
||||
|
||||
function starts_with_pair_end(str) {
|
||||
return is_surrogate_pair_end(str.charCodeAt(0));
|
||||
}
|
||||
|
||||
function ends_with_pair_start(str) {
|
||||
return is_surrogate_pair_start(str.charCodeAt(str.length - 1));
|
||||
}
|
||||
|
||||
function remove_empty_tuples(tuples) {
|
||||
var ret = [];
|
||||
for (var i = 0; i < tuples.length; i++) {
|
||||
if (tuples[i][1].length > 0) {
|
||||
ret.push(tuples[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function make_edit_splice(before, oldMiddle, newMiddle, after) {
|
||||
if (ends_with_pair_start(before) || starts_with_pair_end(after)) {
|
||||
return null;
|
||||
}
|
||||
return remove_empty_tuples([
|
||||
[DIFF_EQUAL, before],
|
||||
[DIFF_DELETE, oldMiddle],
|
||||
[DIFF_INSERT, newMiddle],
|
||||
[DIFF_EQUAL, after]
|
||||
]);
|
||||
}
|
||||
|
||||
function find_cursor_edit_diff(oldText, newText, cursor_pos) {
|
||||
// note: this runs after equality check has ruled out exact equality
|
||||
var oldRange = typeof cursor_pos === 'number' ?
|
||||
{ index: cursor_pos, length: 0 } : cursor_pos.oldRange;
|
||||
var newRange = typeof cursor_pos === 'number' ?
|
||||
null : cursor_pos.newRange;
|
||||
// take into account the old and new selection to generate the best diff
|
||||
// possible for a text edit. for example, a text change from "xxx" to "xx"
|
||||
// could be a delete or forwards-delete of any one of the x's, or the
|
||||
// result of selecting two of the x's and typing "x".
|
||||
var oldLength = oldText.length;
|
||||
var newLength = newText.length;
|
||||
if (oldRange.length === 0 && (newRange === null || newRange.length === 0)) {
|
||||
// see if we have an insert or delete before or after cursor
|
||||
var oldCursor = oldRange.index;
|
||||
var oldBefore = oldText.slice(0, oldCursor);
|
||||
var oldAfter = oldText.slice(oldCursor);
|
||||
var maybeNewCursor = newRange ? newRange.index : null;
|
||||
editBefore: {
|
||||
// is this an insert or delete right before oldCursor?
|
||||
var newCursor = oldCursor + newLength - oldLength;
|
||||
if (maybeNewCursor !== null && maybeNewCursor !== newCursor) {
|
||||
break editBefore;
|
||||
}
|
||||
if (newCursor < 0 || newCursor > newLength) {
|
||||
break editBefore;
|
||||
}
|
||||
var newBefore = newText.slice(0, newCursor);
|
||||
var newAfter = newText.slice(newCursor);
|
||||
if (newAfter !== oldAfter) {
|
||||
break editBefore;
|
||||
}
|
||||
var prefixLength = Math.min(oldCursor, newCursor);
|
||||
var oldPrefix = oldBefore.slice(0, prefixLength);
|
||||
var newPrefix = newBefore.slice(0, prefixLength);
|
||||
if (oldPrefix !== newPrefix) {
|
||||
break editBefore;
|
||||
}
|
||||
var oldMiddle = oldBefore.slice(prefixLength);
|
||||
var newMiddle = newBefore.slice(prefixLength);
|
||||
return make_edit_splice(oldPrefix, oldMiddle, newMiddle, oldAfter);
|
||||
}
|
||||
editAfter: {
|
||||
// is this an insert or delete right after oldCursor?
|
||||
if (maybeNewCursor !== null && maybeNewCursor !== oldCursor) {
|
||||
break editAfter;
|
||||
}
|
||||
var cursor = oldCursor;
|
||||
var newBefore = newText.slice(0, cursor);
|
||||
var newAfter = newText.slice(cursor);
|
||||
if (newBefore !== oldBefore) {
|
||||
break editAfter;
|
||||
}
|
||||
var suffixLength = Math.min(oldLength - cursor, newLength - cursor);
|
||||
var oldSuffix = oldAfter.slice(oldAfter.length - suffixLength);
|
||||
var newSuffix = newAfter.slice(newAfter.length - suffixLength);
|
||||
if (oldSuffix !== newSuffix) {
|
||||
break editAfter;
|
||||
}
|
||||
var oldMiddle = oldAfter.slice(0, oldAfter.length - suffixLength);
|
||||
var newMiddle = newAfter.slice(0, newAfter.length - suffixLength);
|
||||
return make_edit_splice(oldBefore, oldMiddle, newMiddle, oldSuffix);
|
||||
}
|
||||
}
|
||||
if (oldRange.length > 0 && newRange && newRange.length === 0) {
|
||||
replaceRange: {
|
||||
// see if diff could be a splice of the old selection range
|
||||
var oldPrefix = oldText.slice(0, oldRange.index);
|
||||
var oldSuffix = oldText.slice(oldRange.index + oldRange.length);
|
||||
var prefixLength = oldPrefix.length;
|
||||
var suffixLength = oldSuffix.length;
|
||||
if (newLength < prefixLength + suffixLength) {
|
||||
break replaceRange;
|
||||
}
|
||||
var newPrefix = newText.slice(0, prefixLength);
|
||||
var newSuffix = newText.slice(newLength - suffixLength);
|
||||
if (oldPrefix !== newPrefix || oldSuffix !== newSuffix) {
|
||||
break replaceRange;
|
||||
}
|
||||
var oldMiddle = oldText.slice(prefixLength, oldLength - suffixLength);
|
||||
var newMiddle = newText.slice(prefixLength, newLength - suffixLength);
|
||||
return make_edit_splice(oldPrefix, oldMiddle, newMiddle, oldSuffix);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function diff(text1, text2, cursor_pos) {
|
||||
// only pass fix_unicode=true at the top level, not when diff_main is
|
||||
// recursively invoked
|
||||
return diff_main(text1, text2, cursor_pos, true);
|
||||
}
|
||||
|
||||
diff.INSERT = DIFF_INSERT;
|
||||
diff.DELETE = DIFF_DELETE;
|
||||
diff.EQUAL = DIFF_EQUAL;
|
||||
|
||||
module.exports = diff;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "fast-diff",
|
||||
"version": "1.2.0",
|
||||
"description": "Fast Javascript text diff",
|
||||
"author": "Jason Chen <jhchen7@gmail.com>",
|
||||
"main": "diff.js",
|
||||
"types": "diff.d.ts",
|
||||
"devDependencies": {
|
||||
"lodash": "~3.9.3",
|
||||
"seedrandom": "~2.4.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jhchen/fast-diff"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jhchen/fast-diff/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node test.js"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"diff"
|
||||
]
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
var _ = require('lodash');
|
||||
var seedrandom = require('seedrandom');
|
||||
var diff = require('./diff.js');
|
||||
|
||||
var ITERATIONS = 10000;
|
||||
var ALPHABET = 'GATTACA';
|
||||
var LENGTH = 100;
|
||||
var EMOJI_MAX_LENGTH = 50;
|
||||
|
||||
var seed = Math.floor(Math.random() * 10000);
|
||||
var random = seedrandom(seed);
|
||||
|
||||
console.log('Running regression tests...');
|
||||
[
|
||||
['GAATAAAAAAAGATTAACAT', 'AAAAACTTGTAATTAACAAC'],
|
||||
['🔘🤘🔗🔗', '🔗🤗🤗__🤗🤘🤘🤗🔗🤘🔗'],
|
||||
['🔗🤗🤗__🤗🤘🤘🤗🔗🤘🔗', '🤗🤘🔘'],
|
||||
['🤘🤘🔘🔘_🔘🔗🤘🤗🤗__🔗🤘', '🤘🔘🤘🔗🤘🤘🔗🤗🤘🔘🔘'],
|
||||
['🤗🤘🤗🔘🤘🔘🤗_🤗🔗🤘🤗_🤘🔗🤗🤘🔗🤘🤘🤘🔗🤗🔗🔗🔗🤗_🤘🔗🤗🤗🔘🤗🤗🤘🤗',
|
||||
'_🤗🤘_🤘🤘🔘🤗🔘🤘_🔘🤗🔗🔘🔗🤘🔗🤘🤗🔗🔗🔗🤘🔘_🤗🤘🤘🤘__🤘_🔘🤘🤘_🔗🤘🔘'],
|
||||
['🔗🤘🤗🔘🔘🤗', '🤘🤘🤘🤗🔘🔗🔗'],
|
||||
['🔘_🔗🔗🔗🤗🔗', '🤘🤗🔗🤗_🤘🔘_'],
|
||||
].forEach(function (data) {
|
||||
var result = diff(data[0], data[1]);
|
||||
applyDiff(result, data[0], data[1]);
|
||||
});
|
||||
|
||||
console.log('Running computing ' + ITERATIONS + ' diffs with seed ' + seed + '...');
|
||||
|
||||
console.log('Generating strings...');
|
||||
var strings = [];
|
||||
for (var i = 0; i <= ITERATIONS; ++i) {
|
||||
var chars = [];
|
||||
for (var l = 0; l < LENGTH; ++l) {
|
||||
var letter = ALPHABET.substr(Math.floor(random() * ALPHABET.length), 1);
|
||||
chars.push(letter);
|
||||
}
|
||||
strings.push(chars.join(''));
|
||||
}
|
||||
|
||||
console.log('Running fuzz tests *without* cursor information...');
|
||||
for (var i = 0; i < ITERATIONS; ++i) {
|
||||
var result = diff(strings[i], strings[i + 1]);
|
||||
applyDiff(result, strings[i], strings[i + 1]);
|
||||
}
|
||||
|
||||
console.log('Running fuzz tests *with* cursor information');
|
||||
for (var i = 0; i < ITERATIONS; ++i) {
|
||||
var cursor_pos = Math.floor(random() * strings[i].length + 1);
|
||||
var diffs = diff(strings[i], strings[i + 1], cursor_pos);
|
||||
applyDiff(diffs, strings[i], strings[i + 1]);
|
||||
}
|
||||
|
||||
function parseDiff(str) {
|
||||
if (!str) {
|
||||
return [];
|
||||
}
|
||||
return str.split(/(?=[+\-=])/).map(function (piece) {
|
||||
var symbol = piece.charAt(0);
|
||||
var text = piece.slice(1);
|
||||
return [
|
||||
symbol === '+' ? diff.INSERT : symbol === '-' ? diff.DELETE : diff.EQUAL,
|
||||
text
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Running cursor tests');
|
||||
[
|
||||
['', 0, '', null, ''],
|
||||
|
||||
['', 0, 'a', null, '+a'],
|
||||
['a', 0, 'aa', null, '+a=a'],
|
||||
['a', 1, 'aa', null, '=a+a'],
|
||||
['aa', 0, 'aaa', null, '+a=aa'],
|
||||
['aa', 1, 'aaa', null, '=a+a=a'],
|
||||
['aa', 2, 'aaa', null, '=aa+a'],
|
||||
['aaa', 0, 'aaaa', null, '+a=aaa'],
|
||||
['aaa', 1, 'aaaa', null, '=a+a=aa'],
|
||||
['aaa', 2, 'aaaa', null, '=aa+a=a'],
|
||||
['aaa', 3, 'aaaa', null, '=aaa+a'],
|
||||
|
||||
['a', 0, '', null, '-a'],
|
||||
['a', 1, '', null, '-a'],
|
||||
['aa', 0, 'a', null, '-a=a'],
|
||||
['aa', 1, 'a', null, '-a=a'],
|
||||
['aa', 2, 'a', null, '=a-a'],
|
||||
['aaa', 0, 'aa', null, '-a=aa'],
|
||||
['aaa', 1, 'aa', null, '-a=aa'],
|
||||
['aaa', 2, 'aa', null, '=a-a=a'],
|
||||
['aaa', 3, 'aa', null, '=aa-a'],
|
||||
|
||||
['', 0, '', 0, ''],
|
||||
|
||||
['', 0, 'a', 1, '+a'],
|
||||
['a', 0, 'aa', 1, '+a=a'],
|
||||
['a', 1, 'aa', 2, '=a+a'],
|
||||
['aa', 0, 'aaa', 1, '+a=aa'],
|
||||
['aa', 1, 'aaa', 2, '=a+a=a'],
|
||||
['aa', 2, 'aaa', 3, '=aa+a'],
|
||||
['aaa', 0, 'aaaa', 1, '+a=aaa'],
|
||||
['aaa', 1, 'aaaa', 2, '=a+a=aa'],
|
||||
['aaa', 2, 'aaaa', 3, '=aa+a=a'],
|
||||
['aaa', 3, 'aaaa', 4, '=aaa+a'],
|
||||
|
||||
['a', 1, '', 0, '-a'],
|
||||
['aa', 1, 'a', 0, '-a=a'],
|
||||
['aa', 2, 'a', 1, '=a-a'],
|
||||
['aaa', 1, 'aa', 0, '-a=aa'],
|
||||
['aaa', 2, 'aa', 1, '=a-a=a'],
|
||||
['aaa', 3, 'aa', 2, '=aa-a'],
|
||||
|
||||
['a', 1, '', 0, '-a'],
|
||||
['aa', 1, 'a', 0, '-a=a'],
|
||||
['aa', 2, 'a', 1, '=a-a'],
|
||||
['aaa', 1, 'aa', 0, '-a=aa'],
|
||||
['aaa', 2, 'aa', 1, '=a-a=a'],
|
||||
['aaa', 3, 'aa', 2, '=aa-a'],
|
||||
|
||||
// forward-delete
|
||||
['a', 0, '', 0, '-a'],
|
||||
['aa', 0, 'a', 0, '-a=a'],
|
||||
['aa', 1, 'a', 1, '=a-a'],
|
||||
['aaa', 0, 'aa', 0, '-a=aa'],
|
||||
['aaa', 1, 'aa', 1, '=a-a=a'],
|
||||
['aaa', 2, 'aa', 2, '=aa-a'],
|
||||
|
||||
['bob', 0, 'bobob', null, '+bo=bob'],
|
||||
['bob', 1, 'bobob', null, '=b+ob=ob'],
|
||||
['bob', 2, 'bobob', null, '=bo+bo=b'],
|
||||
['bob', 3, 'bobob', null, '=bob+ob'],
|
||||
['bob', 0, 'bobob', 2, '+bo=bob'],
|
||||
['bob', 1, 'bobob', 3, '=b+ob=ob'],
|
||||
['bob', 2, 'bobob', 4, '=bo+bo=b'],
|
||||
['bob', 3, 'bobob', 5, '=bob+ob'],
|
||||
['bobob', 2, 'bob', null, '-bo=bob'],
|
||||
['bobob', 3, 'bob', null, '=b-ob=ob'],
|
||||
['bobob', 4, 'bob', null, '=bo-bo=b'],
|
||||
['bobob', 5, 'bob', null, '=bob-ob'],
|
||||
['bobob', 2, 'bob', 0, '-bo=bob'],
|
||||
['bobob', 3, 'bob', 1, '=b-ob=ob'],
|
||||
['bobob', 4, 'bob', 2, '=bo-bo=b'],
|
||||
['bobob', 5, 'bob', 3, '=bob-ob'],
|
||||
|
||||
['bob', 1, 'b', null, '=b-ob'],
|
||||
|
||||
['hello', [0, 5], 'h', 1, '-hello+h'],
|
||||
['yay', [0, 3], 'y', 1, '-yay+y'],
|
||||
['bobob', [1, 4], 'bob', 2, '=b-obo+o=b'],
|
||||
|
||||
].forEach(function (data) {
|
||||
var oldText = data[0];
|
||||
var newText = data[2];
|
||||
var oldRange = typeof data[1] === 'number' ?
|
||||
{ index: data[1], length: 0 } :
|
||||
{ index: data[1][0], length: data[1][1] - data[1][0] };
|
||||
var newRange = typeof data[3] === 'number' ?
|
||||
{ index: data[3], length: 0 } :
|
||||
data[3] === null ? null : { index: data[3][0], length: data[3][1] - data[3][0] };
|
||||
var expected = parseDiff(data[4]);
|
||||
if (newRange === null && typeof data[1] !== 'number') {
|
||||
throw new Error('invalid test case');
|
||||
}
|
||||
var cursorInfo = newRange === null ? data[1] : {
|
||||
oldRange: oldRange,
|
||||
newRange: newRange,
|
||||
};
|
||||
doCursorTest(oldText, newText, cursorInfo, expected);
|
||||
doCursorTest('x' + oldText, 'x' + newText, shiftCursorInfo(cursorInfo, 1), diffPrepend(expected, 'x'));
|
||||
doCursorTest(oldText + 'x', newText + 'x', cursorInfo, diffAppend(expected, 'x'));
|
||||
});
|
||||
|
||||
function diffPrepend(tuples, text) {
|
||||
if (tuples.length > 0 && tuples[0][0] === diff.EQUAL) {
|
||||
return [[diff.EQUAL, text + tuples[0][1]]].concat(tuples.slice(1));
|
||||
} else {
|
||||
return [[diff.EQUAL, text]].concat(tuples);
|
||||
}
|
||||
}
|
||||
|
||||
function diffAppend(tuples, text) {
|
||||
var lastTuple = tuples[tuples.length - 1];
|
||||
if (lastTuple && lastTuple[0] === diff.EQUAL) {
|
||||
return tuples.slice(0, -1).concat([[diff.EQUAL, lastTuple[1] + text]]);
|
||||
} else {
|
||||
return tuples.concat([[diff.EQUAL, text]]);
|
||||
}
|
||||
}
|
||||
|
||||
function shiftCursorInfo(cursorInfo, amount) {
|
||||
if (typeof cursorInfo === 'number') {
|
||||
return cursorInfo + amount;
|
||||
} else {
|
||||
return {
|
||||
oldRange: {
|
||||
index: cursorInfo.oldRange.index + amount,
|
||||
length: cursorInfo.oldRange.length,
|
||||
},
|
||||
newRange: {
|
||||
index: cursorInfo.newRange.index + amount,
|
||||
length: cursorInfo.newRange.length,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function doCursorTest(oldText, newText, cursorInfo, expected) {
|
||||
var result = diff(oldText, newText, cursorInfo);
|
||||
if (!_.isEqual(result, expected)) {
|
||||
console.log([oldText, newText, cursorInfo]);
|
||||
console.log(result, '!==', expected);
|
||||
throw new Error('cursor test failed');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Running emoji tests');
|
||||
[
|
||||
['🐶', '🐯', '-🐶+🐯'],
|
||||
['👨🏽', '👩🏽', '-👨+👩=🏽'],
|
||||
['👩🏼', '👩🏽', '=👩-🏼+🏽'],
|
||||
|
||||
['🍏🍎', '🍎', '-🍏=🍎'],
|
||||
['🍎', '🍏🍎', '+🍏=🍎'],
|
||||
|
||||
].forEach(function (data) {
|
||||
var oldText = data[0];
|
||||
var newText = data[1];
|
||||
var expected = parseDiff(data[2]);
|
||||
doEmojiTest(oldText, newText, expected);
|
||||
doEmojiTest('x' + oldText, 'x' + newText, diffPrepend(expected, 'x'));
|
||||
doEmojiTest(oldText + 'x', newText + 'x', diffAppend(expected, 'x'));
|
||||
});
|
||||
|
||||
function doEmojiTest(oldText, newText, expected) {
|
||||
var result = diff(oldText, newText);
|
||||
if (!_.isEqual(result, expected)) {
|
||||
console.log(oldText, newText, expected);
|
||||
console.log(result, '!==', expected);
|
||||
throw new Error('Emoji simple test case failed');
|
||||
}
|
||||
}
|
||||
|
||||
// emojis chosen to share high and low surrogates!
|
||||
var EMOJI_ALPHABET = ['_', '🤗', '🔗', '🤘', '🔘'];
|
||||
|
||||
console.log('Generating emoji strings...');
|
||||
var emoji_strings = [];
|
||||
for (var i = 0; i <= ITERATIONS; ++i) {
|
||||
var letters = [];
|
||||
var len = Math.floor(random() * EMOJI_MAX_LENGTH);
|
||||
for (var l = 0; l < len; ++l) {
|
||||
var letter = EMOJI_ALPHABET[Math.floor(random() * EMOJI_ALPHABET.length)];
|
||||
letters.push(letter);
|
||||
}
|
||||
emoji_strings.push(letters.join(''));
|
||||
}
|
||||
|
||||
console.log('Running emoji fuzz tests...');
|
||||
for (var i = 0; i < ITERATIONS; ++i) {
|
||||
var oldText = emoji_strings[i];
|
||||
var newText = emoji_strings[i + 1];
|
||||
var result = diff(oldText, newText);
|
||||
applyDiff(result, oldText, newText);
|
||||
}
|
||||
|
||||
// Applies a diff to text, throwing an error if diff is invalid or incorrect
|
||||
function applyDiff(diffs, text, expectedResult) {
|
||||
var pos = 0;
|
||||
function throwError(message) {
|
||||
console.log(diffs, text, expectedResult);
|
||||
throw new Error(message);
|
||||
}
|
||||
function expect(expected) {
|
||||
var found = text.substr(pos, expected.length);
|
||||
if (found !== expected) {
|
||||
throwError('Expected "' + expected + '", found "' + found + '"');
|
||||
}
|
||||
}
|
||||
var result = '';
|
||||
var inserts_since_last_equality = 0;
|
||||
var deletes_since_last_equality = 0;
|
||||
for (var i = 0; i < diffs.length; i++) {
|
||||
var d = diffs[i];
|
||||
if (!d[1]) {
|
||||
throwError('Empty tuple in diff')
|
||||
}
|
||||
var firstCharCode = d[1].charCodeAt(0);
|
||||
var lastCharCode = d[1].slice(-1).charCodeAt(0);
|
||||
if (firstCharCode >= 0xDC00 && firstCharCode <= 0xDFFF ||
|
||||
lastCharCode >= 0xD800 && lastCharCode <= 0xDBFF) {
|
||||
throwError('Bad unicode diff tuple')
|
||||
}
|
||||
switch (d[0]) {
|
||||
case diff.EQUAL:
|
||||
if (i !== 0 && !inserts_since_last_equality && !deletes_since_last_equality) {
|
||||
throwError('two consecutive equalities in diff');
|
||||
}
|
||||
inserts_since_last_equality = 0;
|
||||
deletes_since_last_equality = 0;
|
||||
expect(d[1]);
|
||||
result += d[1];
|
||||
pos += d[1].length;
|
||||
break;
|
||||
case diff.DELETE:
|
||||
if (deletes_since_last_equality) {
|
||||
throwError('multiple deletes between equalities')
|
||||
}
|
||||
if (inserts_since_last_equality) {
|
||||
throwError('delete following insert in diff')
|
||||
}
|
||||
deletes_since_last_equality++;
|
||||
expect(d[1]);
|
||||
pos += d[1].length;
|
||||
break
|
||||
case diff.INSERT:
|
||||
if (inserts_since_last_equality) {
|
||||
throwError('multiple inserts between equalities')
|
||||
}
|
||||
inserts_since_last_equality++;
|
||||
result += d[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos !== text.length) {
|
||||
throwError('Diff did not consume entire input text');
|
||||
}
|
||||
if (result !== expectedResult) {
|
||||
console.log(diffs, text, expectedResult, result);
|
||||
throw new Error('Diff not correct')
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
console.log("Success!");
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Denis Malinochkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+795
@@ -0,0 +1,795 @@
|
||||
# fast-glob
|
||||
|
||||
> It's a very fast and efficient [glob][glob_definition] library for [Node.js][node_js].
|
||||
|
||||
This package provides methods for traversing the file system and returning pathnames that matched a defined set of a specified pattern according to the rules used by the Unix Bash shell with some simplifications, meanwhile results are returned in **arbitrary order**. Quick, simple, effective.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<details>
|
||||
<summary><strong>Details</strong></summary>
|
||||
|
||||
* [Highlights](#highlights)
|
||||
* [Donation](#donation)
|
||||
* [Old and modern mode](#old-and-modern-mode)
|
||||
* [Pattern syntax](#pattern-syntax)
|
||||
* [Basic syntax](#basic-syntax)
|
||||
* [Advanced syntax](#advanced-syntax)
|
||||
* [Installation](#installation)
|
||||
* [API](#api)
|
||||
* [Asynchronous](#asynchronous)
|
||||
* [Synchronous](#synchronous)
|
||||
* [Stream](#stream)
|
||||
* [patterns](#patterns)
|
||||
* [[options]](#options)
|
||||
* [Helpers](#helpers)
|
||||
* [generateTasks](#generatetaskspatterns-options)
|
||||
* [isDynamicPattern](#isdynamicpatternpattern-options)
|
||||
* [escapePath](#escapepathpattern)
|
||||
* [Options](#options-3)
|
||||
* [Common](#common)
|
||||
* [concurrency](#concurrency)
|
||||
* [cwd](#cwd)
|
||||
* [deep](#deep)
|
||||
* [followSymbolicLinks](#followsymboliclinks)
|
||||
* [fs](#fs)
|
||||
* [ignore](#ignore)
|
||||
* [suppressErrors](#suppresserrors)
|
||||
* [throwErrorOnBrokenSymbolicLink](#throwerroronbrokensymboliclink)
|
||||
* [Output control](#output-control)
|
||||
* [absolute](#absolute)
|
||||
* [markDirectories](#markdirectories)
|
||||
* [objectMode](#objectmode)
|
||||
* [onlyDirectories](#onlydirectories)
|
||||
* [onlyFiles](#onlyfiles)
|
||||
* [stats](#stats)
|
||||
* [unique](#unique)
|
||||
* [Matching control](#matching-control)
|
||||
* [braceExpansion](#braceexpansion)
|
||||
* [caseSensitiveMatch](#casesensitivematch)
|
||||
* [dot](#dot)
|
||||
* [extglob](#extglob)
|
||||
* [globstar](#globstar)
|
||||
* [baseNameMatch](#basenamematch)
|
||||
* [FAQ](#faq)
|
||||
* [What is a static or dynamic pattern?](#what-is-a-static-or-dynamic-pattern)
|
||||
* [How to write patterns on Windows?](#how-to-write-patterns-on-windows)
|
||||
* [Why are parentheses match wrong?](#why-are-parentheses-match-wrong)
|
||||
* [How to exclude directory from reading?](#how-to-exclude-directory-from-reading)
|
||||
* [How to use UNC path?](#how-to-use-unc-path)
|
||||
* [Compatible with `node-glob`?](#compatible-with-node-glob)
|
||||
* [Benchmarks](#benchmarks)
|
||||
* [Server](#server)
|
||||
* [Nettop](#nettop)
|
||||
* [Changelog](#changelog)
|
||||
* [License](#license)
|
||||
|
||||
</details>
|
||||
|
||||
## Highlights
|
||||
|
||||
* Fast. Probably the fastest.
|
||||
* Supports multiple and negative patterns.
|
||||
* Synchronous, Promise and Stream API.
|
||||
* Object mode. Can return more than just strings.
|
||||
* Error-tolerant.
|
||||
|
||||
## Donation
|
||||
|
||||
Do you like this project? Support it by donating, creating an issue or pull request.
|
||||
|
||||
[][paypal_mrmlnc]
|
||||
|
||||
## Old and modern mode
|
||||
|
||||
This package works in two modes, depending on the environment in which it is used.
|
||||
|
||||
* **Old mode**. Node.js below 10.10 or when the [`stats`](#stats) option is *enabled*.
|
||||
* **Modern mode**. Node.js 10.10+ and the [`stats`](#stats) option is *disabled*.
|
||||
|
||||
The modern mode is faster. Learn more about the [internal mechanism][nodelib_fs_scandir_old_and_modern_modern].
|
||||
|
||||
## Pattern syntax
|
||||
|
||||
> :warning: Always use forward-slashes in glob expressions (patterns and [`ignore`](#ignore) option). Use backslashes for escaping characters.
|
||||
|
||||
There is more than one form of syntax: basic and advanced. Below is a brief overview of the supported features. Also pay attention to our [FAQ](#faq).
|
||||
|
||||
> :book: This package uses a [`micromatch`][micromatch] as a library for pattern matching.
|
||||
|
||||
### Basic syntax
|
||||
|
||||
* An asterisk (`*`) — matches everything except slashes (path separators), hidden files (names starting with `.`).
|
||||
* A double star or globstar (`**`) — matches zero or more directories.
|
||||
* Question mark (`?`) – matches any single character except slashes (path separators).
|
||||
* Sequence (`[seq]`) — matches any character in sequence.
|
||||
|
||||
> :book: A few additional words about the [basic matching behavior][picomatch_matching_behavior].
|
||||
|
||||
Some examples:
|
||||
|
||||
* `src/**/*.js` — matches all files in the `src` directory (any level of nesting) that have the `.js` extension.
|
||||
* `src/*.??` — matches all files in the `src` directory (only first level of nesting) that have a two-character extension.
|
||||
* `file-[01].js` — matches files: `file-0.js`, `file-1.js`.
|
||||
|
||||
### Advanced syntax
|
||||
|
||||
* [Escapes characters][micromatch_backslashes] (`\\`) — matching special characters (`$^*+?()[]`) as literals.
|
||||
* [POSIX character classes][picomatch_posix_brackets] (`[[:digit:]]`).
|
||||
* [Extended globs][micromatch_extglobs] (`?(pattern-list)`).
|
||||
* [Bash style brace expansions][micromatch_braces] (`{}`).
|
||||
* [Regexp character classes][micromatch_regex_character_classes] (`[1-5]`).
|
||||
* [Regex groups][regular_expressions_brackets] (`(a|b)`).
|
||||
|
||||
> :book: A few additional words about the [advanced matching behavior][micromatch_extended_globbing].
|
||||
|
||||
Some examples:
|
||||
|
||||
* `src/**/*.{css,scss}` — matches all files in the `src` directory (any level of nesting) that have the `.css` or `.scss` extension.
|
||||
* `file-[[:digit:]].js` — matches files: `file-0.js`, `file-1.js`, …, `file-9.js`.
|
||||
* `file-{1..3}.js` — matches files: `file-1.js`, `file-2.js`, `file-3.js`.
|
||||
* `file-(1|2)` — matches files: `file-1.js`, `file-2.js`.
|
||||
|
||||
## Installation
|
||||
|
||||
```console
|
||||
npm install fast-glob
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Asynchronous
|
||||
|
||||
```js
|
||||
fg(patterns, [options])
|
||||
```
|
||||
|
||||
Returns a `Promise` with an array of matching entries.
|
||||
|
||||
```js
|
||||
const fg = require('fast-glob');
|
||||
|
||||
const entries = await fg(['.editorconfig', '**/index.js'], { dot: true });
|
||||
|
||||
// ['.editorconfig', 'services/index.js']
|
||||
```
|
||||
|
||||
### Synchronous
|
||||
|
||||
```js
|
||||
fg.sync(patterns, [options])
|
||||
```
|
||||
|
||||
Returns an array of matching entries.
|
||||
|
||||
```js
|
||||
const fg = require('fast-glob');
|
||||
|
||||
const entries = fg.sync(['.editorconfig', '**/index.js'], { dot: true });
|
||||
|
||||
// ['.editorconfig', 'services/index.js']
|
||||
```
|
||||
|
||||
### Stream
|
||||
|
||||
```js
|
||||
fg.stream(patterns, [options])
|
||||
```
|
||||
|
||||
Returns a [`ReadableStream`][node_js_stream_readable_streams] when the `data` event will be emitted with matching entry.
|
||||
|
||||
```js
|
||||
const fg = require('fast-glob');
|
||||
|
||||
const stream = fg.stream(['.editorconfig', '**/index.js'], { dot: true });
|
||||
|
||||
for await (const entry of stream) {
|
||||
// .editorconfig
|
||||
// services/index.js
|
||||
}
|
||||
```
|
||||
|
||||
#### patterns
|
||||
|
||||
* Required: `true`
|
||||
* Type: `string | string[]`
|
||||
|
||||
Any correct pattern(s).
|
||||
|
||||
> :1234: [Pattern syntax](#pattern-syntax)
|
||||
>
|
||||
> :warning: This package does not respect the order of patterns. First, all the negative patterns are applied, and only then the positive patterns. If you want to get a certain order of records, use sorting or split calls.
|
||||
|
||||
#### [options]
|
||||
|
||||
* Required: `false`
|
||||
* Type: [`Options`](#options-3)
|
||||
|
||||
See [Options](#options-3) section.
|
||||
|
||||
### Helpers
|
||||
|
||||
#### `generateTasks(patterns, [options])`
|
||||
|
||||
Returns the internal representation of patterns ([`Task`](./src/managers/tasks.ts) is a combining patterns by base directory).
|
||||
|
||||
```js
|
||||
fg.generateTasks('*');
|
||||
|
||||
[{
|
||||
base: '.', // Parent directory for all patterns inside this task
|
||||
dynamic: true, // Dynamic or static patterns are in this task
|
||||
patterns: ['*'],
|
||||
positive: ['*'],
|
||||
negative: []
|
||||
}]
|
||||
```
|
||||
|
||||
##### patterns
|
||||
|
||||
* Required: `true`
|
||||
* Type: `string | string[]`
|
||||
|
||||
Any correct pattern(s).
|
||||
|
||||
##### [options]
|
||||
|
||||
* Required: `false`
|
||||
* Type: [`Options`](#options-3)
|
||||
|
||||
See [Options](#options-3) section.
|
||||
|
||||
#### `isDynamicPattern(pattern, [options])`
|
||||
|
||||
Returns `true` if the passed pattern is a dynamic pattern.
|
||||
|
||||
> :1234: [What is a static or dynamic pattern?](#what-is-a-static-or-dynamic-pattern)
|
||||
|
||||
```js
|
||||
fg.isDynamicPattern('*'); // true
|
||||
fg.isDynamicPattern('abc'); // false
|
||||
```
|
||||
|
||||
##### pattern
|
||||
|
||||
* Required: `true`
|
||||
* Type: `string`
|
||||
|
||||
Any correct pattern.
|
||||
|
||||
##### [options]
|
||||
|
||||
* Required: `false`
|
||||
* Type: [`Options`](#options-3)
|
||||
|
||||
See [Options](#options-3) section.
|
||||
|
||||
#### `escapePath(pattern)`
|
||||
|
||||
Returns a path with escaped special characters (`*?|(){}[]`, `!` at the beginning of line, `@+!` before the opening parenthesis).
|
||||
|
||||
```js
|
||||
fg.escapePath('!abc'); // \\!abc
|
||||
fg.escapePath('C:/Program Files (x86)'); // C:/Program Files \\(x86\\)
|
||||
```
|
||||
|
||||
##### pattern
|
||||
|
||||
* Required: `true`
|
||||
* Type: `string`
|
||||
|
||||
Any string, for example, a path to a file.
|
||||
|
||||
## Options
|
||||
|
||||
### Common options
|
||||
|
||||
#### concurrency
|
||||
|
||||
* Type: `number`
|
||||
* Default: `os.cpus().length`
|
||||
|
||||
Specifies the maximum number of concurrent requests from a reader to read directories.
|
||||
|
||||
> :book: The higher the number, the higher the performance and load on the file system. If you want to read in quiet mode, set the value to a comfortable number or `1`.
|
||||
|
||||
#### cwd
|
||||
|
||||
* Type: `string`
|
||||
* Default: `process.cwd()`
|
||||
|
||||
The current working directory in which to search.
|
||||
|
||||
#### deep
|
||||
|
||||
* Type: `number`
|
||||
* Default: `Infinity`
|
||||
|
||||
Specifies the maximum depth of a read directory relative to the start directory.
|
||||
|
||||
For example, you have the following tree:
|
||||
|
||||
```js
|
||||
dir/
|
||||
└── one/ // 1
|
||||
└── two/ // 2
|
||||
└── file.js // 3
|
||||
```
|
||||
|
||||
```js
|
||||
// With base directory
|
||||
fg.sync('dir/**', { onlyFiles: false, deep: 1 }); // ['dir/one']
|
||||
fg.sync('dir/**', { onlyFiles: false, deep: 2 }); // ['dir/one', 'dir/one/two']
|
||||
|
||||
// With cwd option
|
||||
fg.sync('**', { onlyFiles: false, cwd: 'dir', deep: 1 }); // ['one']
|
||||
fg.sync('**', { onlyFiles: false, cwd: 'dir', deep: 2 }); // ['one', 'one/two']
|
||||
```
|
||||
|
||||
> :book: If you specify a pattern with some base directory, this directory will not participate in the calculation of the depth of the found directories. Think of it as a [`cwd`](#cwd) option.
|
||||
|
||||
#### followSymbolicLinks
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `true`
|
||||
|
||||
Indicates whether to traverse descendants of symbolic link directories when expanding `**` patterns.
|
||||
|
||||
> :book: Note that this option does not affect the base directory of the pattern. For example, if `./a` is a symlink to directory `./b` and you specified `['./a**', './b/**']` patterns, then directory `./a` will still be read.
|
||||
|
||||
> :book: If the [`stats`](#stats) option is specified, the information about the symbolic link (`fs.lstat`) will be replaced with information about the entry (`fs.stat`) behind it.
|
||||
|
||||
#### fs
|
||||
|
||||
* Type: `FileSystemAdapter`
|
||||
* Default: `fs.*`
|
||||
|
||||
Custom implementation of methods for working with the file system.
|
||||
|
||||
```ts
|
||||
export interface FileSystemAdapter {
|
||||
lstat?: typeof fs.lstat;
|
||||
stat?: typeof fs.stat;
|
||||
lstatSync?: typeof fs.lstatSync;
|
||||
statSync?: typeof fs.statSync;
|
||||
readdir?: typeof fs.readdir;
|
||||
readdirSync?: typeof fs.readdirSync;
|
||||
}
|
||||
```
|
||||
|
||||
#### ignore
|
||||
|
||||
* Type: `string[]`
|
||||
* Default: `[]`
|
||||
|
||||
An array of glob patterns to exclude matches. This is an alternative way to use negative patterns.
|
||||
|
||||
```js
|
||||
dir/
|
||||
├── package-lock.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
```js
|
||||
fg.sync(['*.json', '!package-lock.json']); // ['package.json']
|
||||
fg.sync('*.json', { ignore: ['package-lock.json'] }); // ['package.json']
|
||||
```
|
||||
|
||||
#### suppressErrors
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
||||
By default this package suppress only `ENOENT` errors. Set to `true` to suppress any error.
|
||||
|
||||
> :book: Can be useful when the directory has entries with a special level of access.
|
||||
|
||||
#### throwErrorOnBrokenSymbolicLink
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
||||
Throw an error when symbolic link is broken if `true` or safely return `lstat` call if `false`.
|
||||
|
||||
> :book: This option has no effect on errors when reading the symbolic link directory.
|
||||
|
||||
### Output control
|
||||
|
||||
#### absolute
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
||||
Return the absolute path for entries.
|
||||
|
||||
```js
|
||||
fg.sync('*.js', { absolute: false }); // ['index.js']
|
||||
fg.sync('*.js', { absolute: true }); // ['/home/user/index.js']
|
||||
```
|
||||
|
||||
> :book: This option is required if you want to use negative patterns with absolute path, for example, `!${__dirname}/*.js`.
|
||||
|
||||
#### markDirectories
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
||||
Mark the directory path with the final slash.
|
||||
|
||||
```js
|
||||
fg.sync('*', { onlyFiles: false, markDirectories: false }); // ['index.js', 'controllers']
|
||||
fg.sync('*', { onlyFiles: false, markDirectories: true }); // ['index.js', 'controllers/']
|
||||
```
|
||||
|
||||
#### objectMode
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
||||
Returns objects (instead of strings) describing entries.
|
||||
|
||||
```js
|
||||
fg.sync('*', { objectMode: false }); // ['src/index.js']
|
||||
fg.sync('*', { objectMode: true }); // [{ name: 'index.js', path: 'src/index.js', dirent: <fs.Dirent> }]
|
||||
```
|
||||
|
||||
The object has the following fields:
|
||||
|
||||
* name (`string`) — the last part of the path (basename)
|
||||
* path (`string`) — full path relative to the pattern base directory
|
||||
* dirent ([`fs.Dirent`][node_js_fs_class_fs_dirent]) — instance of `fs.Dirent`
|
||||
|
||||
> :book: An object is an internal representation of entry, so getting it does not affect performance.
|
||||
|
||||
#### onlyDirectories
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
||||
Return only directories.
|
||||
|
||||
```js
|
||||
fg.sync('*', { onlyDirectories: false }); // ['index.js', 'src']
|
||||
fg.sync('*', { onlyDirectories: true }); // ['src']
|
||||
```
|
||||
|
||||
> :book: If `true`, the [`onlyFiles`](#onlyfiles) option is automatically `false`.
|
||||
|
||||
#### onlyFiles
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `true`
|
||||
|
||||
Return only files.
|
||||
|
||||
```js
|
||||
fg.sync('*', { onlyFiles: false }); // ['index.js', 'src']
|
||||
fg.sync('*', { onlyFiles: true }); // ['index.js']
|
||||
```
|
||||
|
||||
#### stats
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
||||
Enables an [object mode](#objectmode) with an additional field:
|
||||
|
||||
* stats ([`fs.Stats`][node_js_fs_class_fs_stats]) — instance of `fs.Stats`
|
||||
|
||||
```js
|
||||
fg.sync('*', { stats: false }); // ['src/index.js']
|
||||
fg.sync('*', { stats: true }); // [{ name: 'index.js', path: 'src/index.js', dirent: <fs.Dirent>, stats: <fs.Stats> }]
|
||||
```
|
||||
|
||||
> :book: Returns `fs.stat` instead of `fs.lstat` for symbolic links when the [`followSymbolicLinks`](#followsymboliclinks) option is specified.
|
||||
>
|
||||
> :warning: Unlike [object mode](#objectmode) this mode requires additional calls to the file system. On average, this mode is slower at least twice. See [old and modern mode](#old-and-modern-mode) for more details.
|
||||
|
||||
#### unique
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `true`
|
||||
|
||||
Ensures that the returned entries are unique.
|
||||
|
||||
```js
|
||||
fg.sync(['*.json', 'package.json'], { unique: false }); // ['package.json', 'package.json']
|
||||
fg.sync(['*.json', 'package.json'], { unique: true }); // ['package.json']
|
||||
```
|
||||
|
||||
If `true` and similar entries are found, the result is the first found.
|
||||
|
||||
### Matching control
|
||||
|
||||
#### braceExpansion
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `true`
|
||||
|
||||
Enables Bash-like brace expansion.
|
||||
|
||||
> :1234: [Syntax description][bash_hackers_syntax_expansion_brace] or more [detailed description][micromatch_braces].
|
||||
|
||||
```js
|
||||
dir/
|
||||
├── abd
|
||||
├── acd
|
||||
└── a{b,c}d
|
||||
```
|
||||
|
||||
```js
|
||||
fg.sync('a{b,c}d', { braceExpansion: false }); // ['a{b,c}d']
|
||||
fg.sync('a{b,c}d', { braceExpansion: true }); // ['abd', 'acd']
|
||||
```
|
||||
|
||||
#### caseSensitiveMatch
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `true`
|
||||
|
||||
Enables a [case-sensitive][wikipedia_case_sensitivity] mode for matching files.
|
||||
|
||||
```js
|
||||
dir/
|
||||
├── file.txt
|
||||
└── File.txt
|
||||
```
|
||||
|
||||
```js
|
||||
fg.sync('file.txt', { caseSensitiveMatch: false }); // ['file.txt', 'File.txt']
|
||||
fg.sync('file.txt', { caseSensitiveMatch: true }); // ['file.txt']
|
||||
```
|
||||
|
||||
#### dot
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
||||
Allow patterns to match entries that begin with a period (`.`).
|
||||
|
||||
> :book: Note that an explicit dot in a portion of the pattern will always match dot files.
|
||||
|
||||
```js
|
||||
dir/
|
||||
├── .editorconfig
|
||||
└── package.json
|
||||
```
|
||||
|
||||
```js
|
||||
fg.sync('*', { dot: false }); // ['package.json']
|
||||
fg.sync('*', { dot: true }); // ['.editorconfig', 'package.json']
|
||||
```
|
||||
|
||||
#### extglob
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `true`
|
||||
|
||||
Enables Bash-like `extglob` functionality.
|
||||
|
||||
> :1234: [Syntax description][micromatch_extglobs].
|
||||
|
||||
```js
|
||||
dir/
|
||||
├── README.md
|
||||
└── package.json
|
||||
```
|
||||
|
||||
```js
|
||||
fg.sync('*.+(json|md)', { extglob: false }); // []
|
||||
fg.sync('*.+(json|md)', { extglob: true }); // ['README.md', 'package.json']
|
||||
```
|
||||
|
||||
#### globstar
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `true`
|
||||
|
||||
Enables recursively repeats a pattern containing `**`. If `false`, `**` behaves exactly like `*`.
|
||||
|
||||
```js
|
||||
dir/
|
||||
└── a
|
||||
└── b
|
||||
```
|
||||
|
||||
```js
|
||||
fg.sync('**', { onlyFiles: false, globstar: false }); // ['a']
|
||||
fg.sync('**', { onlyFiles: false, globstar: true }); // ['a', 'a/b']
|
||||
```
|
||||
|
||||
#### baseNameMatch
|
||||
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
||||
If set to `true`, then patterns without slashes will be matched against the basename of the path if it contains slashes.
|
||||
|
||||
```js
|
||||
dir/
|
||||
└── one/
|
||||
└── file.md
|
||||
```
|
||||
|
||||
```js
|
||||
fg.sync('*.md', { baseNameMatch: false }); // []
|
||||
fg.sync('*.md', { baseNameMatch: true }); // ['one/file.md']
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
## What is a static or dynamic pattern?
|
||||
|
||||
All patterns can be divided into two types:
|
||||
|
||||
* **static**. A pattern is considered static if it can be used to get an entry on the file system without using matching mechanisms. For example, the `file.js` pattern is a static pattern because we can just verify that it exists on the file system.
|
||||
* **dynamic**. A pattern is considered dynamic if it cannot be used directly to find occurrences without using a matching mechanisms. For example, the `*` pattern is a dynamic pattern because we cannot use this pattern directly.
|
||||
|
||||
A pattern is considered dynamic if it contains the following characters (`…` — any characters or their absence) or options:
|
||||
|
||||
* The [`caseSensitiveMatch`](#casesensitivematch) option is disabled
|
||||
* `\\` (the escape character)
|
||||
* `*`, `?`, `!` (at the beginning of line)
|
||||
* `[…]`
|
||||
* `(…|…)`
|
||||
* `@(…)`, `!(…)`, `*(…)`, `?(…)`, `+(…)` (respects the [`extglob`](#extglob) option)
|
||||
* `{…,…}`, `{…..…}` (respects the [`braceExpansion`](#braceexpansion) option)
|
||||
|
||||
## How to write patterns on Windows?
|
||||
|
||||
Always use forward-slashes in glob expressions (patterns and [`ignore`](#ignore) option). Use backslashes for escaping characters. With the [`cwd`](#cwd) option use a convenient format.
|
||||
|
||||
**Bad**
|
||||
|
||||
```ts
|
||||
[
|
||||
'directory\\*',
|
||||
path.join(process.cwd(), '**')
|
||||
]
|
||||
```
|
||||
|
||||
**Good**
|
||||
|
||||
```ts
|
||||
[
|
||||
'directory/*',
|
||||
path.join(process.cwd(), '**').replace(/\\/g, '/')
|
||||
]
|
||||
```
|
||||
|
||||
> :book: Use the [`normalize-path`][npm_normalize_path] or the [`unixify`][npm_unixify] package to convert Windows-style path to a Unix-style path.
|
||||
|
||||
Read more about [matching with backslashes][micromatch_backslashes].
|
||||
|
||||
## Why are parentheses match wrong?
|
||||
|
||||
```js
|
||||
dir/
|
||||
└── (special-*file).txt
|
||||
```
|
||||
|
||||
```js
|
||||
fg.sync(['(special-*file).txt']) // []
|
||||
```
|
||||
|
||||
Refers to Bash. You need to escape special characters:
|
||||
|
||||
```js
|
||||
fg.sync(['\\(special-*file\\).txt']) // ['(special-*file).txt']
|
||||
```
|
||||
|
||||
Read more about [matching special characters as literals][picomatch_matching_special_characters_as_literals].
|
||||
|
||||
## How to exclude directory from reading?
|
||||
|
||||
You can use a negative pattern like this: `!**/node_modules` or `!**/node_modules/**`. Also you can use [`ignore`](#ignore) option. Just look at the example below.
|
||||
|
||||
```js
|
||||
first/
|
||||
├── file.md
|
||||
└── second/
|
||||
└── file.txt
|
||||
```
|
||||
|
||||
If you don't want to read the `second` directory, you must write the following pattern: `!**/second` or `!**/second/**`.
|
||||
|
||||
```js
|
||||
fg.sync(['**/*.md', '!**/second']); // ['first/file.md']
|
||||
fg.sync(['**/*.md'], { ignore: ['**/second/**'] }); // ['first/file.md']
|
||||
```
|
||||
|
||||
> :warning: When you write `!**/second/**/*` it means that the directory will be **read**, but all the entries will not be included in the results.
|
||||
|
||||
You have to understand that if you write the pattern to exclude directories, then the directory will not be read under any circumstances.
|
||||
|
||||
## How to use UNC path?
|
||||
|
||||
You cannot use [Uniform Naming Convention (UNC)][unc_path] paths as patterns (due to syntax), but you can use them as [`cwd`](#cwd) directory.
|
||||
|
||||
```ts
|
||||
fg.sync('*', { cwd: '\\\\?\\C:\\Python27' /* or //?/C:/Python27 */ });
|
||||
fg.sync('Python27/*', { cwd: '\\\\?\\C:\\' /* or //?/C:/ */ });
|
||||
```
|
||||
|
||||
## Compatible with `node-glob`?
|
||||
|
||||
| node-glob | fast-glob |
|
||||
| :----------: | :-------: |
|
||||
| `cwd` | [`cwd`](#cwd) |
|
||||
| `root` | – |
|
||||
| `dot` | [`dot`](#dot) |
|
||||
| `nomount` | – |
|
||||
| `mark` | [`markDirectories`](#markdirectories) |
|
||||
| `nosort` | – |
|
||||
| `nounique` | [`unique`](#unique) |
|
||||
| `nobrace` | [`braceExpansion`](#braceexpansion) |
|
||||
| `noglobstar` | [`globstar`](#globstar) |
|
||||
| `noext` | [`extglob`](#extglob) |
|
||||
| `nocase` | [`caseSensitiveMatch`](#casesensitivematch) |
|
||||
| `matchBase` | [`baseNameMatch`](#basenamematch) |
|
||||
| `nodir` | [`onlyFiles`](#onlyfiles) |
|
||||
| `ignore` | [`ignore`](#ignore) |
|
||||
| `follow` | [`followSymbolicLinks`](#followsymboliclinks) |
|
||||
| `realpath` | – |
|
||||
| `absolute` | [`absolute`](#absolute) |
|
||||
|
||||
## Benchmarks
|
||||
|
||||
### Server
|
||||
|
||||
Link: [Vultr Bare Metal][vultr_pricing_baremetal]
|
||||
|
||||
* Processor: E3-1270v6 (8 CPU)
|
||||
* RAM: 32GB
|
||||
* Disk: SSD ([Intel DC S3520 SSDSC2BB240G7][intel_ssd])
|
||||
|
||||
You can see results [here][github_gist_benchmark_server] for latest release.
|
||||
|
||||
### Nettop
|
||||
|
||||
Link: [Zotac bi323][zotac_bi323]
|
||||
|
||||
* Processor: Intel N3150 (4 CPU)
|
||||
* RAM: 8GB
|
||||
* Disk: SSD ([Silicon Power SP060GBSS3S55S25][silicon_power_ssd])
|
||||
|
||||
You can see results [here][github_gist_benchmark_nettop] for latest release.
|
||||
|
||||
## Changelog
|
||||
|
||||
See the [Releases section of our GitHub project][github_releases] for changelog for each release version.
|
||||
|
||||
## License
|
||||
|
||||
This software is released under the terms of the MIT license.
|
||||
|
||||
[bash_hackers_syntax_expansion_brace]: https://wiki.bash-hackers.org/syntax/expansion/brace
|
||||
[github_gist_benchmark_nettop]: https://gist.github.com/mrmlnc/f06246b197f53c356895fa35355a367c#file-fg-benchmark-nettop-product-txt
|
||||
[github_gist_benchmark_server]: https://gist.github.com/mrmlnc/f06246b197f53c356895fa35355a367c#file-fg-benchmark-server-product-txt
|
||||
[github_releases]: https://github.com/mrmlnc/fast-glob/releases
|
||||
[glob_definition]: https://en.wikipedia.org/wiki/Glob_(programming)
|
||||
[glob_linux_man]: http://man7.org/linux/man-pages/man3/glob.3.html
|
||||
[intel_ssd]: https://ark.intel.com/content/www/us/en/ark/products/93012/intel-ssd-dc-s3520-series-240gb-2-5in-sata-6gb-s-3d1-mlc.html
|
||||
[micromatch_backslashes]: https://github.com/micromatch/micromatch#backslashes
|
||||
[micromatch_braces]: https://github.com/micromatch/braces
|
||||
[micromatch_extended_globbing]: https://github.com/micromatch/micromatch#extended-globbing
|
||||
[micromatch_extglobs]: https://github.com/micromatch/micromatch#extglobs
|
||||
[micromatch_regex_character_classes]: https://github.com/micromatch/micromatch#regex-character-classes
|
||||
[micromatch]: https://github.com/micromatch/micromatch
|
||||
[node_js_fs_class_fs_dirent]: https://nodejs.org/api/fs.html#fs_class_fs_dirent
|
||||
[node_js_fs_class_fs_stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats
|
||||
[node_js_stream_readable_streams]: https://nodejs.org/api/stream.html#stream_readable_streams
|
||||
[node_js]: https://nodejs.org/en
|
||||
[nodelib_fs_scandir_old_and_modern_modern]: https://github.com/nodelib/nodelib/blob/master/packages/fs/fs.scandir/README.md#old-and-modern-mode
|
||||
[npm_normalize_path]: https://www.npmjs.com/package/normalize-path
|
||||
[npm_unixify]: https://www.npmjs.com/package/unixify
|
||||
[paypal_mrmlnc]:https://paypal.me/mrmlnc
|
||||
[picomatch_matching_behavior]: https://github.com/micromatch/picomatch#matching-behavior-vs-bash
|
||||
[picomatch_matching_special_characters_as_literals]: https://github.com/micromatch/picomatch#matching-special-characters-as-literals
|
||||
[picomatch_posix_brackets]: https://github.com/micromatch/picomatch#posix-brackets
|
||||
[regular_expressions_brackets]: https://www.regular-expressions.info/brackets.html
|
||||
[silicon_power_ssd]: https://www.silicon-power.com/web/product-1
|
||||
[unc_path]: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/62e862f4-2a51-452e-8eeb-dc4ff5ee33cc
|
||||
[vultr_pricing_baremetal]: https://www.vultr.com/pricing/baremetal
|
||||
[wikipedia_case_sensitivity]: https://en.wikipedia.org/wiki/Case_sensitivity
|
||||
[zotac_bi323]: https://www.zotac.com/ee/product/mini_pcs/zbox-bi323
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
### [5.1.2](https://github.com/gulpjs/glob-parent/compare/v5.1.1...v5.1.2) (2021-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* eliminate ReDoS ([#36](https://github.com/gulpjs/glob-parent/issues/36)) ([f923116](https://github.com/gulpjs/glob-parent/commit/f9231168b0041fea3f8f954b3cceb56269fc6366))
|
||||
|
||||
### [5.1.1](https://github.com/gulpjs/glob-parent/compare/v5.1.0...v5.1.1) (2021-01-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* unescape exclamation mark ([#26](https://github.com/gulpjs/glob-parent/issues/26)) ([a98874f](https://github.com/gulpjs/glob-parent/commit/a98874f1a59e407f4fb1beb0db4efa8392da60bb))
|
||||
|
||||
## [5.1.0](https://github.com/gulpjs/glob-parent/compare/v5.0.0...v5.1.0) (2021-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `flipBackslashes` option to disable auto conversion of slashes (closes [#24](https://github.com/gulpjs/glob-parent/issues/24)) ([#25](https://github.com/gulpjs/glob-parent/issues/25)) ([eecf91d](https://github.com/gulpjs/glob-parent/commit/eecf91d5e3834ed78aee39c4eaaae654d76b87b3))
|
||||
|
||||
## [5.0.0](https://github.com/gulpjs/glob-parent/compare/v4.0.0...v5.0.0) (2021-01-27)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* Drop support for node <6 & bump dependencies
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* Drop support for node <6 & bump dependencies ([896c0c0](https://github.com/gulpjs/glob-parent/commit/896c0c00b4e7362f60b96e7fc295ae929245255a))
|
||||
|
||||
## [4.0.0](https://github.com/gulpjs/glob-parent/compare/v3.1.0...v4.0.0) (2021-01-27)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* question marks are valid path characters on Windows so avoid flagging as a glob when alone
|
||||
* Update is-glob dependency
|
||||
|
||||
### Features
|
||||
|
||||
* hoist regexps and strings for performance gains ([4a80667](https://github.com/gulpjs/glob-parent/commit/4a80667c69355c76a572a5892b0f133c8e1f457e))
|
||||
* question marks are valid path characters on Windows so avoid flagging as a glob when alone ([2a551dd](https://github.com/gulpjs/glob-parent/commit/2a551dd0dc3235e78bf3c94843d4107072d17841))
|
||||
* Update is-glob dependency ([e41fcd8](https://github.com/gulpjs/glob-parent/commit/e41fcd895d1f7bc617dba45c9d935a7949b9c281))
|
||||
|
||||
## [3.1.0](https://github.com/gulpjs/glob-parent/compare/v3.0.1...v3.1.0) (2021-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow basic win32 backslash use ([272afa5](https://github.com/gulpjs/glob-parent/commit/272afa5fd070fc0f796386a5993d4ee4a846988b))
|
||||
* handle extglobs (parentheses) containing separators ([7db1bdb](https://github.com/gulpjs/glob-parent/commit/7db1bdb0756e55fd14619e8ce31aa31b17b117fd))
|
||||
* new approach to braces/brackets handling ([8269bd8](https://github.com/gulpjs/glob-parent/commit/8269bd89290d99fac9395a354fb56fdcdb80f0be))
|
||||
* pre-process braces/brackets sections ([9ef8a87](https://github.com/gulpjs/glob-parent/commit/9ef8a87f66b1a43d0591e7a8e4fc5a18415ee388))
|
||||
* preserve escaped brace/bracket at end of string ([8cfb0ba](https://github.com/gulpjs/glob-parent/commit/8cfb0ba84202d51571340dcbaf61b79d16a26c76))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* trailing escaped square brackets ([99ec9fe](https://github.com/gulpjs/glob-parent/commit/99ec9fecc60ee488ded20a94dd4f18b4f55c4ccf))
|
||||
|
||||
### [3.0.1](https://github.com/gulpjs/glob-parent/compare/v3.0.0...v3.0.1) (2021-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use path-dirname ponyfill ([cdbea5f](https://github.com/gulpjs/glob-parent/commit/cdbea5f32a58a54e001a75ddd7c0fccd4776aacc))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* unescape glob-escaped dirnames on output ([598c533](https://github.com/gulpjs/glob-parent/commit/598c533bdf49c1428bc063aa9b8db40c5a86b030))
|
||||
|
||||
## [3.0.0](https://github.com/gulpjs/glob-parent/compare/v2.0.0...v3.0.0) (2021-01-27)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* update is-glob dependency
|
||||
|
||||
### Features
|
||||
|
||||
* update is-glob dependency ([5c5f8ef](https://github.com/gulpjs/glob-parent/commit/5c5f8efcee362a8e7638cf8220666acd8784f6bd))
|
||||
|
||||
## [2.0.0](https://github.com/gulpjs/glob-parent/compare/v1.3.0...v2.0.0) (2021-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* move up to dirname regardless of glob characters ([f97fb83](https://github.com/gulpjs/glob-parent/commit/f97fb83be2e0a9fc8d3b760e789d2ecadd6aa0c2))
|
||||
|
||||
## [1.3.0](https://github.com/gulpjs/glob-parent/compare/v1.2.0...v1.3.0) (2021-01-27)
|
||||
|
||||
## [1.2.0](https://github.com/gulpjs/glob-parent/compare/v1.1.0...v1.2.0) (2021-01-27)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* feat: make regex test strings smaller ([dc80fa9](https://github.com/gulpjs/glob-parent/commit/dc80fa9658dca20549cfeba44bbd37d5246fcce0))
|
||||
|
||||
## [1.1.0](https://github.com/gulpjs/glob-parent/compare/v1.0.0...v1.1.0) (2021-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* make regex test strings smaller ([cd83220](https://github.com/gulpjs/glob-parent/commit/cd832208638f45169f986d80fcf66e401f35d233))
|
||||
|
||||
## 1.0.0 (2021-01-27)
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2015, 2019 Elan Shanker
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<p align="center">
|
||||
<a href="https://gulpjs.com">
|
||||
<img height="257" width="114" src="https://raw.githubusercontent.com/gulpjs/artwork/master/gulp-2x.png">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# glob-parent
|
||||
|
||||
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Azure Pipelines Build Status][azure-pipelines-image]][azure-pipelines-url] [![Travis Build Status][travis-image]][travis-url] [![AppVeyor Build Status][appveyor-image]][appveyor-url] [![Coveralls Status][coveralls-image]][coveralls-url] [![Gitter chat][gitter-image]][gitter-url]
|
||||
|
||||
Extract the non-magic parent path from a glob string.
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
var globParent = require('glob-parent');
|
||||
|
||||
globParent('path/to/*.js'); // 'path/to'
|
||||
globParent('/root/path/to/*.js'); // '/root/path/to'
|
||||
globParent('/*.js'); // '/'
|
||||
globParent('*.js'); // '.'
|
||||
globParent('**/*.js'); // '.'
|
||||
globParent('path/{to,from}'); // 'path'
|
||||
globParent('path/!(to|from)'); // 'path'
|
||||
globParent('path/?(to|from)'); // 'path'
|
||||
globParent('path/+(to|from)'); // 'path'
|
||||
globParent('path/*(to|from)'); // 'path'
|
||||
globParent('path/@(to|from)'); // 'path'
|
||||
globParent('path/**/*'); // 'path'
|
||||
|
||||
// if provided a non-glob path, returns the nearest dir
|
||||
globParent('path/foo/bar.js'); // 'path/foo'
|
||||
globParent('path/foo/'); // 'path/foo'
|
||||
globParent('path/foo'); // 'path' (see issue #3 for details)
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `globParent(maybeGlobString, [options])`
|
||||
|
||||
Takes a string and returns the part of the path before the glob begins. Be aware of Escaping rules and Limitations below.
|
||||
|
||||
#### options
|
||||
|
||||
```js
|
||||
{
|
||||
// Disables the automatic conversion of slashes for Windows
|
||||
flipBackslashes: true
|
||||
}
|
||||
```
|
||||
|
||||
## Escaping
|
||||
|
||||
The following characters have special significance in glob patterns and must be escaped if you want them to be treated as regular path characters:
|
||||
|
||||
- `?` (question mark) unless used as a path segment alone
|
||||
- `*` (asterisk)
|
||||
- `|` (pipe)
|
||||
- `(` (opening parenthesis)
|
||||
- `)` (closing parenthesis)
|
||||
- `{` (opening curly brace)
|
||||
- `}` (closing curly brace)
|
||||
- `[` (opening bracket)
|
||||
- `]` (closing bracket)
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
globParent('foo/[bar]/') // 'foo'
|
||||
globParent('foo/\\[bar]/') // 'foo/[bar]'
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
### Braces & Brackets
|
||||
This library attempts a quick and imperfect method of determining which path
|
||||
parts have glob magic without fully parsing/lexing the pattern. There are some
|
||||
advanced use cases that can trip it up, such as nested braces where the outer
|
||||
pair is escaped and the inner one contains a path separator. If you find
|
||||
yourself in the unlikely circumstance of being affected by this or need to
|
||||
ensure higher-fidelity glob handling in your library, it is recommended that you
|
||||
pre-process your input with [expand-braces] and/or [expand-brackets].
|
||||
|
||||
### Windows
|
||||
Backslashes are not valid path separators for globs. If a path with backslashes
|
||||
is provided anyway, for simple cases, glob-parent will replace the path
|
||||
separator for you and return the non-glob parent path (now with
|
||||
forward-slashes, which are still valid as Windows path separators).
|
||||
|
||||
This cannot be used in conjunction with escape characters.
|
||||
|
||||
```js
|
||||
// BAD
|
||||
globParent('C:\\Program Files \\(x86\\)\\*.ext') // 'C:/Program Files /(x86/)'
|
||||
|
||||
// GOOD
|
||||
globParent('C:/Program Files\\(x86\\)/*.ext') // 'C:/Program Files (x86)'
|
||||
```
|
||||
|
||||
If you are using escape characters for a pattern without path parts (i.e.
|
||||
relative to `cwd`), prefix with `./` to avoid confusing glob-parent.
|
||||
|
||||
```js
|
||||
// BAD
|
||||
globParent('foo \\[bar]') // 'foo '
|
||||
globParent('foo \\[bar]*') // 'foo '
|
||||
|
||||
// GOOD
|
||||
globParent('./foo \\[bar]') // 'foo [bar]'
|
||||
globParent('./foo \\[bar]*') // '.'
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
ISC
|
||||
|
||||
[expand-braces]: https://github.com/jonschlinkert/expand-braces
|
||||
[expand-brackets]: https://github.com/jonschlinkert/expand-brackets
|
||||
|
||||
[downloads-image]: https://img.shields.io/npm/dm/glob-parent.svg
|
||||
[npm-url]: https://www.npmjs.com/package/glob-parent
|
||||
[npm-image]: https://img.shields.io/npm/v/glob-parent.svg
|
||||
|
||||
[azure-pipelines-url]: https://dev.azure.com/gulpjs/gulp/_build/latest?definitionId=2&branchName=master
|
||||
[azure-pipelines-image]: https://dev.azure.com/gulpjs/gulp/_apis/build/status/glob-parent?branchName=master
|
||||
|
||||
[travis-url]: https://travis-ci.org/gulpjs/glob-parent
|
||||
[travis-image]: https://img.shields.io/travis/gulpjs/glob-parent.svg?label=travis-ci
|
||||
|
||||
[appveyor-url]: https://ci.appveyor.com/project/gulpjs/glob-parent
|
||||
[appveyor-image]: https://img.shields.io/appveyor/ci/gulpjs/glob-parent.svg?label=appveyor
|
||||
|
||||
[coveralls-url]: https://coveralls.io/r/gulpjs/glob-parent
|
||||
[coveralls-image]: https://img.shields.io/coveralls/gulpjs/glob-parent/master.svg
|
||||
|
||||
[gitter-url]: https://gitter.im/gulpjs/gulp
|
||||
[gitter-image]: https://badges.gitter.im/gulpjs/gulp.svg
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
var isGlob = require('is-glob');
|
||||
var pathPosixDirname = require('path').posix.dirname;
|
||||
var isWin32 = require('os').platform() === 'win32';
|
||||
|
||||
var slash = '/';
|
||||
var backslash = /\\/g;
|
||||
var enclosure = /[\{\[].*[\}\]]$/;
|
||||
var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/;
|
||||
var escaped = /\\([\!\*\?\|\[\]\(\)\{\}])/g;
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @param {Object} opts
|
||||
* @param {boolean} [opts.flipBackslashes=true]
|
||||
* @returns {string}
|
||||
*/
|
||||
module.exports = function globParent(str, opts) {
|
||||
var options = Object.assign({ flipBackslashes: true }, opts);
|
||||
|
||||
// flip windows path separators
|
||||
if (options.flipBackslashes && isWin32 && str.indexOf(slash) < 0) {
|
||||
str = str.replace(backslash, slash);
|
||||
}
|
||||
|
||||
// special case for strings ending in enclosure containing path separator
|
||||
if (enclosure.test(str)) {
|
||||
str += slash;
|
||||
}
|
||||
|
||||
// preserves full path in case of trailing path separator
|
||||
str += 'a';
|
||||
|
||||
// remove path parts that are globby
|
||||
do {
|
||||
str = pathPosixDirname(str);
|
||||
} while (isGlob(str) || globby.test(str));
|
||||
|
||||
// remove escape chars and return result
|
||||
return str.replace(escaped, '$1');
|
||||
};
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "glob-parent",
|
||||
"version": "5.1.2",
|
||||
"description": "Extract the non-magic parent path from a glob string.",
|
||||
"author": "Gulp Team <team@gulpjs.com> (https://gulpjs.com/)",
|
||||
"contributors": [
|
||||
"Elan Shanker (https://github.com/es128)",
|
||||
"Blaine Bublitz <blaine.bublitz@gmail.com>"
|
||||
],
|
||||
"repository": "gulpjs/glob-parent",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"index.js"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc mocha --async-only",
|
||||
"azure-pipelines": "nyc mocha --async-only --reporter xunit -O output=test.xunit",
|
||||
"coveralls": "nyc report --reporter=text-lcov | coveralls"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"coveralls": "^3.0.11",
|
||||
"eslint": "^2.13.1",
|
||||
"eslint-config-gulp": "^3.0.1",
|
||||
"expect": "^1.20.2",
|
||||
"mocha": "^6.0.2",
|
||||
"nyc": "^13.3.0"
|
||||
},
|
||||
"keywords": [
|
||||
"glob",
|
||||
"parent",
|
||||
"strip",
|
||||
"path",
|
||||
"dirname",
|
||||
"directory",
|
||||
"base",
|
||||
"wildcard"
|
||||
]
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/// <reference types="node" />
|
||||
import * as taskManager from './managers/tasks';
|
||||
import { Options as OptionsInternal } from './settings';
|
||||
import { Entry as EntryInternal, FileSystemAdapter as FileSystemAdapterInternal, Pattern as PatternInternal } from './types';
|
||||
declare type EntryObjectModePredicate = {
|
||||
[TKey in keyof Pick<OptionsInternal, 'objectMode'>]-?: true;
|
||||
};
|
||||
declare type EntryStatsPredicate = {
|
||||
[TKey in keyof Pick<OptionsInternal, 'stats'>]-?: true;
|
||||
};
|
||||
declare type EntryObjectPredicate = EntryObjectModePredicate | EntryStatsPredicate;
|
||||
declare function FastGlob(source: PatternInternal | PatternInternal[], options: OptionsInternal & EntryObjectPredicate): Promise<EntryInternal[]>;
|
||||
declare function FastGlob(source: PatternInternal | PatternInternal[], options?: OptionsInternal): Promise<string[]>;
|
||||
declare namespace FastGlob {
|
||||
type Options = OptionsInternal;
|
||||
type Entry = EntryInternal;
|
||||
type Task = taskManager.Task;
|
||||
type Pattern = PatternInternal;
|
||||
type FileSystemAdapter = FileSystemAdapterInternal;
|
||||
function sync(source: PatternInternal | PatternInternal[], options: OptionsInternal & EntryObjectPredicate): EntryInternal[];
|
||||
function sync(source: PatternInternal | PatternInternal[], options?: OptionsInternal): string[];
|
||||
function stream(source: PatternInternal | PatternInternal[], options?: OptionsInternal): NodeJS.ReadableStream;
|
||||
function generateTasks(source: PatternInternal | PatternInternal[], options?: OptionsInternal): Task[];
|
||||
function isDynamicPattern(source: PatternInternal, options?: OptionsInternal): boolean;
|
||||
function escapePath(source: PatternInternal): PatternInternal;
|
||||
}
|
||||
export = FastGlob;
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
"use strict";
|
||||
const taskManager = require("./managers/tasks");
|
||||
const patternManager = require("./managers/patterns");
|
||||
const async_1 = require("./providers/async");
|
||||
const stream_1 = require("./providers/stream");
|
||||
const sync_1 = require("./providers/sync");
|
||||
const settings_1 = require("./settings");
|
||||
const utils = require("./utils");
|
||||
async function FastGlob(source, options) {
|
||||
assertPatternsInput(source);
|
||||
const works = getWorks(source, async_1.default, options);
|
||||
const result = await Promise.all(works);
|
||||
return utils.array.flatten(result);
|
||||
}
|
||||
// https://github.com/typescript-eslint/typescript-eslint/issues/60
|
||||
// eslint-disable-next-line no-redeclare
|
||||
(function (FastGlob) {
|
||||
function sync(source, options) {
|
||||
assertPatternsInput(source);
|
||||
const works = getWorks(source, sync_1.default, options);
|
||||
return utils.array.flatten(works);
|
||||
}
|
||||
FastGlob.sync = sync;
|
||||
function stream(source, options) {
|
||||
assertPatternsInput(source);
|
||||
const works = getWorks(source, stream_1.default, options);
|
||||
/**
|
||||
* The stream returned by the provider cannot work with an asynchronous iterator.
|
||||
* To support asynchronous iterators, regardless of the number of tasks, we always multiplex streams.
|
||||
* This affects performance (+25%). I don't see best solution right now.
|
||||
*/
|
||||
return utils.stream.merge(works);
|
||||
}
|
||||
FastGlob.stream = stream;
|
||||
function generateTasks(source, options) {
|
||||
assertPatternsInput(source);
|
||||
const patterns = patternManager.transform([].concat(source));
|
||||
const settings = new settings_1.default(options);
|
||||
return taskManager.generate(patterns, settings);
|
||||
}
|
||||
FastGlob.generateTasks = generateTasks;
|
||||
function isDynamicPattern(source, options) {
|
||||
assertPatternsInput(source);
|
||||
const settings = new settings_1.default(options);
|
||||
return utils.pattern.isDynamicPattern(source, settings);
|
||||
}
|
||||
FastGlob.isDynamicPattern = isDynamicPattern;
|
||||
function escapePath(source) {
|
||||
assertPatternsInput(source);
|
||||
return utils.path.escape(source);
|
||||
}
|
||||
FastGlob.escapePath = escapePath;
|
||||
})(FastGlob || (FastGlob = {}));
|
||||
function getWorks(source, _Provider, options) {
|
||||
const patterns = patternManager.transform([].concat(source));
|
||||
const settings = new settings_1.default(options);
|
||||
const tasks = taskManager.generate(patterns, settings);
|
||||
const provider = new _Provider(settings);
|
||||
return tasks.map(provider.read, provider);
|
||||
}
|
||||
function assertPatternsInput(input) {
|
||||
const source = [].concat(input);
|
||||
const isValidSource = source.every((item) => utils.string.isString(item) && !utils.string.isEmpty(item));
|
||||
if (!isValidSource) {
|
||||
throw new TypeError('Patterns must be a string (non empty) or an array of strings');
|
||||
}
|
||||
}
|
||||
module.exports = FastGlob;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export declare function transform(patterns: string[]): string[];
|
||||
/**
|
||||
* This package only works with forward slashes as a path separator.
|
||||
* Because of this, we cannot use the standard `path.normalize` method, because on Windows platform it will use of backslashes.
|
||||
*/
|
||||
export declare function removeDuplicateSlashes(pattern: string): string;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.removeDuplicateSlashes = exports.transform = void 0;
|
||||
/**
|
||||
* Matches a sequence of two or more consecutive slashes, excluding the first two slashes at the beginning of the string.
|
||||
* The latter is due to the presence of the device path at the beginning of the UNC path.
|
||||
* @todo rewrite to negative lookbehind with the next major release.
|
||||
*/
|
||||
const DOUBLE_SLASH_RE = /(?!^)\/{2,}/g;
|
||||
function transform(patterns) {
|
||||
return patterns.map((pattern) => removeDuplicateSlashes(pattern));
|
||||
}
|
||||
exports.transform = transform;
|
||||
/**
|
||||
* This package only works with forward slashes as a path separator.
|
||||
* Because of this, we cannot use the standard `path.normalize` method, because on Windows platform it will use of backslashes.
|
||||
*/
|
||||
function removeDuplicateSlashes(pattern) {
|
||||
return pattern.replace(DOUBLE_SLASH_RE, '/');
|
||||
}
|
||||
exports.removeDuplicateSlashes = removeDuplicateSlashes;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import Settings from '../settings';
|
||||
import { Pattern, PatternsGroup } from '../types';
|
||||
export declare type Task = {
|
||||
base: string;
|
||||
dynamic: boolean;
|
||||
patterns: Pattern[];
|
||||
positive: Pattern[];
|
||||
negative: Pattern[];
|
||||
};
|
||||
export declare function generate(patterns: Pattern[], settings: Settings): Task[];
|
||||
/**
|
||||
* Returns tasks grouped by basic pattern directories.
|
||||
*
|
||||
* Patterns that can be found inside (`./`) and outside (`../`) the current directory are handled separately.
|
||||
* This is necessary because directory traversal starts at the base directory and goes deeper.
|
||||
*/
|
||||
export declare function convertPatternsToTasks(positive: Pattern[], negative: Pattern[], dynamic: boolean): Task[];
|
||||
export declare function getPositivePatterns(patterns: Pattern[]): Pattern[];
|
||||
export declare function getNegativePatternsAsPositive(patterns: Pattern[], ignore: Pattern[]): Pattern[];
|
||||
export declare function groupPatternsByBaseDirectory(patterns: Pattern[]): PatternsGroup;
|
||||
export declare function convertPatternGroupsToTasks(positive: PatternsGroup, negative: Pattern[], dynamic: boolean): Task[];
|
||||
export declare function convertPatternGroupToTask(base: string, positive: Pattern[], negative: Pattern[], dynamic: boolean): Task;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.convertPatternGroupToTask = exports.convertPatternGroupsToTasks = exports.groupPatternsByBaseDirectory = exports.getNegativePatternsAsPositive = exports.getPositivePatterns = exports.convertPatternsToTasks = exports.generate = void 0;
|
||||
const utils = require("../utils");
|
||||
function generate(patterns, settings) {
|
||||
const positivePatterns = getPositivePatterns(patterns);
|
||||
const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore);
|
||||
const staticPatterns = positivePatterns.filter((pattern) => utils.pattern.isStaticPattern(pattern, settings));
|
||||
const dynamicPatterns = positivePatterns.filter((pattern) => utils.pattern.isDynamicPattern(pattern, settings));
|
||||
const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false);
|
||||
const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true);
|
||||
return staticTasks.concat(dynamicTasks);
|
||||
}
|
||||
exports.generate = generate;
|
||||
/**
|
||||
* Returns tasks grouped by basic pattern directories.
|
||||
*
|
||||
* Patterns that can be found inside (`./`) and outside (`../`) the current directory are handled separately.
|
||||
* This is necessary because directory traversal starts at the base directory and goes deeper.
|
||||
*/
|
||||
function convertPatternsToTasks(positive, negative, dynamic) {
|
||||
const tasks = [];
|
||||
const patternsOutsideCurrentDirectory = utils.pattern.getPatternsOutsideCurrentDirectory(positive);
|
||||
const patternsInsideCurrentDirectory = utils.pattern.getPatternsInsideCurrentDirectory(positive);
|
||||
const outsideCurrentDirectoryGroup = groupPatternsByBaseDirectory(patternsOutsideCurrentDirectory);
|
||||
const insideCurrentDirectoryGroup = groupPatternsByBaseDirectory(patternsInsideCurrentDirectory);
|
||||
tasks.push(...convertPatternGroupsToTasks(outsideCurrentDirectoryGroup, negative, dynamic));
|
||||
/*
|
||||
* For the sake of reducing future accesses to the file system, we merge all tasks within the current directory
|
||||
* into a global task, if at least one pattern refers to the root (`.`). In this case, the global task covers the rest.
|
||||
*/
|
||||
if ('.' in insideCurrentDirectoryGroup) {
|
||||
tasks.push(convertPatternGroupToTask('.', patternsInsideCurrentDirectory, negative, dynamic));
|
||||
}
|
||||
else {
|
||||
tasks.push(...convertPatternGroupsToTasks(insideCurrentDirectoryGroup, negative, dynamic));
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
exports.convertPatternsToTasks = convertPatternsToTasks;
|
||||
function getPositivePatterns(patterns) {
|
||||
return utils.pattern.getPositivePatterns(patterns);
|
||||
}
|
||||
exports.getPositivePatterns = getPositivePatterns;
|
||||
function getNegativePatternsAsPositive(patterns, ignore) {
|
||||
const negative = utils.pattern.getNegativePatterns(patterns).concat(ignore);
|
||||
const positive = negative.map(utils.pattern.convertToPositivePattern);
|
||||
return positive;
|
||||
}
|
||||
exports.getNegativePatternsAsPositive = getNegativePatternsAsPositive;
|
||||
function groupPatternsByBaseDirectory(patterns) {
|
||||
const group = {};
|
||||
return patterns.reduce((collection, pattern) => {
|
||||
const base = utils.pattern.getBaseDirectory(pattern);
|
||||
if (base in collection) {
|
||||
collection[base].push(pattern);
|
||||
}
|
||||
else {
|
||||
collection[base] = [pattern];
|
||||
}
|
||||
return collection;
|
||||
}, group);
|
||||
}
|
||||
exports.groupPatternsByBaseDirectory = groupPatternsByBaseDirectory;
|
||||
function convertPatternGroupsToTasks(positive, negative, dynamic) {
|
||||
return Object.keys(positive).map((base) => {
|
||||
return convertPatternGroupToTask(base, positive[base], negative, dynamic);
|
||||
});
|
||||
}
|
||||
exports.convertPatternGroupsToTasks = convertPatternGroupsToTasks;
|
||||
function convertPatternGroupToTask(base, positive, negative, dynamic) {
|
||||
return {
|
||||
dynamic,
|
||||
positive,
|
||||
negative,
|
||||
base,
|
||||
patterns: [].concat(positive, negative.map(utils.pattern.convertToNegativePattern))
|
||||
};
|
||||
}
|
||||
exports.convertPatternGroupToTask = convertPatternGroupToTask;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/// <reference types="node" />
|
||||
import { Readable } from 'stream';
|
||||
import { Task } from '../managers/tasks';
|
||||
import ReaderStream from '../readers/stream';
|
||||
import { EntryItem, ReaderOptions } from '../types';
|
||||
import Provider from './provider';
|
||||
export default class ProviderAsync extends Provider<Promise<EntryItem[]>> {
|
||||
protected _reader: ReaderStream;
|
||||
read(task: Task): Promise<EntryItem[]>;
|
||||
api(root: string, task: Task, options: ReaderOptions): Readable;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const stream_1 = require("../readers/stream");
|
||||
const provider_1 = require("./provider");
|
||||
class ProviderAsync extends provider_1.default {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this._reader = new stream_1.default(this._settings);
|
||||
}
|
||||
read(task) {
|
||||
const root = this._getRootDirectory(task);
|
||||
const options = this._getReaderOptions(task);
|
||||
const entries = [];
|
||||
return new Promise((resolve, reject) => {
|
||||
const stream = this.api(root, task, options);
|
||||
stream.once('error', reject);
|
||||
stream.on('data', (entry) => entries.push(options.transform(entry)));
|
||||
stream.once('end', () => resolve(entries));
|
||||
});
|
||||
}
|
||||
api(root, task, options) {
|
||||
if (task.dynamic) {
|
||||
return this._reader.dynamic(root, options);
|
||||
}
|
||||
return this._reader.static(task.patterns, options);
|
||||
}
|
||||
}
|
||||
exports.default = ProviderAsync;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { MicromatchOptions, EntryFilterFunction, Pattern } from '../../types';
|
||||
import Settings from '../../settings';
|
||||
export default class DeepFilter {
|
||||
private readonly _settings;
|
||||
private readonly _micromatchOptions;
|
||||
constructor(_settings: Settings, _micromatchOptions: MicromatchOptions);
|
||||
getFilter(basePath: string, positive: Pattern[], negative: Pattern[]): EntryFilterFunction;
|
||||
private _getMatcher;
|
||||
private _getNegativePatternsRe;
|
||||
private _filter;
|
||||
private _isSkippedByDeep;
|
||||
private _getEntryLevel;
|
||||
private _isSkippedSymbolicLink;
|
||||
private _isSkippedByPositivePatterns;
|
||||
private _isSkippedByNegativePatterns;
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const utils = require("../../utils");
|
||||
const partial_1 = require("../matchers/partial");
|
||||
class DeepFilter {
|
||||
constructor(_settings, _micromatchOptions) {
|
||||
this._settings = _settings;
|
||||
this._micromatchOptions = _micromatchOptions;
|
||||
}
|
||||
getFilter(basePath, positive, negative) {
|
||||
const matcher = this._getMatcher(positive);
|
||||
const negativeRe = this._getNegativePatternsRe(negative);
|
||||
return (entry) => this._filter(basePath, entry, matcher, negativeRe);
|
||||
}
|
||||
_getMatcher(patterns) {
|
||||
return new partial_1.default(patterns, this._settings, this._micromatchOptions);
|
||||
}
|
||||
_getNegativePatternsRe(patterns) {
|
||||
const affectDepthOfReadingPatterns = patterns.filter(utils.pattern.isAffectDepthOfReadingPattern);
|
||||
return utils.pattern.convertPatternsToRe(affectDepthOfReadingPatterns, this._micromatchOptions);
|
||||
}
|
||||
_filter(basePath, entry, matcher, negativeRe) {
|
||||
if (this._isSkippedByDeep(basePath, entry.path)) {
|
||||
return false;
|
||||
}
|
||||
if (this._isSkippedSymbolicLink(entry)) {
|
||||
return false;
|
||||
}
|
||||
const filepath = utils.path.removeLeadingDotSegment(entry.path);
|
||||
if (this._isSkippedByPositivePatterns(filepath, matcher)) {
|
||||
return false;
|
||||
}
|
||||
return this._isSkippedByNegativePatterns(filepath, negativeRe);
|
||||
}
|
||||
_isSkippedByDeep(basePath, entryPath) {
|
||||
/**
|
||||
* Avoid unnecessary depth calculations when it doesn't matter.
|
||||
*/
|
||||
if (this._settings.deep === Infinity) {
|
||||
return false;
|
||||
}
|
||||
return this._getEntryLevel(basePath, entryPath) >= this._settings.deep;
|
||||
}
|
||||
_getEntryLevel(basePath, entryPath) {
|
||||
const entryPathDepth = entryPath.split('/').length;
|
||||
if (basePath === '') {
|
||||
return entryPathDepth;
|
||||
}
|
||||
const basePathDepth = basePath.split('/').length;
|
||||
return entryPathDepth - basePathDepth;
|
||||
}
|
||||
_isSkippedSymbolicLink(entry) {
|
||||
return !this._settings.followSymbolicLinks && entry.dirent.isSymbolicLink();
|
||||
}
|
||||
_isSkippedByPositivePatterns(entryPath, matcher) {
|
||||
return !this._settings.baseNameMatch && !matcher.match(entryPath);
|
||||
}
|
||||
_isSkippedByNegativePatterns(entryPath, patternsRe) {
|
||||
return !utils.pattern.matchAny(entryPath, patternsRe);
|
||||
}
|
||||
}
|
||||
exports.default = DeepFilter;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import Settings from '../../settings';
|
||||
import { EntryFilterFunction, MicromatchOptions, Pattern } from '../../types';
|
||||
export default class EntryFilter {
|
||||
private readonly _settings;
|
||||
private readonly _micromatchOptions;
|
||||
readonly index: Map<string, undefined>;
|
||||
constructor(_settings: Settings, _micromatchOptions: MicromatchOptions);
|
||||
getFilter(positive: Pattern[], negative: Pattern[]): EntryFilterFunction;
|
||||
private _filter;
|
||||
private _isDuplicateEntry;
|
||||
private _createIndexRecord;
|
||||
private _onlyFileFilter;
|
||||
private _onlyDirectoryFilter;
|
||||
private _isSkippedByAbsoluteNegativePatterns;
|
||||
/**
|
||||
* First, just trying to apply patterns to the path.
|
||||
* Second, trying to apply patterns to the path with final slash.
|
||||
*/
|
||||
private _isMatchToPatterns;
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const utils = require("../../utils");
|
||||
class EntryFilter {
|
||||
constructor(_settings, _micromatchOptions) {
|
||||
this._settings = _settings;
|
||||
this._micromatchOptions = _micromatchOptions;
|
||||
this.index = new Map();
|
||||
}
|
||||
getFilter(positive, negative) {
|
||||
const positiveRe = utils.pattern.convertPatternsToRe(positive, this._micromatchOptions);
|
||||
const negativeRe = utils.pattern.convertPatternsToRe(negative, this._micromatchOptions);
|
||||
return (entry) => this._filter(entry, positiveRe, negativeRe);
|
||||
}
|
||||
_filter(entry, positiveRe, negativeRe) {
|
||||
if (this._settings.unique && this._isDuplicateEntry(entry)) {
|
||||
return false;
|
||||
}
|
||||
if (this._onlyFileFilter(entry) || this._onlyDirectoryFilter(entry)) {
|
||||
return false;
|
||||
}
|
||||
if (this._isSkippedByAbsoluteNegativePatterns(entry.path, negativeRe)) {
|
||||
return false;
|
||||
}
|
||||
const filepath = this._settings.baseNameMatch ? entry.name : entry.path;
|
||||
const isMatched = this._isMatchToPatterns(filepath, positiveRe) && !this._isMatchToPatterns(entry.path, negativeRe);
|
||||
if (this._settings.unique && isMatched) {
|
||||
this._createIndexRecord(entry);
|
||||
}
|
||||
return isMatched;
|
||||
}
|
||||
_isDuplicateEntry(entry) {
|
||||
return this.index.has(entry.path);
|
||||
}
|
||||
_createIndexRecord(entry) {
|
||||
this.index.set(entry.path, undefined);
|
||||
}
|
||||
_onlyFileFilter(entry) {
|
||||
return this._settings.onlyFiles && !entry.dirent.isFile();
|
||||
}
|
||||
_onlyDirectoryFilter(entry) {
|
||||
return this._settings.onlyDirectories && !entry.dirent.isDirectory();
|
||||
}
|
||||
_isSkippedByAbsoluteNegativePatterns(entryPath, patternsRe) {
|
||||
if (!this._settings.absolute) {
|
||||
return false;
|
||||
}
|
||||
const fullpath = utils.path.makeAbsolute(this._settings.cwd, entryPath);
|
||||
return utils.pattern.matchAny(fullpath, patternsRe);
|
||||
}
|
||||
/**
|
||||
* First, just trying to apply patterns to the path.
|
||||
* Second, trying to apply patterns to the path with final slash.
|
||||
*/
|
||||
_isMatchToPatterns(entryPath, patternsRe) {
|
||||
const filepath = utils.path.removeLeadingDotSegment(entryPath);
|
||||
return utils.pattern.matchAny(filepath, patternsRe) || utils.pattern.matchAny(filepath + '/', patternsRe);
|
||||
}
|
||||
}
|
||||
exports.default = EntryFilter;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import Settings from '../../settings';
|
||||
import { ErrorFilterFunction } from '../../types';
|
||||
export default class ErrorFilter {
|
||||
private readonly _settings;
|
||||
constructor(_settings: Settings);
|
||||
getFilter(): ErrorFilterFunction;
|
||||
private _isNonFatalError;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const utils = require("../../utils");
|
||||
class ErrorFilter {
|
||||
constructor(_settings) {
|
||||
this._settings = _settings;
|
||||
}
|
||||
getFilter() {
|
||||
return (error) => this._isNonFatalError(error);
|
||||
}
|
||||
_isNonFatalError(error) {
|
||||
return utils.errno.isEnoentCodeError(error) || this._settings.suppressErrors;
|
||||
}
|
||||
}
|
||||
exports.default = ErrorFilter;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { Pattern, MicromatchOptions, PatternRe } from '../../types';
|
||||
import Settings from '../../settings';
|
||||
export declare type PatternSegment = StaticPatternSegment | DynamicPatternSegment;
|
||||
declare type StaticPatternSegment = {
|
||||
dynamic: false;
|
||||
pattern: Pattern;
|
||||
};
|
||||
declare type DynamicPatternSegment = {
|
||||
dynamic: true;
|
||||
pattern: Pattern;
|
||||
patternRe: PatternRe;
|
||||
};
|
||||
export declare type PatternSection = PatternSegment[];
|
||||
export declare type PatternInfo = {
|
||||
/**
|
||||
* Indicates that the pattern has a globstar (more than a single section).
|
||||
*/
|
||||
complete: boolean;
|
||||
pattern: Pattern;
|
||||
segments: PatternSegment[];
|
||||
sections: PatternSection[];
|
||||
};
|
||||
export default abstract class Matcher {
|
||||
private readonly _patterns;
|
||||
private readonly _settings;
|
||||
private readonly _micromatchOptions;
|
||||
protected readonly _storage: PatternInfo[];
|
||||
constructor(_patterns: Pattern[], _settings: Settings, _micromatchOptions: MicromatchOptions);
|
||||
private _fillStorage;
|
||||
private _getPatternSegments;
|
||||
private _splitSegmentsIntoSections;
|
||||
}
|
||||
export {};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const utils = require("../../utils");
|
||||
class Matcher {
|
||||
constructor(_patterns, _settings, _micromatchOptions) {
|
||||
this._patterns = _patterns;
|
||||
this._settings = _settings;
|
||||
this._micromatchOptions = _micromatchOptions;
|
||||
this._storage = [];
|
||||
this._fillStorage();
|
||||
}
|
||||
_fillStorage() {
|
||||
/**
|
||||
* The original pattern may include `{,*,**,a/*}`, which will lead to problems with matching (unresolved level).
|
||||
* So, before expand patterns with brace expansion into separated patterns.
|
||||
*/
|
||||
const patterns = utils.pattern.expandPatternsWithBraceExpansion(this._patterns);
|
||||
for (const pattern of patterns) {
|
||||
const segments = this._getPatternSegments(pattern);
|
||||
const sections = this._splitSegmentsIntoSections(segments);
|
||||
this._storage.push({
|
||||
complete: sections.length <= 1,
|
||||
pattern,
|
||||
segments,
|
||||
sections
|
||||
});
|
||||
}
|
||||
}
|
||||
_getPatternSegments(pattern) {
|
||||
const parts = utils.pattern.getPatternParts(pattern, this._micromatchOptions);
|
||||
return parts.map((part) => {
|
||||
const dynamic = utils.pattern.isDynamicPattern(part, this._settings);
|
||||
if (!dynamic) {
|
||||
return {
|
||||
dynamic: false,
|
||||
pattern: part
|
||||
};
|
||||
}
|
||||
return {
|
||||
dynamic: true,
|
||||
pattern: part,
|
||||
patternRe: utils.pattern.makeRe(part, this._micromatchOptions)
|
||||
};
|
||||
});
|
||||
}
|
||||
_splitSegmentsIntoSections(segments) {
|
||||
return utils.array.splitWhen(segments, (segment) => segment.dynamic && utils.pattern.hasGlobStar(segment.pattern));
|
||||
}
|
||||
}
|
||||
exports.default = Matcher;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import Matcher from './matcher';
|
||||
export default class PartialMatcher extends Matcher {
|
||||
match(filepath: string): boolean;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const matcher_1 = require("./matcher");
|
||||
class PartialMatcher extends matcher_1.default {
|
||||
match(filepath) {
|
||||
const parts = filepath.split('/');
|
||||
const levels = parts.length;
|
||||
const patterns = this._storage.filter((info) => !info.complete || info.segments.length > levels);
|
||||
for (const pattern of patterns) {
|
||||
const section = pattern.sections[0];
|
||||
/**
|
||||
* In this case, the pattern has a globstar and we must read all directories unconditionally,
|
||||
* but only if the level has reached the end of the first group.
|
||||
*
|
||||
* fixtures/{a,b}/**
|
||||
* ^ true/false ^ always true
|
||||
*/
|
||||
if (!pattern.complete && levels > section.length) {
|
||||
return true;
|
||||
}
|
||||
const match = parts.every((part, index) => {
|
||||
const segment = pattern.segments[index];
|
||||
if (segment.dynamic && segment.patternRe.test(part)) {
|
||||
return true;
|
||||
}
|
||||
if (!segment.dynamic && segment.pattern === part) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (match) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
exports.default = PartialMatcher;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { Task } from '../managers/tasks';
|
||||
import Settings from '../settings';
|
||||
import { MicromatchOptions, ReaderOptions } from '../types';
|
||||
import DeepFilter from './filters/deep';
|
||||
import EntryFilter from './filters/entry';
|
||||
import ErrorFilter from './filters/error';
|
||||
import EntryTransformer from './transformers/entry';
|
||||
export default abstract class Provider<T> {
|
||||
protected readonly _settings: Settings;
|
||||
readonly errorFilter: ErrorFilter;
|
||||
readonly entryFilter: EntryFilter;
|
||||
readonly deepFilter: DeepFilter;
|
||||
readonly entryTransformer: EntryTransformer;
|
||||
constructor(_settings: Settings);
|
||||
abstract read(_task: Task): T;
|
||||
protected _getRootDirectory(task: Task): string;
|
||||
protected _getReaderOptions(task: Task): ReaderOptions;
|
||||
protected _getMicromatchOptions(): MicromatchOptions;
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const path = require("path");
|
||||
const deep_1 = require("./filters/deep");
|
||||
const entry_1 = require("./filters/entry");
|
||||
const error_1 = require("./filters/error");
|
||||
const entry_2 = require("./transformers/entry");
|
||||
class Provider {
|
||||
constructor(_settings) {
|
||||
this._settings = _settings;
|
||||
this.errorFilter = new error_1.default(this._settings);
|
||||
this.entryFilter = new entry_1.default(this._settings, this._getMicromatchOptions());
|
||||
this.deepFilter = new deep_1.default(this._settings, this._getMicromatchOptions());
|
||||
this.entryTransformer = new entry_2.default(this._settings);
|
||||
}
|
||||
_getRootDirectory(task) {
|
||||
return path.resolve(this._settings.cwd, task.base);
|
||||
}
|
||||
_getReaderOptions(task) {
|
||||
const basePath = task.base === '.' ? '' : task.base;
|
||||
return {
|
||||
basePath,
|
||||
pathSegmentSeparator: '/',
|
||||
concurrency: this._settings.concurrency,
|
||||
deepFilter: this.deepFilter.getFilter(basePath, task.positive, task.negative),
|
||||
entryFilter: this.entryFilter.getFilter(task.positive, task.negative),
|
||||
errorFilter: this.errorFilter.getFilter(),
|
||||
followSymbolicLinks: this._settings.followSymbolicLinks,
|
||||
fs: this._settings.fs,
|
||||
stats: this._settings.stats,
|
||||
throwErrorOnBrokenSymbolicLink: this._settings.throwErrorOnBrokenSymbolicLink,
|
||||
transform: this.entryTransformer.getTransformer()
|
||||
};
|
||||
}
|
||||
_getMicromatchOptions() {
|
||||
return {
|
||||
dot: this._settings.dot,
|
||||
matchBase: this._settings.baseNameMatch,
|
||||
nobrace: !this._settings.braceExpansion,
|
||||
nocase: !this._settings.caseSensitiveMatch,
|
||||
noext: !this._settings.extglob,
|
||||
noglobstar: !this._settings.globstar,
|
||||
posix: true,
|
||||
strictSlashes: false
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = Provider;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/// <reference types="node" />
|
||||
import { Readable } from 'stream';
|
||||
import { Task } from '../managers/tasks';
|
||||
import ReaderStream from '../readers/stream';
|
||||
import { ReaderOptions } from '../types';
|
||||
import Provider from './provider';
|
||||
export default class ProviderStream extends Provider<Readable> {
|
||||
protected _reader: ReaderStream;
|
||||
read(task: Task): Readable;
|
||||
api(root: string, task: Task, options: ReaderOptions): Readable;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const stream_1 = require("stream");
|
||||
const stream_2 = require("../readers/stream");
|
||||
const provider_1 = require("./provider");
|
||||
class ProviderStream extends provider_1.default {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this._reader = new stream_2.default(this._settings);
|
||||
}
|
||||
read(task) {
|
||||
const root = this._getRootDirectory(task);
|
||||
const options = this._getReaderOptions(task);
|
||||
const source = this.api(root, task, options);
|
||||
const destination = new stream_1.Readable({ objectMode: true, read: () => { } });
|
||||
source
|
||||
.once('error', (error) => destination.emit('error', error))
|
||||
.on('data', (entry) => destination.emit('data', options.transform(entry)))
|
||||
.once('end', () => destination.emit('end'));
|
||||
destination
|
||||
.once('close', () => source.destroy());
|
||||
return destination;
|
||||
}
|
||||
api(root, task, options) {
|
||||
if (task.dynamic) {
|
||||
return this._reader.dynamic(root, options);
|
||||
}
|
||||
return this._reader.static(task.patterns, options);
|
||||
}
|
||||
}
|
||||
exports.default = ProviderStream;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { Task } from '../managers/tasks';
|
||||
import ReaderSync from '../readers/sync';
|
||||
import { Entry, EntryItem, ReaderOptions } from '../types';
|
||||
import Provider from './provider';
|
||||
export default class ProviderSync extends Provider<EntryItem[]> {
|
||||
protected _reader: ReaderSync;
|
||||
read(task: Task): EntryItem[];
|
||||
api(root: string, task: Task, options: ReaderOptions): Entry[];
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const sync_1 = require("../readers/sync");
|
||||
const provider_1 = require("./provider");
|
||||
class ProviderSync extends provider_1.default {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this._reader = new sync_1.default(this._settings);
|
||||
}
|
||||
read(task) {
|
||||
const root = this._getRootDirectory(task);
|
||||
const options = this._getReaderOptions(task);
|
||||
const entries = this.api(root, task, options);
|
||||
return entries.map(options.transform);
|
||||
}
|
||||
api(root, task, options) {
|
||||
if (task.dynamic) {
|
||||
return this._reader.dynamic(root, options);
|
||||
}
|
||||
return this._reader.static(task.patterns, options);
|
||||
}
|
||||
}
|
||||
exports.default = ProviderSync;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import Settings from '../../settings';
|
||||
import { EntryTransformerFunction } from '../../types';
|
||||
export default class EntryTransformer {
|
||||
private readonly _settings;
|
||||
constructor(_settings: Settings);
|
||||
getTransformer(): EntryTransformerFunction;
|
||||
private _transform;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const utils = require("../../utils");
|
||||
class EntryTransformer {
|
||||
constructor(_settings) {
|
||||
this._settings = _settings;
|
||||
}
|
||||
getTransformer() {
|
||||
return (entry) => this._transform(entry);
|
||||
}
|
||||
_transform(entry) {
|
||||
let filepath = entry.path;
|
||||
if (this._settings.absolute) {
|
||||
filepath = utils.path.makeAbsolute(this._settings.cwd, filepath);
|
||||
filepath = utils.path.unixify(filepath);
|
||||
}
|
||||
if (this._settings.markDirectories && entry.dirent.isDirectory()) {
|
||||
filepath += '/';
|
||||
}
|
||||
if (!this._settings.objectMode) {
|
||||
return filepath;
|
||||
}
|
||||
return Object.assign(Object.assign({}, entry), { path: filepath });
|
||||
}
|
||||
}
|
||||
exports.default = EntryTransformer;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/// <reference types="node" />
|
||||
import * as fs from 'fs';
|
||||
import * as fsStat from '@nodelib/fs.stat';
|
||||
import Settings from '../settings';
|
||||
import { Entry, ErrnoException, Pattern, ReaderOptions } from '../types';
|
||||
export default abstract class Reader<T> {
|
||||
protected readonly _settings: Settings;
|
||||
protected readonly _fsStatSettings: fsStat.Settings;
|
||||
constructor(_settings: Settings);
|
||||
abstract dynamic(root: string, options: ReaderOptions): T;
|
||||
abstract static(patterns: Pattern[], options: ReaderOptions): T;
|
||||
protected _getFullEntryPath(filepath: string): string;
|
||||
protected _makeEntry(stats: fs.Stats, pattern: Pattern): Entry;
|
||||
protected _isFatalError(error: ErrnoException): boolean;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const path = require("path");
|
||||
const fsStat = require("@nodelib/fs.stat");
|
||||
const utils = require("../utils");
|
||||
class Reader {
|
||||
constructor(_settings) {
|
||||
this._settings = _settings;
|
||||
this._fsStatSettings = new fsStat.Settings({
|
||||
followSymbolicLink: this._settings.followSymbolicLinks,
|
||||
fs: this._settings.fs,
|
||||
throwErrorOnBrokenSymbolicLink: this._settings.followSymbolicLinks
|
||||
});
|
||||
}
|
||||
_getFullEntryPath(filepath) {
|
||||
return path.resolve(this._settings.cwd, filepath);
|
||||
}
|
||||
_makeEntry(stats, pattern) {
|
||||
const entry = {
|
||||
name: pattern,
|
||||
path: pattern,
|
||||
dirent: utils.fs.createDirentFromStats(pattern, stats)
|
||||
};
|
||||
if (this._settings.stats) {
|
||||
entry.stats = stats;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
_isFatalError(error) {
|
||||
return !utils.errno.isEnoentCodeError(error) && !this._settings.suppressErrors;
|
||||
}
|
||||
}
|
||||
exports.default = Reader;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/// <reference types="node" />
|
||||
import { Readable } from 'stream';
|
||||
import * as fsStat from '@nodelib/fs.stat';
|
||||
import * as fsWalk from '@nodelib/fs.walk';
|
||||
import { Pattern, ReaderOptions } from '../types';
|
||||
import Reader from './reader';
|
||||
export default class ReaderStream extends Reader<Readable> {
|
||||
protected _walkStream: typeof fsWalk.walkStream;
|
||||
protected _stat: typeof fsStat.stat;
|
||||
dynamic(root: string, options: ReaderOptions): Readable;
|
||||
static(patterns: Pattern[], options: ReaderOptions): Readable;
|
||||
private _getEntry;
|
||||
private _getStat;
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const stream_1 = require("stream");
|
||||
const fsStat = require("@nodelib/fs.stat");
|
||||
const fsWalk = require("@nodelib/fs.walk");
|
||||
const reader_1 = require("./reader");
|
||||
class ReaderStream extends reader_1.default {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this._walkStream = fsWalk.walkStream;
|
||||
this._stat = fsStat.stat;
|
||||
}
|
||||
dynamic(root, options) {
|
||||
return this._walkStream(root, options);
|
||||
}
|
||||
static(patterns, options) {
|
||||
const filepaths = patterns.map(this._getFullEntryPath, this);
|
||||
const stream = new stream_1.PassThrough({ objectMode: true });
|
||||
stream._write = (index, _enc, done) => {
|
||||
return this._getEntry(filepaths[index], patterns[index], options)
|
||||
.then((entry) => {
|
||||
if (entry !== null && options.entryFilter(entry)) {
|
||||
stream.push(entry);
|
||||
}
|
||||
if (index === filepaths.length - 1) {
|
||||
stream.end();
|
||||
}
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
};
|
||||
for (let i = 0; i < filepaths.length; i++) {
|
||||
stream.write(i);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
_getEntry(filepath, pattern, options) {
|
||||
return this._getStat(filepath)
|
||||
.then((stats) => this._makeEntry(stats, pattern))
|
||||
.catch((error) => {
|
||||
if (options.errorFilter(error)) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
_getStat(filepath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._stat(filepath, this._fsStatSettings, (error, stats) => {
|
||||
return error === null ? resolve(stats) : reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.default = ReaderStream;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import * as fsStat from '@nodelib/fs.stat';
|
||||
import * as fsWalk from '@nodelib/fs.walk';
|
||||
import { Entry, Pattern, ReaderOptions } from '../types';
|
||||
import Reader from './reader';
|
||||
export default class ReaderSync extends Reader<Entry[]> {
|
||||
protected _walkSync: typeof fsWalk.walkSync;
|
||||
protected _statSync: typeof fsStat.statSync;
|
||||
dynamic(root: string, options: ReaderOptions): Entry[];
|
||||
static(patterns: Pattern[], options: ReaderOptions): Entry[];
|
||||
private _getEntry;
|
||||
private _getStat;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fsStat = require("@nodelib/fs.stat");
|
||||
const fsWalk = require("@nodelib/fs.walk");
|
||||
const reader_1 = require("./reader");
|
||||
class ReaderSync extends reader_1.default {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this._walkSync = fsWalk.walkSync;
|
||||
this._statSync = fsStat.statSync;
|
||||
}
|
||||
dynamic(root, options) {
|
||||
return this._walkSync(root, options);
|
||||
}
|
||||
static(patterns, options) {
|
||||
const entries = [];
|
||||
for (const pattern of patterns) {
|
||||
const filepath = this._getFullEntryPath(pattern);
|
||||
const entry = this._getEntry(filepath, pattern, options);
|
||||
if (entry === null || !options.entryFilter(entry)) {
|
||||
continue;
|
||||
}
|
||||
entries.push(entry);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
_getEntry(filepath, pattern, options) {
|
||||
try {
|
||||
const stats = this._getStat(filepath);
|
||||
return this._makeEntry(stats, pattern);
|
||||
}
|
||||
catch (error) {
|
||||
if (options.errorFilter(error)) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
_getStat(filepath) {
|
||||
return this._statSync(filepath, this._fsStatSettings);
|
||||
}
|
||||
}
|
||||
exports.default = ReaderSync;
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
import { FileSystemAdapter, Pattern } from './types';
|
||||
export declare const DEFAULT_FILE_SYSTEM_ADAPTER: FileSystemAdapter;
|
||||
export declare type Options = {
|
||||
/**
|
||||
* Return the absolute path for entries.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
absolute?: boolean;
|
||||
/**
|
||||
* If set to `true`, then patterns without slashes will be matched against
|
||||
* the basename of the path if it contains slashes.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
baseNameMatch?: boolean;
|
||||
/**
|
||||
* Enables Bash-like brace expansion.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
braceExpansion?: boolean;
|
||||
/**
|
||||
* Enables a case-sensitive mode for matching files.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
caseSensitiveMatch?: boolean;
|
||||
/**
|
||||
* Specifies the maximum number of concurrent requests from a reader to read
|
||||
* directories.
|
||||
*
|
||||
* @default os.cpus().length
|
||||
*/
|
||||
concurrency?: number;
|
||||
/**
|
||||
* The current working directory in which to search.
|
||||
*
|
||||
* @default process.cwd()
|
||||
*/
|
||||
cwd?: string;
|
||||
/**
|
||||
* Specifies the maximum depth of a read directory relative to the start
|
||||
* directory.
|
||||
*
|
||||
* @default Infinity
|
||||
*/
|
||||
deep?: number;
|
||||
/**
|
||||
* Allow patterns to match entries that begin with a period (`.`).
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
dot?: boolean;
|
||||
/**
|
||||
* Enables Bash-like `extglob` functionality.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
extglob?: boolean;
|
||||
/**
|
||||
* Indicates whether to traverse descendants of symbolic link directories.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
followSymbolicLinks?: boolean;
|
||||
/**
|
||||
* Custom implementation of methods for working with the file system.
|
||||
*
|
||||
* @default fs.*
|
||||
*/
|
||||
fs?: Partial<FileSystemAdapter>;
|
||||
/**
|
||||
* Enables recursively repeats a pattern containing `**`.
|
||||
* If `false`, `**` behaves exactly like `*`.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
globstar?: boolean;
|
||||
/**
|
||||
* An array of glob patterns to exclude matches.
|
||||
* This is an alternative way to use negative patterns.
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
ignore?: Pattern[];
|
||||
/**
|
||||
* Mark the directory path with the final slash.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
markDirectories?: boolean;
|
||||
/**
|
||||
* Returns objects (instead of strings) describing entries.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
objectMode?: boolean;
|
||||
/**
|
||||
* Return only directories.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
onlyDirectories?: boolean;
|
||||
/**
|
||||
* Return only files.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
onlyFiles?: boolean;
|
||||
/**
|
||||
* Enables an object mode (`objectMode`) with an additional `stats` field.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
stats?: boolean;
|
||||
/**
|
||||
* By default this package suppress only `ENOENT` errors.
|
||||
* Set to `true` to suppress any error.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
suppressErrors?: boolean;
|
||||
/**
|
||||
* Throw an error when symbolic link is broken if `true` or safely
|
||||
* return `lstat` call if `false`.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
throwErrorOnBrokenSymbolicLink?: boolean;
|
||||
/**
|
||||
* Ensures that the returned entries are unique.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
unique?: boolean;
|
||||
};
|
||||
export default class Settings {
|
||||
private readonly _options;
|
||||
readonly absolute: boolean;
|
||||
readonly baseNameMatch: boolean;
|
||||
readonly braceExpansion: boolean;
|
||||
readonly caseSensitiveMatch: boolean;
|
||||
readonly concurrency: number;
|
||||
readonly cwd: string;
|
||||
readonly deep: number;
|
||||
readonly dot: boolean;
|
||||
readonly extglob: boolean;
|
||||
readonly followSymbolicLinks: boolean;
|
||||
readonly fs: FileSystemAdapter;
|
||||
readonly globstar: boolean;
|
||||
readonly ignore: Pattern[];
|
||||
readonly markDirectories: boolean;
|
||||
readonly objectMode: boolean;
|
||||
readonly onlyDirectories: boolean;
|
||||
readonly onlyFiles: boolean;
|
||||
readonly stats: boolean;
|
||||
readonly suppressErrors: boolean;
|
||||
readonly throwErrorOnBrokenSymbolicLink: boolean;
|
||||
readonly unique: boolean;
|
||||
constructor(_options?: Options);
|
||||
private _getValue;
|
||||
private _getFileSystemMethods;
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DEFAULT_FILE_SYSTEM_ADAPTER = void 0;
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
/**
|
||||
* The `os.cpus` method can return zero. We expect the number of cores to be greater than zero.
|
||||
* https://github.com/nodejs/node/blob/7faeddf23a98c53896f8b574a6e66589e8fb1eb8/lib/os.js#L106-L107
|
||||
*/
|
||||
const CPU_COUNT = Math.max(os.cpus().length, 1);
|
||||
exports.DEFAULT_FILE_SYSTEM_ADAPTER = {
|
||||
lstat: fs.lstat,
|
||||
lstatSync: fs.lstatSync,
|
||||
stat: fs.stat,
|
||||
statSync: fs.statSync,
|
||||
readdir: fs.readdir,
|
||||
readdirSync: fs.readdirSync
|
||||
};
|
||||
class Settings {
|
||||
constructor(_options = {}) {
|
||||
this._options = _options;
|
||||
this.absolute = this._getValue(this._options.absolute, false);
|
||||
this.baseNameMatch = this._getValue(this._options.baseNameMatch, false);
|
||||
this.braceExpansion = this._getValue(this._options.braceExpansion, true);
|
||||
this.caseSensitiveMatch = this._getValue(this._options.caseSensitiveMatch, true);
|
||||
this.concurrency = this._getValue(this._options.concurrency, CPU_COUNT);
|
||||
this.cwd = this._getValue(this._options.cwd, process.cwd());
|
||||
this.deep = this._getValue(this._options.deep, Infinity);
|
||||
this.dot = this._getValue(this._options.dot, false);
|
||||
this.extglob = this._getValue(this._options.extglob, true);
|
||||
this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, true);
|
||||
this.fs = this._getFileSystemMethods(this._options.fs);
|
||||
this.globstar = this._getValue(this._options.globstar, true);
|
||||
this.ignore = this._getValue(this._options.ignore, []);
|
||||
this.markDirectories = this._getValue(this._options.markDirectories, false);
|
||||
this.objectMode = this._getValue(this._options.objectMode, false);
|
||||
this.onlyDirectories = this._getValue(this._options.onlyDirectories, false);
|
||||
this.onlyFiles = this._getValue(this._options.onlyFiles, true);
|
||||
this.stats = this._getValue(this._options.stats, false);
|
||||
this.suppressErrors = this._getValue(this._options.suppressErrors, false);
|
||||
this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, false);
|
||||
this.unique = this._getValue(this._options.unique, true);
|
||||
if (this.onlyDirectories) {
|
||||
this.onlyFiles = false;
|
||||
}
|
||||
if (this.stats) {
|
||||
this.objectMode = true;
|
||||
}
|
||||
}
|
||||
_getValue(option, value) {
|
||||
return option === undefined ? value : option;
|
||||
}
|
||||
_getFileSystemMethods(methods = {}) {
|
||||
return Object.assign(Object.assign({}, exports.DEFAULT_FILE_SYSTEM_ADAPTER), methods);
|
||||
}
|
||||
}
|
||||
exports.default = Settings;
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/// <reference types="node" />
|
||||
import * as fsWalk from '@nodelib/fs.walk';
|
||||
export declare type ErrnoException = NodeJS.ErrnoException;
|
||||
export declare type Entry = fsWalk.Entry;
|
||||
export declare type EntryItem = string | Entry;
|
||||
export declare type Pattern = string;
|
||||
export declare type PatternRe = RegExp;
|
||||
export declare type PatternsGroup = Record<string, Pattern[]>;
|
||||
export declare type ReaderOptions = fsWalk.Options & {
|
||||
transform(entry: Entry): EntryItem;
|
||||
deepFilter: DeepFilterFunction;
|
||||
entryFilter: EntryFilterFunction;
|
||||
errorFilter: ErrorFilterFunction;
|
||||
fs: FileSystemAdapter;
|
||||
stats: boolean;
|
||||
};
|
||||
export declare type ErrorFilterFunction = fsWalk.ErrorFilterFunction;
|
||||
export declare type EntryFilterFunction = fsWalk.EntryFilterFunction;
|
||||
export declare type DeepFilterFunction = fsWalk.DeepFilterFunction;
|
||||
export declare type EntryTransformerFunction = (entry: Entry) => EntryItem;
|
||||
export declare type MicromatchOptions = {
|
||||
dot?: boolean;
|
||||
matchBase?: boolean;
|
||||
nobrace?: boolean;
|
||||
nocase?: boolean;
|
||||
noext?: boolean;
|
||||
noglobstar?: boolean;
|
||||
posix?: boolean;
|
||||
strictSlashes?: boolean;
|
||||
};
|
||||
export declare type FileSystemAdapter = fsWalk.FileSystemAdapter;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export declare function flatten<T>(items: T[][]): T[];
|
||||
export declare function splitWhen<T>(items: T[], predicate: (item: T) => boolean): T[][];
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.splitWhen = exports.flatten = void 0;
|
||||
function flatten(items) {
|
||||
return items.reduce((collection, item) => [].concat(collection, item), []);
|
||||
}
|
||||
exports.flatten = flatten;
|
||||
function splitWhen(items, predicate) {
|
||||
const result = [[]];
|
||||
let groupIndex = 0;
|
||||
for (const item of items) {
|
||||
if (predicate(item)) {
|
||||
groupIndex++;
|
||||
result[groupIndex] = [];
|
||||
}
|
||||
else {
|
||||
result[groupIndex].push(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.splitWhen = splitWhen;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import { ErrnoException } from '../types';
|
||||
export declare function isEnoentCodeError(error: ErrnoException): boolean;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isEnoentCodeError = void 0;
|
||||
function isEnoentCodeError(error) {
|
||||
return error.code === 'ENOENT';
|
||||
}
|
||||
exports.isEnoentCodeError = isEnoentCodeError;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/// <reference types="node" />
|
||||
import * as fs from 'fs';
|
||||
import { Dirent } from '@nodelib/fs.walk';
|
||||
export declare function createDirentFromStats(name: string, stats: fs.Stats): Dirent;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createDirentFromStats = void 0;
|
||||
class DirentFromStats {
|
||||
constructor(name, stats) {
|
||||
this.name = name;
|
||||
this.isBlockDevice = stats.isBlockDevice.bind(stats);
|
||||
this.isCharacterDevice = stats.isCharacterDevice.bind(stats);
|
||||
this.isDirectory = stats.isDirectory.bind(stats);
|
||||
this.isFIFO = stats.isFIFO.bind(stats);
|
||||
this.isFile = stats.isFile.bind(stats);
|
||||
this.isSocket = stats.isSocket.bind(stats);
|
||||
this.isSymbolicLink = stats.isSymbolicLink.bind(stats);
|
||||
}
|
||||
}
|
||||
function createDirentFromStats(name, stats) {
|
||||
return new DirentFromStats(name, stats);
|
||||
}
|
||||
exports.createDirentFromStats = createDirentFromStats;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import * as array from './array';
|
||||
import * as errno from './errno';
|
||||
import * as fs from './fs';
|
||||
import * as path from './path';
|
||||
import * as pattern from './pattern';
|
||||
import * as stream from './stream';
|
||||
import * as string from './string';
|
||||
export { array, errno, fs, path, pattern, stream, string };
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.string = exports.stream = exports.pattern = exports.path = exports.fs = exports.errno = exports.array = void 0;
|
||||
const array = require("./array");
|
||||
exports.array = array;
|
||||
const errno = require("./errno");
|
||||
exports.errno = errno;
|
||||
const fs = require("./fs");
|
||||
exports.fs = fs;
|
||||
const path = require("./path");
|
||||
exports.path = path;
|
||||
const pattern = require("./pattern");
|
||||
exports.pattern = pattern;
|
||||
const stream = require("./stream");
|
||||
exports.stream = stream;
|
||||
const string = require("./string");
|
||||
exports.string = string;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { Pattern } from '../types';
|
||||
/**
|
||||
* Designed to work only with simple paths: `dir\\file`.
|
||||
*/
|
||||
export declare function unixify(filepath: string): string;
|
||||
export declare function makeAbsolute(cwd: string, filepath: string): string;
|
||||
export declare function escape(pattern: Pattern): Pattern;
|
||||
export declare function removeLeadingDotSegment(entry: string): string;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.removeLeadingDotSegment = exports.escape = exports.makeAbsolute = exports.unixify = void 0;
|
||||
const path = require("path");
|
||||
const LEADING_DOT_SEGMENT_CHARACTERS_COUNT = 2; // ./ or .\\
|
||||
const UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([()*?[\]{|}]|^!|[!+@](?=\())/g;
|
||||
/**
|
||||
* Designed to work only with simple paths: `dir\\file`.
|
||||
*/
|
||||
function unixify(filepath) {
|
||||
return filepath.replace(/\\/g, '/');
|
||||
}
|
||||
exports.unixify = unixify;
|
||||
function makeAbsolute(cwd, filepath) {
|
||||
return path.resolve(cwd, filepath);
|
||||
}
|
||||
exports.makeAbsolute = makeAbsolute;
|
||||
function escape(pattern) {
|
||||
return pattern.replace(UNESCAPED_GLOB_SYMBOLS_RE, '\\$2');
|
||||
}
|
||||
exports.escape = escape;
|
||||
function removeLeadingDotSegment(entry) {
|
||||
// We do not use `startsWith` because this is 10x slower than current implementation for some cases.
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
||||
if (entry.charAt(0) === '.') {
|
||||
const secondCharactery = entry.charAt(1);
|
||||
if (secondCharactery === '/' || secondCharactery === '\\') {
|
||||
return entry.slice(LEADING_DOT_SEGMENT_CHARACTERS_COUNT);
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
exports.removeLeadingDotSegment = removeLeadingDotSegment;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { MicromatchOptions, Pattern, PatternRe } from '../types';
|
||||
declare type PatternTypeOptions = {
|
||||
braceExpansion?: boolean;
|
||||
caseSensitiveMatch?: boolean;
|
||||
extglob?: boolean;
|
||||
};
|
||||
export declare function isStaticPattern(pattern: Pattern, options?: PatternTypeOptions): boolean;
|
||||
export declare function isDynamicPattern(pattern: Pattern, options?: PatternTypeOptions): boolean;
|
||||
export declare function convertToPositivePattern(pattern: Pattern): Pattern;
|
||||
export declare function convertToNegativePattern(pattern: Pattern): Pattern;
|
||||
export declare function isNegativePattern(pattern: Pattern): boolean;
|
||||
export declare function isPositivePattern(pattern: Pattern): boolean;
|
||||
export declare function getNegativePatterns(patterns: Pattern[]): Pattern[];
|
||||
export declare function getPositivePatterns(patterns: Pattern[]): Pattern[];
|
||||
/**
|
||||
* Returns patterns that can be applied inside the current directory.
|
||||
*
|
||||
* @example
|
||||
* // ['./*', '*', 'a/*']
|
||||
* getPatternsInsideCurrentDirectory(['./*', '*', 'a/*', '../*', './../*'])
|
||||
*/
|
||||
export declare function getPatternsInsideCurrentDirectory(patterns: Pattern[]): Pattern[];
|
||||
/**
|
||||
* Returns patterns to be expanded relative to (outside) the current directory.
|
||||
*
|
||||
* @example
|
||||
* // ['../*', './../*']
|
||||
* getPatternsInsideCurrentDirectory(['./*', '*', 'a/*', '../*', './../*'])
|
||||
*/
|
||||
export declare function getPatternsOutsideCurrentDirectory(patterns: Pattern[]): Pattern[];
|
||||
export declare function isPatternRelatedToParentDirectory(pattern: Pattern): boolean;
|
||||
export declare function getBaseDirectory(pattern: Pattern): string;
|
||||
export declare function hasGlobStar(pattern: Pattern): boolean;
|
||||
export declare function endsWithSlashGlobStar(pattern: Pattern): boolean;
|
||||
export declare function isAffectDepthOfReadingPattern(pattern: Pattern): boolean;
|
||||
export declare function expandPatternsWithBraceExpansion(patterns: Pattern[]): Pattern[];
|
||||
export declare function expandBraceExpansion(pattern: Pattern): Pattern[];
|
||||
export declare function getPatternParts(pattern: Pattern, options: MicromatchOptions): Pattern[];
|
||||
export declare function makeRe(pattern: Pattern, options: MicromatchOptions): PatternRe;
|
||||
export declare function convertPatternsToRe(patterns: Pattern[], options: MicromatchOptions): PatternRe[];
|
||||
export declare function matchAny(entry: string, patternsRe: PatternRe[]): boolean;
|
||||
export {};
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.matchAny = exports.convertPatternsToRe = exports.makeRe = exports.getPatternParts = exports.expandBraceExpansion = exports.expandPatternsWithBraceExpansion = exports.isAffectDepthOfReadingPattern = exports.endsWithSlashGlobStar = exports.hasGlobStar = exports.getBaseDirectory = exports.isPatternRelatedToParentDirectory = exports.getPatternsOutsideCurrentDirectory = exports.getPatternsInsideCurrentDirectory = exports.getPositivePatterns = exports.getNegativePatterns = exports.isPositivePattern = exports.isNegativePattern = exports.convertToNegativePattern = exports.convertToPositivePattern = exports.isDynamicPattern = exports.isStaticPattern = void 0;
|
||||
const path = require("path");
|
||||
const globParent = require("glob-parent");
|
||||
const micromatch = require("micromatch");
|
||||
const GLOBSTAR = '**';
|
||||
const ESCAPE_SYMBOL = '\\';
|
||||
const COMMON_GLOB_SYMBOLS_RE = /[*?]|^!/;
|
||||
const REGEX_CHARACTER_CLASS_SYMBOLS_RE = /\[[^[]*]/;
|
||||
const REGEX_GROUP_SYMBOLS_RE = /(?:^|[^!*+?@])\([^(]*\|[^|]*\)/;
|
||||
const GLOB_EXTENSION_SYMBOLS_RE = /[!*+?@]\([^(]*\)/;
|
||||
const BRACE_EXPANSION_SEPARATORS_RE = /,|\.\./;
|
||||
function isStaticPattern(pattern, options = {}) {
|
||||
return !isDynamicPattern(pattern, options);
|
||||
}
|
||||
exports.isStaticPattern = isStaticPattern;
|
||||
function isDynamicPattern(pattern, options = {}) {
|
||||
/**
|
||||
* A special case with an empty string is necessary for matching patterns that start with a forward slash.
|
||||
* An empty string cannot be a dynamic pattern.
|
||||
* For example, the pattern `/lib/*` will be spread into parts: '', 'lib', '*'.
|
||||
*/
|
||||
if (pattern === '') {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* When the `caseSensitiveMatch` option is disabled, all patterns must be marked as dynamic, because we cannot check
|
||||
* filepath directly (without read directory).
|
||||
*/
|
||||
if (options.caseSensitiveMatch === false || pattern.includes(ESCAPE_SYMBOL)) {
|
||||
return true;
|
||||
}
|
||||
if (COMMON_GLOB_SYMBOLS_RE.test(pattern) || REGEX_CHARACTER_CLASS_SYMBOLS_RE.test(pattern) || REGEX_GROUP_SYMBOLS_RE.test(pattern)) {
|
||||
return true;
|
||||
}
|
||||
if (options.extglob !== false && GLOB_EXTENSION_SYMBOLS_RE.test(pattern)) {
|
||||
return true;
|
||||
}
|
||||
if (options.braceExpansion !== false && hasBraceExpansion(pattern)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
exports.isDynamicPattern = isDynamicPattern;
|
||||
function hasBraceExpansion(pattern) {
|
||||
const openingBraceIndex = pattern.indexOf('{');
|
||||
if (openingBraceIndex === -1) {
|
||||
return false;
|
||||
}
|
||||
const closingBraceIndex = pattern.indexOf('}', openingBraceIndex + 1);
|
||||
if (closingBraceIndex === -1) {
|
||||
return false;
|
||||
}
|
||||
const braceContent = pattern.slice(openingBraceIndex, closingBraceIndex);
|
||||
return BRACE_EXPANSION_SEPARATORS_RE.test(braceContent);
|
||||
}
|
||||
function convertToPositivePattern(pattern) {
|
||||
return isNegativePattern(pattern) ? pattern.slice(1) : pattern;
|
||||
}
|
||||
exports.convertToPositivePattern = convertToPositivePattern;
|
||||
function convertToNegativePattern(pattern) {
|
||||
return '!' + pattern;
|
||||
}
|
||||
exports.convertToNegativePattern = convertToNegativePattern;
|
||||
function isNegativePattern(pattern) {
|
||||
return pattern.startsWith('!') && pattern[1] !== '(';
|
||||
}
|
||||
exports.isNegativePattern = isNegativePattern;
|
||||
function isPositivePattern(pattern) {
|
||||
return !isNegativePattern(pattern);
|
||||
}
|
||||
exports.isPositivePattern = isPositivePattern;
|
||||
function getNegativePatterns(patterns) {
|
||||
return patterns.filter(isNegativePattern);
|
||||
}
|
||||
exports.getNegativePatterns = getNegativePatterns;
|
||||
function getPositivePatterns(patterns) {
|
||||
return patterns.filter(isPositivePattern);
|
||||
}
|
||||
exports.getPositivePatterns = getPositivePatterns;
|
||||
/**
|
||||
* Returns patterns that can be applied inside the current directory.
|
||||
*
|
||||
* @example
|
||||
* // ['./*', '*', 'a/*']
|
||||
* getPatternsInsideCurrentDirectory(['./*', '*', 'a/*', '../*', './../*'])
|
||||
*/
|
||||
function getPatternsInsideCurrentDirectory(patterns) {
|
||||
return patterns.filter((pattern) => !isPatternRelatedToParentDirectory(pattern));
|
||||
}
|
||||
exports.getPatternsInsideCurrentDirectory = getPatternsInsideCurrentDirectory;
|
||||
/**
|
||||
* Returns patterns to be expanded relative to (outside) the current directory.
|
||||
*
|
||||
* @example
|
||||
* // ['../*', './../*']
|
||||
* getPatternsInsideCurrentDirectory(['./*', '*', 'a/*', '../*', './../*'])
|
||||
*/
|
||||
function getPatternsOutsideCurrentDirectory(patterns) {
|
||||
return patterns.filter(isPatternRelatedToParentDirectory);
|
||||
}
|
||||
exports.getPatternsOutsideCurrentDirectory = getPatternsOutsideCurrentDirectory;
|
||||
function isPatternRelatedToParentDirectory(pattern) {
|
||||
return pattern.startsWith('..') || pattern.startsWith('./..');
|
||||
}
|
||||
exports.isPatternRelatedToParentDirectory = isPatternRelatedToParentDirectory;
|
||||
function getBaseDirectory(pattern) {
|
||||
return globParent(pattern, { flipBackslashes: false });
|
||||
}
|
||||
exports.getBaseDirectory = getBaseDirectory;
|
||||
function hasGlobStar(pattern) {
|
||||
return pattern.includes(GLOBSTAR);
|
||||
}
|
||||
exports.hasGlobStar = hasGlobStar;
|
||||
function endsWithSlashGlobStar(pattern) {
|
||||
return pattern.endsWith('/' + GLOBSTAR);
|
||||
}
|
||||
exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
|
||||
function isAffectDepthOfReadingPattern(pattern) {
|
||||
const basename = path.basename(pattern);
|
||||
return endsWithSlashGlobStar(pattern) || isStaticPattern(basename);
|
||||
}
|
||||
exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
|
||||
function expandPatternsWithBraceExpansion(patterns) {
|
||||
return patterns.reduce((collection, pattern) => {
|
||||
return collection.concat(expandBraceExpansion(pattern));
|
||||
}, []);
|
||||
}
|
||||
exports.expandPatternsWithBraceExpansion = expandPatternsWithBraceExpansion;
|
||||
function expandBraceExpansion(pattern) {
|
||||
return micromatch.braces(pattern, {
|
||||
expand: true,
|
||||
nodupes: true
|
||||
});
|
||||
}
|
||||
exports.expandBraceExpansion = expandBraceExpansion;
|
||||
function getPatternParts(pattern, options) {
|
||||
let { parts } = micromatch.scan(pattern, Object.assign(Object.assign({}, options), { parts: true }));
|
||||
/**
|
||||
* The scan method returns an empty array in some cases.
|
||||
* See micromatch/picomatch#58 for more details.
|
||||
*/
|
||||
if (parts.length === 0) {
|
||||
parts = [pattern];
|
||||
}
|
||||
/**
|
||||
* The scan method does not return an empty part for the pattern with a forward slash.
|
||||
* This is another part of micromatch/picomatch#58.
|
||||
*/
|
||||
if (parts[0].startsWith('/')) {
|
||||
parts[0] = parts[0].slice(1);
|
||||
parts.unshift('');
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
exports.getPatternParts = getPatternParts;
|
||||
function makeRe(pattern, options) {
|
||||
return micromatch.makeRe(pattern, options);
|
||||
}
|
||||
exports.makeRe = makeRe;
|
||||
function convertPatternsToRe(patterns, options) {
|
||||
return patterns.map((pattern) => makeRe(pattern, options));
|
||||
}
|
||||
exports.convertPatternsToRe = convertPatternsToRe;
|
||||
function matchAny(entry, patternsRe) {
|
||||
return patternsRe.some((patternRe) => patternRe.test(entry));
|
||||
}
|
||||
exports.matchAny = matchAny;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
/// <reference types="node" />
|
||||
import { Readable } from 'stream';
|
||||
export declare function merge(streams: Readable[]): NodeJS.ReadableStream;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.merge = void 0;
|
||||
const merge2 = require("merge2");
|
||||
function merge(streams) {
|
||||
const mergedStream = merge2(streams);
|
||||
streams.forEach((stream) => {
|
||||
stream.once('error', (error) => mergedStream.emit('error', error));
|
||||
});
|
||||
mergedStream.once('close', () => propagateCloseEventToSources(streams));
|
||||
mergedStream.once('end', () => propagateCloseEventToSources(streams));
|
||||
return mergedStream;
|
||||
}
|
||||
exports.merge = merge;
|
||||
function propagateCloseEventToSources(streams) {
|
||||
streams.forEach((stream) => stream.emit('close'));
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export declare function isString(input: unknown): input is string;
|
||||
export declare function isEmpty(input: string): boolean;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isEmpty = exports.isString = void 0;
|
||||
function isString(input) {
|
||||
return typeof input === 'string';
|
||||
}
|
||||
exports.isString = isString;
|
||||
function isEmpty(input) {
|
||||
return input === '';
|
||||
}
|
||||
exports.isEmpty = isEmpty;
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"name": "fast-glob",
|
||||
"version": "3.2.11",
|
||||
"description": "It's a very fast and efficient glob library for Node.js",
|
||||
"license": "MIT",
|
||||
"repository": "mrmlnc/fast-glob",
|
||||
"author": {
|
||||
"name": "Denis Malinochkin",
|
||||
"url": "https://mrmlnc.com"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6.0"
|
||||
},
|
||||
"main": "out/index.js",
|
||||
"typings": "out/index.d.ts",
|
||||
"files": [
|
||||
"out",
|
||||
"!out/{benchmark,tests}",
|
||||
"!out/**/*.map",
|
||||
"!out/**/*.spec.*"
|
||||
],
|
||||
"keywords": [
|
||||
"glob",
|
||||
"patterns",
|
||||
"fast",
|
||||
"implementation"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@nodelib/fs.macchiato": "^1.0.1",
|
||||
"@types/compute-stdev": "^1.0.0",
|
||||
"@types/easy-table": "^0.0.32",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/glob-parent": "^5.1.0",
|
||||
"@types/is-ci": "^2.0.0",
|
||||
"@types/merge2": "^1.1.4",
|
||||
"@types/micromatch": "^4.0.0",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^12.7.8",
|
||||
"@types/rimraf": "^2.0.2",
|
||||
"@types/sinon": "^7.5.0",
|
||||
"compute-stdev": "^1.0.0",
|
||||
"easy-table": "^1.1.1",
|
||||
"eslint": "^6.5.1",
|
||||
"eslint-config-mrmlnc": "^1.1.0",
|
||||
"execa": "^2.0.4",
|
||||
"fast-glob": "^3.0.4",
|
||||
"fdir": "^5.1.0",
|
||||
"glob": "^7.1.4",
|
||||
"is-ci": "^2.0.0",
|
||||
"log-update": "^4.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mocha": "^6.2.1",
|
||||
"rimraf": "^3.0.0",
|
||||
"sinon": "^7.5.0",
|
||||
"tiny-glob": "^0.2.6",
|
||||
"typescript": "^3.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
"glob-parent": "^5.1.2",
|
||||
"merge2": "^1.3.0",
|
||||
"micromatch": "^4.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf out",
|
||||
"lint": "eslint \"src/**/*.ts\" --cache",
|
||||
"compile": "tsc",
|
||||
"test": "mocha \"out/**/*.spec.js\" -s 0",
|
||||
"smoke": "mocha \"out/**/*.smoke.js\" -s 0",
|
||||
"smoke:sync": "mocha \"out/**/*.smoke.js\" -s 0 --grep \"\\(sync\\)\"",
|
||||
"smoke:async": "mocha \"out/**/*.smoke.js\" -s 0 --grep \"\\(async\\)\"",
|
||||
"smoke:stream": "mocha \"out/**/*.smoke.js\" -s 0 --grep \"\\(stream\\)\"",
|
||||
"build": "npm run clean && npm run compile && npm run lint && npm test",
|
||||
"watch": "npm run clean && npm run compile -- --sourceMap --watch",
|
||||
"bench": "npm run bench-async && npm run bench-stream && npm run bench-sync",
|
||||
"bench-async": "npm run bench-async-flatten && npm run bench-async-deep && npm run bench-async-partial-flatten && npm run bench-async-partial-deep",
|
||||
"bench-stream": "npm run bench-stream-flatten && npm run bench-stream-deep && npm run bench-stream-partial-flatten && npm run bench-stream-partial-deep",
|
||||
"bench-sync": "npm run bench-sync-flatten && npm run bench-sync-deep && npm run bench-sync-partial-flatten && npm run bench-sync-partial-deep",
|
||||
"bench-async-flatten": "node ./out/benchmark --mode async --pattern \"*\"",
|
||||
"bench-async-deep": "node ./out/benchmark --mode async --pattern \"**\"",
|
||||
"bench-async-partial-flatten": "node ./out/benchmark --mode async --pattern \"{fixtures,out}/{first,second}/*\"",
|
||||
"bench-async-partial-deep": "node ./out/benchmark --mode async --pattern \"{fixtures,out}/**\"",
|
||||
"bench-stream-flatten": "node ./out/benchmark --mode stream --pattern \"*\"",
|
||||
"bench-stream-deep": "node ./out/benchmark --mode stream --pattern \"**\"",
|
||||
"bench-stream-partial-flatten": "node ./out/benchmark --mode stream --pattern \"{fixtures,out}/{first,second}/*\"",
|
||||
"bench-stream-partial-deep": "node ./out/benchmark --mode stream --pattern \"{fixtures,out}/**\"",
|
||||
"bench-sync-flatten": "node ./out/benchmark --mode sync --pattern \"*\"",
|
||||
"bench-sync-deep": "node ./out/benchmark --mode sync --pattern \"**\"",
|
||||
"bench-sync-partial-flatten": "node ./out/benchmark --mode sync --pattern \"{fixtures,out}/{first,second}/*\"",
|
||||
"bench-sync-partial-deep": "node ./out/benchmark --mode sync --pattern \"{fixtures,out}/**\""
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
extends: eslint:recommended
|
||||
env:
|
||||
node: true
|
||||
browser: true
|
||||
rules:
|
||||
block-scoped-var: 2
|
||||
callback-return: 2
|
||||
dot-notation: 2
|
||||
indent: 2
|
||||
linebreak-style: [2, unix]
|
||||
new-cap: 2
|
||||
no-console: [2, allow: [warn, error]]
|
||||
no-else-return: 2
|
||||
no-eq-null: 2
|
||||
no-fallthrough: 2
|
||||
no-invalid-this: 2
|
||||
no-return-assign: 2
|
||||
no-shadow: 1
|
||||
no-trailing-spaces: 2
|
||||
no-use-before-define: [2, nofunc]
|
||||
quotes: [2, single, avoid-escape]
|
||||
semi: [2, always]
|
||||
strict: [2, global]
|
||||
valid-jsdoc: [2, requireReturn: false]
|
||||
no-control-regex: 0
|
||||
no-useless-escape: 2
|
||||
+1
@@ -0,0 +1 @@
|
||||
tidelift: "npm/fast-json-stable-stringify"
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "8"
|
||||
- "10"
|
||||
- "12"
|
||||
- "13"
|
||||
after_script:
|
||||
- coveralls < coverage/lcov.info
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
This software is released under the MIT license:
|
||||
|
||||
Copyright (c) 2017 Evgeny Poberezkin
|
||||
Copyright (c) 2013 James Halliday
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
# fast-json-stable-stringify
|
||||
|
||||
Deterministic `JSON.stringify()` - a faster version of [@substack](https://github.com/substack)'s json-stable-strigify without [jsonify](https://github.com/substack/jsonify).
|
||||
|
||||
You can also pass in a custom comparison function.
|
||||
|
||||
[](https://travis-ci.org/epoberezkin/fast-json-stable-stringify)
|
||||
[](https://coveralls.io/github/epoberezkin/fast-json-stable-stringify?branch=master)
|
||||
|
||||
# example
|
||||
|
||||
``` js
|
||||
var stringify = require('fast-json-stable-stringify');
|
||||
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
|
||||
console.log(stringify(obj));
|
||||
```
|
||||
|
||||
output:
|
||||
|
||||
```
|
||||
{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}
|
||||
```
|
||||
|
||||
|
||||
# methods
|
||||
|
||||
``` js
|
||||
var stringify = require('fast-json-stable-stringify')
|
||||
```
|
||||
|
||||
## var str = stringify(obj, opts)
|
||||
|
||||
Return a deterministic stringified string `str` from the object `obj`.
|
||||
|
||||
|
||||
## options
|
||||
|
||||
### cmp
|
||||
|
||||
If `opts` is given, you can supply an `opts.cmp` to have a custom comparison
|
||||
function for object keys. Your function `opts.cmp` is called with these
|
||||
parameters:
|
||||
|
||||
``` js
|
||||
opts.cmp({ key: akey, value: avalue }, { key: bkey, value: bvalue })
|
||||
```
|
||||
|
||||
For example, to sort on the object key names in reverse order you could write:
|
||||
|
||||
``` js
|
||||
var stringify = require('fast-json-stable-stringify');
|
||||
|
||||
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
|
||||
var s = stringify(obj, function (a, b) {
|
||||
return a.key < b.key ? 1 : -1;
|
||||
});
|
||||
console.log(s);
|
||||
```
|
||||
|
||||
which results in the output string:
|
||||
|
||||
```
|
||||
{"c":8,"b":[{"z":6,"y":5,"x":4},7],"a":3}
|
||||
```
|
||||
|
||||
Or if you wanted to sort on the object values in reverse order, you could write:
|
||||
|
||||
```
|
||||
var stringify = require('fast-json-stable-stringify');
|
||||
|
||||
var obj = { d: 6, c: 5, b: [{z:3,y:2,x:1},9], a: 10 };
|
||||
var s = stringify(obj, function (a, b) {
|
||||
return a.value < b.value ? 1 : -1;
|
||||
});
|
||||
console.log(s);
|
||||
```
|
||||
|
||||
which outputs:
|
||||
|
||||
```
|
||||
{"d":6,"c":5,"b":[{"z":3,"y":2,"x":1},9],"a":10}
|
||||
```
|
||||
|
||||
### cycles
|
||||
|
||||
Pass `true` in `opts.cycles` to stringify circular property as `__cycle__` - the result will not be a valid JSON string in this case.
|
||||
|
||||
TypeError will be thrown in case of circular object without this option.
|
||||
|
||||
|
||||
# install
|
||||
|
||||
With [npm](https://npmjs.org) do:
|
||||
|
||||
```
|
||||
npm install fast-json-stable-stringify
|
||||
```
|
||||
|
||||
|
||||
# benchmark
|
||||
|
||||
To run benchmark (requires Node.js 6+):
|
||||
```
|
||||
node benchmark
|
||||
```
|
||||
|
||||
Results:
|
||||
```
|
||||
fast-json-stable-stringify x 17,189 ops/sec ±1.43% (83 runs sampled)
|
||||
json-stable-stringify x 13,634 ops/sec ±1.39% (85 runs sampled)
|
||||
fast-stable-stringify x 20,212 ops/sec ±1.20% (84 runs sampled)
|
||||
faster-stable-stringify x 15,549 ops/sec ±1.12% (84 runs sampled)
|
||||
The fastest is fast-stable-stringify
|
||||
```
|
||||
|
||||
|
||||
## Enterprise support
|
||||
|
||||
fast-json-stable-stringify package is a part of [Tidelift enterprise subscription](https://tidelift.com/subscription/pkg/npm-fast-json-stable-stringify?utm_source=npm-fast-json-stable-stringify&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) - it provides a centralised commercial support to open-source software users, in addition to the support provided by software maintainers.
|
||||
|
||||
|
||||
## Security contact
|
||||
|
||||
To report a security vulnerability, please use the
|
||||
[Tidelift security contact](https://tidelift.com/security).
|
||||
Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerability via GitHub issues.
|
||||
|
||||
|
||||
# license
|
||||
|
||||
[MIT](https://github.com/epoberezkin/fast-json-stable-stringify/blob/master/LICENSE)
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const Benchmark = require('benchmark');
|
||||
const suite = new Benchmark.Suite;
|
||||
const testData = require('./test.json');
|
||||
|
||||
|
||||
const stringifyPackages = {
|
||||
// 'JSON.stringify': JSON.stringify,
|
||||
'fast-json-stable-stringify': require('../index'),
|
||||
'json-stable-stringify': true,
|
||||
'fast-stable-stringify': true,
|
||||
'faster-stable-stringify': true
|
||||
};
|
||||
|
||||
|
||||
for (const name in stringifyPackages) {
|
||||
let func = stringifyPackages[name];
|
||||
if (func === true) func = require(name);
|
||||
|
||||
suite.add(name, function() {
|
||||
func(testData);
|
||||
});
|
||||
}
|
||||
|
||||
suite
|
||||
.on('cycle', (event) => console.log(String(event.target)))
|
||||
.on('complete', function () {
|
||||
console.log('The fastest is ' + this.filter('fastest').map('name'));
|
||||
})
|
||||
.run({async: true});
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
[
|
||||
{
|
||||
"_id": "59ef4a83ee8364808d761beb",
|
||||
"index": 0,
|
||||
"guid": "e50ffae9-7128-4148-9ee5-40c3fc523c5d",
|
||||
"isActive": false,
|
||||
"balance": "$2,341.81",
|
||||
"picture": "http://placehold.it/32x32",
|
||||
"age": 28,
|
||||
"eyeColor": "brown",
|
||||
"name": "Carey Savage",
|
||||
"gender": "female",
|
||||
"company": "VERAQ",
|
||||
"email": "careysavage@veraq.com",
|
||||
"phone": "+1 (897) 574-3014",
|
||||
"address": "458 Willow Street, Henrietta, California, 7234",
|
||||
"about": "Nisi reprehenderit nulla ad officia pariatur non dolore laboris irure cupidatat laborum. Minim eu ex Lorem adipisicing exercitation irure minim sunt est enim mollit incididunt voluptate nulla. Ut mollit anim reprehenderit et aliqua ex esse aliquip. Aute sit duis deserunt do incididunt consequat minim qui dolor commodo deserunt et voluptate.\r\n",
|
||||
"registered": "2014-05-21T01:56:51 -01:00",
|
||||
"latitude": 63.89502,
|
||||
"longitude": 62.369807,
|
||||
"tags": [
|
||||
"nostrud",
|
||||
"nisi",
|
||||
"consectetur",
|
||||
"ullamco",
|
||||
"cupidatat",
|
||||
"culpa",
|
||||
"commodo"
|
||||
],
|
||||
"friends": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "Henry Walls"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Janice Baker"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Russell Bush"
|
||||
}
|
||||
],
|
||||
"greeting": "Hello, Carey Savage! You have 4 unread messages.",
|
||||
"favoriteFruit": "banana"
|
||||
},
|
||||
{
|
||||
"_id": "59ef4a83ff5774a691454e89",
|
||||
"index": 1,
|
||||
"guid": "2bee9efc-4095-4c2e-87ef-d08c8054c89d",
|
||||
"isActive": true,
|
||||
"balance": "$1,618.15",
|
||||
"picture": "http://placehold.it/32x32",
|
||||
"age": 35,
|
||||
"eyeColor": "blue",
|
||||
"name": "Elinor Pearson",
|
||||
"gender": "female",
|
||||
"company": "FLEXIGEN",
|
||||
"email": "elinorpearson@flexigen.com",
|
||||
"phone": "+1 (923) 548-3751",
|
||||
"address": "600 Bayview Avenue, Draper, Montana, 3088",
|
||||
"about": "Mollit commodo ea sit Lorem velit. Irure anim esse Lorem sint quis officia ut. Aliqua nisi dolore in aute deserunt mollit ex ea in mollit.\r\n",
|
||||
"registered": "2017-04-22T07:58:41 -01:00",
|
||||
"latitude": -87.824919,
|
||||
"longitude": 69.538927,
|
||||
"tags": [
|
||||
"fugiat",
|
||||
"labore",
|
||||
"proident",
|
||||
"quis",
|
||||
"eiusmod",
|
||||
"qui",
|
||||
"est"
|
||||
],
|
||||
"friends": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "Massey Wagner"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Marcella Ferrell"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Evans Mckee"
|
||||
}
|
||||
],
|
||||
"greeting": "Hello, Elinor Pearson! You have 3 unread messages.",
|
||||
"favoriteFruit": "strawberry"
|
||||
},
|
||||
{
|
||||
"_id": "59ef4a839ec8a4be4430b36b",
|
||||
"index": 2,
|
||||
"guid": "ddd6e8c0-95bd-416d-8b46-a768d6363809",
|
||||
"isActive": false,
|
||||
"balance": "$2,046.95",
|
||||
"picture": "http://placehold.it/32x32",
|
||||
"age": 40,
|
||||
"eyeColor": "green",
|
||||
"name": "Irwin Davidson",
|
||||
"gender": "male",
|
||||
"company": "DANJA",
|
||||
"email": "irwindavidson@danja.com",
|
||||
"phone": "+1 (883) 537-2041",
|
||||
"address": "439 Cook Street, Chapin, Kentucky, 7398",
|
||||
"about": "Irure velit non commodo aliqua exercitation ut nostrud minim magna. Dolor ad ad ut irure eu. Non pariatur dolor eiusmod ipsum do et exercitation cillum. Et amet laboris minim eiusmod ullamco magna ea reprehenderit proident sunt.\r\n",
|
||||
"registered": "2016-09-01T07:49:08 -01:00",
|
||||
"latitude": -49.803812,
|
||||
"longitude": 104.93279,
|
||||
"tags": [
|
||||
"consequat",
|
||||
"enim",
|
||||
"quis",
|
||||
"magna",
|
||||
"est",
|
||||
"culpa",
|
||||
"tempor"
|
||||
],
|
||||
"friends": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "Ruth Hansen"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Kathrine Austin"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Rivera Munoz"
|
||||
}
|
||||
],
|
||||
"greeting": "Hello, Irwin Davidson! You have 2 unread messages.",
|
||||
"favoriteFruit": "banana"
|
||||
}
|
||||
]
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
var stringify = require('../');
|
||||
|
||||
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
|
||||
var s = stringify(obj, function (a, b) {
|
||||
return a.key < b.key ? 1 : -1;
|
||||
});
|
||||
console.log(s);
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
var stringify = require('../');
|
||||
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
|
||||
console.log(stringify(obj));
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
var stringify = require('../');
|
||||
var obj = { c: 6, b: [4,5], a: 3 };
|
||||
console.log(stringify(obj));
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
var stringify = require('../');
|
||||
|
||||
var obj = { d: 6, c: 5, b: [{z:3,y:2,x:1},9], a: 10 };
|
||||
var s = stringify(obj, function (a, b) {
|
||||
return a.value < b.value ? 1 : -1;
|
||||
});
|
||||
console.log(s);
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
declare module 'fast-json-stable-stringify' {
|
||||
function stringify(obj: any): string;
|
||||
export = stringify;
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (data, opts) {
|
||||
if (!opts) opts = {};
|
||||
if (typeof opts === 'function') opts = { cmp: opts };
|
||||
var cycles = (typeof opts.cycles === 'boolean') ? opts.cycles : false;
|
||||
|
||||
var cmp = opts.cmp && (function (f) {
|
||||
return function (node) {
|
||||
return function (a, b) {
|
||||
var aobj = { key: a, value: node[a] };
|
||||
var bobj = { key: b, value: node[b] };
|
||||
return f(aobj, bobj);
|
||||
};
|
||||
};
|
||||
})(opts.cmp);
|
||||
|
||||
var seen = [];
|
||||
return (function stringify (node) {
|
||||
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
||||
node = node.toJSON();
|
||||
}
|
||||
|
||||
if (node === undefined) return;
|
||||
if (typeof node == 'number') return isFinite(node) ? '' + node : 'null';
|
||||
if (typeof node !== 'object') return JSON.stringify(node);
|
||||
|
||||
var i, out;
|
||||
if (Array.isArray(node)) {
|
||||
out = '[';
|
||||
for (i = 0; i < node.length; i++) {
|
||||
if (i) out += ',';
|
||||
out += stringify(node[i]) || 'null';
|
||||
}
|
||||
return out + ']';
|
||||
}
|
||||
|
||||
if (node === null) return 'null';
|
||||
|
||||
if (seen.indexOf(node) !== -1) {
|
||||
if (cycles) return JSON.stringify('__cycle__');
|
||||
throw new TypeError('Converting circular structure to JSON');
|
||||
}
|
||||
|
||||
var seenIndex = seen.push(node) - 1;
|
||||
var keys = Object.keys(node).sort(cmp && cmp(node));
|
||||
out = '';
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var value = stringify(node[key]);
|
||||
|
||||
if (!value) continue;
|
||||
if (out) out += ',';
|
||||
out += JSON.stringify(key) + ':' + value;
|
||||
}
|
||||
seen.splice(seenIndex, 1);
|
||||
return '{' + out + '}';
|
||||
})(data);
|
||||
};
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "fast-json-stable-stringify",
|
||||
"version": "2.1.0",
|
||||
"description": "deterministic `JSON.stringify()` - a faster version of substack's json-stable-strigify without jsonify",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"coveralls": "^3.0.0",
|
||||
"eslint": "^6.7.0",
|
||||
"fast-stable-stringify": "latest",
|
||||
"faster-stable-stringify": "latest",
|
||||
"json-stable-stringify": "latest",
|
||||
"nyc": "^14.1.0",
|
||||
"pre-commit": "^1.2.2",
|
||||
"tape": "^4.11.0"
|
||||
},
|
||||
"scripts": {
|
||||
"eslint": "eslint index.js test",
|
||||
"test-spec": "tape test/*.js",
|
||||
"test": "npm run eslint && nyc npm run test-spec"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/epoberezkin/fast-json-stable-stringify.git"
|
||||
},
|
||||
"homepage": "https://github.com/epoberezkin/fast-json-stable-stringify",
|
||||
"keywords": [
|
||||
"json",
|
||||
"stringify",
|
||||
"deterministic",
|
||||
"hash",
|
||||
"stable"
|
||||
],
|
||||
"author": {
|
||||
"name": "James Halliday",
|
||||
"email": "mail@substack.net",
|
||||
"url": "http://substack.net"
|
||||
},
|
||||
"license": "MIT",
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"test",
|
||||
"node_modules"
|
||||
],
|
||||
"reporter": [
|
||||
"lcov",
|
||||
"text-summary"
|
||||
]
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var stringify = require('../');
|
||||
|
||||
test('custom comparison function', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
|
||||
var s = stringify(obj, function (a, b) {
|
||||
return a.key < b.key ? 1 : -1;
|
||||
});
|
||||
t.equal(s, '{"c":8,"b":[{"z":6,"y":5,"x":4},7],"a":3}');
|
||||
});
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var stringify = require('../');
|
||||
|
||||
test('nested', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
|
||||
t.equal(stringify(obj), '{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}');
|
||||
});
|
||||
|
||||
test('cyclic (default)', function (t) {
|
||||
t.plan(1);
|
||||
var one = { a: 1 };
|
||||
var two = { a: 2, one: one };
|
||||
one.two = two;
|
||||
try {
|
||||
stringify(one);
|
||||
} catch (ex) {
|
||||
t.equal(ex.toString(), 'TypeError: Converting circular structure to JSON');
|
||||
}
|
||||
});
|
||||
|
||||
test('cyclic (specifically allowed)', function (t) {
|
||||
t.plan(1);
|
||||
var one = { a: 1 };
|
||||
var two = { a: 2, one: one };
|
||||
one.two = two;
|
||||
t.equal(stringify(one, {cycles:true}), '{"a":1,"two":{"a":2,"one":"__cycle__"}}');
|
||||
});
|
||||
|
||||
test('repeated non-cyclic value', function(t) {
|
||||
t.plan(1);
|
||||
var one = { x: 1 };
|
||||
var two = { a: one, b: one };
|
||||
t.equal(stringify(two), '{"a":{"x":1},"b":{"x":1}}');
|
||||
});
|
||||
|
||||
test('acyclic but with reused obj-property pointers', function (t) {
|
||||
t.plan(1);
|
||||
var x = { a: 1 };
|
||||
var y = { b: x, c: x };
|
||||
t.equal(stringify(y), '{"b":{"a":1},"c":{"a":1}}');
|
||||
});
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var stringify = require('../');
|
||||
|
||||
test('simple object', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { c: 6, b: [4,5], a: 3, z: null };
|
||||
t.equal(stringify(obj), '{"a":3,"b":[4,5],"c":6,"z":null}');
|
||||
});
|
||||
|
||||
test('object with undefined', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { a: 3, z: undefined };
|
||||
t.equal(stringify(obj), '{"a":3}');
|
||||
});
|
||||
|
||||
test('object with null', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { a: 3, z: null };
|
||||
t.equal(stringify(obj), '{"a":3,"z":null}');
|
||||
});
|
||||
|
||||
test('object with NaN and Infinity', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { a: 3, b: NaN, c: Infinity };
|
||||
t.equal(stringify(obj), '{"a":3,"b":null,"c":null}');
|
||||
});
|
||||
|
||||
test('array with undefined', function (t) {
|
||||
t.plan(1);
|
||||
var obj = [4, undefined, 6];
|
||||
t.equal(stringify(obj), '[4,null,6]');
|
||||
});
|
||||
|
||||
test('object with empty string', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { a: 3, z: '' };
|
||||
t.equal(stringify(obj), '{"a":3,"z":""}');
|
||||
});
|
||||
|
||||
test('array with empty string', function (t) {
|
||||
t.plan(1);
|
||||
var obj = [4, '', 6];
|
||||
t.equal(stringify(obj), '[4,"",6]');
|
||||
});
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var stringify = require('../');
|
||||
|
||||
test('toJSON function', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { one: 1, two: 2, toJSON: function() { return { one: 1 }; } };
|
||||
t.equal(stringify(obj), '{"one":1}' );
|
||||
});
|
||||
|
||||
test('toJSON returns string', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { one: 1, two: 2, toJSON: function() { return 'one'; } };
|
||||
t.equal(stringify(obj), '"one"');
|
||||
});
|
||||
|
||||
test('toJSON returns array', function (t) {
|
||||
t.plan(1);
|
||||
var obj = { one: 1, two: 2, toJSON: function() { return ['one']; } };
|
||||
t.equal(stringify(obj), '["one"]');
|
||||
});
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
(MIT License)
|
||||
|
||||
Copyright (c) 2013 [Ramesh Nair](http://www.hiddentao.com/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# fast-levenshtein - Levenshtein algorithm in Javascript
|
||||
|
||||
[](http://travis-ci.org/hiddentao/fast-levenshtein)
|
||||
[](https://badge.fury.io/js/fast-levenshtein)
|
||||
[](https://www.npmjs.com/package/fast-levenshtein)
|
||||
[](https://twitter.com/hiddentao)
|
||||
|
||||
An efficient Javascript implementation of the [Levenshtein algorithm](http://en.wikipedia.org/wiki/Levenshtein_distance) with locale-specific collator support.
|
||||
|
||||
## Features
|
||||
|
||||
* Works in node.js and in the browser.
|
||||
* Better performance than other implementations by not needing to store the whole matrix ([more info](http://www.codeproject.com/Articles/13525/Fast-memory-efficient-Levenshtein-algorithm)).
|
||||
* Locale-sensitive string comparisions if needed.
|
||||
* Comprehensive test suite and performance benchmark.
|
||||
* Small: <1 KB minified and gzipped
|
||||
|
||||
## Installation
|
||||
|
||||
### node.js
|
||||
|
||||
Install using [npm](http://npmjs.org/):
|
||||
|
||||
```bash
|
||||
$ npm install fast-levenshtein
|
||||
```
|
||||
|
||||
### Browser
|
||||
|
||||
Using bower:
|
||||
|
||||
```bash
|
||||
$ bower install fast-levenshtein
|
||||
```
|
||||
|
||||
If you are not using any module loader system then the API will then be accessible via the `window.Levenshtein` object.
|
||||
|
||||
## Examples
|
||||
|
||||
**Default usage**
|
||||
|
||||
```javascript
|
||||
var levenshtein = require('fast-levenshtein');
|
||||
|
||||
var distance = levenshtein.get('back', 'book'); // 2
|
||||
var distance = levenshtein.get('我愛你', '我叫你'); // 1
|
||||
```
|
||||
|
||||
**Locale-sensitive string comparisons**
|
||||
|
||||
It supports using [Intl.Collator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator) for locale-sensitive string comparisons:
|
||||
|
||||
```javascript
|
||||
var levenshtein = require('fast-levenshtein');
|
||||
|
||||
levenshtein.get('mikailovitch', 'Mikhaïlovitch', { useCollator: true});
|
||||
// 1
|
||||
```
|
||||
|
||||
## Building and Testing
|
||||
|
||||
To build the code and run the tests:
|
||||
|
||||
```bash
|
||||
$ npm install -g grunt-cli
|
||||
$ npm install
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
_Thanks to [Titus Wormer](https://github.com/wooorm) for [encouraging me](https://github.com/hiddentao/fast-levenshtein/issues/1) to do this._
|
||||
|
||||
Benchmarked against other node.js levenshtein distance modules (on Macbook Air 2012, Core i7, 8GB RAM):
|
||||
|
||||
```bash
|
||||
Running suite Implementation comparison [benchmark/speed.js]...
|
||||
>> levenshtein-edit-distance x 234 ops/sec ±3.02% (73 runs sampled)
|
||||
>> levenshtein-component x 422 ops/sec ±4.38% (83 runs sampled)
|
||||
>> levenshtein-deltas x 283 ops/sec ±3.83% (78 runs sampled)
|
||||
>> natural x 255 ops/sec ±0.76% (88 runs sampled)
|
||||
>> levenshtein x 180 ops/sec ±3.55% (86 runs sampled)
|
||||
>> fast-levenshtein x 1,792 ops/sec ±2.72% (95 runs sampled)
|
||||
Benchmark done.
|
||||
Fastest test is fast-levenshtein at 4.2x faster than levenshtein-component
|
||||
```
|
||||
|
||||
You can run this benchmark yourself by doing:
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
$ npm run build
|
||||
$ npm run benchmark
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
If you wish to submit a pull request please update and/or create new tests for any changes you make and ensure the grunt build passes.
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/hiddentao/fast-levenshtein/blob/master/CONTRIBUTING.md) for details.
|
||||
|
||||
## License
|
||||
|
||||
MIT - see [LICENSE.md](https://github.com/hiddentao/fast-levenshtein/blob/master/LICENSE.md)
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var collator;
|
||||
try {
|
||||
collator = (typeof Intl !== "undefined" && typeof Intl.Collator !== "undefined") ? Intl.Collator("generic", { sensitivity: "base" }) : null;
|
||||
} catch (err){
|
||||
console.log("Collator could not be initialized and wouldn't be used");
|
||||
}
|
||||
// arrays to re-use
|
||||
var prevRow = [],
|
||||
str2Char = [];
|
||||
|
||||
/**
|
||||
* Based on the algorithm at http://en.wikipedia.org/wiki/Levenshtein_distance.
|
||||
*/
|
||||
var Levenshtein = {
|
||||
/**
|
||||
* Calculate levenshtein distance of the two strings.
|
||||
*
|
||||
* @param str1 String the first string.
|
||||
* @param str2 String the second string.
|
||||
* @param [options] Additional options.
|
||||
* @param [options.useCollator] Use `Intl.Collator` for locale-sensitive string comparison.
|
||||
* @return Integer the levenshtein distance (0 and above).
|
||||
*/
|
||||
get: function(str1, str2, options) {
|
||||
var useCollator = (options && collator && options.useCollator);
|
||||
|
||||
var str1Len = str1.length,
|
||||
str2Len = str2.length;
|
||||
|
||||
// base cases
|
||||
if (str1Len === 0) return str2Len;
|
||||
if (str2Len === 0) return str1Len;
|
||||
|
||||
// two rows
|
||||
var curCol, nextCol, i, j, tmp;
|
||||
|
||||
// initialise previous row
|
||||
for (i=0; i<str2Len; ++i) {
|
||||
prevRow[i] = i;
|
||||
str2Char[i] = str2.charCodeAt(i);
|
||||
}
|
||||
prevRow[str2Len] = str2Len;
|
||||
|
||||
var strCmp;
|
||||
if (useCollator) {
|
||||
// calculate current row distance from previous row using collator
|
||||
for (i = 0; i < str1Len; ++i) {
|
||||
nextCol = i + 1;
|
||||
|
||||
for (j = 0; j < str2Len; ++j) {
|
||||
curCol = nextCol;
|
||||
|
||||
// substution
|
||||
strCmp = 0 === collator.compare(str1.charAt(i), String.fromCharCode(str2Char[j]));
|
||||
|
||||
nextCol = prevRow[j] + (strCmp ? 0 : 1);
|
||||
|
||||
// insertion
|
||||
tmp = curCol + 1;
|
||||
if (nextCol > tmp) {
|
||||
nextCol = tmp;
|
||||
}
|
||||
// deletion
|
||||
tmp = prevRow[j + 1] + 1;
|
||||
if (nextCol > tmp) {
|
||||
nextCol = tmp;
|
||||
}
|
||||
|
||||
// copy current col value into previous (in preparation for next iteration)
|
||||
prevRow[j] = curCol;
|
||||
}
|
||||
|
||||
// copy last col value into previous (in preparation for next iteration)
|
||||
prevRow[j] = nextCol;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// calculate current row distance from previous row without collator
|
||||
for (i = 0; i < str1Len; ++i) {
|
||||
nextCol = i + 1;
|
||||
|
||||
for (j = 0; j < str2Len; ++j) {
|
||||
curCol = nextCol;
|
||||
|
||||
// substution
|
||||
strCmp = str1.charCodeAt(i) === str2Char[j];
|
||||
|
||||
nextCol = prevRow[j] + (strCmp ? 0 : 1);
|
||||
|
||||
// insertion
|
||||
tmp = curCol + 1;
|
||||
if (nextCol > tmp) {
|
||||
nextCol = tmp;
|
||||
}
|
||||
// deletion
|
||||
tmp = prevRow[j + 1] + 1;
|
||||
if (nextCol > tmp) {
|
||||
nextCol = tmp;
|
||||
}
|
||||
|
||||
// copy current col value into previous (in preparation for next iteration)
|
||||
prevRow[j] = curCol;
|
||||
}
|
||||
|
||||
// copy last col value into previous (in preparation for next iteration)
|
||||
prevRow[j] = nextCol;
|
||||
}
|
||||
}
|
||||
return nextCol;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// amd
|
||||
if (typeof define !== "undefined" && define !== null && define.amd) {
|
||||
define(function() {
|
||||
return Levenshtein;
|
||||
});
|
||||
}
|
||||
// commonjs
|
||||
else if (typeof module !== "undefined" && module !== null && typeof exports !== "undefined" && module.exports === exports) {
|
||||
module.exports = Levenshtein;
|
||||
}
|
||||
// web worker
|
||||
else if (typeof self !== "undefined" && typeof self.postMessage === 'function' && typeof self.importScripts === 'function') {
|
||||
self.Levenshtein = Levenshtein;
|
||||
}
|
||||
// browser main thread
|
||||
else if (typeof window !== "undefined" && window !== null) {
|
||||
window.Levenshtein = Levenshtein;
|
||||
}
|
||||
}());
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "fast-levenshtein",
|
||||
"version": "2.0.6",
|
||||
"description": "Efficient implementation of Levenshtein algorithm with locale-specific collator support.",
|
||||
"main": "levenshtein.js",
|
||||
"files": [
|
||||
"levenshtein.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "grunt build",
|
||||
"prepublish": "npm run build",
|
||||
"benchmark": "grunt benchmark",
|
||||
"test": "mocha"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "~1.5.0",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-benchmark": "~0.2.0",
|
||||
"grunt-cli": "^1.2.0",
|
||||
"grunt-contrib-jshint": "~0.4.3",
|
||||
"grunt-contrib-uglify": "~0.2.0",
|
||||
"grunt-mocha-test": "~0.2.2",
|
||||
"grunt-npm-install": "~0.1.0",
|
||||
"load-grunt-tasks": "~0.6.0",
|
||||
"lodash": "^4.0.1",
|
||||
"mocha": "~1.9.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hiddentao/fast-levenshtein.git"
|
||||
},
|
||||
"keywords": [
|
||||
"levenshtein",
|
||||
"distance",
|
||||
"string"
|
||||
],
|
||||
"author": "Ramesh Nair <ram@hiddentao.com> (http://www.hiddentao.com/)",
|
||||
"license": "MIT"
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: standard
|
||||
versions:
|
||||
- 16.0.3
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user