folder-hash
Advanced tools
Comparing version 1.0.5 to 1.1.0
135
index.js
@@ -6,10 +6,18 @@ "use strict" | ||
var crypto = require('crypto'); | ||
var minimatch = require('minimatch'); | ||
if (typeof Promise === 'undefined') require('when/es6-shim/Promise'); | ||
var algo = 'sha1'; | ||
var encoding = 'base64'; // 'base64', 'hex' or 'binary' | ||
var defaultOptions = { | ||
algo: 'sha1', // see crypto.getHashes() for options | ||
encoding: 'base64', // 'base64', 'hex' or 'binary' | ||
excludes: [], | ||
match: { | ||
basename: true, | ||
path: true | ||
} | ||
}; | ||
module.exports = { | ||
hashElement: createHash | ||
hashElement: hashElement | ||
} | ||
@@ -19,8 +27,19 @@ | ||
* Create a hash over a folder or file, using either promises or error-first-callbacks. | ||
* The parameter directoryPath is optional. This function may be called | ||
* as createHash(filename, folderpath, fn(err, hash) {}), createHash(filename, folderpath) | ||
* or as createHash(path, fn(err, hash) {}), createHash(path) | ||
* | ||
* Examples: | ||
* - hashElement(filename, folderpath, options, fn(err, hash) {}), hashElement(filename, folderpath, options); | ||
* - hashElement(path, fn(err, hash) {}), hashElement(path) | ||
* | ||
* @param {string} name - element name or an element's path | ||
* @param {string} [dir] - directory that contains the element (if omitted is generated from name) | ||
* @param {Object} [options] - Options | ||
* @param {string} [options.algo='sha1'] - checksum algorithm, see options in crypto.getHashes() | ||
* @param {string} [options.encoding='base64'] - encoding of the resulting hash. One of 'base64', 'hex' or 'binary' | ||
* @param {string[]} [options.excludes=[]] - Array of optional exclude file glob patterns, see minimatch doc | ||
* @param {bool} [options.match.basename=true] - Match the exclude patterns to the file/folder name | ||
* @param {bool} [options.match.path=true] - Match the exclude patterns to the file/folder path | ||
* @param {fn} [callback] - Error-first callback function | ||
*/ | ||
function createHash(name, directoryPath, callback) { | ||
var promise = parseParameters(name, directoryPath); | ||
function hashElement(name, directoryPath, options, callback) { | ||
var promise = parseParameters(arguments); | ||
var callback = arguments[arguments.length-1]; | ||
@@ -39,17 +58,52 @@ | ||
function parseParameters(name, directoryPath) { | ||
if (!isString(name)) { | ||
function parseParameters(args) { | ||
var elementBasename = args[0], | ||
elementDirname = args[1], | ||
options = args[2]; | ||
if (!isString(elementBasename)) { | ||
return Promise.reject(new TypeError('First argument must be a string')); | ||
} | ||
if (!isString(directoryPath)) { | ||
directoryPath = path.dirname(name); | ||
name = path.basename(name); | ||
if (!isString(elementDirname)) { | ||
elementDirname = path.dirname(elementBasename); | ||
elementBasename = path.basename(elementBasename); | ||
options = args[1]; | ||
} | ||
return hashElementPromise(name, directoryPath); | ||
// parse options (fallback default options) | ||
if (!isObject(options)) options = {}; | ||
['algo', 'encoding', 'excludes'].forEach(function(key) { | ||
if (!options.hasOwnProperty(key)) options[key] = defaultOptions[key]; | ||
}); | ||
if (!options.match) options.match = {}; | ||
if (!options.match.hasOwnProperty('basename')) options.match.basename = defaultOptions.match.basename; | ||
if (!options.match.hasOwnProperty('path')) options.match.path = defaultOptions.match.path; | ||
if (!options.excludes || !Array.isArray(options.excludes) || options.excludes.length == 0) { | ||
options.excludes = undefined; | ||
} else { | ||
// combine globs into one single RegEx | ||
options.excludes = new RegExp(options.excludes.reduce(function (acc, exclude) { | ||
return acc + '|' + minimatch.makeRe(exclude).source; | ||
}, '').substr(1)); | ||
} | ||
//console.log('parsed options:', options); | ||
return hashElementPromise(elementBasename, elementDirname, options); | ||
} | ||
function hashElementPromise(name, directoryPath) { | ||
var filepath = path.join(directoryPath, name); | ||
function hashElementPromise(basename, dirname, options) { | ||
var filepath = path.join(dirname, basename); | ||
if (options.match.basename && options.excludes && options.excludes.test(basename)) { | ||
//console.log('regex', options.excludes, 'matched to', basename); | ||
return Promise.resolve(undefined); | ||
} | ||
if (options.match.path && options.excludes && options.excludes.test(filepath)) { | ||
//console.log('regex', options.excludes, 'matched to', filepath); | ||
return Promise.resolve(undefined); | ||
} | ||
return new Promise(function (resolve, reject, notify) { | ||
@@ -62,7 +116,7 @@ fs.stat(filepath, function (err, stats) { | ||
if (stats.isDirectory()) { | ||
resolve(hashFolderPromise(name, directoryPath)); | ||
resolve(hashFolderPromise(basename, dirname, options)); | ||
} else if (stats.isFile()) { | ||
resolve(hashFilePromise(name, directoryPath)); | ||
resolve(hashFilePromise(basename, dirname, options)); | ||
} else { | ||
resolve({ name: name, hash: 'unknown element type' }); | ||
resolve({ name: basename, hash: 'unknown element type' }); | ||
} | ||
@@ -74,8 +128,13 @@ }); | ||
function hashFolderPromise(foldername, directoryPath) { | ||
var TAG = 'hashFolderPromise(' + foldername + ', ' + directoryPath + '):'; | ||
function hashFolderPromise(foldername, directoryPath, options) { | ||
var folderPath = path.join(directoryPath, foldername); | ||
var notExcluded = function notExcluded(basename) { | ||
return !(options.match.basename && options.excludes && options.excludes.test(basename)); | ||
} | ||
return new Promise(function (resolve, reject, notify) { | ||
fs.readdir(folderPath, function (err, files) { | ||
if (err) { | ||
var TAG = 'hashFolderPromise(' + foldername + ', ' + directoryPath + '):'; | ||
console.error(TAG, err); | ||
@@ -85,10 +144,8 @@ reject(err); | ||
var children = files.map(function (child) { | ||
return hashElementPromise(child, folderPath); | ||
var children = files.filter(notExcluded).map(function (child) { | ||
return hashElementPromise(child, folderPath, options); | ||
}); | ||
var allChildren = Promise.all(children); | ||
return allChildren.then(function (children) { | ||
var hash = new HashedFolder(foldername, children); | ||
return Promise.all(children).then(function (children) { | ||
var hash = new HashedFolder(foldername, children.filter(notUndefined), options); | ||
resolve(hash); | ||
@@ -101,6 +158,6 @@ }); | ||
function hashFilePromise(filename, directoryPath) { | ||
function hashFilePromise(filename, directoryPath, options) { | ||
return new Promise(function (resolve, reject, notify) { | ||
try { | ||
var hash = crypto.createHash(algo); | ||
var hash = crypto.createHash(options.algo); | ||
hash.write(filename); | ||
@@ -112,3 +169,3 @@ | ||
f.on('end', function () { | ||
var hashedFile = new HashedFile(filename, hash); | ||
var hashedFile = new HashedFile(filename, hash, options); | ||
resolve(hashedFile); | ||
@@ -124,7 +181,7 @@ }); | ||
var HashedFolder = function (name, children) { | ||
var HashedFolder = function (name, children, options) { | ||
this.name = name; | ||
this.children = children; | ||
var hash = crypto.createHash(algo); | ||
var hash = crypto.createHash(options.algo); | ||
hash.write(name); | ||
@@ -137,3 +194,3 @@ children.forEach(function (child) { | ||
this.hash = hash.digest(encoding); | ||
this.hash = hash.digest(options.encoding); | ||
} | ||
@@ -158,5 +215,5 @@ | ||
var HashedFile = function (name, hash) { | ||
var HashedFile = function (name, hash, options) { | ||
this.name = name; | ||
this.hash = hash.digest(encoding); | ||
this.hash = hash.digest(options.encoding); | ||
} | ||
@@ -173,1 +230,9 @@ | ||
} | ||
function isObject(obj) { | ||
return obj != null && typeof obj === 'object' | ||
} | ||
function notUndefined(obj) { | ||
return typeof obj !== undefined; | ||
} |
{ | ||
"name": "folder-hash", | ||
"version": "1.0.5", | ||
"version": "1.1.0", | ||
"description": "Create a hash checksum over a folder and its content - its children and their content", | ||
@@ -8,3 +8,5 @@ "main": "index.js", | ||
"start": "node sample.js", | ||
"test": "mocha --reporter spec test" | ||
"test": "mocha --reporter spec test", | ||
"cover": "node node_modules/istanbul/lib/cli.js cover --dir test_coverage node_modules/mocha/bin/_mocha test", | ||
"doc": "./node_modules/.bin/jsdoc index.js" | ||
}, | ||
@@ -21,2 +23,3 @@ "author": { | ||
"dependencies": { | ||
"minimatch": "^3.0.3", | ||
"when": "^3.7.7" | ||
@@ -27,5 +30,10 @@ }, | ||
"chai-as-promised": "^6.0.0", | ||
"istanbul": "^0.4.5", | ||
"jsdoc": "^3.4.3", | ||
"mocha": "^3.2.0", | ||
"rimraf": "^2.5.2" | ||
}, | ||
"engines": { | ||
"node": ">=0.10.5" | ||
} | ||
} |
152
README.md
@@ -40,3 +40,11 @@ # folderHash | ||
}); | ||
// pass options (example: exclude dotFiles) | ||
var options = { excludes: ['.*'], match: { basename: true, path: false } }; | ||
hasher.hashElement(__dirname, options, function (error, hash)) { | ||
if (error) return console.error('hashing failed:', error); | ||
console.log('Result for folder "' + __dirname + '":'); | ||
console.log(hash.toString()); | ||
}); | ||
### With callbacks | ||
@@ -57,4 +65,148 @@ | ||
}); | ||
// pass options (example: exclude dotFiles) | ||
var options = { excludes: ['**/.*'], match: { basename: false, path: true } }; | ||
hasher.hashElement(__dirname, options, function (error, hash)) { | ||
if (error) return console.error('hashing failed:', error); | ||
console.log('Result for folder "' + __dirname + '":'); | ||
console.log(hash.toString()); | ||
}); | ||
### Parameters for the hashElement function | ||
<table> | ||
<thead> | ||
<tr> | ||
<th>Name</th> | ||
<th>Type</th> | ||
<th>Attributes</th> | ||
<th>Description</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td>name</td> | ||
<td> | ||
<span>string</span> | ||
</td> | ||
<td> | ||
</td> | ||
<td>element name or an element's path</td> | ||
</tr> | ||
<tr> | ||
<td>dir</td> | ||
<td> | ||
<span>string</span> | ||
</td> | ||
<td> | ||
<optional><br> | ||
</td> | ||
<td>directory that contains the element (if omitted is generated from name)</td> | ||
</tr> | ||
<tr> | ||
<td>options</td> | ||
<td> | ||
<span>Object</span> | ||
</td> | ||
<td> | ||
<optional><br> | ||
</td> | ||
<td> | ||
Options object (see below) | ||
</td> | ||
</tr> | ||
<tr> | ||
<td>callback</td> | ||
<td> | ||
<span>fn</span> | ||
</td> | ||
<td> | ||
<optional><br> | ||
</td> | ||
<td>Error-first callback function</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
#### Options object properties | ||
<table> | ||
<thead> | ||
<tr> | ||
<th>Name</th> | ||
<th>Type</th> | ||
<th>Attributes</th> | ||
<th>Default</th> | ||
<th>Description</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<td>algo</td> | ||
<td> | ||
<span>string</span> | ||
</td> | ||
<td> | ||
<optional><br> | ||
</td> | ||
<td> | ||
'sha1' | ||
</td> | ||
<td>checksum algorithm, see options in crypto.getHashes()</td> | ||
</tr> | ||
<tr> | ||
<td>encoding</td> | ||
<td> | ||
<span>string</span> | ||
</td> | ||
<td> | ||
<optional><br> | ||
</td> | ||
<td> | ||
'base64' | ||
</td> | ||
<td>encoding of the resulting hash. One of 'base64', 'hex' or 'binary'</td> | ||
</tr> | ||
<tr> | ||
<td>excludes</td> | ||
<td> | ||
<span>Array.<string></span> | ||
</td> | ||
<td> | ||
<optional><br> | ||
</td> | ||
<td> | ||
[] | ||
</td> | ||
<td>Array of optional exclude file glob patterns, see minimatch doc</td> | ||
</tr> | ||
<tr> | ||
<td>match.basename</td> | ||
<td> | ||
<span>bool</span> | ||
</td> | ||
<td> | ||
<optional><br> | ||
</td> | ||
<td> | ||
true | ||
</td> | ||
<td>Match the exclude patterns to the file/folder name</td> | ||
</tr> | ||
<tr> | ||
<td>match.path</td> | ||
<td> | ||
<span>bool</span> | ||
</td> | ||
<td> | ||
<optional><br> | ||
</td> | ||
<td> | ||
true | ||
</td> | ||
<td>Match the exclude patterns to the file/folder path</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
## Behavior | ||
@@ -61,0 +213,0 @@ The behavior is documented and verified in the unit tests. Execute `npm test` or `mocha test`, and have a look at the _test_ subfolder. |
@@ -56,2 +56,4 @@ | ||
writeFileSync(path.join(sampleFolder, 'f3', 'subfolder1', 'file1'), 'This is another text'); | ||
mkdirSync(path.join(sampleFolder, 'empty')); | ||
done(); | ||
@@ -58,0 +60,0 @@ }); |
@@ -23,2 +23,3 @@ | ||
folderHash.hashElement().should.be.rejectedWith(TypeError); | ||
folderHash.hashElement(function () {}).should.be.rejectedWith(TypeError); | ||
}); | ||
@@ -37,2 +38,16 @@ }); | ||
}); | ||
it('with options passed', function () { | ||
var options = { | ||
algo: 'sha1', | ||
encoding: 'base64', | ||
excludes: [], | ||
match: { | ||
basename: false, | ||
path: false | ||
} | ||
}; | ||
return folderHash.hashElement('file1', sampleFolder, options)//.should.eventually.have.property('hash'); | ||
.then((a) => console.log('a:', a)).catch(b => console.error('b', b)); | ||
}); | ||
}); | ||
@@ -61,2 +76,13 @@ | ||
}); | ||
describe('and', function () { | ||
it('should return a string representation', function () { | ||
folderHash.hashElement('./', sampleFolder) | ||
.then(function (hash) { | ||
var str = hash.toString(); | ||
assert.ok(str); | ||
assert.ok(str.length > 10); | ||
}) | ||
}); | ||
}); | ||
}); | ||
@@ -139,2 +165,12 @@ | ||
}); | ||
it('f2/subfolder1 should equal f3/subfolder1 if file1 is ignored', function () { | ||
return Promise.all([ | ||
folderHash.hashElement(path.join(sampleFolder, 'f3/subfolder1'), { excludes: ['**/.*', 'file1'] }), | ||
folderHash.hashElement(path.join(sampleFolder, 'f2/subfolder1'), { excludes: ['**/.*', 'file1'] }) | ||
]).then(function (hashes) { | ||
assert.ok(hashes.length == 2, 'should have returned two hashes'); | ||
assert.equal(hashes[0].hash, hashes[1].hash); | ||
}) | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
27277
420
240
2
6
+ Addedminimatch@^3.0.3
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedminimatch@3.1.2(transitive)