broccoli-filter
Advanced tools
Comparing version 1.2.1 to 1.2.2
# master | ||
# 1.2.0 | ||
* Replace with @caitp's cauliflower-filter implementation and @stefanpenner's tests | ||
# 1.1.0 | ||
@@ -4,0 +8,0 @@ |
275
index.js
@@ -1,138 +0,205 @@ | ||
var fs = require('fs') | ||
var path = require('path') | ||
var mkdirp = require('mkdirp') | ||
var Promise = require('rsvp').Promise | ||
var Plugin = require('broccoli-plugin') | ||
var helpers = require('broccoli-kitchen-sink-helpers') | ||
var walkSync = require('walk-sync') | ||
var mapSeries = require('promise-map-series') | ||
var symlinkOrCopySync = require('symlink-or-copy').sync | ||
'use strict'; | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var mkdirp = require('mkdirp'); | ||
var Promise = require('rsvp').Promise; | ||
var Plugin = require('broccoli-plugin'); | ||
var helpers = require('broccoli-kitchen-sink-helpers'); | ||
var walkSync = require('walk-sync'); | ||
var mapSeries = require('promise-map-series'); | ||
var symlinkOrCopySync = require('symlink-or-copy').sync; | ||
var copyDereferenceSync = require('copy-dereference').sync; | ||
var Cache = require('./lib/cache'); | ||
var debugGenerator = require('debug'); | ||
var keyForFile = require('./lib/key-for-file'); | ||
module.exports = Filter | ||
Filter.prototype = Object.create(Plugin.prototype) | ||
Filter.prototype.constructor = Filter | ||
function Filter (inputNode, options) { | ||
if (!inputNode) { | ||
throw new Error('broccoli-filter must be passed an inputNode, instead it received `undefined`'); | ||
module.exports = Filter; | ||
Filter.prototype = Object.create(Plugin.prototype); | ||
Filter.prototype.constructor = Filter; | ||
function Filter(inputTree, options) { | ||
if (!this || !(this instanceof Filter) || | ||
Object.getPrototypeOf(this) === Filter.prototype) { | ||
throw new TypeError('Filter is an abstract class and must be sub-classed'); | ||
} | ||
options = options || {} | ||
Plugin.call(this, [inputNode], { | ||
name: options.name, | ||
annotation: options.annotation | ||
}) | ||
if (options.extensions != null) this.extensions = options.extensions | ||
if (options.targetExtension != null) this.targetExtension = options.targetExtension | ||
if (options.inputEncoding !== undefined) this.inputEncoding = options.inputEncoding | ||
if (options.outputEncoding !== undefined) this.outputEncoding = options.outputEncoding | ||
var name = 'broccoli-filter:' + (this.constructor.name); | ||
if (this.description) { | ||
name += ' > [' + this.description + ']'; | ||
} | ||
this._debug = debugGenerator(name); | ||
Plugin.call(this, [inputTree]); | ||
/* Destructuring assignment in node 0.12.2 would be really handy for this! */ | ||
if (options) { | ||
if (options.extensions != null) | ||
this.extensions = options.extensions; | ||
if (options.targetExtension != null) | ||
this.targetExtension = options.targetExtension; | ||
if (options.inputEncoding != null) | ||
this.inputEncoding = options.inputEncoding; | ||
if (options.outputEncoding != null) | ||
this.outputEncoding = options.outputEncoding; | ||
} | ||
this._cache = new Cache(); | ||
this._canProcessCache = Object.create(null); | ||
this._destFilePathCache = Object.create(null); | ||
} | ||
Filter.prototype.build = function (readTree, destDir) { | ||
var self = this | ||
var srcDir = this.inputPaths[0] | ||
var destDir = this.outputPath | ||
Filter.prototype.build = function build() { | ||
var self = this; | ||
var srcDir = this.inputPaths[0]; | ||
var destDir = this.outputPath; | ||
var paths = walkSync(srcDir); | ||
var paths = walkSync(srcDir) | ||
this._cache.deleteExcept(paths).forEach(function(key) { | ||
fs.unlinkSync(this.cachePath + '/' + key); | ||
}, this); | ||
return mapSeries(paths, function (relativePath) { | ||
return mapSeries(paths, function rebuildEntry(relativePath) { | ||
var destPath = destDir + '/' + relativePath; | ||
if (relativePath.slice(-1) === '/') { | ||
mkdirp.sync(destDir + '/' + relativePath) | ||
mkdirp.sync(destPath); | ||
} else { | ||
if (self.canProcessFile(relativePath)) { | ||
return self.processAndCacheFile(srcDir, destDir, relativePath) | ||
return self.processAndCacheFile(srcDir, destDir, relativePath); | ||
} else { | ||
symlinkOrCopySync(srcDir + '/' + relativePath, destDir + '/' + relativePath) | ||
var srcPath = srcDir + '/' + relativePath; | ||
symlinkOrCopySync(srcPath, destPath); | ||
} | ||
} | ||
}) | ||
} | ||
}); | ||
}; | ||
Filter.prototype.canProcessFile = function (relativePath) { | ||
return this.getDestFilePath(relativePath) != null | ||
} | ||
Filter.prototype.canProcessFile = | ||
function canProcessFile(relativePath) { | ||
return !!this.getDestFilePath(relativePath); | ||
}; | ||
Filter.prototype.getDestFilePath = function (relativePath) { | ||
for (var i = 0; i < this.extensions.length; i++) { | ||
var ext = this.extensions[i] | ||
Filter.prototype.getDestFilePath = function getDestFilePath(relativePath) { | ||
if (this.extensions == null) return relativePath; | ||
for (var i = 0, ii = this.extensions.length; i < ii; ++i) { | ||
var ext = this.extensions[i]; | ||
if (relativePath.slice(-ext.length - 1) === '.' + ext) { | ||
if (this.targetExtension != null) { | ||
relativePath = relativePath.slice(0, -ext.length) + this.targetExtension | ||
relativePath = | ||
relativePath.slice(0, -ext.length) + this.targetExtension; | ||
} | ||
return relativePath | ||
return relativePath; | ||
} | ||
} | ||
return null | ||
return null; | ||
} | ||
Filter.prototype.processAndCacheFile = function (srcDir, destDir, relativePath) { | ||
var self = this | ||
Filter.prototype.processAndCacheFile = | ||
function processAndCacheFile(srcDir, destDir, relativePath) { | ||
var self = this; | ||
var cacheEntry = this._cache.get(relativePath); | ||
var outputRelativeFile = self.getDestFilePath(relativePath); | ||
this._cache = this._cache || {} | ||
this._cacheIndex = this._cacheIndex || 0 | ||
var cacheEntry = this._cache[relativePath] | ||
if (cacheEntry != null && cacheEntry.hash === hash(cacheEntry.inputFiles)) { | ||
symlinkOrCopyFromCache(cacheEntry) | ||
if (cacheEntry) { | ||
var hashResult = hash(srcDir, cacheEntry.inputFile); | ||
if (cacheEntry.hash.hash === hashResult.hash) { | ||
this._debug('cache hit: %s', relativePath); | ||
return symlinkOrCopyFromCache(cacheEntry, destDir, outputRelativeFile); | ||
} else { | ||
this._debug('cache miss: %s \n - previous: %o \n - next: %o ', relativePath, cacheEntry.hash.key, hashResult.key); | ||
} | ||
} else { | ||
return Promise.resolve() | ||
.then(function () { | ||
return self.processFile(srcDir, destDir, relativePath) | ||
}) | ||
.catch(function (err) { | ||
// Augment for helpful error reporting | ||
err.file = relativePath | ||
err.treeDir = srcDir | ||
throw err | ||
}) | ||
.then(function (cacheInfo) { | ||
copyToCache(cacheInfo) | ||
}) | ||
this._debug('cache prime: %s', relativePath); | ||
} | ||
function hash (filePaths) { | ||
return filePaths.map(function (filePath) { | ||
return helpers.hashTree(srcDir + '/' + filePath) | ||
}).join(',') | ||
} | ||
return Promise.resolve(). | ||
then(function asyncProcessFile() { | ||
return self.processFile(srcDir, destDir, relativePath); | ||
}). | ||
then(copyToCache, | ||
// TODO(@caitp): error wrapper is for API compat, but is not particularly | ||
// useful. | ||
// istanbul ignore next | ||
function asyncProcessFileErrorWrapper(e) { | ||
if (typeof e !== 'object') e = new Error('' + e); | ||
e.file = relativePath; | ||
e.treeDir = srcDir; | ||
throw e; | ||
}) | ||
function symlinkOrCopyFromCache (cacheEntry) { | ||
for (var i = 0; i < cacheEntry.outputFiles.length; i++) { | ||
var dest = destDir + '/' + cacheEntry.outputFiles[i] | ||
mkdirp.sync(path.dirname(dest)) | ||
symlinkOrCopySync(self.cachePath + '/' + cacheEntry.cacheFiles[i], dest) | ||
function copyToCache() { | ||
var entry = { | ||
hash: hash(srcDir, relativePath), | ||
inputFile: relativePath, | ||
outputFile: destDir + '/' + outputRelativeFile, | ||
cacheFile: self.cachePath + '/' + outputRelativeFile | ||
}; | ||
if (fs.existsSync(entry.cacheFile)) { | ||
fs.unlinkSync(entry.cacheFile); | ||
} else { | ||
mkdirp.sync(path.dirname(entry.cacheFile)); | ||
} | ||
copyDereferenceSync(entry.outputFile, entry.cacheFile); | ||
return self._cache.set(relativePath, entry); | ||
} | ||
}; | ||
function copyToCache (cacheInfo) { | ||
var cacheEntry = { | ||
inputFiles: (cacheInfo || {}).inputFiles || [relativePath], | ||
outputFiles: (cacheInfo || {}).outputFiles || [self.getDestFilePath(relativePath)], | ||
cacheFiles: [] | ||
} | ||
for (var i = 0; i < cacheEntry.outputFiles.length; i++) { | ||
var cacheFile = (self._cacheIndex++) + '' | ||
cacheEntry.cacheFiles.push(cacheFile) | ||
Filter.prototype.processFile = | ||
function processFile(srcDir, destDir, relativePath) { | ||
var self = this; | ||
var inputEncoding = this.inputEncoding; | ||
var outputEncoding = this.outputEncoding; | ||
if (inputEncoding === void 0) inputEncoding = 'utf8'; | ||
if (outputEncoding === void 0) outputEncoding = 'utf8'; | ||
var contents = fs.readFileSync( | ||
srcDir + '/' + relativePath, { encoding: inputEncoding }); | ||
helpers.copyPreserveSync( | ||
destDir + '/' + cacheEntry.outputFiles[i], | ||
self.cachePath + '/' + cacheFile) | ||
} | ||
cacheEntry.hash = hash(cacheEntry.inputFiles) | ||
self._cache[relativePath] = cacheEntry | ||
} | ||
return Promise.resolve(this.processString(contents, relativePath)). | ||
then(function asyncOutputFilteredFile(outputString) { | ||
var outputPath = self.getDestFilePath(relativePath); | ||
if (outputPath == null) { | ||
throw new Error('canProcessFile("' + relativePath + '") is true, but getDestFilePath("' + relativePath + '") is null'); | ||
} | ||
outputPath = destDir + '/' + outputPath; | ||
mkdirp.sync(path.dirname(outputPath)); | ||
fs.writeFileSync(outputPath, outputString, { | ||
encoding: outputEncoding | ||
}); | ||
}); | ||
}; | ||
Filter.prototype.processString = | ||
function unimplementedProcessString(contents, relativePath) { | ||
throw new Error( | ||
'When subclassing broccoli-filter you must implement the ' + | ||
'`processString()` method.'); | ||
}; | ||
function hash(src, filePath) { | ||
var path = src + '/' + filePath; | ||
var key = keyForFile(path); | ||
return { | ||
key: key, | ||
hash: helpers.hashStrings([ | ||
path, | ||
key.size, | ||
key.mode, | ||
key.mtime | ||
]) | ||
}; | ||
} | ||
Filter.prototype.processFile = function (srcDir, destDir, relativePath) { | ||
var self = this | ||
var inputEncoding = (this.inputEncoding === undefined) ? 'utf8' : this.inputEncoding | ||
var outputEncoding = (this.outputEncoding === undefined) ? 'utf8' : this.outputEncoding | ||
var string = fs.readFileSync(srcDir + '/' + relativePath, { encoding: inputEncoding }) | ||
return Promise.resolve(self.processString(string, relativePath)) | ||
.then(function (outputString) { | ||
var outputPath = self.getDestFilePath(relativePath) | ||
fs.writeFileSync(destDir + '/' + outputPath, outputString, { encoding: outputEncoding }) | ||
}) | ||
function symlinkOrCopyFromCache(entry, dest, relativePath) { | ||
mkdirp.sync(path.dirname(entry.outputFile)); | ||
symlinkOrCopySync(entry.cacheFile, dest + '/' + relativePath); | ||
} | ||
Filter.prototype.processString = function() { | ||
throw new Error('When subclassing broccoli-filter you must implement the `processString()` method.'); | ||
}; |
{ | ||
"name": "broccoli-filter", | ||
"description": "Helper base class for Broccoli plugins that map input files into output files one-to-one", | ||
"version": "1.2.1", | ||
"author": "Jo Liss <joliss42@gmail.com>", | ||
"version": "1.2.2", | ||
"author": [ | ||
"Jo Liss <joliss42@gmail.com>", | ||
"Caitlin Potter <caitpotter88@gmail.com>" | ||
], | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha", | ||
"test:debug": "mocha debug", | ||
"test:debug:brk": "mocha --debug-brk", | ||
"test:watch": "mocha --watch", | ||
"test:coverage": "istanbul cover --config=test/istanbul.yml _mocha" | ||
}, | ||
"license": "MIT", | ||
@@ -18,10 +28,24 @@ "repository": { | ||
"dependencies": { | ||
"broccoli-kitchen-sink-helpers": "^0.2.6", | ||
"broccoli-kitchen-sink-helpers": "^0.2.7", | ||
"broccoli-plugin": "^1.0.0", | ||
"mkdirp": "^0.3.5", | ||
"copy-dereference": "^1.0.0", | ||
"debug": "^2.2.0", | ||
"mkdirp": "^0.5.1", | ||
"promise-map-series": "^0.2.1", | ||
"rsvp": "^3.0.16", | ||
"rsvp": "^3.0.18", | ||
"symlink-or-copy": "^1.0.1", | ||
"walk-sync": "^0.1.3" | ||
}, | ||
"devDependencies": { | ||
"broccoli": "^0.16.3", | ||
"broccoli-test-helpers": "^0.0.8", | ||
"chai": "^3.0.0", | ||
"chai-as-promised": "^5.1.0", | ||
"coveralls": "^2.11.4", | ||
"istanbul": "^0.3.17", | ||
"minimatch": "^2.0.8", | ||
"mocha": "^2.2.5", | ||
"rimraf": "^2.4.2", | ||
"sinon": "^1.15.3" | ||
} | ||
} |
# broccoli-filter | ||
[![Build Status](https://travis-ci.org/broccolijs/broccoli-filter.svg?branch=master)](https://travis-ci.org/broccolijs/broccoli-filter) | ||
[![Build status](https://ci.appveyor.com/api/projects/status/hc68s0vbn9di4ehi/branch/master?svg=true)](https://ci.appveyor.com/project/joliss/broccoli-filter/branch/master) | ||
Helper base class for Broccoli plugins that map input files into output files | ||
@@ -27,24 +30,12 @@ one-to-one. | ||
/** | ||
* Virtual method `canProcessFile`: determine whether hard processing is used | ||
* to move the source file into the output directory, or whether a simple | ||
* symlinking approach should be sufficient. | ||
* Virtual method `getDestFilePath`: determine whether the source file should | ||
* be processed, and optionally rename the output file when processing occurs. | ||
* | ||
* By default, this method returns true if the `extensions` option is a | ||
* non-empty list, and the relativePath ends with one of the extensions. | ||
* Return `null` to pass the file through without processing. Return | ||
* `relativePath` to process the file with `processString`. Return a | ||
* different path to process the file with `processString` and rename it. | ||
* | ||
* The results of this operation are cached automatically, and it will not be | ||
* invoked again for a given relative path. | ||
*/ | ||
virtual canProcessFile(relativePath: string): boolean; | ||
/** | ||
* Virtual method `getDestFilePath`: optionally rename the output file when | ||
* processing occurs. | ||
* | ||
* By default, if the options passed into the Filter constructor contains a | ||
* By default, if the options passed into the `Filter` constructor contain a | ||
* property `extensions`, and `targetExtension` is supplied, the first matching | ||
* extension in the list is replaced with the `targetExtension` option's value. | ||
* | ||
* The results of this operation are cached automatically, and it will not be | ||
* invoked again for a given relative path. | ||
*/ | ||
@@ -71,4 +62,3 @@ virtual getDestFilePath(relativePath: string): string; | ||
All options except `name` and `annotation` can also be set on the prototype | ||
instead of being passed into the constructor. <!-- This was a regrettable | ||
choice. --> | ||
instead of being passed into the constructor. | ||
@@ -75,0 +65,0 @@ ### Example Usage |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
15181
7
214
1
9
10
3
125
2
+ Addedcopy-dereference@^1.0.0
+ Addeddebug@^2.2.0
+ Addedcopy-dereference@1.0.0(transitive)
+ Addeddebug@2.6.9(transitive)
+ Addedms@2.0.0(transitive)
- Removedmkdirp@0.3.5(transitive)
Updatedmkdirp@^0.5.1
Updatedrsvp@^3.0.18