diff options
author | LinuxWizard42 <computerwizard@linuxmail.org> | 2022-10-12 22:54:37 +0300 |
---|---|---|
committer | LinuxWizard42 <computerwizard@linuxmail.org> | 2022-10-12 22:54:37 +0300 |
commit | 703e03aba33f234712206769f57717ba7d92d23d (patch) | |
tree | 0041f04ccb75bd5379c764e9fe42249fffe75fc3 /node_modules/dir-compare | |
parent | ab6e257e6e9d9a483d7e86f220d8b209a2cd7753 (diff) | |
download | FlashRunner-703e03aba33f234712206769f57717ba7d92d23d.tar.gz FlashRunner-703e03aba33f234712206769f57717ba7d92d23d.tar.zst |
Added export_allowed file to make repository visible in cgit
Diffstat (limited to 'node_modules/dir-compare')
34 files changed, 5695 insertions, 0 deletions
diff --git a/node_modules/dir-compare/LICENSE b/node_modules/dir-compare/LICENSE new file mode 100644 index 0000000..50df0a3 --- /dev/null +++ b/node_modules/dir-compare/LICENSE @@ -0,0 +1,22 @@ +Copyright 2014 Liviu Grigorescu (grigoresculiviu@gmail.com) + +This project is free software released under the MIT license: +http://www.opensource.org/licenses/mit-license.php + +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.
\ No newline at end of file diff --git a/node_modules/dir-compare/README.md b/node_modules/dir-compare/README.md new file mode 100644 index 0000000..ec0b265 --- /dev/null +++ b/node_modules/dir-compare/README.md @@ -0,0 +1,374 @@ +dir-compare +========== +Node JS directory compare + +[](https://travis-ci.org/gliviu/dir-compare) +[](https://ci.appveyor.com/project/gliviu/dir-compare) +[](http://codecov.io/github/gliviu/dir-compare?branch=master) + +- [Installation](#installation) +- [Library](#library) + * [Use](#use) + * [Api](#api) + * [Glob patterns](#glob-patterns) + * [Custom file content comparators](#custom-file-content-comparators) + + [Ignore line endings and white spaces](#ignore-line-endings-and-white-spaces) + * [Custom name comparators](#custom-name-comparators) + * [Custom result builder](#custom-result-builder) + * [Symbolic links](#symbolic-links) +- [Command line](#command-line) +- [Changelog](#changelog) + +# Installation +```shell +$ npm install dir-compare +``` +or +```shell +$ npm install -g dir-compare +``` +for command line utility. + +# Library + +## Use +```javascript +const dircompare = require('dir-compare'); + +const options = { compareSize: true }; +// Multiple compare strategy can be used simultaneously - compareSize, compareContent, compareDate, compareSymlink. +// If one comparison fails for a pair of files, they are considered distinct. +const path1 = '...'; +const path2 = '...'; + +// Synchronous +const res = dircompare.compareSync(path1, path2, options) +print(res) + +// Asynchronous +dircompare.compare(path1, path2, options) + .then(res => print(res)) + .catch(error => console.error(error)); + + +function print(result) { + console.log('Directories are %s', result.same ? 'identical' : 'different') + + console.log('Statistics - equal entries: %s, distinct entries: %s, left only entries: %s, right only entries: %s, differences: %s', + result.equal, result.distinct, result.left, result.right, result.differences) + + result.diffSet.forEach(dif => console.log('Difference - name1: %s, type1: %s, name2: %s, type2: %s, state: %s', + dif.name1, dif.type1, dif.name2, dif.type2, dif.state)) +} +``` + +Typescript +```typescript +import { compare, compareSync, Options, Result } from "dir-compare"; +const path1 = '...'; +const path2 = '...'; +const options: Options = { compareSize: true }; + +const res: Result = compareSync(path1, path2, options); +console.log(res) + +compare(path1, path2, options) + .then(res => console.log(res)) + .catch(error => console.error(error)); +``` + +## Api + + +Below is a quick recap of the api. For more details check the [reference documentation](https://gliviu.github.io/dc-api/). +```typescript +compare(path1: string, path2: string, options?: Options): Promise<Result> +compareSync(path1: string, path2: string, options?: Options): Result +``` + +```Options``` +* **compareSize**: true/false - Compares files by size. Defaults to 'false'. +* **compareContent**: true/false - Compares files by content. Defaults to 'false'. +* **compareFileSync**, **compareFileAsync**: Callbacks for file comparison. See [Custom file content comparators](#custom-file-content-comparators). +* **compareDate**: true/false - Compares files by date of modification (stat.mtime). Defaults to 'false'. +* **compareNameHandler**: Callback for name comparison. See [Custom name comparators](#custom-name-comparators). +* **dateTolerance**: milliseconds - Two files are considered to have the same date if the difference between their modification dates fits within date tolerance. Defaults to 1000 ms. +* **compareSymlink**: true/false - Compares entries by symlink. Defaults to 'false'. +* **skipSymlinks**: true/false - Ignore symbolic links. Defaults to 'false'. +* **skipSubdirs**: true/false - Skips sub directories. Defaults to 'false'. +* **ignoreCase**: true/false - Ignores case when comparing names. Defaults to 'false'. +* **noDiffSet**: true/false - Toggles presence of diffSet in output. If true, only statistics are provided. Use this when comparing large number of files to avoid out of memory situations. Defaults to 'false'. +* **includeFilter**: File name filter. Comma separated [minimatch](https://www.npmjs.com/package/minimatch) patterns. See [Glob patterns](#glob-patterns) below. +* **excludeFilter**: File/directory name exclude filter. Comma separated [minimatch](https://www.npmjs.com/package/minimatch) patterns. See [Glob patterns](#glob-patterns) below. +* **resultBuilder**: Callback for constructing result. Called for each compared entry pair. Updates `statistics` and `diffSet`. More details in [Custom result builder](#custom-result-builder). + +```Result``` +* **same**: true if directories are identical +* **distinct**: number of distinct entries +* **equal**: number of equal entries +* **left**: number of entries only in path1 +* **right**: number of entries only in path2 +* **differences**: total number of differences (distinct+left+right) +* **total**: total number of entries (differences+equal) +* **distinctFiles**: number of distinct files +* **equalFiles**: number of equal files +* **leftFiles**: number of files only in path1 +* **rightFiles**: number of files only in path2 +* **differencesFiles**: total number of different files (distinctFiles+leftFiles+rightFiles) +* **totalFiles**: total number of files (differencesFiles+equalFiles) +* **distinctDirs**: number of distinct directories +* **equalDirs**: number of equal directories +* **leftDirs**: number of directories only in path1 +* **rightDirs**: number of directories only in path2 +* **differencesDirs**: total number of different directories (distinctDirs+leftDirs+rightDirs) +* **totalDirs**: total number of directories (differencesDirs+equalDirs) +* **brokenLinks**: + * **leftBrokenLinks**: number of broken links only in path1 + * **rightBrokenLinks**: number of broken links only in path2 + * **distinctBrokenLinks**: number of broken links with same name appearing in both path1 and path2 + * **totalBrokenLinks**: total number of broken links (leftBrokenLinks+rightBrokenLinks+distinctBrokenLinks) +* **symlinks**: Statistics available if `compareSymlink` options is used + * **distinctSymlinks**: number of distinct links + * **equalSymlinks**: number of equal links + * **leftSymlinks**: number of links only in path1 + * **rightSymlinks**: number of links only in path2 + * **differencesSymlinks**: total number of different links (distinctSymlinks+leftSymlinks+rightSymlinks) + * **totalSymlinks**: total number of links (differencesSymlinks+equalSymlinks) +* **diffSet** - List of changes (present if `options.noDiffSet` is false) + * **path1**: path not including file/directory name; can be relative or absolute depending on call to compare(), + * **path2**: path not including file/directory name; can be relative or absolute depending on call to compare(), + * **relativePath**: path relative to root, + * **name1**: file/directory name + * **name2**: file/directory name + * **state**: one of equal, left, right, distinct, + * **type1**: one of missing, file, directory, broken-link + * **type2**: one of missing, file, directory, broken-link + * **size1**: file size + * **size2**: file size + * **date1**: modification date (stat.mtime) + * **date2**: modification date (stat.mtime) + * **level**: depth + * **reason**: Provides reason when two identically named entries are distinct. + Not available if entries are equal. + One of "different-size", "different-date", "different-content", "broken-link", "different-symlink". + +## Glob patterns +[Minimatch](https://www.npmjs.com/package/minimatch) patterns are used to include/exclude files to be compared. + +The pattern is matched against the relative path of the entry being compared. + +Following examples assume we are comparing two [dir-compare](https://github.com/gliviu/dir-compare) code bases. + + +``` +dircompare -x ".git,node_modules" dir1 dir2') exclude git and node modules directories +dircompare -x "expected" dir1 dir2') exclude '/tests/expected' directory +dircompare -x "/tests/expected" dir1 dir2') exclude '/tests/expected' directory +dircompare -x "**/expected" dir1 dir2') exclude '/tests/expected' directory +dircompare -x "**/tests/**/*.js" dir1 dir2') exclude all js files in '/tests' directory and subdirectories +dircompare -f "*.js,*.yml" dir1 dir2') include js and yaml files +dircompare -f "/tests/**/*.js" dir1 dir2') include all js files in '/tests' directory and subdirectories +dircompare -f "**/tests/**/*.ts" dir1 dir2') include all js files in '/tests' directory and subdirectories +``` + +## Custom file content comparators +By default file content is binary compared. As of version 1.5.0 custom file comparison handlers may be specified. + +Custom handlers are specified by `compareFileSync` and `compareFileAsync` options which correspond to `dircompare.compareSync()` or `dircompare.compare()` methods. + +A couple of handlers are included in the library: +* binary sync compare - `dircompare.fileCompareHandlers.defaultFileCompare.compareSync` +* binary async compare - `dircompare.fileCompareHandlers.defaultFileCompare.compareAsync` +* text sync compare - `dircompare.fileCompareHandlers.lineBasedFileCompare.compareSync` +* text async compare - `dircompare.fileCompareHandlers.lineBasedFileCompare.compareAsync` + +Use [defaultFileCompare.js](https://github.com/gliviu/dir-compare/blob/master/src/fileCompareHandler/defaultFileCompare.js) as an example to create your own. + +### Ignore line endings and white spaces +Line based comparator can be used to ignore line ending and white space differences. This comparator is not available in [CLI](#command-line) mode. +```javascript +var dircompare = require('dir-compare'); + +var options = { + compareContent: true, + compareFileSync: dircompare.fileCompareHandlers.lineBasedFileCompare.compareSync, + compareFileAsync: dircompare.fileCompareHandlers.lineBasedFileCompare.compareAsync, + ignoreLineEnding: true, + ignoreWhiteSpaces: true +}; + +var path1 = '...'; +var path2 = '...'; +var res = dircompare.compareSync(path1, path2, options); +console.log(res) + +dircompare.compare(path1, path2, options) +.then(res => console.log(res)) +``` +## Custom name comparators +If [default](https://github.com/gliviu/dir-compare/blob/master/src/nameCompare/defaultNameCompare.js) name comparison is not enough, custom behavior can be specified with [compareNameHandler](https://gliviu.github.io/dc-api/index.html#comparenamehandler) option. +Following example adds the possibility to ignore file extensions. +```typescript +import { Options, compare } from 'dir-compare' +import path from 'path' + +var options: Options = { + compareSize: false, // compare only name by disabling size and content criteria + compareContent: false, + compareNameHandler: customNameCompare, // new name comparator used to ignore extensions + ignoreExtension: true, // supported by the custom name compare below +}; + +function customNameCompare(name1: string, name2: string, options: Options) { + if (options.ignoreCase) { + name1 = name1.toLowerCase() + name2 = name2.toLowerCase() + } + if (options.ignoreExtension) { + name1 = path.basename(name1, path.extname(name1)) + name2 = path.basename(name2, path.extname(name2)) + } + return ((name1 === name2) ? 0 : ((name1 > name2) ? 1 : -1)) +} + +var path1 = '/tmp/a'; +var path2 = '/tmp/b'; + +var res = compare(path1, path2, options).then(res => { + console.log(`Same: ${res.same}`) + if (!res.diffSet) { + return + } + res.diffSet.forEach(dif => console.log(`${dif.name1} ${dif.name2} ${dif.state}`)) +}) + +// Outputs +// icon.svg icon.png equal +// logo.svg logo.jpg equal +``` + +## Custom result builder +[Result builder](https://gliviu.github.io/dc-api/index.html#resultbuilder) is called for each pair of entries encountered during comparison. Its purpose is to append entries in `diffSet` and eventually update `statistics` object with new stats. + +If needed it can be replaced with custom implementation. + +```javascript +var dircompare = require("dircompare") + +var customResultBuilder = function (entry1, entry2, state, level, relativePath, options, statistics, diffSet, reason) { + ... +} + +var options = { + compareSize: true, + resultBuilder: customResultBuilder +} +var res = dircompare.compareSync('...', '...', options) + +``` + +The [default](https://github.com/gliviu/dir-compare/blob/master/src/resultBuilder/defaultResultBuilderCallback.js) builder can be used as an example. + +## Symbolic links +Unless `compareSymlink` option is used, symbolic links are resolved and any comparison is applied to the file/directory they point to. + +Circular loops are handled by breaking the loop as soon as it is detected. + +Version `1.x` treats broken links as `ENOENT: no such file or directory`. +Since `2.0` they are treated as a special type of entry - `broken-link` - and are available as stats (`totalBrokenLinks`, `distinctBrokenLinks`, ...). + +Using `compareSymlink` option causes `dircompare` to check symlink values for equality. +In this mode two entries with identical name are considered different if +* one is symlink, the other is not +* both are symlinks but point to different locations + +These rules are applied in addition to the other comparison modes; ie. by content, by size... + +If entries are different because of symlinks, `reason` will be `different-symlink`. Also statistics summarizes differences caused by symbolik links. + +# Command line +``` + Usage: dircompare [options] leftdir rightdir + + Options: + + -h, --help output usage information + -V, --version output the version number + -c, --compare-content compare files by content + -D, --compare-date compare files by date + --date-tolerance [type] tolerance to be used in date comparison (milliseconds) + --compare-symlink compare files and directories by symlink + -f, --filter [type] file name filter + -x, --exclude [type] file/directory name exclude filter + -S, --skip-subdirs do not recurse into subdirectories + -L, --skip-symlinks ignore symlinks + -i, --ignore-case ignores case when comparing file names + -l, --show-left report - show entries occurring in left dir + -r, --show-right report - show entries occurring in right dir + -e, --show-equal report - show identic entries occurring in both dirs + -d, --show-distinct report - show distinct entries occurring in both dirs + -a, --show-all report - show all entries + -w, --whole-report report - include directories in detailed report + --reason report - show reason when entries are distinct + --csv report - print details as csv + --nocolors don't use console colors + --async Make use of multiple cores + + By default files are compared by size. + --date-tolerance defaults to 1000 ms. Two files are considered to have + the same date if the difference between their modification dates fits + within date tolerance. + + Exit codes: + 0 - entries are identical + 1 - entries are different + 2 - error occurred + + Examples: + compare by content dircompare -c dir1 dir2 + show only different files dircompare -d dir1 dir2 + + exclude filter dircompare -x ".git,node_modules" dir1 dir2 + dircompare -x "/tests/expected" dir1 dir2 + dircompare -x "**/expected" dir1 dir2 + dircompare -x "**/tests/**/*.ts" dir1 dir2 + + include filter dircompare -f "*.js,*.yml" dir1 dir2 + dircompare -f "/tests/**/*.js" dir1 dir2 + dircompare -f "**/tests/**/*.ts" dir1 dir2 +``` + +# Changelog +* v2.4.0 New option to customize file/folder name comparison +* v2.3.0 Fixes +* v2.1.0 Removed [bluebird](https://github.com/petkaantonov/bluebird/#note) dependency +* v2.0.0 + * New option to compare symlinks. + * New field indicating reason for two entries being distinct. + * Improved command line output format. + * Tests are no longer part of published package. + * Generated [Api](#api) documentation. + + Breaking changes: + * Broken links are no longer treated as errors. As a result there are new statistics (leftBrokenLinks, rightBrokenLinks, distinctBrokenLinks, totalBrokenLinks) and new entry type - broken-link. + Details in [Symbolic links](#symbolic-links). + * Typescript correction: new interface `Result` replaced `Statistics`. +* v1.8.0 + * globstar patterns + * typescript corrections + * removed support for node 0.11, 0.12, iojs +* v1.7.0 performance improvements +* v1.6.0 typescript support +* v1.5.0 added option to ignore line endings and white space differences +* v1.3.0 added date tolerance option +* v1.2.0 added compare by date option +* v1.1.0 + * detect symlink loops + * improved color scheme for command line utility +* v1.0.0 + * asynchronous processing + * new library options: noDiffSet, resultBuilder + * new statistics: distinctFiles, equalFiles, leftFiles, rightFiles, distinctDirs, equalDirs, leftDirs, rightDirs + * new --async command line option + * Fix for https://github.com/tj/commander.js/issues/125 +* v0.0.3 Fix fille ordering issue for newer node versions diff --git a/node_modules/dir-compare/node_modules/commander/History.md b/node_modules/dir-compare/node_modules/commander/History.md new file mode 100644 index 0000000..1b47439 --- /dev/null +++ b/node_modules/dir-compare/node_modules/commander/History.md @@ -0,0 +1,261 @@ + +2.9.0 / 2015-10-13 +================== + + * Add option `isDefault` to set default subcommand #415 @Qix- + * Add callback to allow filtering or post-processing of help text #434 @djulien + * Fix `undefined` text in help information close #414 #416 @zhiyelee + +2.8.1 / 2015-04-22 +================== + + * Back out `support multiline description` Close #396 #397 + +2.8.0 / 2015-04-07 +================== + + * Add `process.execArg` support, execution args like `--harmony` will be passed to sub-commands #387 @DigitalIO @zhiyelee + * Fix bug in Git-style sub-commands #372 @zhiyelee + * Allow commands to be hidden from help #383 @tonylukasavage + * When git-style sub-commands are in use, yet none are called, display help #382 @claylo + * Add ability to specify arguments syntax for top-level command #258 @rrthomas + * Support multiline descriptions #208 @zxqfox + +2.7.1 / 2015-03-11 +================== + + * Revert #347 (fix collisions when option and first arg have same name) which causes a bug in #367. + +2.7.0 / 2015-03-09 +================== + + * Fix git-style bug when installed globally. Close #335 #349 @zhiyelee + * Fix collisions when option and first arg have same name. Close #346 #347 @tonylukasavage + * Add support for camelCase on `opts()`. Close #353 @nkzawa + * Add node.js 0.12 and io.js to travis.yml + * Allow RegEx options. #337 @palanik + * Fixes exit code when sub-command failing. Close #260 #332 @pirelenito + * git-style `bin` files in $PATH make sense. Close #196 #327 @zhiyelee + +2.6.0 / 2014-12-30 +================== + + * added `Command#allowUnknownOption` method. Close #138 #318 @doozr @zhiyelee + * Add application description to the help msg. Close #112 @dalssoft + +2.5.1 / 2014-12-15 +================== + + * fixed two bugs incurred by variadic arguments. Close #291 @Quentin01 #302 @zhiyelee + +2.5.0 / 2014-10-24 +================== + + * add support for variadic arguments. Closes #277 @whitlockjc + +2.4.0 / 2014-10-17 +================== + + * fixed a bug on executing the coercion function of subcommands option. Closes #270 + * added `Command.prototype.name` to retrieve command name. Closes #264 #266 @tonylukasavage + * added `Command.prototype.opts` to retrieve all the options as a simple object of key-value pairs. Closes #262 @tonylukasavage + * fixed a bug on subcommand name. Closes #248 @jonathandelgado + * fixed function normalize doesn’t honor option terminator. Closes #216 @abbr + +2.3.0 / 2014-07-16 +================== + + * add command alias'. Closes PR #210 + * fix: Typos. Closes #99 + * fix: Unused fs module. Closes #217 + +2.2.0 / 2014-03-29 +================== + + * add passing of previous option value + * fix: support subcommands on windows. Closes #142 + * Now the defaultValue passed as the second argument of the coercion function. + +2.1.0 / 2013-11-21 +================== + + * add: allow cflag style option params, unit test, fixes #174 + +2.0.0 / 2013-07-18 +================== + + * remove input methods (.prompt, .confirm, etc) + +1.3.2 / 2013-07-18 +================== + + * add support for sub-commands to co-exist with the original command + +1.3.1 / 2013-07-18 +================== + + * add quick .runningCommand hack so you can opt-out of other logic when running a sub command + +1.3.0 / 2013-07-09 +================== + + * add EACCES error handling + * fix sub-command --help + +1.2.0 / 2013-06-13 +================== + + * allow "-" hyphen as an option argument + * support for RegExp coercion + +1.1.1 / 2012-11-20 +================== + + * add more sub-command padding + * fix .usage() when args are present. Closes #106 + +1.1.0 / 2012-11-16 +================== + + * add git-style executable subcommand support. Closes #94 + +1.0.5 / 2012-10-09 +================== + + * fix `--name` clobbering. Closes #92 + * fix examples/help. Closes #89 + +1.0.4 / 2012-09-03 +================== + + * add `outputHelp()` method. + +1.0.3 / 2012-08-30 +================== + + * remove invalid .version() defaulting + +1.0.2 / 2012-08-24 +================== + + * add `--foo=bar` support [arv] + * fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus] + +1.0.1 / 2012-08-03 +================== + + * fix issue #56 + * fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode()) + +1.0.0 / 2012-07-05 +================== + + * add support for optional option descriptions + * add defaulting of `.version()` to package.json's version + +0.6.1 / 2012-06-01 +================== + + * Added: append (yes or no) on confirmation + * Added: allow node.js v0.7.x + +0.6.0 / 2012-04-10 +================== + + * Added `.prompt(obj, callback)` support. Closes #49 + * Added default support to .choose(). Closes #41 + * Fixed the choice example + +0.5.1 / 2011-12-20 +================== + + * Fixed `password()` for recent nodes. Closes #36 + +0.5.0 / 2011-12-04 +================== + + * Added sub-command option support [itay] + +0.4.3 / 2011-12-04 +================== + + * Fixed custom help ordering. Closes #32 + +0.4.2 / 2011-11-24 +================== + + * Added travis support + * Fixed: line-buffered input automatically trimmed. Closes #31 + +0.4.1 / 2011-11-18 +================== + + * Removed listening for "close" on --help + +0.4.0 / 2011-11-15 +================== + + * Added support for `--`. Closes #24 + +0.3.3 / 2011-11-14 +================== + + * Fixed: wait for close event when writing help info [Jerry Hamlet] + +0.3.2 / 2011-11-01 +================== + + * Fixed long flag definitions with values [felixge] + +0.3.1 / 2011-10-31 +================== + + * Changed `--version` short flag to `-V` from `-v` + * Changed `.version()` so it's configurable [felixge] + +0.3.0 / 2011-10-31 +================== + + * Added support for long flags only. Closes #18 + +0.2.1 / 2011-10-24 +================== + + * "node": ">= 0.4.x < 0.7.0". Closes #20 + +0.2.0 / 2011-09-26 +================== + + * Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs] + +0.1.0 / 2011-08-24 +================== + + * Added support for custom `--help` output + +0.0.5 / 2011-08-18 +================== + + * Changed: when the user enters nothing prompt for password again + * Fixed issue with passwords beginning with numbers [NuckChorris] + +0.0.4 / 2011-08-15 +================== + + * Fixed `Commander#args` + +0.0.3 / 2011-08-15 +================== + + * Added default option value support + +0.0.2 / 2011-08-15 +================== + + * Added mask support to `Command#password(str[, mask], fn)` + * Added `Command#password(str, fn)` + +0.0.1 / 2010-01-03 +================== + + * Initial release diff --git a/node_modules/dir-compare/node_modules/commander/LICENSE b/node_modules/dir-compare/node_modules/commander/LICENSE new file mode 100644 index 0000000..10f997a --- /dev/null +++ b/node_modules/dir-compare/node_modules/commander/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca> + +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. diff --git a/node_modules/dir-compare/node_modules/commander/Readme.md b/node_modules/dir-compare/node_modules/commander/Readme.md new file mode 100644 index 0000000..08b9e4c --- /dev/null +++ b/node_modules/dir-compare/node_modules/commander/Readme.md @@ -0,0 +1,351 @@ +# Commander.js + + +[](http://travis-ci.org/tj/commander.js) +[](https://www.npmjs.org/package/commander) +[](https://www.npmjs.org/package/commander) +[](https://gitter.im/tj/commander.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + + The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/tj/commander). + [API documentation](http://tj.github.com/commander.js/) + + +## Installation + + $ npm install commander + +## Option parsing + + Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options. + +```js +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('commander'); + +program + .version('0.0.1') + .option('-p, --peppers', 'Add peppers') + .option('-P, --pineapple', 'Add pineapple') + .option('-b, --bbq-sauce', 'Add bbq sauce') + .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble') + .parse(process.argv); + +console.log('you ordered a pizza with:'); +if (program.peppers) console.log(' - peppers'); +if (program.pineapple) console.log(' - pineapple'); +if (program.bbqSauce) console.log(' - bbq'); +console.log(' - %s cheese', program.cheese); +``` + + Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc. + + +## Coercion + +```js +function range(val) { + return val.split('..').map(Number); +} + +function list(val) { + return val.split(','); +} + +function collect(val, memo) { + memo.push(val); + return memo; +} + +function increaseVerbosity(v, total) { + return total + 1; +} + +program + .version('0.0.1') + .usage('[options] <file ...>') + .option('-i, --integer <n>', 'An integer argument', parseInt) + .option('-f, --float <n>', 'A float argument', parseFloat) + .option('-r, --range <a>..<b>', 'A range', range) + .option('-l, --list <items>', 'A list', list) + .option('-o, --optional [value]', 'An optional value') + .option('-c, --collect [value]', 'A repeatable value', collect, []) + .option('-v, --verbose', 'A value that can be increased', increaseVerbosity, 0) + .parse(process.argv); + +console.log(' int: %j', program.integer); +console.log(' float: %j', program.float); +console.log(' optional: %j', program.optional); +program.range = program.range || []; +console.log(' range: %j..%j', program.range[0], program.range[1]); +console.log(' list: %j', program.list); +console.log(' collect: %j', program.collect); +console.log(' verbosity: %j', program.verbose); +console.log(' args: %j', program.args); +``` + +## Regular Expression +```js +program + .version('0.0.1') + .option('-s --size <size>', 'Pizza size', /^(large|medium|small)$/i, 'medium') + .option('-d --drink [drink]', 'Drink', /^(coke|pepsi|izze)$/i) + .parse(process.argv); + +console.log(' size: %j', program.size); +console.log(' drink: %j', program.drink); +``` + +## Variadic arguments + + The last argument of a command can be variadic, and only the last argument. To make an argument variadic you have to + append `...` to the argument name. Here is an example: + +```js +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('commander'); + +program + .version('0.0.1') + .command('rmdir <dir> [otherDirs...]') + .action(function (dir, otherDirs) { + console.log('rmdir %s', dir); + if (otherDirs) { + otherDirs.forEach(function (oDir) { + console.log('rmdir %s', oDir); + }); + } + }); + +program.parse(process.argv); +``` + + An `Array` is used for the value of a variadic argument. This applies to `program.args` as well as the argument passed + to your action as demonstrated above. + +## Specify the argument syntax + +```js +#!/usr/bin/env node + +var program = require('../'); + +program + .version('0.0.1') + .arguments('<cmd> [env]') + .action(function (cmd, env) { + cmdValue = cmd; + envValue = env; + }); + +program.parse(process.argv); + +if (typeof cmdValue === 'undefined') { + console.error('no command given!'); + process.exit(1); +} +console.log('command:', cmdValue); +console.log('environment:', envValue || "no environment given"); +``` + +## Git-style sub-commands + +```js +// file: ./examples/pm +var program = require('..'); + +program + .version('0.0.1') + .command('install [name]', 'install one or more packages') + .command('search [query]', 'search with optional query') + .command('list', 'list packages installed', {isDefault: true}) + .parse(process.argv); +``` + +When `.command()` is invoked with a description argument, no `.action(callback)` should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools. +The commander will try to search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-command`, like `pm-install`, `pm-search`. + +Options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the option from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified. + +If the program is designed to be installed globally, make sure the executables have proper modes, like `755`. + +### `--harmony` + +You can enable `--harmony` option in two ways: +* Use `#! /usr/bin/env node --harmony` in the sub-commands scripts. Note some os version don’t support this pattern. +* Use the `--harmony` option when call the command, like `node --harmony examples/pm publish`. The `--harmony` option will be preserved when spawning sub-command process. + +## Automated --help + + The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free: + +``` + $ ./examples/pizza --help + + Usage: pizza [options] + + An application for pizzas ordering + + Options: + + -h, --help output usage information + -V, --version output the version number + -p, --peppers Add peppers + -P, --pineapple Add pineapple + -b, --bbq Add bbq sauce + -c, --cheese <type> Add the specified type of cheese [marble] + -C, --no-cheese You do not want any cheese + +``` + +## Custom help + + You can display arbitrary `-h, --help` information + by listening for "--help". Commander will automatically + exit once you are done so that the remainder of your program + does not execute causing undesired behaviours, for example + in the following executable "stuff" will not output when + `--help` is used. + +```js +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('commander'); + +program + .version('0.0.1') + .option('-f, --foo', 'enable some foo') + .option('-b, --bar', 'enable some bar') + .option('-B, --baz', 'enable some baz'); + +// must be before .parse() since +// node's emit() is immediate + +program.on('--help', function(){ + console.log(' Examples:'); + console.log(''); + console.log(' $ custom-help --help'); + console.log(' $ custom-help -h'); + console.log(''); +}); + +program.parse(process.argv); + +console.log('stuff'); +``` + +Yields the following help output when `node script-name.js -h` or `node script-name.js --help` are run: + +``` + +Usage: custom-help [options] + +Options: + + -h, --help output usage information + -V, --version output the version number + -f, --foo enable some foo + -b, --bar enable some bar + -B, --baz enable some baz + +Examples: + + $ custom-help --help + $ custom-help -h + +``` + +## .outputHelp(cb) + +Output help information without exiting. +Optional callback cb allows post-processing of help text before it is displayed. + +If you want to display help by default (e.g. if no command was provided), you can use something like: + +```js +var program = require('commander'); +var colors = require('colors'); + +program + .version('0.0.1') + .command('getstream [url]', 'get stream URL') + .parse(process.argv); + + if (!process.argv.slice(2).length) { + program.outputHelp(make_red); + } + +function make_red(txt) { + return colors.red(txt); //display the help text in red on the console +} +``` + +## .help(cb) + + Output help information and exit immediately. + Optional callback cb allows post-processing of help text before it is displayed. + +## Examples + +```js +var program = require('commander'); + +program + .version('0.0.1') + .option('-C, --chdir <path>', 'change the working directory') + .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf') + .option('-T, --no-tests', 'ignore test hook') + +program + .command('setup [env]') + .description('run setup commands for all envs') + .option("-s, --setup_mode [mode]", "Which setup mode to use") + .action(function(env, options){ + var mode = options.setup_mode || "normal"; + env = env || 'all'; + console.log('setup for %s env(s) with %s mode', env, mode); + }); + +program + .command('exec <cmd>') + .alias('ex') + .description('execute the given remote cmd') + .option("-e, --exec_mode <mode>", "Which exec mode to use") + .action(function(cmd, options){ + console.log('exec "%s" using %s mode', cmd, options.exec_mode); + }).on('--help', function() { + console.log(' Examples:'); + console.log(); + console.log(' $ deploy exec sequential'); + console.log(' $ deploy exec async'); + console.log(); + }); + +program + .command('*') + .action(function(env){ + console.log('deploying "%s"', env); + }); + +program.parse(process.argv); +``` + +More Demos can be found in the [examples](https://github.com/tj/commander.js/tree/master/examples) directory. + +## License + +MIT + diff --git a/node_modules/dir-compare/node_modules/commander/index.js b/node_modules/dir-compare/node_modules/commander/index.js new file mode 100644 index 0000000..a19c05d --- /dev/null +++ b/node_modules/dir-compare/node_modules/commander/index.js @@ -0,0 +1,1110 @@ +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter; +var spawn = require('child_process').spawn; +var readlink = require('graceful-readlink').readlinkSync; +var path = require('path'); +var dirname = path.dirname; +var basename = path.basename; +var fs = require('fs'); + +/** + * Expose the root command. + */ + +exports = module.exports = new Command(); + +/** + * Expose `Command`. + */ + +exports.Command = Command; + +/** + * Expose `Option`. + */ + +exports.Option = Option; + +/** + * Initialize a new `Option` with the given `flags` and `description`. + * + * @param {String} flags + * @param {String} description + * @api public + */ + +function Option(flags, description) { + this.flags = flags; + this.required = ~flags.indexOf('<'); + this.optional = ~flags.indexOf('['); + this.bool = !~flags.indexOf('-no-'); + flags = flags.split(/[ ,|]+/); + if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift(); + this.long = flags.shift(); + this.description = description || ''; +} + +/** + * Return option name. + * + * @return {String} + * @api private + */ + +Option.prototype.name = function() { + return this.long + .replace('--', '') + .replace('no-', ''); +}; + +/** + * Check if `arg` matches the short or long flag. + * + * @param {String} arg + * @return {Boolean} + * @api private + */ + +Option.prototype.is = function(arg) { + return arg == this.short || arg == this.long; +}; + +/** + * Initialize a new `Command`. + * + * @param {String} name + * @api public + */ + +function Command(name) { + this.commands = []; + this.options = []; + this._execs = {}; + this._allowUnknownOption = false; + this._args = []; + this._name = name || ''; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Command.prototype.__proto__ = EventEmitter.prototype; + +/** + * Add command `name`. + * + * The `.action()` callback is invoked when the + * command `name` is specified via __ARGV__, + * and the remaining arguments are applied to the + * function for access. + * + * When the `name` is "*" an un-matched command + * will be passed as the first arg, followed by + * the rest of __ARGV__ remaining. + * + * Examples: + * + * program + * .version('0.0.1') + * .option('-C, --chdir <path>', 'change the working directory') + * .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf') + * .option('-T, --no-tests', 'ignore test hook') + * + * program + * .command('setup') + * .description('run remote setup commands') + * .action(function() { + * console.log('setup'); + * }); + * + * program + * .command('exec <cmd>') + * .description('run the given remote command') + * .action(function(cmd) { + * console.log('exec "%s"', cmd); + * }); + * + * program + * .command('teardown <dir> [otherDirs...]') + * .description('run teardown commands') + * .action(function(dir, otherDirs) { + * console.log('dir "%s"', dir); + * if (otherDirs) { + * otherDirs.forEach(function (oDir) { + * console.log('dir "%s"', oDir); + * }); + * } + * }); + * + * program + * .command('*') + * .description('deploy the given env') + * .action(function(env) { + * console.log('deploying "%s"', env); + * }); + * + * program.parse(process.argv); + * + * @param {String} name + * @param {String} [desc] for git-style sub-commands + * @return {Command} the new command + * @api public + */ + +Command.prototype.command = function(name, desc, opts) { + opts = opts || {}; + var args = name.split(/ +/); + var cmd = new Command(args.shift()); + + if (desc) { + cmd.description(desc); + this.executables = true; + this._execs[cmd._name] = true; + if (opts.isDefault) this.defaultExecutable = cmd._name; + } + + cmd._noHelp = !!opts.noHelp; + this.commands.push(cmd); + cmd.parseExpectedArgs(args); + cmd.parent = this; + + if (desc) return this; + return cmd; +}; + +/** + * Define argument syntax for the top-level command. + * + * @api public + */ + +Command.prototype.arguments = function (desc) { + return this.parseExpectedArgs(desc.split(/ +/)); +}; + +/** + * Add an implicit `help [cmd]` subcommand + * which invokes `--help` for the given command. + * + * @api private + */ + +Command.prototype.addImplicitHelpCommand = function() { + this.command('help [cmd]', 'display help for [cmd]'); +}; + +/** + * Parse expected `args`. + * + * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`. + * + * @param {Array} args + * @return {Command} for chaining + * @api public + */ + +Command.prototype.parseExpectedArgs = function(args) { + if (!args.length) return; + var self = this; + args.forEach(function(arg) { + var argDetails = { + required: false, + name: '', + variadic: false + }; + + switch (arg[0]) { + case '<': + argDetails.required = true; + argDetails.name = arg.slice(1, -1); + break; + case '[': + argDetails.name = arg.slice(1, -1); + break; + } + + if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') { + argDetails.variadic = true; + argDetails.name = argDetails.name.slice(0, -3); + } + if (argDetails.name) { + self._args.push(argDetails); + } + }); + return this; +}; + +/** + * Register callback `fn` for the command. + * + * Examples: + * + * program + * .command('help') + * .description('display verbose help') + * .action(function() { + * // output help here + * }); + * + * @param {Function} fn + * @return {Command} for chaining + * @api public + */ + +Command.prototype.action = function(fn) { + var self = this; + var listener = function(args, unknown) { + // Parse any so-far unknown options + args = args || []; + unknown = unknown || []; + + var parsed = self.parseOptions(unknown); + + // Output help if necessary + outputHelpIfNecessary(self, parsed.unknown); + + // If there are still any unknown options, then we simply + // die, unless someone asked for help, in which case we give it + // to them, and then we die. + if (parsed.unknown.length > 0) { + self.unknownOption(parsed.unknown[0]); + } + + // Leftover arguments need to be pushed back. Fixes issue #56 + if (parsed.args.length) args = parsed.args.concat(args); + + self._args.forEach(function(arg, i) { + if (arg.required && null == args[i]) { + self.missingArgument(arg.name); + } else if (arg.variadic) { + if (i !== self._args.length - 1) { + self.variadicArgNotLast(arg.name); + } + + args[i] = args.splice(i); + } + }); + + // Always append ourselves to the end of the arguments, + // to make sure we match the number of arguments the user + // expects + if (self._args.length) { + args[self._args.length] = self; + } else { + args.push(self); + } + + fn.apply(self, args); + }; + var parent = this.parent || this; + var name = parent === this ? '*' : this._name; + parent.on(name, listener); + if (this._alias) parent.on(this._alias, listener); + return this; +}; + +/** + * Define option with `flags`, `description` and optional + * coercion `fn`. + * + * The `flags` string should contain both the short and long flags, + * separated by comma, a pipe or space. The following are all valid + * all will output this way when `--help` is used. + * + * "-p, --pepper" + * "-p|--pepper" + * "-p --pepper" + * + * Examples: + * + * // simple boolean defaulting to false + * program.option('-p, --pepper', 'add pepper'); + * + * --pepper + * program.pepper + * // => Boolean + * + * // simple boolean defaulting to true + * program.option('-C, --no-cheese', 'remove cheese'); + * + * program.cheese + * // => true + * + * --no-cheese + * program.cheese + * // => false + * + * // required argument + * program.option('-C, --chdir <path>', 'change the working directory'); + * + * --chdir /tmp + * program.chdir + * // => "/tmp" + * + * // optional argument + * program.option('-c, --cheese [type]', 'add cheese [marble]'); + * + * @param {String} flags + * @param {String} description + * @param {Function|Mixed} fn or default + * @param {Mixed} defaultValue + * @return {Command} for chaining + * @api public + */ + +Command.prototype.option = function(flags, description, fn, defaultValue) { + var self = this + , option = new Option(flags, description) + , oname = option.name() + , name = camelcase(oname); + + // default as 3rd arg + if (typeof fn != 'function') { + if (fn instanceof RegExp) { + var regex = fn; + fn = function(val, def) { + var m = regex.exec(val); + return m ? m[0] : def; + } + } + else { + defaultValue = fn; + fn = null; + } + } + + // preassign default value only for --no-*, [optional], or <required> + if (false == option.bool || option.optional || option.required) { + // when --no-* we make sure default is true + if (false == option.bool) defaultValue = true; + // preassign only if we have a default + if (undefined !== defaultValue) self[name] = defaultValue; + } + + // register the option + this.options.push(option); + + // when it's passed assign the value + // and conditionally invoke the callback + this.on(oname, function(val) { + // coercion + if (null !== val && fn) val = fn(val, undefined === self[name] + ? defaultValue + : self[name]); + + // unassigned or bool + if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) { + // if no value, bool true, and we have a default, then use it! + if (null == val) { + self[name] = option.bool + ? defaultValue || true + : false; + } else { + self[name] = val; + } + } else if (null !== val) { + // reassign + self[name] = val; + } + }); + + return this; +}; + +/** + * Allow unknown options on the command line. + * + * @param {Boolean} arg if `true` or omitted, no error will be thrown + * for unknown options. + * @api public + */ +Command.prototype.allowUnknownOption = function(arg) { + this._allowUnknownOption = arguments.length === 0 || arg; + return this; +}; + +/** + * Parse `argv`, settings options and invoking commands when defined. + * + * @param {Array} argv + * @return {Command} for chaining + * @api public + */ + +Command.prototype.parse = function(argv) { + // implicit help + if (this.executables) this.addImplicitHelpCommand(); + + // store raw args + this.rawArgs = argv; + + // guess name + this._name = this._name || basename(argv[1], '.js'); + + // github-style sub-commands with no sub-command + if (this.executables && argv.length < 3 && !this.defaultExecutable) { + // this user needs help + argv.push('--help'); + } + + // process argv + var parsed = this.parseOptions(this.normalize(argv.slice(2))); + var args = this.args = parsed.args; + + var result = this.parseArgs(this.args, parsed.unknown); + + // executable sub-commands + var name = result.args[0]; + if (this._execs[name] && typeof this._execs[name] != "function") { + return this.executeSubCommand(argv, args, parsed.unknown); + } else if (this.defaultExecutable) { + // use the default subcommand + args.unshift(name = this.defaultExecutable); + return this.executeSubCommand(argv, args, parsed.unknown); + } + + return result; +}; + +/** + * Execute a sub-command executable. + * + * @param {Array} argv + * @param {Array} args + * @param {Array} unknown + * @api private + */ + +Command.prototype.executeSubCommand = function(argv, args, unknown) { + args = args.concat(unknown); + + if (!args.length) this.help(); + if ('help' == args[0] && 1 == args.length) this.help(); + + // <cmd> --help + if ('help' == args[0]) { + args[0] = args[1]; + args[1] = '--help'; + } + + // executable + var f = argv[1]; + // name of the subcommand, link `pm-install` + var bin = basename(f, '.js') + '-' + args[0]; + + + // In case of globally installed, get the base dir where executable + // subcommand file should be located at + var baseDir + , link = readlink(f); + + // when symbolink is relative path + if (link !== f && link.charAt(0) !== '/') { + link = path.join(dirname(f), link) + } + baseDir = dirname(link); + + // prefer local `./<bin>` to bin in the $PATH + var localBin = path.join(baseDir, bin); + + // whether bin file is a js script with explicit `.js` extension + var isExplicitJS = false; + if (exists(localBin + '.js')) { + bin = localBin + '.js'; + isExplicitJS = true; + } else if (exists(localBin)) { + bin = localBin; + } + + args = args.slice(1); + + var proc; + if (process.platform !== 'win32') { + if (isExplicitJS) { + args.unshift(localBin); + // add executable arguments to spawn + args = (process.execArgv || []).concat(args); + + proc = spawn('node', args, { stdio: 'inherit', customFds: [0, 1, 2] }); + } else { + proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] }); + } + } else { + args.unshift(localBin); + proc = spawn(process.execPath, args, { stdio: 'inherit'}); + } + + proc.on('close', process.exit.bind(process)); + proc.on('error', function(err) { + if (err.code == "ENOENT") { + console.error('\n %s(1) does not exist, try --help\n', bin); + } else if (err.code == "EACCES") { + console.error('\n %s(1) not executable. try chmod or run with root\n', bin); + } + process.exit(1); + }); + + // Store the reference to the child process + this.runningCommand = proc; +}; + +/** + * Normalize `args`, splitting joined short flags. For example + * the arg "-abc" is equivalent to "-a -b -c". + * This also normalizes equal sign and splits "--abc=def" into "--abc def". + * + * @param {Array} args + * @return {Array} + * @api private + */ + +Command.prototype.normalize = function(args) { + var ret = [] + , arg + , lastOpt + , index; + + for (var i = 0, len = args.length; i < len; ++i) { + arg = args[i]; + if (i > 0) { + lastOpt = this.optionFor(args[i-1]); + } + + if (arg === '--') { + // Honor option terminator + ret = ret.concat(args.slice(i)); + break; + } else if (lastOpt && lastOpt.required) { + ret.push(arg); + } else if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) { + arg.slice(1).split('').forEach(function(c) { + ret.push('-' + c); + }); + } else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) { + ret.push(arg.slice(0, index), arg.slice(index + 1)); + } else { + ret.push(arg); + } + } + + return ret; +}; + +/** + * Parse command `args`. + * + * When listener(s) are available those + * callbacks are invoked, otherwise the "*" + * event is emitted and those actions are invoked. + * + * @param {Array} args + * @return {Command} for chaining + * @api private + */ + +Command.prototype.parseArgs = function(args, unknown) { + var name; + + if (args.length) { + name = args[0]; + if (this.listeners(name).length) { + this.emit(args.shift(), args, unknown); + } else { + this.emit('*', args); + } + } else { + outputHelpIfNecessary(this, unknown); + + // If there were no args and we have unknown options, + // then they are extraneous and we need to error. + if (unknown.length > 0) { + this.unknownOption(unknown[0]); + } + } + + return this; +}; + +/** + * Return an option matching `arg` if any. + * + * @param {String} arg + * @return {Option} + * @api private + */ + +Command.prototype.optionFor = function(arg) { + for (var i = 0, len = this.options.length; i < len; ++i) { + if (this.options[i].is(arg)) { + return this.options[i]; + } + } +}; + +/** + * Parse options from `argv` returning `argv` + * void of these options. + * + * @param {Array} argv + * @return {Array} + * @api public + */ + +Command.prototype.parseOptions = function(argv) { + var args = [] + , len = argv.length + , literal + , option + , arg; + + var unknownOptions = []; + + // parse options + for (var i = 0; i < len; ++i) { + arg = argv[i]; + + // literal args after -- + if ('--' == arg) { + literal = true; + continue; + } + + if (literal) { + args.push(arg); + continue; + } + + // find matching Option + option = this.optionFor(arg); + + // option is defined + if (option) { + // requires arg + if (option.required) { + arg = argv[++i]; + if (null == arg) return this.optionMissingArgument(option); + this.emit(option.name(), arg); + // optional arg + } else if (option.optional) { + arg = argv[i+1]; + if (null == arg || ('-' == arg[0] && '-' != arg)) { + arg = null; + } else { + ++i; + } + this.emit(option.name(), arg); + // bool + } else { + this.emit(option.name()); + } + continue; + } + + // looks like an option + if (arg.length > 1 && '-' == arg[0]) { + unknownOptions.push(arg); + + // If the next argument looks like it might be + // an argument for this option, we pass it on. + // If it isn't, then it'll simply be ignored + if (argv[i+1] && '-' != argv[i+1][0]) { + unknownOptions.push(argv[++i]); + } + continue; + } + + // arg + args.push(arg); + } + + return { args: args, unknown: unknownOptions }; +}; + +/** + * Return an object containing options as key-value pairs + * + * @return {Object} + * @api public + */ +Command.prototype.opts = function() { + var result = {} + , len = this.options.length; + + for (var i = 0 ; i < len; i++) { + var key = camelcase(this.options[i].name()); + result[key] = key === 'version' ? this._version : this[key]; + } + return result; +}; + +/** + * Argument `name` is missing. + * + * @param {String} name + * @api private + */ + +Command.prototype.missingArgument = function(name) { + console.error(); + console.error(" error: missing required argument `%s'", name); + console.error(); + process.exit(1); +}; + +/** + * `Option` is missing an argument, but received `flag` or nothing. + * + * @param {String} option + * @param {String} flag + * @api private + */ + +Command.prototype.optionMissingArgument = function(option, flag) { + console.error(); + if (flag) { + console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag); + } else { + console.error(" error: option `%s' argument missing", option.flags); + } + console.error(); + process.exit(1); +}; + +/** + * Unknown option `flag`. + * + * @param {String} flag + * @api private + */ + +Command.prototype.unknownOption = function(flag) { + if (this._allowUnknownOption) return; + console.error(); + console.error(" error: unknown option `%s'", flag); + console.error(); + process.exit(1); +}; + +/** + * Variadic argument with `name` is not the last argument as required. + * + * @param {String} name + * @api private + */ + +Command.prototype.variadicArgNotLast = function(name) { + console.error(); + console.error(" error: variadic arguments must be last `%s'", name); + console.error(); + process.exit(1); +}; + +/** + * Set the program version to `str`. + * + * This method auto-registers the "-V, --version" flag + * which will print the version number when passed. + * + * @param {String} str + * @param {String} flags + * @return {Command} for chaining + * @api public + */ + +Command.prototype.version = function(str, flags) { + if (0 == arguments.length) return this._version; + this._version = str; + flags = flags || '-V, --version'; + this.option(flags, 'output the version number'); + this.on('version', function() { + process.stdout.write(str + '\n'); + process.exit(0); + }); + return this; +}; + +/** + * Set the description to `str`. + * + * @param {String} str + * @return {String|Command} + * @api public + */ + +Command.prototype.description = function(str) { + if (0 === arguments.length) return this._description; + this._description = str; + return this; +}; + +/** + * Set an alias for the command + * + * @param {String} alias + * @return {String|Command} + * @api public + */ + +Command.prototype.alias = function(alias) { + if (0 == arguments.length) return this._alias; + this._alias = alias; + return this; +}; + +/** + * Set / get the command usage `str`. + * + * @param {String} str + * @return {String|Command} + * @api public + */ + +Command.prototype.usage = function(str) { + var args = this._args.map(function(arg) { + return humanReadableArgName(arg); + }); + + var usage = '[options]' + + (this.commands.length ? ' [command]' : '') + + (this._args.length ? ' ' + args.join(' ') : ''); + + if (0 == arguments.length) return this._usage || usage; + this._usage = str; + + return this; +}; + +/** + * Get the name of the command + * + * @param {String} name + * @return {String|Command} + * @api public + */ + +Command.prototype.name = function() { + return this._name; +}; + +/** + * Return the largest option length. + * + * @return {Number} + * @api private + */ + +Command.prototype.largestOptionLength = function() { + return this.options.reduce(function(max, option) { + return Math.max(max, option.flags.length); + }, 0); +}; + +/** + * Return help for options. + * + * @return {String} + * @api private + */ + +Command.prototype.optionHelp = function() { + var width = this.largestOptionLength(); + + // Prepend the help information + return [pad('-h, --help', width) + ' ' + 'output usage information'] + .concat(this.options.map(function(option) { + return pad(option.flags, width) + ' ' + option.description; + })) + .join('\n'); +}; + +/** + * Return command help documentation. + * + * @return {String} + * @api private + */ + +Command.prototype.commandHelp = function() { + if (!this.commands.length) return ''; + + var commands = this.commands.filter(function(cmd) { + return !cmd._noHelp; + }).map(function(cmd) { + var args = cmd._args.map(function(arg) { + return humanReadableArgName(arg); + }).join(' '); + + return [ + cmd._name + + (cmd._alias ? '|' + cmd._alias : '') + + (cmd.options.length ? ' [options]' : '') + + ' ' + args + , cmd.description() + ]; + }); + + var width = commands.reduce(function(max, command) { + return Math.max(max, command[0].length); + }, 0); + + return [ + '' + , ' Commands:' + , '' + , commands.map(function(cmd) { + var desc = cmd[1] ? ' ' + cmd[1] : ''; + return pad(cmd[0], width) + desc; + }).join('\n').replace(/^/gm, ' ') + , '' + ].join('\n'); +}; + +/** + * Return program help documentation. + * + * @return {String} + * @api private + */ + +Command.prototype.helpInformation = function() { + var desc = []; + if (this._description) { + desc = [ + ' ' + this._description + , '' + ]; + } + + var cmdName = this._name; + if (this._alias) { + cmdName = cmdName + '|' + this._alias; + } + var usage = [ + '' + ,' Usage: ' + cmdName + ' ' + this.usage() + , '' + ]; + + var cmds = []; + var commandHelp = this.commandHelp(); + if (commandHelp) cmds = [commandHelp]; + + var options = [ + ' Options:' + , '' + , '' + this.optionHelp().replace(/^/gm, ' ') + , '' + , '' + ]; + + return usage + .concat(cmds) + .concat(desc) + .concat(options) + .join('\n'); +}; + +/** + * Output help information for this command + * + * @api public + */ + +Command.prototype.outputHelp = function(cb) { + if (!cb) { + cb = function(passthru) { + return passthru; + } + } + process.stdout.write(cb(this.helpInformation())); + this.emit('--help'); +}; + +/** + * Output help information and exit. + * + * @api public + */ + +Command.prototype.help = function(cb) { + this.outputHelp(cb); + process.exit(); +}; + +/** + * Camel-case the given `flag` + * + * @param {String} flag + * @return {String} + * @api private + */ + +function camelcase(flag) { + return flag.split('-').reduce(function(str, word) { + return str + word[0].toUpperCase() + word.slice(1); + }); +} + +/** + * Pad `str` to `width`. + * + * @param {String} str + * @param {Number} width + * @return {String} + * @api private + */ + +function pad(str, width) { + var len = Math.max(0, width - str.length); + return str + Array(len + 1).join(' '); +} + +/** + * Output help information if necessary + * + * @param {Command} command to output help for + * @param {Array} array of options to search for -h or --help + * @api private + */ + +function outputHelpIfNecessary(cmd, options) { + options = options || []; + for (var i = 0; i < options.length; i++) { + if (options[i] == '--help' || options[i] == '-h') { + cmd.outputHelp(); + process.exit(0); + } + } +} + +/** + * Takes an argument an returns its human readable equivalent for help usage. + * + * @param {Object} arg + * @return {String} + * @api private + */ + +function humanReadableArgName(arg) { + var nameOutput = arg.name + (arg.variadic === true ? '...' : ''); + + return arg.required + ? '<' + nameOutput + '>' + : '[' + nameOutput + ']' +} + +// for versions before node v0.8 when there weren't `fs.existsSync` +function exists(file) { + try { + if (fs.statSync(file).isFile()) { + return true; + } + } catch (e) { + return false; + } +} + diff --git a/node_modules/dir-compare/node_modules/commander/package.json b/node_modules/dir-compare/node_modules/commander/package.json new file mode 100644 index 0000000..7731149 --- /dev/null +++ b/node_modules/dir-compare/node_modules/commander/package.json @@ -0,0 +1,33 @@ +{ + "name": "commander", + "version": "2.9.0", + "description": "the complete solution for node.js command-line programs", + "keywords": [ + "command", + "option", + "parser" + ], + "author": "TJ Holowaychuk <tj@vision-media.ca>", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/tj/commander.js.git" + }, + "devDependencies": { + "should": ">= 0.0.1", + "sinon": ">=1.17.1" + }, + "scripts": { + "test": "make test" + }, + "main": "index", + "engines": { + "node": ">= 0.6.x" + }, + "files": [ + "index.js" + ], + "dependencies": { + "graceful-readlink": ">= 1.0.0" + } +} diff --git a/node_modules/dir-compare/node_modules/minimatch/LICENSE b/node_modules/dir-compare/node_modules/minimatch/LICENSE new file mode 100644 index 0000000..19129e3 --- /dev/null +++ b/node_modules/dir-compare/node_modules/minimatch/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +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. diff --git a/node_modules/dir-compare/node_modules/minimatch/README.md b/node_modules/dir-compare/node_modules/minimatch/README.md new file mode 100644 index 0000000..ad72b81 --- /dev/null +++ b/node_modules/dir-compare/node_modules/minimatch/README.md @@ -0,0 +1,209 @@ +# minimatch + +A minimal matching utility. + +[](http://travis-ci.org/isaacs/minimatch) + + +This is the matching library used internally by npm. + +It works by converting glob expressions into JavaScript `RegExp` +objects. + +## Usage + +```javascript +var minimatch = require("minimatch") + +minimatch("bar.foo", "*.foo") // true! +minimatch("bar.foo", "*.bar") // false! +minimatch("bar.foo", "*.+(bar|foo)", { debug: true }) // true, and noisy! +``` + +## Features + +Supports these glob features: + +* Brace Expansion +* Extended glob matching +* "Globstar" `**` matching + +See: + +* `man sh` +* `man bash` +* `man 3 fnmatch` +* `man 5 gitignore` + +## Minimatch Class + +Create a minimatch object by instantiating the `minimatch.Minimatch` class. + +```javascript +var Minimatch = require("minimatch").Minimatch +var mm = new Minimatch(pattern, options) +``` + +### Properties + +* `pattern` The original pattern the minimatch object represents. +* `options` The options supplied to the constructor. +* `set` A 2-dimensional array of regexp or string expressions. + Each row in the + array corresponds to a brace-expanded pattern. Each item in the row + corresponds to a single path-part. For example, the pattern + `{a,b/c}/d` would expand to a set of patterns like: + + [ [ a, d ] + , [ b, c, d ] ] + + If a portion of the pattern doesn't have any "magic" in it + (that is, it's something like `"foo"` rather than `fo*o?`), then it + will be left as a string rather than converted to a regular + expression. + +* `regexp` Created by the `makeRe` method. A single regular expression + expressing the entire pattern. This is useful in cases where you wish + to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled. +* `negate` True if the pattern is negated. +* `comment` True if the pattern is a comment. +* `empty` True if the pattern is `""`. + +### Methods + +* `makeRe` Generate the `regexp` member if necessary, and return it. + Will return `false` if the pattern is invalid. +* `match(fname)` Return true if the filename matches the pattern, or + false otherwise. +* `matchOne(fileArray, patternArray, partial)` Take a `/`-split + filename, and match it against a single row in the `regExpSet`. This + method is mainly for internal use, but is exposed so that it can be + used by a glob-walker that needs to avoid excessive filesystem calls. + +All other methods are internal, and will be called as necessary. + +### minimatch(path, pattern, options) + +Main export. Tests a path against the pattern using the options. + +```javascript +var isJS = minimatch(file, "*.js", { matchBase: true }) +``` + +### minimatch.filter(pattern, options) + +Returns a function that tests its +supplied argument, suitable for use with `Array.filter`. Example: + +```javascript +var javascripts = fileList.filter(minimatch.filter("*.js", {matchBase: true})) +``` + +### minimatch.match(list, pattern, options) + +Match against the list of +files, in the style of fnmatch or glob. If nothing is matched, and +options.nonull is set, then return a list containing the pattern itself. + +```javascript +var javascripts = minimatch.match(fileList, "*.js", {matchBase: true})) +``` + +### minimatch.makeRe(pattern, options) + +Make a regular expression object from the pattern. + +## Options + +All options are `false` by default. + +### debug + +Dump a ton of stuff to stderr. + +### nobrace + +Do not expand `{a,b}` and `{1..3}` brace sets. + +### noglobstar + +Disable `**` matching against multiple folder names. + +### dot + +Allow patterns to match filenames starting with a period, even if +the pattern does not explicitly have a period in that spot. + +Note that by default, `a/**/b` will **not** match `a/.d/b`, unless `dot` +is set. + +### noext + +Disable "extglob" style patterns like `+(a|b)`. + +### nocase + +Perform a case-insensitive match. + +### nonull + +When a match is not found by `minimatch.match`, return a list containing +the pattern itself if this option is set. When not set, an empty list +is returned if there are no matches. + +### matchBase + +If set, then patterns without slashes will be matched +against the basename of the path if it contains slashes. For example, +`a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`. + +### nocomment + +Suppress the behavior of treating `#` at the start of a pattern as a +comment. + +### nonegate + +Suppress the behavior of treating a leading `!` character as negation. + +### flipNegate + +Returns from negate expressions the same as if they were not negated. +(Ie, true on a hit, false on a miss.) + + +## Comparisons to other fnmatch/glob implementations + +While strict compliance with the existing standards is a worthwhile +goal, some discrepancies exist between minimatch and other +implementations, and are intentional. + +If the pattern starts with a `!` character, then it is negated. Set the +`nonegate` flag to suppress this behavior, and treat leading `!` +characters normally. This is perhaps relevant if you wish to start the +pattern with a negative extglob pattern like `!(a|B)`. Multiple `!` +characters at the start of a pattern will negate the pattern multiple +times. + +If a pattern starts with `#`, then it is treated as a comment, and +will not match anything. Use `\#` to match a literal `#` at the +start of a line, or set the `nocomment` flag to suppress this behavior. + +The double-star character `**` is supported by default, unless the +`noglobstar` flag is set. This is supported in the manner of bsdglob +and bash 4.1, where `**` only has special significance if it is the only +thing in a path part. That is, `a/**/b` will match `a/x/y/b`, but +`a/**b` will not. + +If an escaped pattern has no matches, and the `nonull` flag is set, +then minimatch.match returns the pattern as-provided, rather than +interpreting the character escapes. For example, +`minimatch.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than +`"*a?"`. This is akin to setting the `nullglob` option in bash, except +that it does not resolve escaped pattern characters. + +If brace expansion is not disabled, then it is performed before any +other interpretation of the glob pattern. Thus, a pattern like +`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded +**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are +checked for validity. Since those two are valid, matching proceeds. diff --git a/node_modules/dir-compare/node_modules/minimatch/minimatch.js b/node_modules/dir-compare/node_modules/minimatch/minimatch.js new file mode 100644 index 0000000..5b5f8cf --- /dev/null +++ b/node_modules/dir-compare/node_modules/minimatch/minimatch.js @@ -0,0 +1,923 @@ +module.exports = minimatch +minimatch.Minimatch = Minimatch + +var path = { sep: '/' } +try { + path = require('path') +} catch (er) {} + +var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} +var expand = require('brace-expansion') + +var plTypes = { + '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, + '?': { open: '(?:', close: ')?' }, + '+': { open: '(?:', close: ')+' }, + '*': { open: '(?:', close: ')*' }, + '@': { open: '(?:', close: ')' } +} + +// any single thing other than / +// don't need to escape / when using new RegExp() +var qmark = '[^/]' + +// * => any number of characters +var star = qmark + '*?' + +// ** when dots are allowed. Anything goes, except .. and . +// not (^ or / followed by one or two dots followed by $ or /), +// followed by anything, any number of times. +var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' + +// not a ^ or / followed by a dot, +// followed by anything, any number of times. +var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' + +// characters that need to be escaped in RegExp. +var reSpecials = charSet('().*{}+?[]^$\\!') + +// "abc" -> { a:true, b:true, c:true } +function charSet (s) { + return s.split('').reduce(function (set, c) { + set[c] = true + return set + }, {}) +} + +// normalizes slashes. +var slashSplit = /\/+/ + +minimatch.filter = filter +function filter (pattern, options) { + options = options || {} + return function (p, i, list) { + return minimatch(p, pattern, options) + } +} + +function ext (a, b) { + a = a || {} + b = b || {} + var t = {} + Object.keys(b).forEach(function (k) { + t[k] = b[k] + }) + Object.keys(a).forEach(function (k) { + t[k] = a[k] + }) + return t +} + +minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return minimatch + + var orig = minimatch + + var m = function minimatch (p, pattern, options) { + return orig.minimatch(p, pattern, ext(def, options)) + } + + m.Minimatch = function Minimatch (pattern, options) { + return new orig.Minimatch(pattern, ext(def, options)) + } + + return m +} + +Minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return Minimatch + return minimatch.defaults(def).Minimatch +} + +function minimatch (p, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + + // shortcut: comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + return false + } + + // "" only matches "" + if (pattern.trim() === '') return p === '' + + return new Minimatch(pattern, options).match(p) +} + +function Minimatch (pattern, options) { + if (!(this instanceof Minimatch)) { + return new Minimatch(pattern, options) + } + + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + pattern = pattern.trim() + + // windows support: need to use /, not \ + if (path.sep !== '/') { + pattern = pattern.split(path.sep).join('/') + } + + this.options = options + this.set = [] + this.pattern = pattern + this.regexp = null + this.negate = false + this.comment = false + this.empty = false + + // make the set of regexps etc. + this.make() +} + +Minimatch.prototype.debug = function () {} + +Minimatch.prototype.make = make +function make () { + // don't do it more than once. + if (this._made) return + + var pattern = this.pattern + var options = this.options + + // empty patterns and comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + this.comment = true + return + } + if (!pattern) { + this.empty = true + return + } + + // step 1: figure out negation, etc. + this.parseNegate() + + // step 2: expand braces + var set = this.globSet = this.braceExpand() + + if (options.debug) this.debug = console.error + + this.debug(this.pattern, set) + + // step 3: now we have a set, so turn each one into a series of path-portion + // matching patterns. + // These will be regexps, except in the case of "**", which is + // set to the GLOBSTAR object for globstar behavior, + // and will not contain any / characters + set = this.globParts = set.map(function (s) { + return s.split(slashSplit) + }) + + this.debug(this.pattern, set) + + // glob --> regexps + set = set.map(function (s, si, set) { + return s.map(this.parse, this) + }, this) + + this.debug(this.pattern, set) + + // filter out everything that didn't compile properly. + set = set.filter(function (s) { + return s.indexOf(false) === -1 + }) + + this.debug(this.pattern, set) + + this.set = set +} + +Minimatch.prototype.parseNegate = parseNegate +function parseNegate () { + var pattern = this.pattern + var negate = false + var options = this.options + var negateOffset = 0 + + if (options.nonegate) return + + for (var i = 0, l = pattern.length + ; i < l && pattern.charAt(i) === '!' + ; i++) { + negate = !negate + negateOffset++ + } + + if (negateOffset) this.pattern = pattern.substr(negateOffset) + this.negate = negate +} + +// Brace expansion: +// a{b,c}d -> abd acd +// a{b,}c -> abc ac +// a{0..3}d -> a0d a1d a2d a3d +// a{b,c{d,e}f}g -> abg acdfg acefg +// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg +// +// Invalid sets are not expanded. +// a{2..}b -> a{2..}b +// a{b}c -> a{b}c +minimatch.braceExpand = function (pattern, options) { + return braceExpand(pattern, options) +} + +Minimatch.prototype.braceExpand = braceExpand + +function braceExpand (pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options + } else { + options = {} + } + } + + pattern = typeof pattern === 'undefined' + ? this.pattern : pattern + + if (typeof pattern === 'undefined') { + throw new TypeError('undefined pattern') + } + + if (options.nobrace || + !pattern.match(/\{.*\}/)) { + // shortcut. no need to expand. + return [pattern] + } + + return expand(pattern) +} + +// parse a component of the expanded set. +// At this point, no pattern may contain "/" in it +// so we're going to return a 2d array, where each entry is the full +// pattern, split on '/', and then turned into a regular expression. +// A regexp is made at the end which joins each array with an +// escaped /, and another full one which joins each regexp with |. +// +// Following the lead of Bash 4.1, note that "**" only has special meaning +// when it is the *only* thing in a path portion. Otherwise, any series +// of * is equivalent to a single *. Globstar behavior is enabled by +// default, and can be disabled by setting options.noglobstar. +Minimatch.prototype.parse = parse +var SUBPARSE = {} +function parse (pattern, isSub) { + if (pattern.length > 1024 * 64) { + throw new TypeError('pattern is too long') + } + + var options = this.options + + // shortcuts + if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '') return '' + + var re = '' + var hasMagic = !!options.nocase + var escaping = false + // ? => one single character + var patternListStack = [] + var negativeLists = [] + var stateChar + var inClass = false + var reClassStart = -1 + var classStart = -1 + // . and .. never match anything that doesn't start with ., + // even when options.dot is set. + var patternStart = pattern.charAt(0) === '.' ? '' // anything + // not (start or / followed by . or .. followed by / or end) + : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' + : '(?!\\.)' + var self = this + + function clearStateChar () { + if (stateChar) { + // we had some state-tracking character + // that wasn't consumed by this pass. + switch (stateChar) { + case '*': + re += star + hasMagic = true + break + case '?': + re += qmark + hasMagic = true + break + default: + re += '\\' + stateChar + break + } + self.debug('clearStateChar %j %j', stateChar, re) + stateChar = false + } + } + + for (var i = 0, len = pattern.length, c + ; (i < len) && (c = pattern.charAt(i)) + ; i++) { + this.debug('%s\t%s %s %j', pattern, i, re, c) + + // skip over any that are escaped. + if (escaping && reSpecials[c]) { + re += '\\' + c + escaping = false + continue + } + + switch (c) { + case '/': + // completely not allowed, even escaped. + // Should already be path-split by now. + return false + + case '\\': + clearStateChar() + escaping = true + continue + + // the various stateChar values + // for the "extglob" stuff. + case '?': + case '*': + case '+': + case '@': + case '!': + this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) + + // all of those are literals inside a class, except that + // the glob [!a] means [^a] in regexp + if (inClass) { + this.debug(' in class') + if (c === '!' && i === classStart + 1) c = '^' + re += c + continue + } + + // if we already have a stateChar, then it means + // that there was something like ** or +? in there. + // Handle the stateChar, then proceed with this one. + self.debug('call clearStateChar %j', stateChar) + clearStateChar() + stateChar = c + // if extglob is disabled, then +(asdf|foo) isn't a thing. + // just clear the statechar *now*, rather than even diving into + // the patternList stuff. + if (options.noext) clearStateChar() + continue + + case '(': + if (inClass) { + re += '(' + continue + } + + if (!stateChar) { + re += '\\(' + continue + } + + patternListStack.push({ + type: stateChar, + start: i - 1, + reStart: re.length, + open: plTypes[stateChar].open, + close: plTypes[stateChar].close + }) + // negation is (?:(?!js)[^/]*) + re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + this.debug('plType %j %j', stateChar, re) + stateChar = false + continue + + case ')': + if (inClass || !patternListStack.length) { + re += '\\)' + continue + } + + clearStateChar() + hasMagic = true + var pl = patternListStack.pop() + // negation is (?:(?!js)[^/]*) + // The others are (?:<pattern>)<type> + re += pl.close + if (pl.type === '!') { + negativeLists.push(pl) + } + pl.reEnd = re.length + continue + + case '|': + if (inClass || !patternListStack.length || escaping) { + re += '\\|' + escaping = false + continue + } + + clearStateChar() + re += '|' + continue + + // these are mostly the same in regexp and glob + case '[': + // swallow any state-tracking char before the [ + clearStateChar() + + if (inClass) { + re += '\\' + c + continue + } + + inClass = true + classStart = i + reClassStart = re.length + re += c + continue + + case ']': + // a right bracket shall lose its special + // meaning and represent itself in + // a bracket expression if it occurs + // first in the list. -- POSIX.2 2.8.3.2 + if (i === classStart + 1 || !inClass) { + re += '\\' + c + escaping = false + continue + } + + // handle the case where we left a class open. + // "[z-a]" is valid, equivalent to "\[z-a\]" + if (inClass) { + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue + } + } + + // finish up the class. + hasMagic = true + inClass = false + re += c + continue + + default: + // swallow any state char that wasn't consumed + clearStateChar() + + if (escaping) { + // no need + escaping = false + } else if (reSpecials[c] + && !(c === '^' && inClass)) { + re += '\\' + } + + re += c + + } // switch + } // for + + // handle the case where we left a class open. + // "[abc" is valid, equivalent to "\[abc" + if (inClass) { + // split where the last [ was, and escape it + // this is a huge pita. We now have to re-walk + // the contents of the would-be class to re-translate + // any characters that were passed through as-is + cs = pattern.substr(classStart + 1) + sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + hasMagic = hasMagic || sp[1] + } + + // handle the case where we had a +( thing at the *end* + // of the pattern. + // each pattern list stack adds 3 chars, and we need to go through + // and escape any | chars that were passed through as-is for the regexp. + // Go through and escape them, taking care not to double-escape any + // | chars that were already escaped. + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + pl.open.length) + this.debug('setting tail', re, pl) + // maybe some even number of \, then maybe 1 \, followed by a | + tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { + if (!$2) { + // the | isn't already escaped, so escape it. + $2 = '\\' + } + + // need to escape all those slashes *again*, without escaping the + // one that we need for escaping the | character. As it works out, + // escaping an even number of slashes can be done by simply repeating + // it exactly after itself. That's why this trick works. + // + // I am sorry that you have to see this. + return $1 + $1 + $2 + '|' + }) + + this.debug('tail=%j\n %s', tail, tail, pl, re) + var t = pl.type === '*' ? star + : pl.type === '?' ? qmark + : '\\' + pl.type + + hasMagic = true + re = re.slice(0, pl.reStart) + t + '\\(' + tail + } + + // handle trailing things that only matter at the very end. + clearStateChar() + if (escaping) { + // trailing \\ + re += '\\\\' + } + + // only need to apply the nodot start if the re starts with + // something that could conceivably capture a dot + var addPatternStart = false + switch (re.charAt(0)) { + case '.': + case '[': + case '(': addPatternStart = true + } + + // Hack to work around lack of negative lookbehind in JS + // A pattern like: *.!(x).!(y|z) needs to ensure that a name + // like 'a.xyz.yz' doesn't match. So, the first negative + // lookahead, has to look ALL the way ahead, to the end of + // the pattern. + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n] + + var nlBefore = re.slice(0, nl.reStart) + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) + var nlAfter = re.slice(nl.reEnd) + + nlLast += nlAfter + + // Handle nested stuff like *(*.js|!(*.json)), where open parens + // mean that we should *not* include the ) in the bit that is considered + // "after" the negated section. + var openParensBefore = nlBefore.split('(').length - 1 + var cleanAfter = nlAfter + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + } + nlAfter = cleanAfter + + var dollar = '' + if (nlAfter === '' && isSub !== SUBPARSE) { + dollar = '$' + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast + re = newRe + } + + // if the re is not "" at this point, then we need to make sure + // it doesn't match against an empty path part. + // Otherwise a/* will match a/, which it should not. + if (re !== '' && hasMagic) { + re = '(?=.)' + re + } + + if (addPatternStart) { + re = patternStart + re + } + + // parsing just a piece of a larger pattern. + if (isSub === SUBPARSE) { + return [re, hasMagic] + } + + // skip the regexp for non-magical patterns + // unescape anything in it, though, so that it'll be + // an exact match against a file etc. + if (!hasMagic) { + return globUnescape(pattern) + } + + var flags = options.nocase ? 'i' : '' + try { + var regExp = new RegExp('^' + re + '$', flags) + } catch (er) { + // If it was an invalid regular expression, then it can't match + // anything. This trick looks for a character after the end of + // the string, which is of course impossible, except in multi-line + // mode, but it's not a /m regex. + return new RegExp('$.') + } + + regExp._glob = pattern + regExp._src = re + + return regExp +} + +minimatch.makeRe = function (pattern, options) { + return new Minimatch(pattern, options || {}).makeRe() +} + +Minimatch.prototype.makeRe = makeRe +function makeRe () { + if (this.regexp || this.regexp === false) return this.regexp + + // at this point, this.set is a 2d array of partial + // pattern strings, or "**". + // + // It's better to use .match(). This function shouldn't + // be used, really, but it's pretty convenient sometimes, + // when you just want to work with a regex. + var set = this.set + + if (!set.length) { + this.regexp = false + return this.regexp + } + var options = this.options + + var twoStar = options.noglobstar ? star + : options.dot ? twoStarDot + : twoStarNoDot + var flags = options.nocase ? 'i' : '' + + var re = set.map(function (pattern) { + return pattern.map(function (p) { + return (p === GLOBSTAR) ? twoStar + : (typeof p === 'string') ? regExpEscape(p) + : p._src + }).join('\\\/') + }).join('|') + + // must match entire pattern + // ending in a * or ** will make it less strict. + re = '^(?:' + re + ')$' + + // can match anything, as long as it's not this. + if (this.negate) re = '^(?!' + re + ').*$' + + try { + this.regexp = new RegExp(re, flags) + } catch (ex) { + this.regexp = false + } + return this.regexp +} + +minimatch.match = function (list, pattern, options) { + options = options || {} + var mm = new Minimatch(pattern, options) + list = list.filter(function (f) { + return mm.match(f) + }) + if (mm.options.nonull && !list.length) { + list.push(pattern) + } + return list +} + +Minimatch.prototype.match = match +function match (f, partial) { + this.debug('match', f, this.pattern) + // short-circuit in the case of busted things. + // comments, etc. + if (this.comment) return false + if (this.empty) return f === '' + + if (f === '/' && partial) return true + + var options = this.options + + // windows: need to use /, not \ + if (path.sep !== '/') { + f = f.split(path.sep).join('/') + } + + // treat the test path as a set of pathparts. + f = f.split(slashSplit) + this.debug(this.pattern, 'split', f) + + // just ONE of the pattern sets in this.set needs to match + // in order for it to be valid. If negating, then just one + // match means that we have failed. + // Either way, return on the first hit. + + var set = this.set + this.debug(this.pattern, 'set', set) + + // Find the basename of the path by looking for the last non-empty segment + var filename + var i + for (i = f.length - 1; i >= 0; i--) { + filename = f[i] + if (filename) break + } + + for (i = 0; i < set.length; i++) { + var pattern = set[i] + var file = f + if (options.matchBase && pattern.length === 1) { + file = [filename] + } + var hit = this.matchOne(file, pattern, partial) + if (hit) { + if (options.flipNegate) return true + return !this.negate + } + } + + // didn't get any hits. this is success if it's a negative + // pattern, failure otherwise. + if (options.flipNegate) return false + return this.negate +} + +// set partial to true to test if, for example, +// "/a/b" matches the start of "/*/b/*/d" +// Partial means, if you run out of file before you run +// out of pattern, then that's fine, as long as all +// the parts match. +Minimatch.prototype.matchOne = function (file, pattern, partial) { + var options = this.options + + this.debug('matchOne', + { 'this': this, file: file, pattern: pattern }) + + this.debug('matchOne', file.length, pattern.length) + + for (var fi = 0, + pi = 0, + fl = file.length, + pl = pattern.length + ; (fi < fl) && (pi < pl) + ; fi++, pi++) { + this.debug('matchOne loop') + var p = pattern[pi] + var f = file[fi] + + this.debug(pattern, p, f) + + // should be impossible. + // some invalid regexp stuff in the set. + if (p === false) return false + + if (p === GLOBSTAR) { + this.debug('GLOBSTAR', [pattern, p, f]) + + // "**" + // a/**/b/**/c would match the following: + // a/b/x/y/z/c + // a/x/y/z/b/c + // a/b/x/b/x/c + // a/b/c + // To do this, take the rest of the pattern after + // the **, and see if it would match the file remainder. + // If so, return success. + // If not, the ** "swallows" a segment, and try again. + // This is recursively awful. + // + // a/**/b/**/c matching a/b/x/y/z/c + // - a matches a + // - doublestar + // - matchOne(b/x/y/z/c, b/**/c) + // - b matches b + // - doublestar + // - matchOne(x/y/z/c, c) -> no + // - matchOne(y/z/c, c) -> no + // - matchOne(z/c, c) -> no + // - matchOne(c, c) yes, hit + var fr = fi + var pr = pi + 1 + if (pr === pl) { + this.debug('** at the end') + // a ** at the end will just swallow the rest. + // We have found a match. + // however, it will not swallow /.x, unless + // options.dot is set. + // . and .. are *never* matched by **, for explosively + // exponential reasons. + for (; fi < fl; fi++) { + if (file[fi] === '.' || file[fi] === '..' || + (!options.dot && file[fi].charAt(0) === '.')) return false + } + return true + } + + // ok, let's see if we can swallow whatever we can. + while (fr < fl) { + var swallowee = file[fr] + + this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) + + // XXX remove this slice. Just pass the start index. + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug('globstar found match!', fr, fl, swallowee) + // found a match. + return true + } else { + // can't swallow "." or ".." ever. + // can only swallow ".foo" when explicitly asked. + if (swallowee === '.' || swallowee === '..' || + (!options.dot && swallowee.charAt(0) === '.')) { + this.debug('dot detected!', file, fr, pattern, pr) + break + } + + // ** swallows a segment, and continue. + this.debug('globstar swallow a segment, and continue') + fr++ + } + } + + // no match was found. + // However, in partial mode, we can't say this is necessarily over. + // If there's more *pattern* left, then + if (partial) { + // ran out of file + this.debug('\n>>> no match, partial?', file, fr, pattern, pr) + if (fr === fl) return true + } + return false + } + + // something other than ** + // non-magic patterns just have to match exactly + // patterns with magic have been turned into regexps. + var hit + if (typeof p === 'string') { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase() + } else { + hit = f === p + } + this.debug('string match', p, f, hit) + } else { + hit = f.match(p) + this.debug('pattern match', p, f, hit) + } + + if (!hit) return false + } + + // Note: ending in / means that we'll get a final "" + // at the end of the pattern. This can only match a + // corresponding "" at the end of the file. + // If the file ends in /, then it can only match a + // a pattern that ends in /, unless the pattern just + // doesn't have any more for it. But, a/b/ should *not* + // match "a/b/*", even though "" matches against the + // [^/]*? pattern, except in partial mode, where it might + // simply not be reached yet. + // However, a/b/ should still satisfy a/* + + // now either we fell off the end of the pattern, or we're done. + if (fi === fl && pi === pl) { + // ran out of pattern and filename at the same time. + // an exact hit! + return true + } else if (fi === fl) { + // ran out of file, but still had pattern left. + // this is ok if we're doing the match as part of + // a glob fs traversal. + return partial + } else if (pi === pl) { + // ran out of pattern, still have file left. + // this is only acceptable if we're on the very last + // empty segment of a file with a trailing slash. + // a/* should match a/b/ + var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') + return emptyFileEnd + } + + // should be unreachable. + throw new Error('wtf?') +} + +// replace stuff like \* with * +function globUnescape (s) { + return s.replace(/\\(.)/g, '$1') +} + +function regExpEscape (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') +} diff --git a/node_modules/dir-compare/node_modules/minimatch/package.json b/node_modules/dir-compare/node_modules/minimatch/package.json new file mode 100644 index 0000000..c4514c8 --- /dev/null +++ b/node_modules/dir-compare/node_modules/minimatch/package.json @@ -0,0 +1,30 @@ +{ + "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)", + "name": "minimatch", + "description": "a glob matcher in javascript", + "version": "3.0.4", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/minimatch.git" + }, + "main": "minimatch.js", + "scripts": { + "test": "tap test/*.js --cov", + "preversion": "npm test", + "postversion": "npm publish", + "postpublish": "git push origin --all; git push origin --tags" + }, + "engines": { + "node": "*" + }, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "devDependencies": { + "tap": "^10.3.2" + }, + "license": "ISC", + "files": [ + "minimatch.js" + ] +} diff --git a/node_modules/dir-compare/package.json b/node_modules/dir-compare/package.json new file mode 100644 index 0000000..d5ccb56 --- /dev/null +++ b/node_modules/dir-compare/package.json @@ -0,0 +1,59 @@ +{ + "name": "dir-compare", + "version": "2.4.0", + "description": "Node JS directory compare", + "main": "src/index.js", + "types": "src/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/gliviu/dir-compare" + }, + "keywords": [ + "compare", + "directory", + "folder" + ], + "files": [ + "src" + ], + "scripts": { + "clean": "rm -rf build && rm -rf .nyc_output && rm -rf coverage", + "copydeps": "copyfiles \"test/expected/**\" test/testdir.tar \"test/extended/res/**\" package.json build", + "build": "tsc && npm run copydeps", + "lint": "tslint -p tsconfig.json", + "pretest": "npm install && npm run build", + "test": "node build/test/runTests.js", + "extest": "npm run pretest && ./test/extended/init.sh && test/extended/runall.sh", + "coverage": "npx nyc --exclude \"build/test/**\" --reporter=lcov npm test && npx nyc report", + "toc": "npx markdown-toc README.md; echo \n", + "docs": "typedoc --includeVersion --includeDeclarations --excludeExternals --theme minimal --mode file --readme none --gitRevision master --toc compare,compareSync,fileCompareHandlers,Options,Result --out docs ./src/index.d.ts" + }, + "dependencies": { + "buffer-equal": "1.0.0", + "colors": "1.0.3", + "commander": "2.9.0", + "minimatch": "3.0.4" + }, + "devDependencies": { + "@types/node": "^12.11.7", + "copyfiles": "^1.2.0", + "memory-streams": "0.1.0", + "semver": "5.6.0", + "shelljs": "0.3.0", + "tar-fs": "2.1.1", + "temp": "0.9.0", + "ts-node": "^8.5.4", + "tslint": "^5.20.0", + "tslint-config-prettier": "^1.13.0", + "typedoc": "0.19.2", + "typescript": "^3.7.4" + }, + "bin": { + "dircompare": "src/cli/dircompare.js" + }, + "author": "Liviu Grigorescu", + "license": "MIT", + "bugs": { + "url": "https://github.com/gliviu/dir-compare/issues" + } +} diff --git a/node_modules/dir-compare/src/cli/dircompare.js b/node_modules/dir-compare/src/cli/dircompare.js new file mode 100755 index 0000000..593621d --- /dev/null +++ b/node_modules/dir-compare/src/cli/dircompare.js @@ -0,0 +1,147 @@ +#!/usr/bin/env node + +var program = require('commander') +var dircompare = require('../index') +var fs = require('fs') +var util = require('util') +var print = require('./print') +var pjson = require('../../package.json') + +program + .version(pjson.version) + .usage('[options] leftdir rightdir') + .option('-c, --compare-content', 'compare files by content') + .option('-D, --compare-date', 'compare files by date') + .option('--date-tolerance [type]', 'tolerance to be used in date comparison (milliseconds)') + .option('--compare-symlink', 'compare files and directories by symlink') + .option('-f, --filter [type]', 'file name filter', undefined) + .option('-x, --exclude [type]', 'file/directory name exclude filter', undefined) + .option('-S, --skip-subdirs', 'do not recurse into subdirectories') + .option('-L, --skip-symlinks', 'ignore symlinks') + .option('-i, --ignore-case', 'ignores case when comparing file names') + .option('-l, --show-left', 'report - show entries occurring in left dir') + .option('-r, --show-right', 'report - show entries occurring in right dir') + .option('-e, --show-equal', 'report - show identic entries occurring in both dirs') + .option('-d, --show-distinct', 'report - show distinct entries occurring in both dirs') + .option('-a, --show-all', 'report - show all entries') + .option('-w, --whole-report', 'report - include directories in detailed report') + .option('--reason', 'report - show reason when entries are distinct') + .option('--csv', 'report - print details as csv') + .option('--nocolors', 'don\'t use console colors') + .option('--async', 'Make use of multiple cores') + + +program.on('--help', function () { + console.log(' By default files are compared by size.') + console.log(' --date-tolerance defaults to 1000 ms. Two files are considered to have') + console.log(' the same date if the difference between their modification dates fits') + console.log(' within date tolerance.') + console.log() + console.log(' Exit codes:') + console.log(' 0 - entries are identical') + console.log(' 1 - entries are different') + console.log(' 2 - error occurred') + console.log() + console.log(' Examples:') + console.log(' compare by content dircompare -c dir1 dir2') + console.log(' show only different files dircompare -d dir1 dir2') + console.log() + console.log(' exclude filter dircompare -x ".git,node_modules" dir1 dir2') + console.log(' dircompare -x "/tests/expected" dir1 dir2') + console.log(' dircompare -x "**/expected" dir1 dir2') + console.log(' dircompare -x "**/tests/**/*.ts" dir1 dir2') + console.log() + console.log(' include filter dircompare -f "*.js,*.yml" dir1 dir2') + console.log(' dircompare -f "/tests/**/*.js" dir1 dir2') + console.log(' dircompare -f "**/tests/**/*.ts" dir1 dir2') +}) + +// Fix for https://github.com/tj/commander.js/issues/125 +program.allowUnknownOption() +program.parse(process.argv) +var parsed = program.parseOptions(program.normalize(process.argv.slice(2))) +if (parsed.unknown.length > 0) { + console.error('Unknown options: ' + parsed.unknown) + process.exit(2) +} + +var run = function () { + try { + if (program.args.length !== 2) { + program.outputHelp() + process.exit(2) + } else { + var options = {} + + + options.compareContent = program.compareContent + options.compareDate = program.compareDate + options.compareSymlink = program.compareSymlink + options.compareSize = true + options.skipSubdirs = program.skipSubdirs + options.skipSymlinks = program.skipSymlinks + options.ignoreCase = program.ignoreCase + options.includeFilter = program.filter + options.excludeFilter = program.exclude + options.noDiffSet = !(program.showAll || program.showEqual || program.showLeft || program.showRight || program.showDistinct) + options.dateTolerance = program.dateTolerance || 1000 + + var async = program.async + + var path1 = program.args[0] + var path2 = program.args[1] + var abort = false + if (!isNumeric(options.dateTolerance)) { + console.error("Numeric value expected for --date-tolerance") + abort = true + } + if (!fs.existsSync(path1)) { + console.error(util.format("Path '%s' missing"), path1) + abort = true + } + if (!fs.existsSync(path2)) { + console.error(util.format("Path '%s' missing"), path2) + abort = true + } + if (!abort) { + // compare + var comparePromise + if (async) { + comparePromise = dircompare.compare(path1, path2, options) + } else { + comparePromise = new Promise(function (resolve, reject) { + resolve(dircompare.compareSync(path1, path2, options)) + }) + } + + comparePromise.then( + function (res) { + // PRINT DETAILS + print(res, process.stdout, program) + if (res.same) { + process.exit(0) + } else { + process.exit(1) + } + }, + function (error) { + console.error('Error occurred: ' + (error instanceof Error ? error.stack : error)) + process.exit(2) + }) + } else { + process.exit(2) + } + } + } catch (e) { + console.error(e.stack) + process.exit(2) + } +} + +function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n) +} + + + +run() diff --git a/node_modules/dir-compare/src/cli/print.js b/node_modules/dir-compare/src/cli/print.js new file mode 100644 index 0000000..9861896 --- /dev/null +++ b/node_modules/dir-compare/src/cli/print.js @@ -0,0 +1,192 @@ +var colors = require('colors') +var util = require('util') +var pathUtils = require('path') + +var PATH_SEP = pathUtils.sep + +// Prints dir compare results. +// 'program' represents display options and correspond to dircompare command line parameters. +// Example: 'dircompare --show-all --exclude *.js dir1 dir2' translates into +// program: {showAll: true, exclude: '*.js'} +// +var print = function (res, writer, program) { + var noColor = function (str) { return str } + var colorEqual = program.nocolors ? noColor : colors.green + var colorDistinct = program.nocolors ? noColor : colors.red + var colorLeft = noColor + var colorRight = noColor + var colorDir = noColor + var colorBrokenLinks = noColor + var colorMissing = program.nocolors ? noColor : colors.yellow + + // calculate relative path length for pretty print + var relativePathMaxLength = 0, fileNameMaxLength = 0 + if (!program.csv && res.diffSet) { + res.diffSet.forEach(function (diff) { + if (diff.relativePath.length > relativePathMaxLength) { + relativePathMaxLength = diff.relativePath.length + } + var len = getCompareFile(diff, '??', colorMissing).length + if (len > fileNameMaxLength) { + fileNameMaxLength = len + } + }) + } + + // csv header + if (program.csv) { + writer.write('path,name,state,type,size1,size2,date1,date2,reason\n') + } + if (res.diffSet) { + for (var i = 0; i < res.diffSet.length; i++) { + var detail = res.diffSet[i] + var color, show = true + + if (!program.wholeReport) { + // show only files or broken links + var type = detail.type1 !== 'missing' ? detail.type1 : detail.type2 + if (type !== 'file' && type !== 'broken-link') { + show = false + } + } + if (show) { + switch (detail.state) { + case 'equal': + color = colorEqual + show = program.showAll || program.showEqual ? true : false + break + case 'left': + color = colorLeft + show = program.showAll || program.showLeft ? true : false + break + case 'right': + color = colorRight + show = program.showAll || program.showRight ? true : false + break + case 'distinct': + color = colorDistinct + show = program.showAll || program.showDistinct ? true : false + break + default: + show = true + color = colors.gray + } + if (show) { + if (program.csv) { + printCsv(writer, detail, color) + } else { + printPretty(writer, program, detail, color, colorDir, colorMissing, relativePathMaxLength, fileNameMaxLength) + } + } + } + } + } + + // PRINT STATISTICS + var statTotal, statEqual, statLeft, statRight, statDistinct + if (program.wholeReport) { + statTotal = res.total + statEqual = res.equal + statLeft = res.left + statRight = res.right + statDistinct = res.distinct + } else { + var brokenLInksStats = res.brokenLinks + statTotal = res.totalFiles + brokenLInksStats.totalBrokenLinks + statEqual = res.equalFiles + statLeft = res.leftFiles + brokenLInksStats.leftBrokenLinks + statRight = res.rightFiles + brokenLInksStats.rightBrokenLinks + statDistinct = res.distinctFiles + brokenLInksStats.distinctBrokenLinks + } + if (!program.noDiffIndicator) { + writer.write(res.same ? colorEqual('Entries are identical\n') : colorDistinct('Entries are different\n')) + } + var stats = util.format('total: %s, equal: %s, distinct: %s, only left: %s, only right: %s', + statTotal, + colorEqual(statEqual), + colorDistinct(statDistinct), + colorLeft(statLeft), + colorRight(statRight) + ) + if (res.brokenLinks.totalBrokenLinks > 0) { + stats += util.format(', broken links: %s', colorBrokenLinks(res.brokenLinks.totalBrokenLinks)) + } + stats += '\n' + writer.write(stats) +} + +/** + * Print details for default view mode + */ +var printPretty = function (writer, program, detail, color, dirColor, missingColor, relativePathMaxLength, fileNameMaxLength) { + var path = detail.relativePath === '' ? PATH_SEP : detail.relativePath + + var state + switch (detail.state) { + case 'equal': + state = '==' + break + case 'left': + state = '->' + break + case 'right': + state = '<-' + break + case 'distinct': + state = '<>' + break + default: + state = '?' + } + var type = '' + type = detail.type1 !== 'missing' ? detail.type1 : detail.type2 + if (type === 'directory') { + type = dirColor(type) + } + var cmpEntry = getCompareFile(detail, color(state), missingColor) + var reason = '' + if (program.reason && detail.reason) { + reason = util.format(' <%s>', detail.reason) + } + if (program.wholeReport || type === 'broken-link') { + writer.write(util.format('[%s] %s (%s)%s\n', path, cmpEntry, type, reason)) + } else { + writer.write(util.format('[%s] %s%s\n', path, cmpEntry, reason)) + } +} + +var getCompareFile = function (detail, state, missingcolor) { + p1 = detail.name1 ? detail.name1 : '' + p2 = detail.name2 ? detail.name2 : '' + var missing1 = detail.type1 === 'missing' ? missingcolor('missing') : '' + var missing2 = detail.type2 === 'missing' ? missingcolor('missing') : '' + return util.format('%s%s %s %s%s', missing1, p1, state, missing2, p2) +} + +/** + * Print csv details. + */ +var printCsv = function (writer, detail, color) { + var size1 = '', size2 = '' + if (detail.type1 === 'file') { + size1 = detail.size1 !== undefined ? detail.size1 : '' + } + if (detail.type2 === 'file') { + size2 = detail.size2 !== undefined ? detail.size2 : '' + } + + var date1 = '', date2 = '' + date1 = detail.date1 !== undefined ? detail.date1.toISOString() : '' + date2 = detail.date2 !== undefined ? detail.date2.toISOString() : '' + + var type = '' + type = detail.type1 !== 'missing' ? detail.type1 : detail.type2 + + var path = detail.relativePath ? detail.relativePath : PATH_SEP + var name = (detail.name1 ? detail.name1 : detail.name2) + var reason = detail.reason || '' + + writer.write(util.format('%s,%s,%s,%s,%s,%s,%s,%s,%s\n', path, name, color(detail.state), type, size1, size2, date1, date2, reason)) +} + +module.exports = print diff --git a/node_modules/dir-compare/src/compareAsync.js b/node_modules/dir-compare/src/compareAsync.js new file mode 100644 index 0000000..26a9d68 --- /dev/null +++ b/node_modules/dir-compare/src/compareAsync.js @@ -0,0 +1,141 @@ +var fs = require('fs') +var entryBuilder = require('./entry/entryBuilder') +var entryEquality = require('./entry/entryEquality') +var stats = require('./statistics/statisticsUpdate') +var pathUtils = require('path') +var fsPromise = require('./fs/fsPromise') +var loopDetector = require('./symlink/loopDetector') +var entryComparator = require('./entry/entryComparator') +var entryType = require('./entry/entryType') + +/** + * Returns the sorted list of entries in a directory. + */ +var getEntries = function (rootEntry, relativePath, loopDetected, options) { + if (!rootEntry || loopDetected) { + return Promise.resolve([]) + } + if (rootEntry.isDirectory) { + return fsPromise.readdir(rootEntry.absolutePath) + .then(function (entries) { + return entryBuilder.buildDirEntries(rootEntry, entries, relativePath, options) + }) + } + return Promise.resolve([rootEntry]) +} + +/** + * Compares two directories asynchronously. + */ +var compare = function (rootEntry1, rootEntry2, level, relativePath, options, statistics, diffSet, symlinkCache) { + var loopDetected1 = loopDetector.detectLoop(rootEntry1, symlinkCache.dir1) + var loopDetected2 = loopDetector.detectLoop(rootEntry2, symlinkCache.dir2) + loopDetector.updateSymlinkCache(symlinkCache, rootEntry1, rootEntry2, loopDetected1, loopDetected2) + + return Promise.all([getEntries(rootEntry1, relativePath, loopDetected1, options), getEntries(rootEntry2, relativePath, loopDetected2, options)]).then( + function (entriesResult) { + var entries1 = entriesResult[0] + var entries2 = entriesResult[1] + var i1 = 0, i2 = 0 + var comparePromises = [] + var compareFilePromises = [] + var subDiffSet + + while (i1 < entries1.length || i2 < entries2.length) { + var entry1 = entries1[i1] + var entry2 = entries2[i2] + var type1, type2 + + // compare entry name (-1, 0, 1) + var cmp + if (i1 < entries1.length && i2 < entries2.length) { + cmp = entryComparator.compareEntry(entry1, entry2, options) + type1 = entryType.getType(entry1) + type2 = entryType.getType(entry2) + } else if (i1 < entries1.length) { + type1 = entryType.getType(entry1) + type2 = entryType.getType(undefined) + cmp = -1 + } else { + type1 = entryType.getType(undefined) + type2 = entryType.getType(entry2) + cmp = 1 + } + + // process entry + if (cmp === 0) { + // Both left/right exist and have the same name and type + var compareAsyncRes = entryEquality.isEntryEqualAsync(entry1, entry2, type1, diffSet, options) + var samePromise = compareAsyncRes.samePromise + var same = compareAsyncRes.same + if (same !== undefined) { + options.resultBuilder(entry1, entry2, + same ? 'equal' : 'distinct', + level, relativePath, options, statistics, diffSet, + compareAsyncRes.reason) + stats.updateStatisticsBoth(entry1, entry2, compareAsyncRes.same, compareAsyncRes.reason, type1, statistics, options) + } else { + compareFilePromises.push(samePromise) + } + + i1++ + i2++ + if (!options.skipSubdirs && type1 === 'directory') { + if (!options.noDiffSet) { + subDiffSet = [] + diffSet.push(subDiffSet) + } + comparePromises.push(compare(entry1, entry2, level + 1, + pathUtils.join(relativePath, entry1.name), + options, statistics, subDiffSet, loopDetector.cloneSymlinkCache(symlinkCache))) + } + } else if (cmp < 0) { + // Right missing + options.resultBuilder(entry1, undefined, 'left', level, relativePath, options, statistics, diffSet) + stats.updateStatisticsLeft(entry1, type1, statistics, options) + i1++ + if (type1 === 'directory' && !options.skipSubdirs) { + if (!options.noDiffSet) { + subDiffSet = [] + diffSet.push(subDiffSet) + } + comparePromises.push(compare(entry1, undefined, + level + 1, + pathUtils.join(relativePath, entry1.name), options, statistics, subDiffSet, loopDetector.cloneSymlinkCache(symlinkCache))) + } + } else { + // Left missing + options.resultBuilder(undefined, entry2, 'right', level, relativePath, options, statistics, diffSet) + stats.updateStatisticsRight(entry2, type2, statistics, options) + i2++ + if (type2 === 'directory' && !options.skipSubdirs) { + if (!options.noDiffSet) { + subDiffSet = [] + diffSet.push(subDiffSet) + } + comparePromises.push(compare(undefined, entry2, + level + 1, + pathUtils.join(relativePath, entry2.name), options, statistics, subDiffSet, loopDetector.cloneSymlinkCache(symlinkCache))) + } + } + } + return Promise.all(comparePromises).then(function () { + return Promise.all(compareFilePromises).then(function (sameResults) { + for (var i = 0; i < sameResults.length; i++) { + var sameResult = sameResults[i] + if (sameResult.error) { + return Promise.reject(sameResult.error) + } else { + options.resultBuilder(sameResult.entry1, sameResult.entry2, + sameResult.same ? 'equal' : 'distinct', + level, relativePath, options, statistics, sameResult.diffSet, + sameResult.reason) + stats.updateStatisticsBoth(sameResult.entries1, sameResult.entries2, sameResult.same, sameResult.reason, sameResult.type1, statistics, options) + } + } + }) + }) + }) +} + +module.exports = compare diff --git a/node_modules/dir-compare/src/compareSync.js b/node_modules/dir-compare/src/compareSync.js new file mode 100644 index 0000000..84ff4b3 --- /dev/null +++ b/node_modules/dir-compare/src/compareSync.js @@ -0,0 +1,90 @@ +var fs = require('fs') +var pathUtils = require('path') +var entryBuilder = require('./entry/entryBuilder') +var entryEquality = require('./entry/entryEquality') +var stats = require('./statistics/statisticsUpdate') +var loopDetector = require('./symlink/loopDetector') +var entryComparator = require('./entry/entryComparator') +var entryType = require('./entry/entryType') + +/** + * Returns the sorted list of entries in a directory. + */ +var getEntries = function (rootEntry, relativePath, loopDetected, options) { + if (!rootEntry || loopDetected) { + return [] + } + if (rootEntry.isDirectory) { + var entries = fs.readdirSync(rootEntry.absolutePath) + return entryBuilder.buildDirEntries(rootEntry, entries, relativePath, options) + } + return [rootEntry] +} + +/** + * Compares two directories synchronously. + */ +var compare = function (rootEntry1, rootEntry2, level, relativePath, options, statistics, diffSet, symlinkCache) { + var loopDetected1 = loopDetector.detectLoop(rootEntry1, symlinkCache.dir1) + var loopDetected2 = loopDetector.detectLoop(rootEntry2, symlinkCache.dir2) + loopDetector.updateSymlinkCache(symlinkCache, rootEntry1, rootEntry2, loopDetected1, loopDetected2) + + var entries1 = getEntries(rootEntry1, relativePath, loopDetected1, options) + var entries2 = getEntries(rootEntry2, relativePath, loopDetected2, options) + var i1 = 0, i2 = 0 + while (i1 < entries1.length || i2 < entries2.length) { + var entry1 = entries1[i1] + var entry2 = entries2[i2] + var type1, type2 + + // compare entry name (-1, 0, 1) + var cmp + if (i1 < entries1.length && i2 < entries2.length) { + cmp = entryComparator.compareEntry(entry1, entry2, options) + type1 = entryType.getType(entry1) + type2 = entryType.getType(entry2) + } else if (i1 < entries1.length) { + type1 = entryType.getType(entry1) + type2 = entryType.getType(undefined) + cmp = -1 + } else { + type1 = entryType.getType(undefined) + type2 = entryType.getType(entry2) + cmp = 1 + } + + // process entry + if (cmp === 0) { + // Both left/right exist and have the same name and type + var compareEntryRes = entryEquality.isEntryEqualSync(entry1, entry2, type1, options) + options.resultBuilder(entry1, entry2, + compareEntryRes.same ? 'equal' : 'distinct', + level, relativePath, options, statistics, diffSet, + compareEntryRes.reason) + stats.updateStatisticsBoth(entry1, entry2, compareEntryRes.same, compareEntryRes.reason, type1, statistics, options) + i1++ + i2++ + if (!options.skipSubdirs && type1 === 'directory') { + compare(entry1, entry2, level + 1, pathUtils.join(relativePath, entry1.name), options, statistics, diffSet, loopDetector.cloneSymlinkCache(symlinkCache)) + } + } else if (cmp < 0) { + // Right missing + options.resultBuilder(entry1, undefined, 'left', level, relativePath, options, statistics, diffSet) + stats.updateStatisticsLeft(entry1, type1, statistics, options) + i1++ + if (type1 === 'directory' && !options.skipSubdirs) { + compare(entry1, undefined, level + 1, pathUtils.join(relativePath, entry1.name), options, statistics, diffSet, loopDetector.cloneSymlinkCache(symlinkCache)) + } + } else { + // Left missing + options.resultBuilder(undefined, entry2, 'right', level, relativePath, options, statistics, diffSet) + stats.updateStatisticsRight(entry2, type2, statistics, options) + i2++ + if (type2 === 'directory' && !options.skipSubdirs) { + compare(undefined, entry2, level + 1, pathUtils.join(relativePath, entry2.name), options, statistics, diffSet, loopDetector.cloneSymlinkCache(symlinkCache)) + } + } + } +} + +module.exports = compare diff --git a/node_modules/dir-compare/src/entry/entryBuilder.js b/node_modules/dir-compare/src/entry/entryBuilder.js new file mode 100644 index 0000000..6a46c62 --- /dev/null +++ b/node_modules/dir-compare/src/entry/entryBuilder.js @@ -0,0 +1,102 @@ +var fs = require('fs') +var minimatch = require('minimatch') +var pathUtils = require('path') +var entryComparator = require('./entryComparator') + +var PATH_SEP = pathUtils.sep + +module.exports = { + /** + * Returns the sorted list of entries in a directory. + */ + buildDirEntries: function (rootEntry, dirEntries, relativePath, options) { + var res = [] + for (var i = 0; i < dirEntries.length; i++) { + var entryName = dirEntries[i] + var entryAbsolutePath = rootEntry.absolutePath + PATH_SEP + entryName + var entryPath = rootEntry.path + PATH_SEP + entryName + + var entry = this.buildEntry(entryAbsolutePath, entryPath, entryName) + if (options.skipSymlinks && entry.isSymlink) { + entry.stat = undefined + } + + if (filterEntry(entry, relativePath, options)) { + res.push(entry) + } + } + return res.sort((a, b) => entryComparator.compareEntry(a, b, options)) + }, + + buildEntry: function (absolutePath, path, name) { + var stats = getStatIgnoreBrokenLink(absolutePath) + + return { + name: name, + absolutePath: absolutePath, + path: path, + stat: stats.stat, + lstat: stats.lstat, + isSymlink: stats.lstat.isSymbolicLink(), + isBrokenLink: stats.isBrokenLink, + isDirectory: stats.stat.isDirectory() + } + }, + +} + + +function getStatIgnoreBrokenLink(absolutePath) { + var lstat = fs.lstatSync(absolutePath) + try { + return { + stat: fs.statSync(absolutePath), + lstat: lstat, + isBrokenLink: false + } + } catch (error) { + if (error.code === 'ENOENT') { + return { + stat: lstat, + lstat: lstat, + isBrokenLink: true + } + } + throw error + } +} + +/** + * Filter entries by file name. Returns true if the file is to be processed. + */ +function filterEntry(entry, relativePath, options) { + if (entry.isSymlink && options.skipSymlinks) { + return false + } + var path = pathUtils.join(relativePath, entry.name) + + if ((entry.stat.isFile() && options.includeFilter) && (!match(path, options.includeFilter))) { + return false + } + + if ((options.excludeFilter) && (match(path, options.excludeFilter))) { + return false + } + + return true +} + +/** + * Matches path by pattern. + */ +function match(path, pattern) { + var patternArray = pattern.split(',') + for (var i = 0; i < patternArray.length; i++) { + var pat = patternArray[i] + if (minimatch(path, pat, { dot: true, matchBase: true })) { //nocase + return true + } + } + return false +} + diff --git a/node_modules/dir-compare/src/entry/entryComparator.js b/node_modules/dir-compare/src/entry/entryComparator.js new file mode 100644 index 0000000..d0361f5 --- /dev/null +++ b/node_modules/dir-compare/src/entry/entryComparator.js @@ -0,0 +1,20 @@ +/** + * Determines order criteria for sorting entries in a directory. + */ +module.exports = { + compareEntry: function (a, b, options) { + if (a.isBrokenLink && b.isBrokenLink) { + return options.compareNameHandler(a.name, b.name, options) + } else if (a.isBrokenLink) { + return -1 + } else if (b.isBrokenLink) { + return 1 + } else if (a.stat.isDirectory() && b.stat.isFile()) { + return -1 + } else if (a.stat.isFile() && b.stat.isDirectory()) { + return 1 + } else { + return options.compareNameHandler(a.name, b.name, options) + } + } +} diff --git a/node_modules/dir-compare/src/entry/entryEquality.js b/node_modules/dir-compare/src/entry/entryEquality.js new file mode 100644 index 0000000..f1b8d78 --- /dev/null +++ b/node_modules/dir-compare/src/entry/entryEquality.js @@ -0,0 +1,135 @@ +var fs = require('fs') +/** + * Compares two entries with identical name and type. + */ +module.exports = { + isEntryEqualSync: function (entry1, entry2, type, options) { + if (type === 'file') { + return isFileEqualSync(entry1, entry2, options) + } + if (type === 'directory') { + return isDirectoryEqual(entry1, entry2, options) + } + if (type === 'broken-link') { + return isBrokenLinkEqual() + } + throw new Error('Unexpected type ' + type) + }, + + isEntryEqualAsync: function (entry1, entry2, type, diffSet, options) { + if (type === 'file') { + return isFileEqualAsync(entry1, entry2, type, diffSet, options) + } + if (type === 'directory') { + return isDirectoryEqual(entry1, entry2, options) + } + if (type === 'broken-link') { + return isBrokenLinkEqual() + } + throw new Error('Unexpected type ' + type) + } +} + + +function isFileEqualSync(entry1, entry2, options) { + var p1 = entry1 ? entry1.absolutePath : undefined + var p2 = entry2 ? entry2.absolutePath : undefined + if (options.compareSymlink && !isSymlinkEqual(entry1, entry2)) { + return { same: false, reason: 'different-symlink' } + } + if (options.compareSize && entry1.stat.size !== entry2.stat.size) { + return { same: false, reason: 'different-size' } + } + if (options.compareDate && !isDateEqual(entry1.stat.mtime, entry2.stat.mtime, options.dateTolerance)) { + return { same: false, reason: 'different-date' } + } + if (options.compareContent && !options.compareFileSync(p1, entry1.stat, p2, entry2.stat, options)) { + return { same: false, reason: 'different-content' } + } + return { same: true } +} + +function isFileEqualAsync(entry1, entry2, type, diffSet, options) { + var p1 = entry1 ? entry1.absolutePath : undefined + var p2 = entry2 ? entry2.absolutePath : undefined + if (options.compareSymlink && !isSymlinkEqual(entry1, entry2)) { + return { same: false, reason: 'different-symlink' } + } + if (options.compareSize && entry1.stat.size !== entry2.stat.size) { + return { same: false, samePromise: undefined, reason: 'different-size' } + } + + if (options.compareDate && !isDateEqual(entry1.stat.mtime, entry2.stat.mtime, options.dateTolerance)) { + return { same: false, samePromise: undefined, reason: 'different-date' } + } + + if (options.compareContent) { + var samePromise = undefined + var subDiffSet + if (!options.noDiffSet) { + subDiffSet = [] + diffSet.push(subDiffSet) + } + samePromise = options.compareFileAsync(p1, entry1.stat, p2, entry2.stat, options) + .then(function (comparisonResult) { + var same, error + if (typeof (comparisonResult) === "boolean") { + same = comparisonResult + } else { + error = comparisonResult + } + + return { + entry1: entry1, entry2: entry2, same: same, + error: error, type1: type, type2: type, + diffSet: subDiffSet, + reason: same ? undefined : 'different-content' + } + }) + .catch(function (error) { + return { + error: error + } + }) + + return { same: undefined, samePromise: samePromise } + } + + return { same: true, samePromise: undefined } +} + +function isDirectoryEqual(entry1, entry2, options) { + if (options.compareSymlink && !isSymlinkEqual(entry1, entry2)) { + return { same: false, reason: 'different-symlink' } + } + return { same: true } +} + +function isBrokenLinkEqual() { + return { same: false, reason: 'broken-link' } // broken links are never considered equal +} + +/** + * Compares two dates and returns true/false depending on tolerance (milliseconds). + * Two dates are considered equal if the difference in milliseconds between them is less or equal than tolerance. + */ +function isDateEqual(date1, date2, tolerance) { + return Math.abs(date1.getTime() - date2.getTime()) <= tolerance ? true : false +} + +/** + * Compares two entries for symlink equality. + */ +function isSymlinkEqual(entry1, entry2) { + if (!entry1.isSymlink && !entry2.isSymlink) { + return true + } + if (entry1.isSymlink && entry2.isSymlink && hasIdenticalLink(entry1.absolutePath, entry2.absolutePath)) { + return true + } + return false +} + +function hasIdenticalLink(path1, path2) { + return fs.readlinkSync(path1) === fs.readlinkSync(path2) +}
\ No newline at end of file diff --git a/node_modules/dir-compare/src/entry/entryType.js b/node_modules/dir-compare/src/entry/entryType.js new file mode 100644 index 0000000..5dac42a --- /dev/null +++ b/node_modules/dir-compare/src/entry/entryType.js @@ -0,0 +1,18 @@ + +module.exports = { + /** + * One of 'missing','file','directory','broken-link' + */ + getType: function (entry) { + if (!entry) { + return 'missing' + } + if (entry.isBrokenLink) { + return 'broken-link' + } + if (entry.isDirectory) { + return 'directory' + } + return 'file' + } +}
\ No newline at end of file diff --git a/node_modules/dir-compare/src/fileCompareHandler/closeFile.js b/node_modules/dir-compare/src/fileCompareHandler/closeFile.js new file mode 100644 index 0000000..10118e1 --- /dev/null +++ b/node_modules/dir-compare/src/fileCompareHandler/closeFile.js @@ -0,0 +1,28 @@ +var fs = require('fs') + +var closeFilesSync = function (fd1, fd2) { + if (fd1) { + fs.closeSync(fd1) + } + if (fd2) { + fs.closeSync(fd2) + } +} + +var closeFilesAsync = function (fd1, fd2, fdQueue) { + if (fd1 && fd2) { + return fdQueue.promises.close(fd1).then(() => fdQueue.promises.close(fd2)) + } + if (fd1) { + return fdQueue.promises.close(fd1) + } + if (fd2) { + return fdQueue.promises.close(fd2) + } +} + + +module.exports = { + closeFilesSync: closeFilesSync, + closeFilesAsync: closeFilesAsync +} diff --git a/node_modules/dir-compare/src/fileCompareHandler/defaultFileCompare.js b/node_modules/dir-compare/src/fileCompareHandler/defaultFileCompare.js new file mode 100644 index 0000000..26188ca --- /dev/null +++ b/node_modules/dir-compare/src/fileCompareHandler/defaultFileCompare.js @@ -0,0 +1,113 @@ +var fs = require('fs') +var bufferEqual = require('buffer-equal') +var FileDescriptorQueue = require('../fs/FileDescriptorQueue') +var closeFilesSync = require('./closeFile').closeFilesSync +var closeFilesAsync = require('./closeFile').closeFilesAsync +var fsPromise = require('../fs/fsPromise') +var BufferPool = require('../fs/BufferPool') + +var MAX_CONCURRENT_FILE_COMPARE = 8 +var BUF_SIZE = 100000 +var fdQueue = new FileDescriptorQueue(MAX_CONCURRENT_FILE_COMPARE * 2) +var bufferPool = new BufferPool(BUF_SIZE, MAX_CONCURRENT_FILE_COMPARE); // fdQueue guarantees there will be no more than MAX_CONCURRENT_FILE_COMPARE async processes accessing the buffers concurrently + + +/** + * Compares two partial buffers. + */ +var compareBuffers = function (buf1, buf2, contentSize) { + return bufferEqual(buf1.slice(0, contentSize), buf2.slice(0, contentSize)) +} + +/** + * Compares two files by content. + */ +var compareSync = function (path1, stat1, path2, stat2, options) { + var fd1, fd2 + if (stat1.size !== stat2.size) { + return false + } + var bufferPair = bufferPool.allocateBuffers() + try { + fd1 = fs.openSync(path1, 'r') + fd2 = fs.openSync(path2, 'r') + var buf1 = bufferPair.buf1 + var buf2 = bufferPair.buf2 + var progress = 0 + while (true) { + var size1 = fs.readSync(fd1, buf1, 0, BUF_SIZE, null) + var size2 = fs.readSync(fd2, buf2, 0, BUF_SIZE, null) + if (size1 !== size2) { + return false + } else if (size1 === 0) { + // End of file reached + return true + } else if (!compareBuffers(buf1, buf2, size1)) { + return false + } + } + } finally { + closeFilesSync(fd1, fd2) + bufferPool.freeBuffers(bufferPair) + } +} + + +/** + * Compares two files by content + */ +var compareAsync = function (path1, stat1, path2, stat2, options) { + var fd1, fd2 + var bufferPair + if (stat1.size !== stat2.size) { + return Promise.resolve(false) + } + return Promise.all([fdQueue.promises.open(path1, 'r'), fdQueue.promises.open(path2, 'r')]) + .then(function (fds) { + bufferPair = bufferPool.allocateBuffers() + fd1 = fds[0] + fd2 = fds[1] + var buf1 = bufferPair.buf1 + var buf2 = bufferPair.buf2 + var progress = 0 + var compareAsyncInternal = function () { + return Promise.all([ + fsPromise.read(fd1, buf1, 0, BUF_SIZE, null), + fsPromise.read(fd2, buf2, 0, BUF_SIZE, null) + ]).then(function (bufferSizes) { + var size1 = bufferSizes[0] + var size2 = bufferSizes[1] + if (size1 !== size2) { + return false + } else if (size1 === 0) { + // End of file reached + return true + } else if (!compareBuffers(buf1, buf2, size1)) { + return false + } else { + return compareAsyncInternal() + } + }) + } + return compareAsyncInternal() + }) + .then( + // 'finally' polyfill for node 8 and below + function (res) { + return finalizeAsync(fd1, fd2, bufferPair).then(() => res) + }, + function (err) { + return finalizeAsync(fd1, fd2, bufferPair).then(() => { throw err; }) + } + ) +} + +function finalizeAsync(fd1, fd2, bufferPair) { + bufferPool.freeBuffers(bufferPair) + return closeFilesAsync(fd1, fd2, fdQueue) +} + +module.exports = { + compareSync: compareSync, + compareAsync: compareAsync +} diff --git a/node_modules/dir-compare/src/fileCompareHandler/lineBasedFileCompare.js b/node_modules/dir-compare/src/fileCompareHandler/lineBasedFileCompare.js new file mode 100644 index 0000000..c2de4b2 --- /dev/null +++ b/node_modules/dir-compare/src/fileCompareHandler/lineBasedFileCompare.js @@ -0,0 +1,196 @@ +/** + * Compare files line by line with options to ignore + * line endings and white space differencies. + */ +var fs = require('fs') +var FileDescriptorQueue = require('../fs/FileDescriptorQueue') +var closeFilesSync = require('./closeFile').closeFilesSync +var closeFilesAsync = require('./closeFile').closeFilesAsync +var fsPromise = require('../fs/fsPromise') +var BufferPool = require('../fs/BufferPool') + +const LINE_TOKENIZER_REGEXP = /[^\n]+\n?|\n/g +const TRIM_LINE_ENDING_REGEXP = /\r\n$/g +const SPLIT_CONTENT_AND_LINE_ENDING_REGEXP = /([^\r\n]*)([\r\n]*)/ +const TRIM_WHITE_SPACES_REGEXP = /^[ \f\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+|[ \f\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+$/g + +var MAX_CONCURRENT_FILE_COMPARE = 8 +var BUF_SIZE = 100000 +var fdQueue = new FileDescriptorQueue(MAX_CONCURRENT_FILE_COMPARE * 2) +var bufferPool = new BufferPool(BUF_SIZE, MAX_CONCURRENT_FILE_COMPARE); // fdQueue guarantees there will be no more than MAX_CONCURRENT_FILE_COMPARE async processes accessing the buffers concurrently + +function compareSync(path1, stat1, path2, stat2, options) { + var fd1, fd2 + var bufferPair = bufferPool.allocateBuffers() + var bufferSize = options.lineBasedHandlerBufferSize || BUF_SIZE + try { + fd1 = fs.openSync(path1, 'r') + fd2 = fs.openSync(path2, 'r') + var buf1 = bufferPair.buf1 + var buf2 = bufferPair.buf2 + var nextPosition1 = 0, nextPosition2 = 0 + while (true) { + var lines1 = readLinesSync(fd1, buf1, bufferSize, nextPosition1) + var lines2 = readLinesSync(fd2, buf2, bufferSize, nextPosition2) + if (lines1.length === 0 && lines2.length === 0) { + // End of file reached + return true + } + var equalLines = compareLines(lines1, lines2, options) + if (equalLines === 0) { + return false + } + nextPosition1 += calculateSize(lines1, equalLines) + nextPosition2 += calculateSize(lines2, equalLines) + } + } finally { + closeFilesSync(fd1, fd2) + bufferPool.freeBuffers(bufferPair) + } +} + +async function compareAsync(path1, stat1, path2, stat2, options) { + var fd1, fd2 + var bufferSize = options.lineBasedHandlerBufferSize || BUF_SIZE + var bufferPair + try { + var fds = await Promise.all([fdQueue.promises.open(path1, 'r'), fdQueue.promises.open(path2, 'r')]) + bufferPair = bufferPool.allocateBuffers() + fd1 = fds[0] + fd2 = fds[1] + var buf1 = bufferPair.buf1 + var buf2 = bufferPair.buf2 + var nextPosition1 = 0, nextPosition2 = 0 + while (true) { + var lines1 = await readLinesAsync(fd1, buf1, bufferSize, nextPosition1) + var lines2 = await readLinesAsync(fd2, buf2, bufferSize, nextPosition2) + if (lines1.length === 0 && lines2.length === 0) { + // End of file reached + return true + } + var equalLines = compareLines(lines1, lines2, options) + if (equalLines === 0) { + return false + } + nextPosition1 += calculateSize(lines1, equalLines) + nextPosition2 += calculateSize(lines2, equalLines) + } + } finally { + bufferPool.freeBuffers(bufferPair) + await closeFilesAsync(fd1, fd2, fdQueue) + } +} + +/** + * Read lines from file starting with nextPosition. + * Returns 0 lines if eof is reached, otherwise returns at least one complete line. + */ +function readLinesSync(fd, buf, bufferSize, nextPosition) { + var lines = [] + var chunk = "" + while (true) { + var size = fs.readSync(fd, buf, 0, bufferSize, nextPosition) + if (size === 0) { + // end of file + normalizeLastFileLine(lines) + return lines + } + chunk += buf.toString('utf8', 0, size) + lines = chunk.match(LINE_TOKENIZER_REGEXP) + if (lines.length > 1) { + return removeLastIncompleteLine(lines) + } + nextPosition += size + } +} + +/** + * Read lines from file starting with nextPosition. + * Returns 0 lines if eof is reached, otherwise returns at least one complete line. + */ +async function readLinesAsync(fd, buf, bufferSize, nextPosition) { + var lines = [] + var chunk = "" + while (true) { + var size = await fsPromise.read(fd, buf, 0, bufferSize, nextPosition) + if (size === 0) { + // end of file + normalizeLastFileLine(lines) + return lines + } + chunk += buf.toString('utf8', 0, size) + lines = chunk.match(LINE_TOKENIZER_REGEXP) + if (lines.length > 1) { + return removeLastIncompleteLine(lines) + } + nextPosition += size + } +} + +function removeLastIncompleteLine(lines) { + const lastLine = lines[lines.length - 1] + if (!lastLine.endsWith('\n')) { + return lines.slice(0, lines.length - 1) + } + return lines +} + +function normalizeLastFileLine(lines) { + if (lines.length === 0) { + return + } + const lastLine = lines[lines.length - 1] + if (!lastLine.endsWith('\n')) { + lines[lines.length - 1] = lastLine + '\n' + } +} + +function calculateSize(lines, numberOfLines) { + var size = 0 + for (var i = 0; i < numberOfLines; i++) { + var line = lines[i] + size += line.length + } + return size +} + +function compareLines(lines1, lines2, options) { + var equalLines = 0 + var len = lines1.length < lines2.length ? lines1.length : lines2.length + for (var i = 0; i < len; i++) { + var line1 = lines1[i] + var line2 = lines2[i] + if (options.ignoreLineEnding) { + line1 = trimLineEnding(line1) + line2 = trimLineEnding(line2) + } + if (options.ignoreWhiteSpaces) { + line1 = trimSpaces(line1) + line2 = trimSpaces(line2) + } + if (line1 !== line2) { + return equalLines + } + equalLines++ + } + return equalLines +} + +// Trims string like ' abc \n' into 'abc\n' +function trimSpaces(s) { + var matchResult = s.match(SPLIT_CONTENT_AND_LINE_ENDING_REGEXP); + var content = matchResult[1] + var lineEnding = matchResult[2] + var trimmed = content.replace(TRIM_WHITE_SPACES_REGEXP, '') + return trimmed + lineEnding +} + +// Trims string like 'abc\r\n' into 'abc\n' +function trimLineEnding(s) { + return s.replace(TRIM_LINE_ENDING_REGEXP, '\n') +} + +module.exports = { + compareSync: compareSync, + compareAsync: compareAsync +} diff --git a/node_modules/dir-compare/src/fs/BufferPool.js b/node_modules/dir-compare/src/fs/BufferPool.js new file mode 100644 index 0000000..48febce --- /dev/null +++ b/node_modules/dir-compare/src/fs/BufferPool.js @@ -0,0 +1,46 @@ +/** + * Collection of buffers to be shared between async processes. + * Avoids allocating buffers each time async process starts. + * bufSize - size of each buffer + * bufNo - number of buffers + * Caller has to make sure no more than bufNo async processes run simultaneously. + */ +function BufferPool(bufSize, bufNo) { + var bufferPool = [] + for (var i = 0; i < bufNo; i++) { + bufferPool.push({ + buf1: alloc(bufSize), + buf2: alloc(bufSize), + busy: false + }) + } + + var allocateBuffers = function () { + for (var j = 0; j < bufNo; j++) { + var bufferPair = bufferPool[j] + if (!bufferPair.busy) { + bufferPair.busy = true + return bufferPair + } + } + throw new Error('Async buffer limit reached') + } + + return { + allocateBuffers: allocateBuffers, + freeBuffers: freeBuffers + } + + function freeBuffers(bufferPair) { + bufferPair.busy = false + } +} + +function alloc(bufSize) { + if (Buffer.alloc) { + return Buffer.alloc(bufSize) + } + return new Buffer(bufSize) +} + +module.exports = BufferPool diff --git a/node_modules/dir-compare/src/fs/FileDescriptorQueue.js b/node_modules/dir-compare/src/fs/FileDescriptorQueue.js new file mode 100644 index 0000000..f017b20 --- /dev/null +++ b/node_modules/dir-compare/src/fs/FileDescriptorQueue.js @@ -0,0 +1,78 @@ +'use strict' + +var fs = require('fs') +var Queue = require('./Queue') +/** + * Limits the number of concurrent file handlers. + * Use it as a wrapper over fs.open() and fs.close(). + * Example: + * var fdQueue = new FileDescriptorQueue(8) + * fdQueue.open(path, flags, (err, fd) =>{ + * ... + * fdQueue.close(fd, (err) =>{ + * ... + * }) + * }) + * As of node v7, calling fd.close without a callback is deprecated. + */ +var FileDescriptorQueue = function (maxFilesNo) { + var pendingJobs = new Queue() + var activeCount = 0 + + var open = function (path, flags, callback) { + pendingJobs.enqueue({ + path: path, + flags: flags, + callback: callback + }) + process() + } + + var process = function () { + if (pendingJobs.getLength() > 0 && activeCount < maxFilesNo) { + var job = pendingJobs.dequeue() + activeCount++ + fs.open(job.path, job.flags, job.callback) + } + } + + var close = function (fd, callback) { + activeCount-- + fs.close(fd, callback) + process() + } + + var promises = { + open: function (path, flags) { + return new Promise(function (resolve, reject) { + open(path, flags, function (err, fd) { + if (err) { + reject(err) + } else { + resolve(fd) + } + }) + }) + }, + + close: function (fd) { + return new Promise(function (resolve, reject) { + close(fd, function (err) { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + } + } + + return { + open: open, + close: close, + promises: promises + } +} + +module.exports = FileDescriptorQueue diff --git a/node_modules/dir-compare/src/fs/Queue.js b/node_modules/dir-compare/src/fs/Queue.js new file mode 100644 index 0000000..4d86dd5 --- /dev/null +++ b/node_modules/dir-compare/src/fs/Queue.js @@ -0,0 +1,63 @@ +/* + +Queue.js + +A function to represent a queue + +Created by Kate Morley - http://code.iamkate.com/ - and released under the terms +of the CC0 1.0 Universal legal code: + +http://creativecommons.org/publicdomain/zero/1.0/legalcode + +*/ + +var MAX_UNUSED_ARRAY_SIZE = 10000 + +/* Creates a new queue. A queue is a first-in-first-out (FIFO) data structure - + * items are added to the end of the queue and removed from the front. + */ +function Queue() { + + // initialise the queue and offset + var queue = [] + var offset = 0 + + // Returns the length of the queue. + this.getLength = function () { + return (queue.length - offset) + } + + /* Enqueues the specified item. The parameter is: + * + * item - the item to enqueue + */ + this.enqueue = function (item) { + queue.push(item) + } + + /* Dequeues an item and returns it. If the queue is empty, the value + * 'undefined' is returned. + */ + this.dequeue = function () { + + // if the queue is empty, return immediately + if (queue.length === 0) { + return undefined + } + + // store the item at the front of the queue + var item = queue[offset] + + // increment the offset and remove the free space if necessary + if (++offset > MAX_UNUSED_ARRAY_SIZE) { + queue = queue.slice(offset) + offset = 0 + } + + // return the dequeued item + return item + + } +} + +module.exports = Queue diff --git a/node_modules/dir-compare/src/fs/fsPromise.js b/node_modules/dir-compare/src/fs/fsPromise.js new file mode 100644 index 0000000..f6e3c5a --- /dev/null +++ b/node_modules/dir-compare/src/fs/fsPromise.js @@ -0,0 +1,26 @@ +var fs = require('fs') + +module.exports = { + readdir: function (path) { + return new Promise(function (resolve, reject) { + fs.readdir(path, function (err, files) { + if (err) { + reject(err) + } else { + resolve(files) + } + }) + }) + }, + read: function (fd, buffer, offset, length, position) { + return new Promise(function (resolve, reject) { + fs.read(fd, buffer, offset, length, position, function(err, bytesRead) { + if(err){ + reject(err) + } else { + resolve(bytesRead) + } + }) + }) + }, +} diff --git a/node_modules/dir-compare/src/index.d.ts b/node_modules/dir-compare/src/index.d.ts new file mode 100644 index 0000000..fc84980 --- /dev/null +++ b/node_modules/dir-compare/src/index.d.ts @@ -0,0 +1,476 @@ +/// <reference types="node" /> + +import * as fs from "fs" + +/** + * Synchronously compares given paths. + * @param path1 Left file or directory to be compared. + * @param path2 Right file or directory to be compared. + * @param options Comparison options. + */ +export function compareSync(path1: string, path2: string, options?: Options): Result + +/** + * Asynchronously compares given paths. + * @param path1 Left file or directory to be compared. + * @param path2 Right file or directory to be compared. + * @param options Comparison options. + */ +export function compare(path1: string, path2: string, options?: Options): Promise<Result> + +/** + * Comparison options. + */ +export interface Options { + /** + * Properties to be used in various extension points ie. result builder. + */ + [key: string]: any + + /** + * Compares files by size. Defaults to 'false'. + */ + compareSize?: boolean + + /** + * Compares files by date of modification (stat.mtime). Defaults to 'false'. + */ + compareDate?: boolean + + /** + * Two files are considered to have the same date if the difference between their modification dates fits within date tolerance. Defaults to 1000 ms. + */ + dateTolerance?: number + + /** + * Compares files by content. Defaults to 'false'. + */ + compareContent?: boolean + + /** + * Compares entries by symlink. Defaults to 'false'. + */ + compareSymlink?: boolean + + /** + * Skips sub directories. Defaults to 'false'. + */ + skipSubdirs?: boolean + + /** + * Ignore symbolic links. Defaults to 'false'. + */ + skipSymlinks?: boolean + + /** + * Ignores case when comparing names. Defaults to 'false'. + */ + ignoreCase?: boolean + + /** + * Toggles presence of diffSet in output. If true, only statistics are provided. Use this when comparing large number of files to avoid out of memory situations. Defaults to 'false'. + */ + noDiffSet?: boolean + + /** + * File name filter. Comma separated minimatch patterns. See [Glob patterns](https://github.com/gliviu/dir-compare#glob-patterns). + */ + includeFilter?: string + + /** + * File/directory name exclude filter. Comma separated minimatch patterns. See [Glob patterns](https://github.com/gliviu/dir-compare#glob-patterns) + */ + excludeFilter?: string + + /** + * Callback for constructing result. Called for each compared entry pair. + * + * Updates 'statistics' and 'diffSet'. + * + * See [Custom result builder](https://github.com/gliviu/dir-compare#custom-result-builder). + */ + resultBuilder?: ResultBuilder + + /** + * File comparison handler. See [Custom file comparators](https://github.com/gliviu/dir-compare#custom-file-content-comparators). + */ + compareFileSync?: CompareFileSync + + /** + * File comparison handler. See [Custom file comparators](https://github.com/gliviu/dir-compare#custom-file-content-comparators). + */ + compareFileAsync?: CompareFileAsync + + /** + * Entry name comparison handler. See [Custom name comparators](https://github.com/gliviu/dir-compare#custom-name-comparators). + */ + compareNameHandler?: CompareNameHandler +} + +/** + * Callback for constructing result. Called for each compared entry pair. + * + * Updates 'statistics' and 'diffSet'. + */ +export type ResultBuilder = + /** + * @param entry1 Left entry. + * @param entry2 Right entry. + * @param state See [[DifferenceState]]. + * @param level Depth level relative to root dir. + * @param relativePath Path relative to root dir. + * @param statistics Statistics to be updated. + * @param diffSet Status per each entry to be appended. + * Do not append if [[Options.noDiffSet]] is false. + * @param reason See [[Reason]]. Not available if entries are equal. + */ + ( + entry1: Entry | undefined, + entry2: Entry | undefined, + state: DifferenceState, + level: number, + relativePath: string, + options: Options, + statistics: Statistics, + diffSet: Array<Difference> | undefined, + reason: Reason | undefined + ) => void + +export interface Entry { + name: string + absolutePath: string + path: string + stat: fs.Stats + lstat: fs.Stats + symlink: boolean +} + +/** + * Comparison result. + */ +export interface Result extends Statistics { + /** + * List of changes (present if [[Options.noDiffSet]] is false). + */ + diffSet?: Array<Difference> +} + +export interface Statistics { + /** + * Any property is allowed if default result builder is not used. + */ + [key: string]: any + + /** + * True if directories are identical. + */ + same: boolean + + /** + * Number of distinct entries. + */ + distinct: number + + /** + * Number of equal entries. + */ + equal: number + + /** + * Number of entries only in path1. + */ + left: number + + /** + * Number of entries only in path2. + */ + right: number + + /** + * Total number of differences (distinct+left+right). + */ + differences: number + + /** + * Total number of entries (differences+equal). + */ + total: number + + /** + * Number of distinct files. + */ + distinctFiles: number + + /** + * Number of equal files. + */ + equalFiles: number + + /** + * Number of files only in path1. + */ + leftFiles: number + + /** + * Number of files only in path2 + */ + rightFiles: number + + /** + * Total number of different files (distinctFiles+leftFiles+rightFiles). + */ + differencesFiles: number + + /** + * Total number of files (differencesFiles+equalFiles). + */ + totalFiles: number + + /** + * Number of distinct directories. + */ + distinctDirs: number + + /** + * Number of equal directories. + */ + equalDirs: number + + /** + * Number of directories only in path1. + */ + leftDirs: number + + /** + * Number of directories only in path2. + */ + rightDirs: number + + /** + * Total number of different directories (distinctDirs+leftDirs+rightDirs). + */ + differencesDirs: number + + /** + * Total number of directories (differencesDirs+equalDirs). + */ + totalDirs: number + + /** + * Stats about broken links. + */ + brokenLinks: BrokenLinksStatistics + + /** + * Statistics available if 'compareSymlink' options is used. + */ + symlinks?: SymlinkStatistics +} + +export interface BrokenLinksStatistics { + /** + * Number of broken links only in path1 + */ + leftBrokenLinks: number + + /** + * Number of broken links only in path2 + */ + rightBrokenLinks: number + + /** + * Number of broken links with same name appearing in both path1 and path2 (leftBrokenLinks+rightBrokenLinks+distinctBrokenLinks) + */ + distinctBrokenLinks: number + + /** + * Total number of broken links + */ + totalBrokenLinks: number + +} + +export interface SymlinkStatistics { + /** + * Number of distinct links. + */ + distinctSymlinks: number + + /** + * Number of equal links. + */ + equalSymlinks: number + + /** + * Number of links only in path1. + */ + leftSymlinks: number + + /** + * Number of links only in path2 + */ + rightSymlinks: number + + /** + * Total number of different links (distinctSymlinks+leftSymlinks+rightSymlinks). + */ + differencesSymlinks: number + + /** + * Total number of links (differencesSymlinks+equalSymlinks). + */ + totalSymlinks: number + +} + +/** + * State of left/right entries relative to each other. + */ +export type DifferenceState = "equal" | "left" | "right" | "distinct" + +/** + * Type of entry. + */ +export type DifferenceType = "missing" | "file" | "directory" | "broken-link" + +/** + * Provides reason when two identically named entries are distinct. + */ +export type Reason = "different-size" | "different-date" | "different-content" | "broken-link" | 'different-symlink' + +export interface Difference { + /** + * Any property is allowed if default result builder is not used. + */ + [key: string]: any + + /** + * Path not including file/directory name; can be relative or absolute depending on call to compare(). + */ + path1?: string + + /** + * Path not including file/directory name; can be relative or absolute depending on call to compare(). + */ + path2?: string + + /** + * Path relative to root dir. + */ + relativePath: string + + /** + * Left file/directory name. + */ + name1?: string + + /** + * Right file/directory name. + */ + name2?: string + + /** + * See [[DifferenceState]] + */ + state: DifferenceState + + /** + * Type of left entry. + */ + type1: DifferenceType + + /** + * Type of right entry. + */ + type2: DifferenceType + + /** + * Left file size. + */ + size1?: number + + /** + * Right file size. + */ + size2?: number + + /** + * Left entry modification date (stat.mtime). + */ + date1?: number + + /** + * Right entry modification date (stat.mtime). + */ + date2?: number + + /** + * Depth level relative to root dir. + */ + level: number + + /** + * See [[Reason]]. + * Not available if entries are equal. + */ + reason?: Reason +} + +/** + * Synchronous file content comparison handler. + */ +export type CompareFileSync = ( + path1: string, + stat1: fs.Stats, + path2: string, + stat2: fs.Stats, + options: Options +) => boolean + +/** + * Asynchronous file content comparison handler. + */ +export type CompareFileAsync = ( + path1: string, + stat1: fs.Stats, + path2: string, + stat2: fs.Stats, + options: Options +) => Promise<boolean> + +export interface CompareFileHandler { + compareSync: CompareFileSync, + compareAsync: CompareFileAsync +} + +/** + * Available file content comparison handlers. + * These handlers are used when [[Options.compareContent]] is set. + */ +export const fileCompareHandlers: { + /** + * Default file content comparison handlers, used if [[Options.compareFileAsync]] or [[Options.compareFileSync]] are not specified. + * + * Performs binary comparison. + */ + defaultFileCompare: CompareFileHandler, + /** + * Compares files line by line. + * + * Options: + * * ignoreLineEnding - tru/false (default: false) + * * ignoreWhiteSpaces - tru/false (default: false) + */ + lineBasedFileCompare: CompareFileHandler +} + +/** + * Compares the names of two entries. + * The comparison should be dependent on received options (ie. case sensitive, ...). + * Returns 0 if names are identical, -1 if name1<name2, 1 if name1>name2. + */ +export type CompareNameHandler = ( + name1: string, + name2: string, + options: Options +) => 0 | 1 | -1 diff --git a/node_modules/dir-compare/src/index.js b/node_modules/dir-compare/src/index.js new file mode 100644 index 0000000..fb1b6b8 --- /dev/null +++ b/node_modules/dir-compare/src/index.js @@ -0,0 +1,204 @@ +var util = require('util') +var pathUtils = require('path') +var fs = require('fs') +var compareSyncInternal = require('./compareSync') +var compareAsyncInternal = require('./compareAsync') +var defaultResultBuilderCallback = require('./resultBuilder/defaultResultBuilderCallback') +var defaultFileCompare = require('./fileCompareHandler/defaultFileCompare') +var lineBasedFileCompare = require('./fileCompareHandler/lineBasedFileCompare') +var defaultNameCompare = require('./nameCompare/defaultNameCompare') +var entryBuilder = require('./entry/entryBuilder') +var statsLifecycle = require('./statistics/statisticsLifecycle') +var loopDetector = require('./symlink/loopDetector') + +var ROOT_PATH = pathUtils.sep + +var compareSync = function (path1, path2, options) { + 'use strict' + // realpathSync() is necessary for loop detection to work properly + var absolutePath1 = pathUtils.normalize(pathUtils.resolve(fs.realpathSync(path1))) + var absolutePath2 = pathUtils.normalize(pathUtils.resolve(fs.realpathSync(path2))) + var diffSet + options = prepareOptions(options) + if (!options.noDiffSet) { + diffSet = [] + } + var statistics = statsLifecycle.initStats(options) + compareSyncInternal( + entryBuilder.buildEntry(absolutePath1, path1, pathUtils.basename(absolutePath1)), + entryBuilder.buildEntry(absolutePath2, path2, pathUtils.basename(absolutePath2)), + 0, ROOT_PATH, options, statistics, diffSet, loopDetector.initSymlinkCache()) + statsLifecycle.completeStatistics(statistics, options) + statistics.diffSet = diffSet + + return statistics +} + +var wrapper = { + realPath: function(path, options) { + return new Promise(function (resolve, reject) { + fs.realpath(path, options, function(err, resolvedPath) { + if(err){ + reject(err) + } else { + resolve(resolvedPath) + } + }) + }) + } +} + +var compareAsync = function (path1, path2, options) { + 'use strict' + var absolutePath1, absolutePath2 + return Promise.resolve() + .then(function () { + return Promise.all([wrapper.realPath(path1), wrapper.realPath(path2)]) + }) + .then(function (realPaths) { + var realPath1 = realPaths[0] + var realPath2 = realPaths[1] + // realpath() is necessary for loop detection to work properly + absolutePath1 = pathUtils.normalize(pathUtils.resolve(realPath1)) + absolutePath2 = pathUtils.normalize(pathUtils.resolve(realPath2)) + }) + .then(function () { + options = prepareOptions(options) + var asyncDiffSet + if (!options.noDiffSet) { + asyncDiffSet = [] + } + var statistics = statsLifecycle.initStats(options) + return compareAsyncInternal( + entryBuilder.buildEntry(absolutePath1, path1, pathUtils.basename(path1)), + entryBuilder.buildEntry(absolutePath2, path2, pathUtils.basename(path2)), + 0, ROOT_PATH, options, statistics, asyncDiffSet, loopDetector.initSymlinkCache()).then( + function () { + statsLifecycle.completeStatistics(statistics, options) + if (!options.noDiffSet) { + var diffSet = [] + rebuildAsyncDiffSet(statistics, asyncDiffSet, diffSet) + statistics.diffSet = diffSet + } + return statistics + }) + }) +} + +var prepareOptions = function (options) { + options = options || {} + var clone = JSON.parse(JSON.stringify(options)) + clone.resultBuilder = options.resultBuilder + clone.compareFileSync = options.compareFileSync + clone.compareFileAsync = options.compareFileAsync + clone.compareNameHandler = options.compareNameHandler + if (!clone.resultBuilder) { + clone.resultBuilder = defaultResultBuilderCallback + } + if (!clone.compareFileSync) { + clone.compareFileSync = defaultFileCompare.compareSync + } + if (!clone.compareFileAsync) { + clone.compareFileAsync = defaultFileCompare.compareAsync + } + if(!clone.compareNameHandler) { + clone.compareNameHandler = defaultNameCompare + } + clone.dateTolerance = clone.dateTolerance || 1000 + clone.dateTolerance = Number(clone.dateTolerance) + if (isNaN(clone.dateTolerance)) { + throw new Error('Date tolerance is not a number') + } + return clone +} + + +// Async diffsets are kept into recursive structures. +// This method transforms them into one dimensional arrays. +var rebuildAsyncDiffSet = function (statistics, asyncDiffSet, diffSet) { + asyncDiffSet.forEach(function (rawDiff) { + if (!Array.isArray(rawDiff)) { + diffSet.push(rawDiff) + } else { + rebuildAsyncDiffSet(statistics, rawDiff, diffSet) + } + }) +} + + +/** + * Options: + * compareSize: true/false - Compares files by size. Defaults to 'false'. + * compareDate: true/false - Compares files by date of modification (stat.mtime). Defaults to 'false'. + * dateTolerance: milliseconds - Two files are considered to have the same date if the difference between their modification dates fits within date tolerance. Defaults to 1000 ms. + * compareContent: true/false - Compares files by content. Defaults to 'false'. + * compareSymlink: true/false - Compares entries by symlink. Defaults to 'false'. + * skipSubdirs: true/false - Skips sub directories. Defaults to 'false'. + * skipSymlinks: true/false - Skips symbolic links. Defaults to 'false'. + * ignoreCase: true/false - Ignores case when comparing names. Defaults to 'false'. + * noDiffSet: true/false - Toggles presence of diffSet in output. If true, only statistics are provided. Use this when comparing large number of files to avoid out of memory situations. Defaults to 'false'. + * includeFilter: File name filter. Comma separated [minimatch](https://www.npmjs.com/package/minimatch) patterns. + * excludeFilter: File/directory name exclude filter. Comma separated [minimatch](https://www.npmjs.com/package/minimatch) patterns. + * resultBuilder: Callback for constructing result. + * function (entry1, entry2, state, level, relativePath, options, statistics, diffSet). Called for each compared entry pair. Updates 'statistics' and 'diffSet'. + * compareFileSync, compareFileAsync: Callbacks for file comparison. + * compareNameHandler: Callback for name comparison + * + * Result: + * same: true if directories are identical + * distinct: number of distinct entries + * equal: number of equal entries + * left: number of entries only in path1 + * right: number of entries only in path2 + * differences: total number of differences (distinct+left+right) + * total: total number of entries (differences+equal) + * distinctFiles: number of distinct files + * equalFiles: number of equal files + * leftFiles: number of files only in path1 + * rightFiles: number of files only in path2 + * differencesFiles: total number of different files (distinctFiles+leftFiles+rightFiles) + * totalFiles: total number of files (differencesFiles+equalFiles) + * distinctDirs: number of distinct directories + * equalDirs: number of equal directories + * leftDirs: number of directories only in path1 + * rightDirs: number of directories only in path2 + * differencesDirs: total number of different directories (distinctDirs+leftDirs+rightDirs) + * totalDirs: total number of directories (differencesDirs+equalDirs) + * brokenLinks: + * leftBrokenLinks: number of broken links only in path1 + * rightBrokenLinks: number of broken links only in path2 + * distinctBrokenLinks: number of broken links with same name appearing in both path1 and path2 + * totalBrokenLinks: total number of broken links (leftBrokenLinks+rightBrokenLinks+distinctBrokenLinks) + * symlinks: Statistics available if 'compareSymlink' options is used + * distinctSymlinks: number of distinct links + * equalSymlinks: number of equal links + * leftSymlinks: number of links only in path1 + * rightSymlinks: number of links only in path2 + * differencesSymlinks: total number of different links (distinctSymlinks+leftSymlinks+rightSymlinks) + * totalSymlinks: total number of links (differencesSymlinks+equalSymlinks) + * diffSet - List of changes (present if Options.noDiffSet is false) + * path1: absolute path not including file/directory name, + * path2: absolute path not including file/directory name, + * relativePath: common path relative to root, + * name1: file/directory name + * name2: file/directory name + * state: one of equal, left, right, distinct, + * type1: one of missing, file, directory, broken-link + * type2: one of missing, file, directory, broken-link + * size1: file size + * size2: file size + * date1: modification date (stat.mtime) + * date2: modification date (stat.mtime) + * level: depth + * reason: Provides reason when two identically named entries are distinct + * Not available if entries are equal + * One of "different-size", "different-date", "different-content", "broken-link", "different-symlink" + */ +module.exports = { + compareSync: compareSync, + compare: compareAsync, + fileCompareHandlers: { + defaultFileCompare: defaultFileCompare, + lineBasedFileCompare: lineBasedFileCompare + } +} diff --git a/node_modules/dir-compare/src/nameCompare/defaultNameCompare.js b/node_modules/dir-compare/src/nameCompare/defaultNameCompare.js new file mode 100644 index 0000000..4ed2e99 --- /dev/null +++ b/node_modules/dir-compare/src/nameCompare/defaultNameCompare.js @@ -0,0 +1,12 @@ + +module.exports = function compareName(name1, name2, options) { + if (options.ignoreCase) { + name1 = name1.toLowerCase() + name2 = name2.toLowerCase() + } + return strcmp(name1, name2) +} + +function strcmp(str1, str2) { + return ((str1 === str2) ? 0 : ((str1 > str2) ? 1 : -1)) +} diff --git a/node_modules/dir-compare/src/resultBuilder/defaultResultBuilderCallback.js b/node_modules/dir-compare/src/resultBuilder/defaultResultBuilderCallback.js new file mode 100644 index 0000000..224b927 --- /dev/null +++ b/node_modules/dir-compare/src/resultBuilder/defaultResultBuilderCallback.js @@ -0,0 +1,27 @@ +'use strict' + +var pathUtils = require('path') +var common = require('../entry/entryBuilder') +var entryType = require('../entry/entryType') + +module.exports = function (entry1, entry2, state, level, relativePath, options, statistics, diffSet, reason) { + if (options.noDiffSet) { + return + } + diffSet.push({ + path1: entry1 ? pathUtils.dirname(entry1.path) : undefined, + path2: entry2 ? pathUtils.dirname(entry2.path) : undefined, + relativePath: relativePath, + name1: entry1 ? entry1.name : undefined, + name2: entry2 ? entry2.name : undefined, + state: state, + type1: entryType.getType(entry1), + type2: entryType.getType(entry2), + level: level, + size1: entry1 ? entry1.stat.size : undefined, + size2: entry2 ? entry2.stat.size : undefined, + date1: entry1 ? entry1.stat.mtime : undefined, + date2: entry2 ? entry2.stat.mtime : undefined, + reason: reason + }) +} diff --git a/node_modules/dir-compare/src/statistics/statisticsLifecycle.js b/node_modules/dir-compare/src/statistics/statisticsLifecycle.js new file mode 100644 index 0000000..549ec5e --- /dev/null +++ b/node_modules/dir-compare/src/statistics/statisticsLifecycle.js @@ -0,0 +1,59 @@ +/** + * Controls creation/completion of global statistics object. + */ +module.exports = { + initStats(options) { + var symlinkStatistics = undefined + if (options.compareSymlink) { + symlinkStatistics = { + distinctSymlinks: 0, + equalSymlinks: 0, + leftSymlinks: 0, + rightSymlinks: 0, + differencesSymlinks: 0, + totalSymlinks: 0, + } + } + var brokenLinksStatistics = { + leftBrokenLinks: 0, + rightBrokenLinks: 0, + distinctBrokenLinks: 0, + } + return { + distinct: 0, + equal: 0, + left: 0, + right: 0, + distinctFiles: 0, + equalFiles: 0, + leftFiles: 0, + rightFiles: 0, + distinctDirs: 0, + equalDirs: 0, + leftDirs: 0, + rightDirs: 0, + brokenLinks: brokenLinksStatistics, + symlinks: symlinkStatistics, + same: undefined + } + }, + + completeStatistics(statistics, options) { + statistics.differences = statistics.distinct + statistics.left + statistics.right + statistics.differencesFiles = statistics.distinctFiles + statistics.leftFiles + statistics.rightFiles + statistics.differencesDirs = statistics.distinctDirs + statistics.leftDirs + statistics.rightDirs + statistics.total = statistics.equal + statistics.differences + statistics.totalFiles = statistics.equalFiles + statistics.differencesFiles + statistics.totalDirs = statistics.equalDirs + statistics.differencesDirs + var brokenLInksStats = statistics.brokenLinks + brokenLInksStats.totalBrokenLinks = brokenLInksStats.leftBrokenLinks + brokenLInksStats.rightBrokenLinks + brokenLInksStats.distinctBrokenLinks + statistics.same = statistics.differences ? false : true + + if (options.compareSymlink) { + statistics.symlinks.differencesSymlinks = statistics.symlinks.distinctSymlinks + + statistics.symlinks.leftSymlinks + statistics.symlinks.rightSymlinks + statistics.symlinks.totalSymlinks = statistics.symlinks.differencesSymlinks + statistics.symlinks.equalSymlinks + } + } + +}
\ No newline at end of file diff --git a/node_modules/dir-compare/src/statistics/statisticsUpdate.js b/node_modules/dir-compare/src/statistics/statisticsUpdate.js new file mode 100644 index 0000000..a1b2312 --- /dev/null +++ b/node_modules/dir-compare/src/statistics/statisticsUpdate.js @@ -0,0 +1,62 @@ +/** + * Calculates comparison statistics. + */ +module.exports = { + updateStatisticsBoth: function (entry1, entry2, same, reason, type, statistics, options) { + same ? statistics.equal++ : statistics.distinct++ + if (type === 'file') { + same ? statistics.equalFiles++ : statistics.distinctFiles++ + } else if (type === 'directory') { + same ? statistics.equalDirs++ : statistics.distinctDirs++ + } else if (type === 'broken-link') { + statistics.brokenLinks.distinctBrokenLinks++ + } else { + throw new Error('Unexpected type ' + type) + } + + var isSymlink1 = entry1 ? entry1.isSymlink : false + var isSymlink2 = entry2 ? entry2.isSymlink : false + var isSymlink = isSymlink1 || isSymlink2 + if (options.compareSymlink && isSymlink) { + var symlinks = statistics.symlinks + if (reason === 'different-symlink') { + symlinks.distinctSymlinks++ + } else { + symlinks.equalSymlinks++ + } + } + + }, + updateStatisticsLeft: function (entry1, type, statistics, options) { + statistics.left++ + if (type === 'file') { + statistics.leftFiles++ + } else if (type === 'directory') { + statistics.leftDirs++ + } else if (type === 'broken-link') { + statistics.brokenLinks.leftBrokenLinks++ + } else { + throw new Error('Unexpected type ' + type) + } + + if (options.compareSymlink && entry1.isSymlink) { + statistics.symlinks.leftSymlinks++ + } + }, + updateStatisticsRight: function (entry2, type, statistics, options) { + statistics.right++ + if (type === 'file') { + statistics.rightFiles++ + } else if (type === 'directory') { + statistics.rightDirs++ + } else if (type === 'broken-link') { + statistics.brokenLinks.rightBrokenLinks++ + } else { + throw new Error('Unexpected type ' + type) + } + + if (options.compareSymlink && entry2.isSymlink) { + statistics.symlinks.rightSymlinks++ + } + }, +}
\ No newline at end of file diff --git a/node_modules/dir-compare/src/symlink/loopDetector.js b/node_modules/dir-compare/src/symlink/loopDetector.js new file mode 100644 index 0000000..0376ebb --- /dev/null +++ b/node_modules/dir-compare/src/symlink/loopDetector.js @@ -0,0 +1,51 @@ +var fs = require('fs') + +/** + * Provides symlink loop detection to directory traversal algorithm. + */ +module.exports = { + detectLoop: function (entry, symlinkCache) { + if (entry && entry.isSymlink) { + var realPath = fs.realpathSync(entry.absolutePath) + if (symlinkCache[realPath]) { + return true + } + } + return false + }, + + initSymlinkCache: function() { + return { + dir1: {}, + dir2: {} + } + }, + + updateSymlinkCache: function(symlinkCache, rootEntry1, rootEntry2, loopDetected1, loopDetected2) { + var symlinkCachePath1, symlinkCachePath2 + if (rootEntry1 && !loopDetected1) { + symlinkCachePath1 = rootEntry1.isSymlink ? fs.realpathSync(rootEntry1.absolutePath) : rootEntry1.absolutePath + symlinkCache.dir1[symlinkCachePath1] = true + } + if (rootEntry2 && !loopDetected2) { + symlinkCachePath2 = rootEntry2.isSymlink ? fs.realpathSync(rootEntry2.absolutePath) : rootEntry2.absolutePath + symlinkCache.dir2[symlinkCachePath2] = true + } + }, + + cloneSymlinkCache: function (symlinkCache) { + return { + dir1: shallowClone(symlinkCache.dir1), + dir2: shallowClone(symlinkCache.dir2) + } + }, +} + +function shallowClone(obj) { + var cloned = {} + Object.keys(obj).forEach(function (key) { + cloned[key] = obj[key] + }) + return cloned +} + |