More work on watching / joining ... Fixes #1941, fixes #365. Watched files may now be added and removed, including with --join

This commit is contained in:
Jeremy Ashkenas
2011-12-18 11:27:02 -05:00
parent 0069c4901f
commit b9805f3f80
2 changed files with 209 additions and 123 deletions

View File

@@ -1,5 +1,5 @@
(function() {
var BANNER, CoffeeScript, EventEmitter, SWITCHES, compileJoin, compileOptions, compileScript, compileScripts, compileStdio, exec, forkNode, fs, helpers, lint, loadRequires, optionParser, optparse, opts, parseOptions, path, printLine, printTokens, printWarn, sourceCode, sources, spawn, usage, version, watch, writeJs, _ref;
var BANNER, CoffeeScript, EventEmitter, SWITCHES, compileJoin, compileOptions, compilePath, compileScript, compileStdio, exec, forkNode, fs, helpers, lint, loadRequires, notSources, optionParser, optparse, opts, outputPath, parseOptions, path, printLine, printTokens, printWarn, removeSource, sourceCode, sources, spawn, timeLog, usage, version, watch, watchDir, writeJs, _ref;
fs = require('fs');
@@ -35,9 +35,12 @@
sourceCode = [];
notSources = {};
optionParser = null;
exports.run = function() {
var source, _i, _len, _results;
parseOptions();
if (opts.nodejs) return forkNode();
if (opts.help) return usage();
@@ -54,66 +57,60 @@
process.argv = process.argv.slice(0, 2).concat(opts.literals);
process.argv[0] = 'coffee';
process.execPath = require.main.filename;
return compileScripts();
};
compileScripts = function() {
var base, compile, index, source, _len, _results;
_results = [];
for (index = 0, _len = sources.length; index < _len; index++) {
source = sources[index];
sourceCode[index] = null;
base = path.join(source);
compile = function(source, topLevel) {
return path.exists(source, function(exists) {
if (topLevel && !exists && source.slice(-7) !== '.coffee') {
source = sources[sources.indexOf(source)] = "" + source + ".coffee";
return compile(source, topLevel);
}
if (topLevel && !exists) {
console.error("File not found: " + source);
process.exit(1);
}
return fs.stat(source, function(err, stats) {
if (err) throw err;
if (stats.isDirectory()) {
return fs.readdir(source, function(err, files) {
var file, _i, _len2, _ref2, _results2;
if (err) throw err;
files = files.map(function(file) {
return path.join(source, file);
});
index = sources.indexOf(source);
[].splice.apply(sources, [index, index - index + 1].concat(files)), files;
[].splice.apply(sourceCode, [index, index - index + 1].concat(_ref2 = files.map(function() {
return null;
}))), _ref2;
_results2 = [];
for (_i = 0, _len2 = files.length; _i < _len2; _i++) {
file = files[_i];
_results2.push(compile(file));
}
return _results2;
});
} else if (topLevel || path.extname(source) === '.coffee') {
fs.readFile(source, function(err, code) {
if (err) throw err;
return compileScript(source, code.toString(), base);
});
if (opts.watch) return watch(source, base);
} else {
index = sources.indexOf(source);
sources.splice(index, 1);
return sourceCode.splice(index, 1);
}
});
});
};
_results.push(compile(source, true));
for (_i = 0, _len = sources.length; _i < _len; _i++) {
source = sources[_i];
_results.push(compilePath(source, true, path.join(source)));
}
return _results;
};
compilePath = function(source, topLevel, base) {
return path.exists(source, function(exists) {
if (topLevel && !exists && source.slice(-7) !== '.coffee') {
source = sources[sources.indexOf(source)] = "" + source + ".coffee";
return compilePath(source, topLevel, base);
}
if (topLevel && !exists) {
console.error("File not found: " + source);
process.exit(1);
}
return fs.stat(source, function(err, stats) {
if (err) throw err;
if (stats.isDirectory()) {
if (opts.watch) watchDir(source, base);
return fs.readdir(source, function(err, files) {
var file, index, _i, _len, _ref2, _results;
if (err) throw err;
files = files.map(function(file) {
return path.join(source, file);
});
index = sources.indexOf(source);
[].splice.apply(sources, [index, index - index + 1].concat(files)), files;
[].splice.apply(sourceCode, [index, index - index + 1].concat(_ref2 = files.map(function() {
return null;
}))), _ref2;
_results = [];
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
_results.push(compilePath(file, false, base));
}
return _results;
});
} else if (topLevel || path.extname(source) === '.coffee') {
if (opts.watch) watch(source, base);
return fs.readFile(source, function(err, code) {
if (err) throw err;
return compileScript(source, code.toString(), base);
});
} else {
notSources[source] = true;
return removeSource(source);
}
});
});
};
compileScript = function(file, input, base) {
var o, options, t, task;
o = opts;
@@ -131,8 +128,9 @@
return printLine(CoffeeScript.nodes(t.input).toString().trim());
} else if (o.run) {
return CoffeeScript.run(t.input, t.options);
} else if (o.join && file !== o.join) {
return compileJoin(t.file, t.input);
} else if (o.join && t.file !== o.join) {
sourceCode[sources.indexOf(t.file)] = t.input;
return compileJoin();
} else {
t.output = CoffeeScript.compile(t.input, t.options);
CoffeeScript.emit('success', task);
@@ -165,8 +163,7 @@
});
};
compileJoin = function(file, code) {
sourceCode[sources.indexOf(file)] = code;
compileJoin = function() {
if (!sourceCode.some(function(code) {
return code === null;
})) {
@@ -208,39 +205,92 @@
} else if (event === 'rename') {
watcher.close();
return setTimeout(function() {
compile();
return watcher = fs.watch(source, callback);
return path.exists(source, function(exists) {
if (exists) {
compile();
return watcher = fs.watch(source, callback);
} else {
removeSource(source);
if (opts.join) {
compileJoin();
} else {
fs.unlink(outputPath(source, base), function(err) {
if (err) throw err;
});
}
return timeLog("removed " + source);
}
});
}, 250);
}
});
};
writeJs = function(source, js, base) {
var baseDir, compile, dir, filename, jsPath, srcDir;
watchDir = function(source, base) {
var watcher;
return watcher = fs.watch(source, function() {
return fs.readdir(source, function(err, files) {
var file, _i, _len, _results;
if (err) throw err;
files = files.map(function(file) {
return path.join(source, file);
});
_results = [];
for (_i = 0, _len = files.length; _i < _len; _i++) {
file = files[_i];
if (!(!notSources[file] && sources.indexOf(file) < 0)) continue;
sources.push(file);
sourceCode.push(null);
_results.push(compilePath(file, false, base));
}
return _results;
});
});
};
removeSource = function(source) {
var index;
index = sources.indexOf(source);
sources.splice(index, 1);
return sourceCode.splice(index, 1);
};
outputPath = function(source, base) {
var baseDir, dir, filename, srcDir;
filename = path.basename(source, path.extname(source)) + '.js';
srcDir = path.dirname(source);
baseDir = base === '.' ? srcDir : srcDir.substring(base.length);
dir = opts.output ? path.join(opts.output, baseDir) : srcDir;
jsPath = path.join(dir, filename);
return path.join(dir, filename);
};
writeJs = function(source, js, base) {
var compile, jsDir, jsPath;
jsPath = outputPath(source, base);
jsDir = path.dirname(jsPath);
compile = function() {
if (js.length <= 0) js = ' ';
return fs.writeFile(jsPath, js, function(err) {
if (err) {
return printLine(err.message);
} else if (opts.compile && opts.watch) {
return console.log("" + ((new Date).toLocaleTimeString()) + " - compiled " + source);
return timeLog("compiled " + source);
}
});
};
return path.exists(dir, function(exists) {
return path.exists(jsDir, function(exists) {
if (exists) {
return compile();
} else {
return exec("mkdir -p " + dir, compile);
return exec("mkdir -p " + jsDir, compile);
}
});
};
timeLog = function(message) {
return console.log("" + ((new Date).toLocaleTimeString()) + " - " + message);
};
lint = function(file, js) {
var conf, jsl, printIt;
printIt = function(buffer) {
@@ -270,13 +320,17 @@
};
parseOptions = function() {
var o;
var i, o, source, _len;
optionParser = new optparse.OptionParser(SWITCHES, BANNER);
o = opts = optionParser.parse(process.argv.slice(2));
o.compile || (o.compile = !!o.output);
o.run = !(o.compile || o.print || o.lint);
o.print = !!(o.print || (o.eval || o.stdio && o.compile));
return sources = o.arguments;
sources = o.arguments;
for (i = 0, _len = sources.length; i < _len; i++) {
source = sources[i];
sourceCode[i] = null;
}
};
compileOptions = function(filename) {

View File

@@ -50,6 +50,7 @@ SWITCHES = [
opts = {}
sources = []
sourceCode = []
notSources = {}
optionParser = null
# Run `coffee` by parsing passed options and determining what action to take.
@@ -72,47 +73,40 @@ exports.run = ->
process.argv = process.argv.slice(0, 2).concat opts.literals
process.argv[0] = 'coffee'
process.execPath = require.main.filename
compileScripts()
# Asynchronously read in each CoffeeScript in a list of source files and
# compile them. If a directory is passed, recursively compile all
# '.coffee' extension source files in it and all subdirectories.
compileScripts = ->
for source, index in sources
sourceCode[index] = null
for source in sources
compilePath source, yes, path.join source
base = path.join source
compile = (source, topLevel) ->
path.exists source, (exists) ->
if topLevel and not exists and source[-7..] isnt '.coffee'
source = sources[sources.indexOf(source)] = "#{source}.coffee"
return compile source, topLevel
if topLevel and not exists
console.error "File not found: #{source}"
process.exit 1
fs.stat source, (err, stats) ->
# Compile a path, which could be a script or a directory. If a directory
# is passed, recursively compile all '.coffee' extension source files in it
# and all subdirectories.
compilePath = (source, topLevel, base) ->
path.exists source, (exists) ->
if topLevel and not exists and source[-7..] isnt '.coffee'
source = sources[sources.indexOf(source)] = "#{source}.coffee"
return compilePath source, topLevel, base
if topLevel and not exists
console.error "File not found: #{source}"
process.exit 1
fs.stat source, (err, stats) ->
throw err if err
if stats.isDirectory()
watchDir source, base if opts.watch
fs.readdir source, (err, files) ->
throw err if err
if stats.isDirectory()
fs.readdir source, (err, files) ->
throw err if err
files = files.map (file) -> path.join source, file
index = sources.indexOf source
sources[index..index] = files
sourceCode[index..index] = files.map -> null
compile file for file in files
else if topLevel or path.extname(source) is '.coffee'
fs.readFile source, (err, code) ->
throw err if err
compileScript(source, code.toString(), base)
watch source, base if opts.watch
else
index = sources.indexOf source
sources.splice index, 1
sourceCode.splice index, 1
files = files.map (file) -> path.join source, file
index = sources.indexOf source
sources[index..index] = files
sourceCode[index..index] = files.map -> null
compilePath file, no, base for file in files
else if topLevel or path.extname(source) is '.coffee'
watch source, base if opts.watch
fs.readFile source, (err, code) ->
throw err if err
compileScript(source, code.toString(), base)
else
notSources[source] = yes
removeSource source
compile source, true
# Compile a single source script, containing the given code, according to the
# requested options. If evaluating the script directly sets `__filename`,
@@ -126,8 +120,9 @@ compileScript = (file, input, base) ->
if o.tokens then printTokens CoffeeScript.tokens t.input
else if o.nodes then printLine CoffeeScript.nodes(t.input).toString().trim()
else if o.run then CoffeeScript.run t.input, t.options
else if o.join and file isnt o.join
compileJoin t.file, t.input
else if o.join and t.file isnt o.join
sourceCode[sources.indexOf(t.file)] = t.input
compileJoin()
else
t.output = CoffeeScript.compile t.input, t.options
CoffeeScript.emit 'success', task
@@ -153,8 +148,7 @@ compileStdio = ->
# If all of the source files are done being read, concatenate and compile
# them together.
compileJoin = (file, code) ->
sourceCode[sources.indexOf(file)] = code
compileJoin = ->
unless sourceCode.some((code) -> code is null)
compileScript opts.join, sourceCode.join('\n'), opts.join
@@ -188,28 +182,64 @@ watch = (source, base) ->
else if event is 'rename'
watcher.close()
setTimeout ->
compile()
watcher = fs.watch source, callback
path.exists source, (exists) ->
if exists
compile()
watcher = fs.watch source, callback
else
removeSource source
if opts.join
compileJoin()
else
fs.unlink outputPath(source, base), (err) ->
throw err if err
timeLog "removed #{source}"
, 250
# Watch a directory of files for new additions.
watchDir = (source, base) ->
watcher = fs.watch source, ->
fs.readdir source, (err, files) ->
throw err if err
files = files.map (file) -> path.join source, file
for file in files when not notSources[file] and sources.indexOf(file) < 0
sources.push file
sourceCode.push null
compilePath file, no, base
# Remove a file from our source list, and source code cache.
removeSource = (source) ->
index = sources.indexOf source
sources.splice index, 1
sourceCode.splice index, 1
# Get the corresponding output JavaScript path for a source file.
outputPath = (source, base) ->
filename = path.basename(source, path.extname(source)) + '.js'
srcDir = path.dirname source
baseDir = if base is '.' then srcDir else srcDir.substring base.length
dir = if opts.output then path.join opts.output, baseDir else srcDir
path.join dir, filename
# Write out a JavaScript source file with the compiled code. By default, files
# are written out in `cwd` as `.js` files with the same name, but the output
# directory can be customized with `--output`.
writeJs = (source, js, base) ->
filename = path.basename(source, path.extname(source)) + '.js'
srcDir = path.dirname source
baseDir = if base is '.' then srcDir else srcDir.substring base.length
dir = if opts.output then path.join opts.output, baseDir else srcDir
jsPath = path.join dir, filename
compile = ->
jsPath = outputPath source, base
jsDir = path.dirname jsPath
compile = ->
js = ' ' if js.length <= 0
fs.writeFile jsPath, js, (err) ->
if err
printLine err.message
else if opts.compile and opts.watch
console.log "#{(new Date).toLocaleTimeString()} - compiled #{source}"
path.exists dir, (exists) ->
if exists then compile() else exec "mkdir -p #{dir}", compile
timeLog "compiled #{source}"
path.exists jsDir, (exists) ->
if exists then compile() else exec "mkdir -p #{jsDir}", compile
# When watching scripts, it's useful to log changes with the timestamp.
timeLog = (message) ->
console.log "#{(new Date).toLocaleTimeString()} - #{message}"
# Pipe compiled JS through JSLint (requires a working `jsl` command), printing
# any errors or warnings that arise.
@@ -238,6 +268,8 @@ parseOptions = ->
o.run = not (o.compile or o.print or o.lint)
o.print = !! (o.print or (o.eval or o.stdio and o.compile))
sources = o.arguments
sourceCode[i] = null for source, i in sources
return
# The compile-time options to pass to the CoffeeScript compiler.
compileOptions = (filename) -> {filename, bare: opts.bare}