coffeemill
Version:
CoffeeScript packager
444 lines (412 loc) • 16.3 kB
JavaScript
(function() {
var CoffeeMill, Deferred, EventEmitter, coffee, fs, path, pkg, spawn, uglify,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
path = require('path');
fs = require('fs');
spawn = require('child_process').spawn;
EventEmitter = require('events').EventEmitter;
coffee = require('coffee-script');
Deferred = require('jsdeferred').Deferred;
uglify = require('uglify-js');
pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')));
CoffeeMill = (function(_super) {
var EXT_NAMES;
__extends(CoffeeMill, _super);
EXT_NAMES = ['.coffee'];
CoffeeMill.rTagVersion = /^v?([0-9\.]+)$/;
CoffeeMill.rDocComment = /\/\*\*([\s\S]+?)\*\/\s*(.*)/g;
CoffeeMill.rParam = /\s+{?(\S+?)}?\s+(\S+)\s+(.*)/g;
CoffeeMill.rReturn = /\s+{?(\S+?)}?\s+(.*)/g;
CoffeeMill.rCompletelyBlank = /^\s*$/;
CoffeeMill.rLineEndSpace = /[ \t]+$/g;
CoffeeMill.rBreak = /[\r\n]{3,}/g;
function CoffeeMill(options) {
this.options = options;
this.changed = __bind(this.changed, this);
CoffeeMill.__super__.constructor.call(this);
if (options.input == null) {
options.input = ['src'];
}
if (options.output == null) {
options.output = ['lib'];
}
if (options.name == null) {
options.name = 'main';
}
if (options.ver == null) {
options.ver = '';
}
if ((options.js == null) && (options.uglify == null) && (options.coffee == null) && (options.map == null)) {
options.js = true;
}
}
CoffeeMill.prototype.changed = function() {
var _this = this;
clearTimeout(this.timeoutId);
return this.timeoutId = setTimeout(function() {
return _this.run();
}, 500);
};
CoffeeMill.prototype.run = function() {
this.scanInput();
this.compile();
return this;
};
CoffeeMill.prototype.scanInput = function() {
var watcher, _i, _len, _ref;
if (this.watchers != null) {
_ref = this.watchers;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
watcher = _ref[_i];
watcher.close();
}
}
this.watchers = [];
this.hasError = false;
this.files = this.findFiles(this.options.input, this.options.watch ? this.changed : null);
return this;
};
CoffeeMill.prototype.findFiles = function(dirs, change, basedir, files) {
var childs, className, code, dir, dirPath, err, extendsClassName, extname, file, filePath, filename, i, isBasedir, name, namespace, namespaces, packages, r, stats, _i, _j, _len, _len1;
if (files == null) {
files = [];
}
isBasedir = basedir != null;
for (_i = 0, _len = dirs.length; _i < _len; _i++) {
dir = dirs[_i];
if (isBasedir) {
dirPath = dir;
} else {
dirPath = basedir = dir;
}
stats = fs.statSync(dirPath);
if (stats.isFile()) {
filePath = dirPath;
if (EXT_NAMES.indexOf(path.extname(filePath)) !== -1) {
packages = path.relative(basedir, filePath).split(path.sep);
packages.pop();
filename = path.basename(filePath);
extname = path.extname(filePath);
name = path.basename(filePath, extname);
className = extendsClassName = null;
code = fs.readFileSync(filePath, 'utf8');
if (extname === '.coffee') {
try {
coffee.compile(code);
} catch (_error) {
err = _error;
this.hasError = true;
this.reportCompileError(filename, code, err);
}
}
r = code.match(/class\s+(\S+)(?:\s+extends\s+(\S+))?/m);
if (r != null) {
r[0], className = r[1], extendsClassName = r[2];
}
namespaces = packages.concat([name]);
namespace = namespaces.join('.');
if ((className != null) && className !== namespace) {
this.emit('warn', "class name isn't '" + namespace + "' (" + filePath + ")");
}
files.push({
filePath: filePath,
extname: extname,
packages: packages,
name: name,
namespaces: namespaces,
namespace: namespace,
className: className,
extendsClassName: extendsClassName,
code: code
});
}
} else if (stats.isDirectory()) {
if (change != null) {
this.watchers.push(fs.watch(dirPath, change));
}
childs = fs.readdirSync(dirPath);
for (i = _j = 0, _len1 = childs.length; _j < _len1; i = ++_j) {
file = childs[i];
childs[i] = path.join(dirPath, file);
}
this.findFiles(childs, change, basedir, files);
}
}
return files;
};
CoffeeMill.prototype.compile = function() {
var cs, csName,
_this = this;
if (this.hasError) {
return;
}
cs = '';
csName = '';
Deferred.next(function() {
switch (_this.options.ver) {
case 'none':
return '';
case 'gitTag':
return _this.gitTag();
default:
return _this.options.ver;
}
}).error(function(err) {
return _this.emit('error', 'fail to fetch version');
}).next(function(version) {
var classFiles, className, classNames, code, codes, counter, cwd, data, exp, exports, exportsCodes, ext, extendsClassName, file, filename, i, j, js, k, len, map, name, namespace, normalFiles, outputDir, outputPath, outputs, packageNamespace, packages, postfix, resolvedFiles, type, uglified, v, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _len7, _m, _n, _o, _p, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
if (version !== '') {
postfix = "-" + version;
} else {
postfix = '';
}
normalFiles = [];
classFiles = [];
classNames = [];
resolvedFiles = [];
_ref = _this.files;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
file = _ref[_i];
if (file.className != null) {
classFiles.push(file);
classNames.push(file.className);
} else {
normalFiles.push(file);
}
}
i = classFiles.length;
while (i--) {
extendsClassName = classFiles[i].extendsClassName;
if ((extendsClassName == null) || classNames.indexOf(extendsClassName) === -1) {
resolvedFiles.unshift(classFiles.splice(i, 1)[0]);
}
}
while (i = classFiles.length) {
while (i--) {
extendsClassName = classFiles[i].extendsClassName;
for (j = _j = 0, _len1 = resolvedFiles.length; _j < _len1; j = ++_j) {
className = resolvedFiles[j].className;
if (className === extendsClassName) {
resolvedFiles.splice(j + 1, 0, classFiles.splice(i, 1)[0]);
break;
}
}
}
}
_this.files = normalFiles.concat(resolvedFiles);
codes = [];
exports = {};
_ref1 = _this.files;
for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) {
_ref2 = _ref1[_k], code = _ref2.code, name = _ref2.name, className = _ref2.className, packages = _ref2.packages, namespace = _ref2.namespace;
codes.push(code);
exp = exports;
for (_l = 0, _len3 = packages.length; _l < _len3; _l++) {
packageNamespace = packages[_l];
if (exp[packageNamespace] == null) {
exp[packageNamespace] = {};
}
exp = exp[packageNamespace];
}
}
exportsCodes = [];
exportsCodes.push("___exports = if module?.exports? then module.exports else if window? then window else {}\n___extend = (child, parent) ->\n for key, val of parent\n continue unless Object::hasOwnProperty.call parent, key\n if Object::toString.call(val) is '[object Object]'\n child[key] = {}\n ___extend child[key], val\n else\n child[key] = val");
for (k in exports) {
v = exports[k];
exportsCodes.push("___exports." + k + " ?= {}\n" + k + " = ___exports." + k + "\n___extend " + k + ", " + (JSON.stringify(v)));
}
cs = exportsCodes.concat(codes.map(function(code) {
return code.replace(/class\s+(\S+)/g, '___exports.$1 = class $1');
})).join('\n\n');
csName = "" + _this.options.name + postfix + ".coffee";
outputs = [];
if (_this.options.coffee) {
outputs.push({
type: 'coffee',
filename: csName,
data: cs
});
}
if (_this.options.map) {
_ref3 = coffee.compile(cs, {
sourceMap: true,
generatedFile: "" + _this.options.name + postfix + ".js",
sourceRoot: '',
sourceFiles: ["" + _this.options.name + postfix + ".coffee"]
}), js = _ref3.js, map = _ref3.v3SourceMap;
} else {
js = coffee.compile(cs);
}
if (_this.options.js) {
if (map != null) {
js += "\n/*\n//@ sourceMappingURL=" + _this.options.name + postfix + ".map\n*/";
}
outputs.push({
type: 'js',
filename: "" + _this.options.name + postfix + ".js",
data: js
});
}
if (map != null) {
outputs.push({
type: 'source map',
filename: "" + _this.options.name + postfix + ".map",
data: map
});
}
if (_this.options.uglify) {
uglified = uglify.minify(js, {
fromString: true
}).code;
if (postfix === '') {
ext = '-min.js';
} else {
ext = '.min.js';
}
outputs.push({
type: 'uglify',
filename: "" + _this.options.name + postfix + ext,
data: uglified
});
}
len = 0;
for (_m = 0, _len4 = outputs.length; _m < _len4; _m++) {
type = outputs[_m].type;
len = Math.max(len, type.length);
}
for (i = _n = 0, _len5 = outputs.length; _n < _len5; i = ++_n) {
type = outputs[i].type;
while (type.length < len) {
type += ' ';
}
outputs[i].type = type;
}
cwd = process.cwd();
counter = 0;
_ref4 = _this.options.output;
for (_o = 0, _len6 = _ref4.length; _o < _len6; _o++) {
outputDir = _ref4[_o];
outputDir = path.resolve(cwd, outputDir);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
for (_p = 0, _len7 = outputs.length; _p < _len7; _p++) {
_ref5 = outputs[_p], type = _ref5.type, filename = _ref5.filename, data = _ref5.data;
outputPath = path.resolve(cwd, path.join(outputDir, filename));
fs.writeFileSync(outputPath, data, 'utf8');
_this.emit('created', path.relative('.', outputPath));
counter++;
}
}
return _this.emit('complete', counter);
}).error(function(err) {
if (err.location != null) {
return _this.reportCompileError(csName, cs, err);
} else {
return _this.emit('error', "" + err.stack);
}
});
return this;
};
CoffeeMill.prototype.reportCompileError = function(csName, cs, err) {
var code, first_column, first_line, last_column, last_line, lineNumber, lines, mark, nextLineNumber, _ref;
if (err.location != null) {
_ref = err.location, first_line = _ref.first_line, first_column = _ref.first_column, last_line = _ref.last_line, last_column = _ref.last_column;
lines = cs.split(/\r?\n/);
code = lines.splice(first_line, 1);
if (first_line !== last_line) {
last_line = first_line;
last_column = code.length - 1;
}
if (last_column <= first_column) {
last_column = first_column;
}
mark = '';
while (mark.length < first_column) {
mark += ' ';
}
while (mark.length <= last_column) {
mark += '^';
}
lineNumber = '' + first_line;
nextLineNumber = '';
while (nextLineNumber.length < lineNumber.length) {
nextLineNumber += ' ';
}
return this.emit('error', "CoffeeScript compile error\n" + csName + ":" + first_line + ":" + first_column + "\n" + (lineNumber + '.') + code + "\n" + (nextLineNumber + '.') + mark);
} else {
return this.emit('error', "CoffeeScript compile error\n" + err);
}
};
CoffeeMill.prototype.indent = function(code) {
var i, line, lines, _i, _len;
lines = code.split(/\r?\n/g);
for (i = _i = 0, _len = lines.length; _i < _len; i = ++_i) {
line = lines[i];
lines[i] = ' ' + line;
}
return lines.join('\n');
};
CoffeeMill.prototype.gitTag = function() {
var d, err, gitTag, out;
d = new Deferred();
gitTag = spawn('git', ['tag']);
out = '';
gitTag.stdout.setEncoding('utf8');
gitTag.stdout.on('data', function(data) {
return out += data;
});
err = '';
gitTag.stderr.setEncoding('utf8');
gitTag.stderr.on('data', function(data) {
return err += data.red;
});
gitTag.on('close', function() {
var i, minor, r, tag, tags, versions;
if (err !== '') {
return d.fail(err);
}
tags = out.split('\n');
i = tags.length;
while (i--) {
tag = tags[i];
r = tag.match(CoffeeMill.rTagVersion);
if ((r != null ? r[1] : void 0) == null) {
continue;
}
versions = r[1].split('.');
minor = parseInt(versions[versions.length - 1], 10);
versions[versions.length - 1] = minor + 1;
d.call(versions.join('.'));
return;
}
return d.fail('no tag as version');
});
return d;
};
return CoffeeMill;
})(EventEmitter);
module.exports = {
CoffeeMill: CoffeeMill,
run: function() {
var commander, list, util;
util = require('util');
commander = require('commander');
list = function(val) {
return val.split(',');
};
commander.version(pkg.version).usage('[options]').option('-i, --input <dirnames>', 'output directory (defualt is \'src\')', list, ['src']).option('-o, --output <dirnames>', 'output directory (defualt is \'lib\')', list, ['lib']).option('-n, --name [basename]', 'output directory (defualt is \'main\')', 'main').option('-v, --ver <version>', 'file version: supports version string, \'gitTag\' or \'none\' (default is \'\')', '').option('-j, --js', 'write JavaScript file (.js)', true).option('-u, --uglify', 'write uglified JavaScript file (.min.js)').option('-c, --coffee', 'write CoffeeScript file (.coffee)').option('-m, --map', 'write source maps file JavaScript to CoffeeScript (.map)').option('-w, --watch', 'watch the change of input directory recursively').parse(process.argv);
return new CoffeeMill(commander).on('warn', function(message) {
return util.puts(message);
}).on('error', function(message) {
return util.error(message);
}).on('created', function(filepath) {
return util.puts("File " + filepath + " created");
}).on('complete', function(filenum) {
return util.puts("Done without errors");
}).run();
}
};
}).call(this);