fs2
Version:
fs (file system package) extensions
267 lines (240 loc) • 7.43 kB
JavaScript
/* eslint max-statements: off, max-lines: off */
"use strict";
const invoke = require("es5-ext/function/invoke")
, noop = require("es5-ext/function/noop")
, isCallable = require("es5-ext/object/is-callable")
, isValue = require("es5-ext/object/is-value")
, forEach = require("es5-ext/object/for-each")
, memoize = require("memoizee")
, deferred = require("deferred")
, ignore = require("ignore")
, pathUtils = require("path")
, modes = require("./lib/ignore-modes")
, getMap = require("./lib/get-conf-file-map")
, memoizeWatcher = require("./lib/memoize-watcher")
, findRoot = require("./lib/find-root");
const { isArray } = Array
, { push } = Array.prototype
, { call } = Function.prototype
, trim = call.bind(String.prototype.trim)
, { dirname } = pathUtils
, { resolve } = pathUtils
, { sep } = pathUtils
, { ConfMap } = getMap
, eolRe = /(?:\r\n|[\n\r\u2028\u2029])/u;
const prepareRules = function (data) { return data.map(trim).filter(Boolean).reverse(); };
const parseSrc = function (src) { return prepareRules(String(src).split(eolRe)); };
const compile = function (maps, result) {
const data = (result.data = {}), paths = (result.paths = []);
// Merge rules found in ignorefiles
maps.forEach(map => {
forEach(map.map, (rules, path) => {
if (!rules.length) return;
if (!data[path]) {
paths.push(path);
data[path] = [];
}
data[path].push(rules);
});
});
result.paths.sort();
return result;
};
const applyRules = function (rules, rootPath, path) {
if (!rootPath.endsWith(sep)) rootPath += sep;
if (!path.startsWith(rootPath)) return { value: false, target: path };
rules = rules
.slice()
.reverse()
.filter(rule => !rule.startsWith("#"));
const ig = ignore().add(rules);
const testPath = path.slice(rootPath.length);
let result = ig.ignores(testPath);
if (!result) {
const excludeRules = rules.filter(rule => rule.startsWith("!"));
if (excludeRules.length) {
const ig2 = ignore().add(excludeRules.map(rule => rule.slice(1)));
if (!ig2.ignores(testPath)) result = null;
} else {
result = null;
}
}
return { value: result, target: path };
};
const applyGlobalRules = function (path, rules) {
// Check global rules
const value = applyRules(rules, path.slice(0, path.indexOf(sep) + 1), path);
return Boolean(value.value);
};
const buildMap = function (lDirname, lGetMap, watch) {
let promise, maps;
const data = {};
lGetMap = lGetMap.map((getSubMap, index) => {
const map = getSubMap(lDirname);
if (watch) {
map.on("change", targetMap => {
if (maps) {
maps[index] = targetMap;
compile(maps, data);
promise.emit("change", data);
}
});
}
return map;
});
if (lGetMap.length > 1) {
promise = deferred.map(lGetMap)(result => {
maps = result;
return compile(maps, data);
});
} else {
promise = lGetMap[0](map => {
maps = [map];
return compile(maps, data);
});
}
if (watch) {
promise.close = function () { lGetMap.forEach(invoke("close")); };
}
return promise;
};
const IsIgnored = function (path, watch) {
this.path = path;
this.dirname = dirname(path);
this.watch = watch;
};
IsIgnored.prototype = {
init(mapPromise) {
this.mapPromise = mapPromise;
this.promise = mapPromise(data => {
this.data = data;
return this.calculate();
});
if (this.watch) {
mapPromise.on("change", () => {
const value = this.calculate();
if (value !== this.promise.value) {
this.promise.value = value;
this.promise.emit("change", value, this.path);
}
});
this.promise.close = this.close.bind(this);
}
return this.promise;
},
close() { this.mapPromise.close(); },
calculate() {
let current, result = false;
if (!this.data.paths) return false;
// Apply rules
current = this.path;
this.data.paths.some(function (rulesPath, index) {
if (rulesPath.length > this.dirname.length) return true;
if (index) {
const dirIgnored = this.data.paths
.slice(0, index)
.some(function (preRulesPath) {
return this.data.data[preRulesPath].some(
rules => applyRules(rules, preRulesPath, rulesPath).value, this
);
}, this);
if (dirIgnored) return false;
}
this.data.data[rulesPath].forEach(function (rules) {
let data = applyRules(rules, rulesPath, current);
if (data.value === false && current !== pathUtils) {
data = applyRules(rules, rulesPath, this.path);
}
if (data.target !== current || isValue(data.value)) {
result = data.value;
}
current = data.target;
}, this);
return false;
}, this);
return Boolean(result);
}
};
const isIgnored = function (mode, path, options) {
let globalRules, getMapFns, promise;
if (isValue(options.globalRules)) {
globalRules = isArray(options.globalRules)
? options.globalRules
: String(options.globalRules).split(eolRe);
}
const { watch } = options;
const lIsIgnored = new IsIgnored(path, watch);
const lDirname = lIsIgnored.dirname;
if (mode) {
getMapFns = [];
if (!globalRules) globalRules = [];
if (!isArray(mode)) {
if (!modes[mode]) throw new Error(`Unknown mode '${ mode }'`);
mode = [mode];
}
mode.forEach(name => {
const lMode = modes[name];
if (!lMode) throw new Error(`Unknown mode '${ name }'`);
getMapFns.push(pathIgnored => getMap(lDirname, lMode, watch, parseSrc));
if (lMode.globalRules) push.apply(globalRules, lMode.globalRules);
});
}
if (globalRules) {
globalRules = prepareRules(globalRules);
if (applyGlobalRules(path, globalRules)) {
promise = deferred(true);
if (watch) promise.close = noop;
return promise;
}
}
if (!mode) {
promise = deferred(false);
if (watch) promise.close = noop;
return promise;
}
return lIsIgnored.init(buildMap(lDirname, getMapFns, watch));
};
isIgnored.returnsPromise = true;
module.exports = exports = function (mode, path, options = {}, cb = null) {
path = resolve(String(path));
if (!cb) {
if (isCallable(options)) {
cb = options;
options = {};
}
}
return isIgnored(mode, path, options).cb(cb);
};
exports.returnsPromise = true;
exports.isIgnored = isIgnored;
exports.IsIgnored = IsIgnored;
exports.applyGlobalRules = applyGlobalRules;
exports.getIsIgnored = function (modeNames, globalRules, watch) {
const mapGetters = [];
if (!globalRules) globalRules = [];
const memo = watch ? memoizeWatcher : memoize;
modeNames.forEach(name => {
const mode = modes[name];
if (!mode) throw new Error(`Unknown mode '${ name }'`);
const isRoot = memo(mode[watch ? "isRootWatcher" : "isRoot"], { primitive: true });
const readRules = memo(getMap[watch ? "readRulesWatcher" : "readRules"], {
primitive: true
});
mapGetters.push(path => {
const map = new ConfMap(path, watch);
map.filename = mode.filename;
map.readRules = readRules;
map.parse = parseSrc;
return map.init(findRoot(isRoot, path, { watch }));
});
if (mode.globalRules) push.apply(globalRules, mode.globalRules);
});
const build = memo(lDirname => buildMap(lDirname, mapGetters, watch), { primitive: true });
return {
isIgnored(path) {
const lIsIgnored = new IsIgnored(path, watch);
return lIsIgnored.init(build(lIsIgnored.dirname));
},
globalRules: globalRules.length ? prepareRules(globalRules) : null
};
};