coffeescript-concat
Version:
A utility for combining coffeescript files and resolving their dependencies.
296 lines (273 loc) • 10.9 kB
JavaScript
// Generated by CoffeeScript 1.10.0
(function() {
var _, argv, concatFiles, concatenate, findClassDependencies, findClasses, findExternClasses, findFileDependencies, fs, getFileNamesInDirs, getFileNamesInDirsR, includeDirectories, includeDirectoriesRecursive, mapDependencies, options, path, removeDirectives, sourceFiles, util,
indexOf = [].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;
};
findExternClasses = function(file) {
var classNames, externRegex, result;
file = '\n' + file;
externRegex = /#=\s*extern\s+([A-Za-z_$-][A-Za-z0-9_$-.]*)/g;
classNames = [];
while ((result = externRegex.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) {
if (result[1] !== "this") {
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-z0-9_$-][A-Za-z0-9_$-.]*)>/g;
while ((result = fileDirectiveRegex.exec(file)) !== null) {
dependencies.push(result[1]);
}
return dependencies;
};
getFileNamesInDirsR = function(dirs, filesFound, callback) {
var nextDir;
if (dirs.length > 0) {
nextDir = dirs[dirs.length - 1];
return fs.readdir(nextDir, function(err, files) {
var directories, file, filePath, i, len, stats;
directories = [];
if (err) {
throw err;
} else {
for (i = 0, len = files.length; i < len; i++) {
file = files[i];
filePath = nextDir.replace(/\/$/, '') + '/' + file;
stats = fs.statSync(filePath);
if (stats.isDirectory()) {
directories.push(filePath);
} else if (stats.isFile()) {
filesFound.push(filePath);
}
}
dirs.splice(dirs.length - 1, 1);
dirs = dirs.concat(directories);
return getFileNamesInDirsR(dirs, filesFound, function(innerFilesFound) {
return callback(innerFilesFound);
});
}
});
} else {
return callback(filesFound);
}
};
getFileNamesInDirs = function(dirs, callback) {
return getFileNamesInDirsR(dirs, [], callback);
};
mapDependencies = function(sourceFiles, searchDirectories, searchDirectoriesRecursive, callback) {
var dir, f, files, i, len;
files = sourceFiles;
for (i = 0, len = searchDirectories.length; i < len; i++) {
dir = searchDirectories[i];
files = files.concat((function() {
var j, len1, ref, results;
ref = fs.readdirSync(dir);
results = [];
for (j = 0, len1 = ref.length; j < len1; j++) {
f = ref[j];
results.push(path.join(dir, f));
}
return results;
})());
}
return getFileNamesInDirs(searchDirectoriesRecursive, function(filesFound) {
var classes, contents, dependencies, extern, file, fileDef, fileDefs, fileDependencies, j, len1;
files = files.concat(filesFound);
fileDefs = [];
for (j = 0, len1 = files.length; j < len1; j++) {
file = files[j];
if (!(/\.coffee$/.test(file))) {
continue;
}
contents = fs.readFileSync(file).toString();
classes = findClasses(contents);
extern = findExternClasses(contents);
dependencies = findClassDependencies(contents);
fileDependencies = findFileDependencies(contents);
dependencies = _.select(dependencies, function(d) {
return _.indexOf(classes, d) === -1;
});
dependencies = _.select(dependencies, function(d) {
return _.indexOf(extern, d) === -1;
});
fileDef = {
name: file,
classes: classes,
extern: extern,
dependencies: dependencies,
fileDependencies: fileDependencies,
contents: contents
};
fileDefs.push(fileDef);
}
return callback(fileDefs);
});
};
concatFiles = function(sourceFiles, fileDefs, listFilesOnly) {
var allFileDefs, fd, fileDefStack, fileProp, findFileDefByClass, findFileDefByName, i, len, nextFileDef, output, resolveDependencies, resolvedDef, sourceFileDefs, usedFiles;
usedFiles = [];
allFileDefs = fileDefs.slice(0);
if (sourceFiles.length > 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;
})();
} else {
sourceFileDefs = fileDefs;
}
findFileDefByClass = function(className) {
var c, fileDef, i, j, len, len1, searchInClasses;
for (i = 0, len = allFileDefs.length; i < len; i++) {
fileDef = allFileDefs[i];
searchInClasses = fileDef.classes.concat(fileDef.extern);
for (j = 0, len1 = searchInClasses.length; j < len1; j++) {
c = searchInClasses[j];
if (c === className) {
return fileDef;
}
}
}
return null;
};
findFileDefByName = function(fileName) {
var fileDef, i, len, name, temp;
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, i, j, len, len1, neededFile, neededFileDef, neededFileName, nextStack, ref, ref1;
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 : []);
}
}
ref1 = fileDef.fileDependencies;
for (j = 0, len1 = ref1.length; j < len1; j++) {
neededFile = ref1[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 = '';
fileProp = listFilesOnly ? 'name' : 'contents';
for (i = 0, len = fileDefStack.length; i < len; i++) {
nextFileDef = fileDefStack[i];
output += nextFileDef[fileProp] + '\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, includeDirectoriesRecursive, outputFile, listFilesOnly) {
return mapDependencies(sourceFiles, includeDirectories, includeDirectoriesRecursive, function(deps) {
var output;
output = concatFiles(sourceFiles, deps, listFilesOnly);
output = removeDirectives(output);
if (outputFile) {
return fs.writeFile(outputFile, output, function(err) {
if (err) {
return console.error(err);
}
});
} else {
return console.log(output);
}
});
};
options = require('yargs').usage("Usage: coffeescript-concat [options] a.coffee b.coffee ...\nIf no output file is specified, the resulting source will sent to stdout").describe('h', 'display this help').alias('h', 'help').describe('I', 'directory to search for files').alias('I', 'include-dir').describe('R', 'directory to search for files recursively').alias('R', 'include-dir-recursive').describe('o', 'output file name').alias('o', 'output-file').describe('list-files', 'list file names instead of outputting file contents');
argv = options.argv;
includeDirectories = typeof argv.I === 'string' ? [argv.I] : argv.I || [];
includeDirectoriesRecursive = typeof argv.R === 'string' ? [argv.R] : argv.R || [];
sourceFiles = typeof argv._ === 'string' ? [argv._] : argv._;
if (argv.help || (includeDirectories.length === 0 && includeDirectoriesRecursive.length === 0 && sourceFiles.length === 0)) {
options.showHelp();
}
concatenate(sourceFiles, includeDirectories, includeDirectoriesRecursive, argv.o, argv['list-files']);
}).call(this);