ember-introjs
Version:
An Ember Component for intro.js
261 lines (215 loc) • 7.11 kB
JavaScript
'use strict';
var fs = require('fs');
var RSVP = require('rsvp');
var rimraf = RSVP.denodeify(require('rimraf'));
var helpers = require('broccoli-kitchen-sink-helpers');
var Plugin = require('broccoli-plugin');
var debugGenerator = require('debug');
var Key = require('./key');
var canUseInputFiles = require('./can-use-input-files');
var walkSync = require('walk-sync');
CachingWriter.prototype = Object.create(Plugin.prototype);
CachingWriter.prototype.constructor = CachingWriter;
function CachingWriter (inputNodes, options) {
options = options || {};
Plugin.call(this, inputNodes, {
name: options.name,
annotation: options.annotation,
persistentOutput: true
});
this._cachingWriterPersistentOutput = !!options.persistentOutput;
this._lastKeys = null;
this._shouldBeIgnoredCache = Object.create(null);
this._resetStats();
this._cacheInclude = options.cacheInclude || [];
this._cacheExclude = options.cacheExclude || [];
this._inputFiles = options.inputFiles || {};
if (!Array.isArray(this._cacheInclude)) {
throw new Error('Invalid cacheInclude option, it must be an array or undefined.');
}
if (!Array.isArray(this._cacheExclude)) {
throw new Error('Invalid cacheExclude option, it must be an array or undefined.');
}
}
Object.defineProperty(CachingWriter.prototype, 'debug', {
get: function() {
return this._debug || (this._debug = debugGenerator(
'broccoli-caching-writer:' +
this._name +
(this._annotation ? (' > [' + this._annotation + ']') : '')));
}
});
CachingWriter.prototype._resetStats = function() {
this._stats = {
stats: 0,
files: 0
};
};
CachingWriter.prototype.getCallbackObject = function() {
return {
build: this._conditionalBuild.bind(this)
};
};
CachingWriter.prototype._conditionalBuild = function () {
var writer = this;
var start = new Date();
var invalidateCache = false;
var dir;
var lastKeys = [];
if (!writer._lastKeys) {
writer._lastKeys = [];
// Force initial build even if inputNodes is []
invalidateCache = true;
}
function shouldNotBeIgnored(relativePath) {
/*jshint validthis:true */
return !this.shouldBeIgnored(relativePath);
}
function keyForFile(relativePath) {
var fullPath = dir + '/' + relativePath;
var stats = fs.statSync(fullPath);
/*jshint validthis:true */
return new Key({
relativePath: relativePath,
fullPath: dir + '/' + relativePath,
basePath: dir,
mode: stats.mode,
size: stats.size,
mtime: stats.mtime.getTime(),
isDirectory: function() {
return false;
}
}, undefined, this.debug);
}
for (var i = 0, l = writer.inputPaths.length; i < l; i++) {
dir = writer.inputPaths[i];
var files;
if (canUseInputFiles(this._inputFiles)) {
this.debug('using inputFiles directly');
files = this._inputFiles.filter(shouldNotBeIgnored, this).map(keyForFile, this);
} else {
this.debug('walking %o', this.inputFiles);
files = walkSync.entries(dir, this.inputFiles).filter(entriesShouldNotBeIgnored, this).map(keyForEntry, this);
}
this._stats.files += files.length;
var lastKey = writer._lastKeys[i];
var key = keyForDir(dir, files, this.debug);
lastKeys.push(key);
if (!invalidateCache /* short circuit */ && !key.isEqual(lastKey)) {
invalidateCache = true;
}
}
this._stats.inputPaths = writer.inputPaths;
this.debug('%o', this._stats);
this.debug('derive cacheKey in %dms', new Date() - start);
this._resetStats();
if (invalidateCache) {
start = new Date();
writer._lastKeys = lastKeys;
var promise = RSVP.Promise.resolve();
if (!this._cachingWriterPersistentOutput) {
promise = promise.then(function() {
return rimraf(writer.outputPath);
}).then(function() {
fs.mkdirSync(writer.outputPath);
}).finally(function() {
this.debug('purge output in %dms', new Date() - start);
start = new Date();
}.bind(this));
}
return promise.then(function() {
return writer.build();
}).finally( function() {
this.debug('rebuilding cache in %dms', new Date() - start);
}.bind(this));
}
};
// Takes in a path and { include, exclude }. Tests the path using regular expressions and
// returns true if the path does not match any exclude patterns AND matches atleast
// one include pattern.
CachingWriter.prototype.shouldBeIgnored = function (fullPath) {
if (this._shouldBeIgnoredCache[fullPath] !== undefined) {
return this._shouldBeIgnoredCache[fullPath];
}
var excludePatterns = this._cacheExclude;
var includePatterns = this._cacheInclude;
var i = null;
// Check exclude patterns
for (i = 0; i < excludePatterns.length; i++) {
// An exclude pattern that returns true should be ignored
if (excludePatterns[i].test(fullPath) === true) {
return (this._shouldBeIgnoredCache[fullPath] = true);
}
}
// Check include patterns
if (includePatterns !== undefined && includePatterns.length > 0) {
for (i = 0; i < includePatterns.length; i++) {
// An include pattern that returns true (and wasn't excluded at all)
// should _not_ be ignored
if (includePatterns[i].test(fullPath) === true) {
return (this._shouldBeIgnoredCache[fullPath] = false);
}
}
// If no include patterns were matched, ignore this file.
return (this._shouldBeIgnoredCache[fullPath] = true);
}
// Otherwise, don't ignore this file
return (this._shouldBeIgnoredCache[fullPath] = false);
};
// Returns a list of matched files
CachingWriter.prototype.listFiles = function() {
return this.listEntries().map(function(entry) {
return entry.fullPath;
});
};
// Returns a list of matched files
CachingWriter.prototype.listEntries = function() {
function listEntries(keys, files) {
for (var i=0; i< keys.length; i++) {
var key = keys[i];
if (key.isDirectory()) {
var children = key.children;
if(children && children.length > 0) {
listEntries(children, files);
}
} else {
files.push(key.entry);
}
}
return files;
}
return listEntries(this._lastKeys, []).sort(function (a, b) {
var pathA = a.relativePath;
var pathB = b.relativePath;
if (pathA === pathB) {
return 0;
} else if (pathA < pathB) {
return -1;
} else {
return 1;
}
}
);
};
module.exports = CachingWriter;
function keyForDir(dir, children, debug) {
// no need to stat dir, we don't care about anything other then that they are
// a dir
return new Key({
relativePath: '/',
basePath: dir,
fullPath: dir,
mode: 16877,
size: 0,
mtime: 0,
isDirectory: function() { return true; }
}, children, debug);
}
function entriesShouldNotBeIgnored(entry) {
/*jshint validthis:true */
return !this.shouldBeIgnored(entry.relativePath);
}
function keyForEntry(entry) {
/*jshint validthis:true */
return new Key(entry, undefined, this.debug);
}