Comparing version
195
index.js
@@ -1,119 +0,122 @@ | ||
/** | ||
* matched <https://github.com/jonschlinkert/matched> | ||
* | ||
* Copyright (c) 2014 Jon Schlinkert, contributors. | ||
* Licensed under the MIT license. | ||
*/ | ||
'use strict'; | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var root = require('find-root'); | ||
var glob = require('globby'); | ||
var flatten = require('array-flatten'); | ||
var differ = require('array-differ'); | ||
var union = require('array-union'); | ||
var unique = require('array-uniq'); | ||
var normalize = require('normalize-path'); | ||
var segments = require('path-segments'); | ||
var parsePath = require('parse-filepath'); | ||
var extend = require('xtend'); | ||
var utils = require('./utils'); | ||
module.exports = function (patterns, config, cb) { | ||
if (typeof config === 'function') { | ||
cb = config; | ||
config = {}; | ||
} | ||
function resolve(cwd) { | ||
return function(filepath) { | ||
return normalize(path.resolve(cwd, filepath)); | ||
}; | ||
} | ||
if (typeof cb !== 'function') { | ||
throw new Error('expected a callback function.'); | ||
} | ||
function listDirs(cwd) { | ||
return fs.readdirSync(cwd).filter(function(filepath) { | ||
filepath = path.join(cwd, filepath); | ||
return fs.statSync(filepath).isDirectory(); | ||
}).map(resolve(cwd)); | ||
} | ||
if (!utils.isValidGlob(patterns)) { | ||
cb(new Error('invalid glob pattern: ' + patterns)); | ||
return; | ||
} | ||
var base = function(options) { | ||
options = options || {}; | ||
var filepath = path.resolve(options.cwd || root()); | ||
return normalize(filepath); | ||
}; | ||
// shallow clone options | ||
var options = utils.extend({cwd: ''}, config); | ||
options.cwd = cwd(options); | ||
var sifted = siftPatterns(patterns, options); | ||
var arrayify = function(arr) { | ||
return !Array.isArray(arr) ? [arr] : arr; | ||
function updateOptions(inclusive) { | ||
return setIgnores(options, sifted.excludes, inclusive.index); | ||
} | ||
utils.reduce(sifted.includes, [], function (acc, include, next) { | ||
var opts = updateOptions(include); | ||
utils.glob(include.pattern, opts, function (err, files) { | ||
if (err) return next(err); | ||
next(null, acc.concat(files)); | ||
}) | ||
}, cb); | ||
}; | ||
module.exports.sync = function (patterns, config) { | ||
if (!utils.isValidGlob(patterns)) { | ||
throw new Error('invalid glob pattern: ' + patterns); | ||
} | ||
/** | ||
* ## filterDirs | ||
* | ||
* Return an array of directories except for exclusions. | ||
* | ||
* @param {String} `base` The base directory to start from. | ||
* @param {Object} `options` | ||
* @return {Array} | ||
* @api private | ||
*/ | ||
// shallow clone options | ||
var options = utils.extend({cwd: ''}, config); | ||
options.cwd = cwd(options); | ||
var sifted = siftPatterns(patterns, options); | ||
var filterDirs = function(options) { | ||
options = options || {}; | ||
var cwd = base(options); | ||
var len = sifted.includes.length, i = -1; | ||
var res = []; | ||
// list directories starting with the cwd | ||
var dirs = listDirs(cwd).map(resolve(cwd)); | ||
// Omit folders from root directory | ||
var rootOmit = ['.git', 'node_modules', 'temp', 'tmp']; | ||
var rootDirs = union(rootOmit, arrayify(options.omit || [])); | ||
return differ(dirs, rootDirs.map(resolve(cwd))); | ||
while (++i < len) { | ||
var include = sifted.includes[i]; | ||
var opts = setIgnores(options, sifted.excludes, include.index); | ||
res.push.apply(res, utils.glob.sync(include.pattern, opts)); | ||
} | ||
return res; | ||
}; | ||
var splitPatterns = function(patterns) { | ||
var arr = []; | ||
patterns.forEach(function(pattern) { | ||
var re = /^\*\*\//; | ||
arr.push(pattern); | ||
if (re.test(pattern)) { | ||
arr.push(pattern.replace(re, '')); | ||
function siftPatterns(patterns, opts) { | ||
patterns = utils.arrayify(patterns); | ||
var res = { includes: [], excludes: [] }; | ||
var len = patterns.length, i = -1; | ||
while (++i < len) { | ||
var stats = new Stats(patterns[i], i); | ||
if (opts.relative) { | ||
stats.pattern = toRelative(stats.pattern, opts); | ||
delete opts.cwd; | ||
} | ||
}); | ||
return unique(arr); | ||
}; | ||
if (stats.isNegated) { | ||
res.excludes.push(stats); | ||
} else { | ||
res.includes.push(stats); | ||
} | ||
} | ||
return res; | ||
} | ||
module.exports = function matched(patterns, options) { | ||
options = options || {}; | ||
var cwd = base(options); | ||
var opts; | ||
function setIgnores(options, excludes, inclusiveIndex) { | ||
var opts = utils.extend({}, options); | ||
var negations = []; | ||
var p = splitPatterns(arrayify(patterns)); | ||
var len = excludes.length, i = -1; | ||
while (++i < len) { | ||
var exclusion = excludes[i]; | ||
if (exclusion.index > inclusiveIndex) { | ||
negations.push(exclusion.pattern); | ||
} | ||
} | ||
opts.ignore = utils.arrayify(opts.ignore || []); | ||
opts.ignore.push.apply(opts.ignore, negations); | ||
return opts; | ||
} | ||
return unique(flatten(filterDirs(options).reduce(function (acc, start) { | ||
var normalized = p.map(function (pattern) { | ||
var dir = start; | ||
if (!/\*\*\//.test(pattern)) { | ||
start = cwd; | ||
} | ||
if (/\{,/.test(pattern)) { | ||
start = normalize(path.resolve(options.cwd || start)); | ||
} | ||
function Stats(pattern, i) { | ||
this.index = i | ||
this.isNegated = false; | ||
this.pattern = pattern; | ||
opts = extend({}, options, { | ||
pattern: pattern, | ||
cwd: start, | ||
root: start | ||
}); | ||
if (pattern.charAt(0) === '!') { | ||
this.isNegated = true; | ||
this.pattern = pattern.slice(1); | ||
} | ||
} | ||
// reset cwd; | ||
start = dir; | ||
return opts; | ||
}); | ||
function cwd(opts) { | ||
if (/^\W/.test(opts.cwd)) { | ||
return utils.resolve(opts.cwd); | ||
} | ||
return opts.cwd; | ||
} | ||
return normalized.map(function(obj) { | ||
opts = extend({}, {cwd: obj.cwd, srcBase: obj.srcBase, root: obj.root}); | ||
var result = glob.sync(obj.pattern, opts); | ||
return result.map(resolve(opts.cwd)); | ||
}); | ||
}, []))); | ||
}; | ||
function toRelative(pattern, opts) { | ||
var fp = path.resolve(opts.cwd, pattern); | ||
return path.relative(process.cwd(), fp); | ||
} | ||
{ | ||
"name": "matched", | ||
"description": "Super fast globbing library.", | ||
"version": "0.2.2", | ||
"description": "Adds array support to node-glob, sync and async. Also supports tilde expansion (user home) and resolving to global npm modules.", | ||
"version": "0.3.0", | ||
"homepage": "https://github.com/jonschlinkert/matched", | ||
"author": { | ||
"name": "Jon Schlinkert", | ||
"url": "https://github.com/jonschlinkert" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/jonschlinkert/matched.git" | ||
}, | ||
"author": "Jon Schlinkert (https://github.com/jonschlinkert)", | ||
"repository": "jonschlinkert/matched", | ||
"bugs": { | ||
"url": "https://github.com/jonschlinkert/matched/issues" | ||
}, | ||
"licenses": [ | ||
{ | ||
"type": "MIT", | ||
"url": "https://github.com/jonschlinkert/matched/blob/master/LICENSE-MIT" | ||
} | ||
], | ||
"license": "MIT", | ||
"main": "index.js", | ||
@@ -28,23 +17,20 @@ "engines": { | ||
"dependencies": { | ||
"array-differ": "^0.1.0", | ||
"array-flatten": "0.0.2", | ||
"array-union": "^0.1.0", | ||
"array-uniq": "^0.1.0", | ||
"find-root": "^0.1.1", | ||
"globby": "^0.1.1", | ||
"normalize-path": "^0.1.0", | ||
"parse-filepath": "^0.3.0", | ||
"path-segments": "^0.1.1", | ||
"xtend": "^3.0.0" | ||
"async-array-reduce": "^0.1.0", | ||
"extend-shallow": "^2.0.1", | ||
"glob": "^5.0.15", | ||
"is-valid-glob": "^0.3.0", | ||
"lazy-cache": "^0.2.3", | ||
"resolve-dir": "^0.1.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^1.9.1", | ||
"mocha": "^1.20.1", | ||
"verb": "^0.2.13" | ||
"gulp": "^3.9.0", | ||
"gulp-istanbul": "^0.10.1", | ||
"gulp-jshint": "^1.11.2", | ||
"gulp-mocha": "^2.1.3", | ||
"jshint-stylish": "^2.0.1", | ||
"mocha": "^2.3.3" | ||
}, | ||
"keywords": [ | ||
"array", | ||
"card", | ||
"directories", | ||
"dirs", | ||
"exclude", | ||
@@ -55,3 +41,2 @@ "exclusions", | ||
"filesystem", | ||
"filter", | ||
"find", | ||
@@ -63,2 +48,3 @@ "fnmatch", | ||
"globbing", | ||
"globby", | ||
"globs", | ||
@@ -69,9 +55,10 @@ "globstar", | ||
"matcher", | ||
"matching", | ||
"minimatch", | ||
"multi", | ||
"multimatch", | ||
"multiple", | ||
"negate", | ||
"node_modules", | ||
"omissions", | ||
"omit", | ||
"node", | ||
"node-glob", | ||
"paths", | ||
@@ -81,9 +68,14 @@ "pattern", | ||
"star", | ||
"traverse", | ||
"util", | ||
"utility", | ||
"wild", | ||
"wildcard", | ||
"wildcards" | ||
] | ||
], | ||
"verb": { | ||
"related": { | ||
"list": [ | ||
"is-glob", | ||
"micromatch", | ||
"look-up" | ||
] | ||
} | ||
} | ||
} |
128
README.md
@@ -1,12 +0,17 @@ | ||
# matched [](http://badge.fury.io/js/matched) | ||
# matched [](http://badge.fury.io/js/matched) | ||
> Super fast globbing library. | ||
> Adds array support to node-glob, sync and async. Also supports tilde expansion (user home) and resolving to global npm modules. | ||
#### [Why?](#why) | ||
**TODO** | ||
* [x] async | ||
* [x] sync | ||
* [ ] promise | ||
## Install | ||
Install with [npm](npmjs.org): | ||
```bash | ||
npm i matched --save-dev | ||
Install with [npm](https://www.npmjs.com/) | ||
```sh | ||
$ npm i matched --save | ||
``` | ||
@@ -16,112 +21,63 @@ | ||
```js | ||
var glob = require('matched'); | ||
``` | ||
├── node_modules/... | ||
├── index.js | ||
├── README.md | ||
└── test/test.js | ||
``` | ||
## API | ||
**async** | ||
```js | ||
matched(patterns, options) | ||
glob(['*.js'], function(err, files) { | ||
//=> ['utils.js', 'index.js'] | ||
}); | ||
``` | ||
See supported [globby](https://github.com/sindresorhus/globby) options. | ||
**sync** | ||
### patterns | ||
*Required* | ||
Type: `string|array` | ||
See [minimatch](https://github.com/isaacs/minimatch#usage) for options and supported patterns. | ||
### options | ||
Type: `object` | ||
#### exclusions | ||
Matched excludes certain directories to speed up search. | ||
Type: `array` | ||
Default: `['.git', 'node_modules', 'temp', 'tmp']` | ||
Pass an array of additional directories to exclude: | ||
```js | ||
matched(['**/*.js'], {omit: ['vendor']}); | ||
var files = glob.sync(['*.js']); | ||
//=> ['utils.js', 'index.js'] | ||
``` | ||
See [node-glob](https://github.com/isaacs/node-glob#properties) for all supported options. | ||
**options** | ||
Both sync and async take options as the second argument: | ||
## Tests | ||
In the command line, run: | ||
```bash | ||
mocha | ||
```js | ||
glob(['*.js'], {cwd: 'test'}, function(err, files) { | ||
//=> ['test.js'] | ||
}); | ||
``` | ||
## Globbing patterns | ||
## Related projects | ||
Just a quick overview. | ||
* [is-glob](https://www.npmjs.com/package/is-glob): Returns `true` if the given string looks like a glob pattern or an extglob pattern.… [more](https://www.npmjs.com/package/is-glob) | [homepage](https://github.com/jonschlinkert/is-glob) | ||
* [look-up](https://www.npmjs.com/package/look-up): Faster drop-in replacement for find-up and findup-sync. | [homepage](https://github.com/jonschlinkert/look-up) | ||
* [micromatch](https://www.npmjs.com/package/micromatch): Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch. Just… [more](https://www.npmjs.com/package/micromatch) | [homepage](https://github.com/jonschlinkert/micromatch) | ||
- `*` matches any number of characters, but not `/` | ||
- `?` matches a single character, but not `/` | ||
- `**` matches any number of characters, including `/`, as long as it's the only thing in a path part | ||
- `{}` allows for a comma-separated list of "or" expressions | ||
- `!` at the beginning of a pattern will negate the match | ||
## Running tests | ||
[Various patterns and expected matches](https://github.com/sindresorhus/multimatch/blob/master/test.js). | ||
Install dev dependencies: | ||
## Related | ||
See [multimatch](https://github.com/sindresorhus/multimatch) if you need to match against a list instead of the filesystem. | ||
## Why? | ||
**Problem** | ||
Let's say you want to return all of the `.js` files in a project, excluding `node_modules` and a few other similar directories. Normally you would need to do something like this: | ||
```js | ||
glob(['**/*.js', '!**/node_modules/**']); | ||
```sh | ||
$ npm i -d && npm test | ||
``` | ||
The problem with this approach is that typically _even the excluded file paths are fully expanded before the result set is returned_. e.g. all of the files in `node_modules` would be scanned first. | ||
## Contributing | ||
**Solution** | ||
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/matched/issues/new). | ||
Matched allows you to exlude directories before they reach [globby](https://github.com/sindresorhus/globby). In a nutshell, matched uses `fs.readdirSync` to build up a list of directories relative to the `cwd`, then for each (non-excluded) directory matched passes the glob pattern to globby. The following is all that's needed to return all of the `.js` files in a project. | ||
```js | ||
var matched = require('matched'); | ||
matched('**/*.js'); | ||
//=> ['index.js', 'test/test.js'] | ||
``` | ||
Try it, you'll be surprised how fast it is comparatively. However, I'm not sure how well this approach will scale with complicated patterns. See [the tests](./test/test.js) for the use cases that are covered so far. Feel free to create pull requests or issues if you have suggestions for improvement. | ||
## Author | ||
**Jon Schlinkert** | ||
+ [github/jonschlinkert](https://github.com/jonschlinkert) | ||
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) | ||
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) | ||
## License | ||
Copyright (c) 2014 Jon Schlinkert, contributors. | ||
Released under the MIT license | ||
Copyright © 2015 Jon Schlinkert | ||
Released under the MIT license. | ||
*** | ||
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on June 26, 2014._ | ||
[globule]: https://github.com/cowboy/node-globule | ||
[multimatch]: https://github.com/sindresorhus/multimatch | ||
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on October 08, 2015._ |
220
test/test.js
@@ -1,108 +0,146 @@ | ||
/*! | ||
* matched <https://github.com/jonschlinkert/matched> | ||
* | ||
* Copyright (c) 2014 Jon Schlinkert, contributors. | ||
* Licensed under the MIT License | ||
*/ | ||
'use strict'; | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var expect = require('chai').expect; | ||
var matched = require('../'); | ||
require('mocha'); | ||
var assert = require('assert'); | ||
var glob = require('..'); | ||
// True if the filepath actually exist. | ||
var exists = function() { | ||
var filepath = path.join.apply(path, arguments); | ||
return fs.existsSync(filepath); | ||
}; | ||
describe('glob', function () { | ||
describe('async', function () { | ||
it('should be a function', function() { | ||
assert(glob); | ||
assert(typeof glob === 'function'); | ||
}); | ||
// log the files found | ||
var log = function(arr) { | ||
console.log(' Found', arr.length, 'files.'); | ||
}; | ||
it('should support globs as a string', function(done) { | ||
glob('*.js', function(err, files) { | ||
assert(!err); | ||
assert(files); | ||
done(); | ||
}); | ||
}); | ||
describe('when a cwd is used:', function () { | ||
it('should only return file paths from the cwd.', function () { | ||
var actual = matched(['{,*/}/*.*'], {cwd: 'test'}); | ||
log(actual); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
}); | ||
it('should take options', function(done) { | ||
glob('*.txt', {cwd: 'test/fixtures'}, function(err, files) { | ||
assert(!err); | ||
assert(files); | ||
assert(files.length); | ||
done(); | ||
}); | ||
}); | ||
it('should only return file paths from the cwd.', function () { | ||
var actual = matched('*.js', {cwd: 'test'}); | ||
log(actual); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
}); | ||
it('should return filepaths relative to process.cwd', function(done) { | ||
var opts = {cwd: 'test/fixtures', relative: true}; | ||
glob('*.txt', opts, function(err, files) { | ||
assert(!err); | ||
assert(files); | ||
assert(files.length); | ||
assert(files[0] === 'test/fixtures/a.txt'); | ||
assert(files[1] === 'test/fixtures/b.txt'); | ||
assert(files[2] === 'test/fixtures/c.txt'); | ||
done(); | ||
}); | ||
}); | ||
it('should only return file paths from the cwd.', function () { | ||
var actual = matched('**/*.js', {cwd: 'test/a'}); | ||
log(actual); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
}); | ||
it('should take ignore patterns', function(done) { | ||
var opts = {cwd: 'test/fixtures', ignore: ['*.js']}; | ||
glob(['*.*'], opts, function(err, files) { | ||
assert(!err); | ||
assert(files); | ||
assert(files.length); | ||
assert(~files.indexOf('a.md')); | ||
assert(!~files.indexOf('a.js')); | ||
done(); | ||
}); | ||
}); | ||
it('should return correct file paths.', function () { | ||
var actual = matched(['{,*/}/*.md', '**/*.md'], {cwd: 'test'}); | ||
log(actual); | ||
it('should take negation patterns', function(done) { | ||
var opts = {cwd: 'test/fixtures'}; | ||
glob(['*.*', '!*.js'], opts, function(err, files) { | ||
assert(!err); | ||
assert(files); | ||
assert(files.length); | ||
assert(~files.indexOf('a.md')); | ||
assert(!~files.indexOf('a.js')); | ||
done(); | ||
}); | ||
}); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
}); | ||
}); | ||
it('should use ignore and negation patterns', function(done) { | ||
glob(['*.js', '!gulpfile.js'], {ignore: ['utils.js']}, function(err, files) { | ||
assert(!err); | ||
assert(files); | ||
assert(files.length === 1); | ||
assert(files.indexOf('gulpfile.js') === -1); | ||
done(); | ||
}); | ||
}); | ||
describe('when glob patterns are passed:', function () { | ||
it('should return correct file paths.', function () { | ||
var actual = matched(['{,*/}/*.md', '**/*.md']); | ||
log(actual); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
}); | ||
it('should expand tildes in cwd', function(done) { | ||
glob(['*'], {cwd: '~'}, function(err, files) { | ||
assert(!err); | ||
assert(files); | ||
assert(files.length > 0); | ||
done(); | ||
}); | ||
}); | ||
it('should return correct file paths.', function () { | ||
var actual = matched(['**/*.{js,md}', '**/*.md']); | ||
log(actual); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
}); | ||
it('should expand @ in cwd (global npm modules)', function(done) { | ||
glob(['*'], {cwd: '@'}, function(err, files) { | ||
assert(!err); | ||
assert(files); | ||
assert(files.length > 0); | ||
done(); | ||
}); | ||
}); | ||
it('should return correct file paths.', function () { | ||
var actual = matched(['**/*']); | ||
log(actual); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
}); | ||
it('should pass an error in the callback if the glob is bad', function(done) { | ||
glob({}, {cwd: 'test/fixtures'}, function(err, files) { | ||
assert(err); | ||
assert(err.message); | ||
assert(err.message === 'invalid glob pattern: [object Object]'); | ||
done(); | ||
}); | ||
}); | ||
it('should return correct file paths.', function () { | ||
var actual = matched('*.js'); | ||
log(actual); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
it('should throw an error if no callback is passed', function(done) { | ||
try { | ||
glob('abc'); | ||
done(new Error('expected an error')); | ||
} catch(err) { | ||
assert(err); | ||
assert(err.message); | ||
assert(err.message === 'expected a callback function.'); | ||
done(); | ||
} | ||
}); | ||
}); | ||
it('should return correct file paths.', function () { | ||
var actual = matched(['*.js', '**/*.js']); | ||
log(actual); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
}); | ||
describe('sync', function () { | ||
it('should expose a sync method', function() { | ||
assert(glob.sync); | ||
assert(typeof glob.sync === 'function'); | ||
}); | ||
it('should return correct file paths.', function () { | ||
var actual = matched('**/*.js'); | ||
log(actual); | ||
actual.forEach(function(filepath) { | ||
expect(exists(filepath)).to.equal(true); | ||
}) | ||
it('should support globs as a string', function() { | ||
var files = glob.sync('*.js'); | ||
assert(files); | ||
}); | ||
it('should take options', function() { | ||
var files = glob.sync('*.txt', {cwd: 'test/fixtures'}); | ||
assert(files); | ||
assert(files.length > 1); | ||
}); | ||
it('should throw an error if the glob is bad', function() { | ||
try { | ||
glob.sync({}); | ||
done(new Error('expected an error')); | ||
} catch(err) { | ||
assert(err); | ||
assert(err.message); | ||
assert(err.message === 'invalid glob pattern: [object Object]'); | ||
} | ||
}); | ||
}); | ||
}); | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
6
-40%20
42.86%276
43.75%14216
-7.68%6
100%82
-34.92%4
100%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed