coffeescript-concat
Version:
A utility for combining coffeescript files and resolving their dependencies.
211 lines (194 loc) • 7.85 kB
JavaScript
(function() {
var argv, concatFiles, concatenate, findClassDependencies, findClasses, findFileDependencies, fs, includeDirectories, mapDependencies, path, removeDirectives, sourceFiles, util, _,
__indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
util = require('util');
fs = require('fs');
path = require('path');
_ = require('underscore');
findClasses = function(file) {
var classNames, classRegex, result;
file = '\n' + file;
classRegex = /\n[^#\n]*class\s([A-Za-z_$-][A-Za-z0-9_$-]*)/g;
classNames = [];
while ((result = classRegex.exec(file)) !== null) {
classNames.push(result[1]);
}
return classNames;
};
findClassDependencies = function(file) {
var classDirectiveRegex, dependencies, dependencyRegex, result;
file = '\n' + file;
dependencyRegex = /\n[^#\n]*extends\s([A-Za-z_$-][A-Za-z0-9_$-]*)/g;
dependencies = [];
while ((result = dependencyRegex.exec(file)) !== null) {
dependencies.push(result[1]);
}
file = file.replace(dependencyRegex, '');
classDirectiveRegex = /#=\s*require\s+([A-Za-z_$-][A-Za-z0-9_$-]*)/g;
while ((result = classDirectiveRegex.exec(file)) !== null) {
dependencies.push(result[1]);
}
return dependencies;
};
findFileDependencies = function(file) {
var dependencies, fileDirectiveRegex, result;
file = '\n' + file;
dependencies = [];
fileDirectiveRegex = /#=\s*require\s+<([A-Za-z_$-][A-Za-z0-9_$-.]*)>/g;
while ((result = fileDirectiveRegex.exec(file)) !== null) {
dependencies.push(result[1]);
}
return dependencies;
};
mapDependencies = function(sourceFiles, searchDirectories) {
var classes, contents, dependencies, dir, f, file, fileDef, fileDefs, fileDependencies, files, _i, _j, _len, _len2;
files = sourceFiles;
for (_i = 0, _len = searchDirectories.length; _i < _len; _i++) {
dir = searchDirectories[_i];
files = files.concat((function() {
var _j, _len2, _ref, _results;
_ref = fs.readdirSync(dir);
_results = [];
for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
f = _ref[_j];
_results.push(path.join(dir, f));
}
return _results;
})());
}
fileDefs = [];
for (_j = 0, _len2 = files.length; _j < _len2; _j++) {
file = files[_j];
if (!(/\.coffee$/.test(file))) continue;
contents = fs.readFileSync(file).toString();
classes = findClasses(contents);
dependencies = findClassDependencies(contents);
fileDependencies = findFileDependencies(contents);
dependencies = _.select(dependencies, function(d) {
return _.indexOf(classes, d) === -1;
});
fileDef = {
name: file,
classes: classes,
dependencies: dependencies,
fileDependencies: fileDependencies,
contents: contents
};
fileDefs.push(fileDef);
}
return fileDefs;
};
concatFiles = function(sourceFiles, fileDefs) {
var allFileDefs, fd, fileDefStack, findFileDefByClass, findFileDefByName, nextFileDef, output, resolveDependencies, resolvedDef, sourceFileDefs, usedFiles, _i, _len;
usedFiles = [];
allFileDefs = fileDefs.slice(0);
sourceFileDefs = (function() {
var _i, _len, _ref, _results;
_results = [];
for (_i = 0, _len = fileDefs.length; _i < _len; _i++) {
fd = fileDefs[_i];
if (_ref = fd.name, __indexOf.call(sourceFiles, _ref) >= 0) {
_results.push(fd);
}
}
return _results;
})();
findFileDefByClass = function(className) {
var c, fileDef, _i, _j, _len, _len2, _ref;
for (_i = 0, _len = allFileDefs.length; _i < _len; _i++) {
fileDef = allFileDefs[_i];
_ref = fileDef.classes;
for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
c = _ref[_j];
if (c === className) return fileDef;
}
}
return null;
};
findFileDefByName = function(fileName) {
var fileDef, name, temp, _i, _len;
for (_i = 0, _len = allFileDefs.length; _i < _len; _i++) {
fileDef = allFileDefs[_i];
temp = fileDef.name.split('/');
name = temp[temp.length - 1].split('.')[0];
if (fileName === name) return fileDef;
}
return null;
};
resolveDependencies = function(fileDef) {
var depFileDef, dependenciesStack, dependency, neededFile, neededFileDef, neededFileName, nextStack, _i, _j, _len, _len2, _ref, _ref2;
dependenciesStack = [];
if (_.indexOf(usedFiles, fileDef.name) !== -1) {
return null;
} else if (fileDef.dependencies.length === 0 && fileDef.fileDependencies.length === 0) {
dependenciesStack.push(fileDef);
usedFiles.push(fileDef.name);
} else {
dependenciesStack = [];
_ref = fileDef.dependencies;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
dependency = _ref[_i];
depFileDef = findFileDefByClass(dependency);
if (depFileDef === null) {
console.error("Error: couldn't find needed class: " + dependency);
} else {
nextStack = resolveDependencies(depFileDef);
dependenciesStack = dependenciesStack.concat(nextStack !== null ? nextStack : []);
}
}
_ref2 = fileDef.fileDependencies;
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
neededFile = _ref2[_j];
neededFileName = neededFile.split('.')[0];
neededFileDef = findFileDefByName(neededFileName);
if (neededFileDef === null) {
console.error("Error: couldn't find needed file: " + neededFileName);
} else {
nextStack = resolveDependencies(neededFileDef);
dependenciesStack = dependenciesStack.concat(nextStack !== null ? nextStack : []);
}
}
if (_.indexOf(usedFiles, fileDef.name) === -1) {
dependenciesStack.push(fileDef);
usedFiles.push(fileDef.name);
}
}
return dependenciesStack;
};
fileDefStack = [];
while (sourceFileDefs.length > 0) {
nextFileDef = sourceFileDefs.pop();
resolvedDef = resolveDependencies(nextFileDef);
if (resolvedDef) fileDefStack = fileDefStack.concat(resolvedDef);
}
output = '';
for (_i = 0, _len = fileDefStack.length; _i < _len; _i++) {
nextFileDef = fileDefStack[_i];
output += nextFileDef.contents + '\n';
}
return output;
};
removeDirectives = function(file) {
var classDirectiveRegex, fileDirectiveRegex;
fileDirectiveRegex = /#=\s*require\s+<([A-Za-z_$-][A-Za-z0-9_$-.]*)>/g;
classDirectiveRegex = /#=\s*require\s+([A-Za-z_$-][A-Za-z0-9_$-]*)/g;
file = file.replace(fileDirectiveRegex, '');
file = file.replace(classDirectiveRegex, '');
return file;
};
concatenate = function(sourceFiles, includeDirectories, outputFile) {
var deps, output;
deps = mapDependencies(sourceFiles, includeDirectories);
output = concatFiles(sourceFiles, deps);
output = removeDirectives(output);
if (outputFile) {
return fs.writeFile(outputFile, output);
} else {
return util.puts(output);
}
};
argv = require('optimist').usage("Usage: coffee coffeescript-concat.coffee [-I .] [-o outputfile.coffee] a.coffee b.coffee\nIf no output file is specified, the resulting source will sent to stdout").describe('I', 'directory to search for files').alias('I', 'include-dir').describe('o', 'output file name').alias('o', 'output-file').demand(2).argv;
includeDirectories = argv.I;
sourceFiles = argv._;
concatenate(sourceFiles, includeDirectories, argv.o);
}).call(this);