1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

[CS2] Fix #2870: Allow specifying output filename (#4661)

* Fix #2870: If --output ends with a filename, and the input is a file and not a path, save as the desired filename

* If an output path ends in a slash, force saving into an output folder even if that folder name would contain a period (e.g. /scripts.js/); if output filename is only periods, treat it as a path

* Restrict exceptions
This commit is contained in:
Geoffrey Booth 2017-08-24 00:03:57 -07:00 committed by GitHub
parent 892c4699dd
commit 3dd458267b
3 changed files with 100 additions and 82 deletions

View file

@ -45,7 +45,7 @@
BANNER = 'Usage: coffee [options] path/to/script.coffee [args]\n\nIf called without options, `coffee` will run your script.';
// The list of all the valid option flags that `coffee` knows how to handle.
SWITCHES = [['-b', '--bare', 'compile without a top-level function wrapper'], ['-c', '--compile', 'compile to JavaScript and save as .js files'], ['-e', '--eval', 'pass a string from the command line as input'], ['-h', '--help', 'display this help message'], ['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling'], ['-m', '--map', 'generate source map and save as .js.map files'], ['-M', '--inline-map', 'generate source map and include it directly in output'], ['-n', '--nodes', 'print out the parse tree that the parser produces'], ['--nodejs [ARGS]', 'pass options directly to the "node" binary'], ['--no-header', 'suppress the "Generated by" header'], ['-o', '--output [DIR]', 'set the output directory for compiled JavaScript'], ['-p', '--print', 'print out the compiled JavaScript'], ['-r', '--require [MODULE*]', 'require the given module before eval or REPL'], ['-s', '--stdio', 'listen for and compile scripts over stdio'], ['-l', '--literate', 'treat stdio as literate style coffeescript'], ['-t', '--tokens', 'print out the tokens that the lexer/rewriter produce'], ['-v', '--version', 'display the version number'], ['-w', '--watch', 'watch scripts for changes and rerun commands']];
SWITCHES = [['-b', '--bare', 'compile without a top-level function wrapper'], ['-c', '--compile', 'compile to JavaScript and save as .js files'], ['-e', '--eval', 'pass a string from the command line as input'], ['-h', '--help', 'display this help message'], ['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling'], ['-m', '--map', 'generate source map and save as .js.map files'], ['-M', '--inline-map', 'generate source map and include it directly in output'], ['-n', '--nodes', 'print out the parse tree that the parser produces'], ['--nodejs [ARGS]', 'pass options directly to the "node" binary'], ['--no-header', 'suppress the "Generated by" header'], ['-o', '--output [PATH]', 'set the output path or path/filename for compiled JavaScript'], ['-p', '--print', 'print out the compiled JavaScript'], ['-r', '--require [MODULE*]', 'require the given module before eval or REPL'], ['-s', '--stdio', 'listen for and compile scripts over stdio'], ['-l', '--literate', 'treat stdio as literate style coffeescript'], ['-t', '--tokens', 'print out the tokens that the lexer/rewriter produce'], ['-v', '--version', 'display the version number'], ['-w', '--watch', 'watch scripts for changes and rerun commands']];
// Top-level objects shared by all the functions.
opts = {};
@ -68,7 +68,7 @@
// Many flags cause us to divert before compiling anything. Flags passed after
// `--` will be passed verbatim to your script as arguments in `process.argv`
exports.run = function() {
var err, i, len, literals, ref, replCliOpts, results, source;
var err, i, len, literals, outputBasename, ref, replCliOpts, results, source;
optionParser = buildCSOptionParser();
try {
parseOptions();
@ -117,7 +117,16 @@
process.argv = process.argv.slice(0, 2).concat(literals);
process.argv[0] = 'coffee';
if (opts.output) {
opts.output = path.resolve(opts.output);
outputBasename = path.basename(opts.output);
if (indexOf.call(outputBasename, '.') >= 0 && (outputBasename !== '.' && outputBasename !== '..') && !helpers.ends(opts.output, path.sep)) {
// An output filename was specified, e.g. `/dist/scripts.js`.
opts.outputFilename = outputBasename;
opts.outputPath = path.resolve(path.dirname(opts.output));
} else {
// An output path was specified, e.g. `/dist`.
opts.outputFilename = null;
opts.outputPath = path.resolve(opts.output);
}
}
if (opts.join) {
opts.join = path.resolve(opts.join);
@ -235,43 +244,43 @@
};
// Compile a single source script, containing the given code, according to the
// requested options. If evaluating the script directly sets `__filename`,
// requested options. If evaluating the script directly, set `__filename`,
// `__dirname` and `module.filename` to be correct relative to the script's path.
compileScript = function(file, input, base = null) {
var compiled, err, message, o, options, t, task;
o = opts;
var compiled, err, message, options, saveTo, task;
options = compileOptions(file, base);
try {
t = task = {file, input, options};
task = {file, input, options};
CoffeeScript.emit('compile', task);
if (o.tokens) {
return printTokens(CoffeeScript.tokens(t.input, t.options));
} else if (o.nodes) {
return printLine(CoffeeScript.nodes(t.input, t.options).toString().trim());
} else if (o.run) {
if (opts.tokens) {
return printTokens(CoffeeScript.tokens(task.input, task.options));
} else if (opts.nodes) {
return printLine(CoffeeScript.nodes(task.input, task.options).toString().trim());
} else if (opts.run) {
CoffeeScript.register();
if (opts.prelude) {
CoffeeScript.eval(opts.prelude, t.options);
CoffeeScript.eval(opts.prelude, task.options);
}
return CoffeeScript.run(t.input, t.options);
} else if (o.join && t.file !== o.join) {
return CoffeeScript.run(task.input, task.options);
} else if (opts.join && task.file !== opts.join) {
if (helpers.isLiterate(file)) {
t.input = helpers.invertLiterate(t.input);
task.input = helpers.invertLiterate(task.input);
}
sourceCode[sources.indexOf(t.file)] = t.input;
sourceCode[sources.indexOf(task.file)] = task.input;
return compileJoin();
} else {
compiled = CoffeeScript.compile(t.input, t.options);
t.output = compiled;
if (o.map) {
t.output = compiled.js;
t.sourceMap = compiled.v3SourceMap;
compiled = CoffeeScript.compile(task.input, task.options);
task.output = compiled;
if (opts.map) {
task.output = compiled.js;
task.sourceMap = compiled.v3SourceMap;
}
CoffeeScript.emit('success', task);
if (o.print) {
return printLine(t.output.trim());
} else if (o.compile || o.map) {
return writeJs(base, t.file, t.output, options.jsPath, t.sourceMap);
if (opts.print) {
return printLine(task.output.trim());
} else if (opts.compile || opts.map) {
saveTo = opts.outputFilename && sources.length === 1 ? path.join(opts.outputPath, opts.outputFilename) : options.jsPath;
return writeJs(base, task.file, task.output, saveTo, task.sourceMap);
}
}
} catch (error) {
@ -281,7 +290,7 @@
return;
}
message = (err != null ? err.stack : void 0) || `${err}`;
if (o.watch) {
if (opts.watch) {
return printLine(message + '\x07');
} else {
printWarn(message);
@ -486,13 +495,7 @@
var basename, dir, srcDir;
basename = helpers.baseFileName(source, true, useWinPathSep);
srcDir = path.dirname(source);
if (!opts.output) {
dir = srcDir;
} else if (source === base) {
dir = opts.output;
} else {
dir = path.join(opts.output, path.relative(base, srcDir));
}
dir = !opts.outputPath ? srcDir : source === base ? opts.outputPath : path.join(opts.outputPath, path.relative(base, srcDir));
return path.join(dir, basename + extension);
};

View file

@ -43,7 +43,7 @@ SWITCHES = [
['-n', '--nodes', 'print out the parse tree that the parser produces']
[ '--nodejs [ARGS]', 'pass options directly to the "node" binary']
[ '--no-header', 'suppress the "Generated by" header']
['-o', '--output [DIR]', 'set the output directory for compiled JavaScript']
['-o', '--output [PATH]', 'set the output path or path/filename for compiled JavaScript']
['-p', '--print', 'print out the compiled JavaScript']
['-r', '--require [MODULE*]', 'require the given module before eval or REPL']
['-s', '--stdio', 'listen for and compile scripts over stdio']
@ -102,7 +102,19 @@ exports.run = ->
process.argv = process.argv[0..1].concat literals
process.argv[0] = 'coffee'
opts.output = path.resolve opts.output if opts.output
if opts.output
outputBasename = path.basename opts.output
if '.' in outputBasename and
outputBasename not in ['.', '..'] and
not helpers.ends(opts.output, path.sep)
# An output filename was specified, e.g. `/dist/scripts.js`.
opts.outputFilename = outputBasename
opts.outputPath = path.resolve path.dirname opts.output
else
# An output path was specified, e.g. `/dist`.
opts.outputFilename = null
opts.outputPath = path.resolve opts.output
if opts.join
opts.join = path.resolve opts.join
console.error '''
@ -167,7 +179,7 @@ compilePath = (source, topLevel, base) ->
code = fs.readFileSync source
catch err
if err.code is 'ENOENT' then return else throw err
compileScript(source, code.toString(), base)
compileScript source, code.toString(), base
else
notSources[source] = yes
@ -182,43 +194,46 @@ findDirectoryIndex = (source) ->
process.exit 1
# Compile a single source script, containing the given code, according to the
# requested options. If evaluating the script directly sets `__filename`,
# requested options. If evaluating the script directly, set `__filename`,
# `__dirname` and `module.filename` to be correct relative to the script's path.
compileScript = (file, input, base = null) ->
o = opts
options = compileOptions file, base
try
t = task = {file, input, options}
task = {file, input, options}
CoffeeScript.emit 'compile', task
if o.tokens
printTokens CoffeeScript.tokens t.input, t.options
else if o.nodes
printLine CoffeeScript.nodes(t.input, t.options).toString().trim()
else if o.run
if opts.tokens
printTokens CoffeeScript.tokens task.input, task.options
else if opts.nodes
printLine CoffeeScript.nodes(task.input, task.options).toString().trim()
else if opts.run
CoffeeScript.register()
CoffeeScript.eval opts.prelude, t.options if opts.prelude
CoffeeScript.run t.input, t.options
else if o.join and t.file isnt o.join
t.input = helpers.invertLiterate t.input if helpers.isLiterate file
sourceCode[sources.indexOf(t.file)] = t.input
CoffeeScript.eval opts.prelude, task.options if opts.prelude
CoffeeScript.run task.input, task.options
else if opts.join and task.file isnt opts.join
task.input = helpers.invertLiterate task.input if helpers.isLiterate file
sourceCode[sources.indexOf(task.file)] = task.input
compileJoin()
else
compiled = CoffeeScript.compile t.input, t.options
t.output = compiled
if o.map
t.output = compiled.js
t.sourceMap = compiled.v3SourceMap
compiled = CoffeeScript.compile task.input, task.options
task.output = compiled
if opts.map
task.output = compiled.js
task.sourceMap = compiled.v3SourceMap
CoffeeScript.emit 'success', task
if o.print
printLine t.output.trim()
else if o.compile or o.map
writeJs base, t.file, t.output, options.jsPath, t.sourceMap
if opts.print
printLine task.output.trim()
else if opts.compile or opts.map
saveTo = if opts.outputFilename and sources.length is 1
path.join opts.outputPath, opts.outputFilename
else
options.jsPath
writeJs base, task.file, task.output, saveTo, task.sourceMap
catch err
CoffeeScript.emit 'failure', err, task
return if CoffeeScript.listeners('failure').length
message = err?.stack or "#{err}"
if o.watch
if opts.watch
printLine message + '\x07'
else
printWarn message
@ -352,12 +367,12 @@ silentUnlink = (path) ->
outputPath = (source, base, extension=".js") ->
basename = helpers.baseFileName source, yes, useWinPathSep
srcDir = path.dirname source
if not opts.output
dir = srcDir
dir = unless opts.outputPath
srcDir
else if source is base
dir = opts.output
opts.outputPath
else
dir = path.join opts.output, path.relative base, srcDir
path.join opts.outputPath, path.relative base, srcDir
path.join dir, basename + extension
# Recursively mkdir, like `mkdir -p`.

View file

@ -130,7 +130,7 @@ If called without options, `coffee` will run your script.
-n, --nodes print out the parse tree that the parser produces
--nodejs pass options directly to the "node" binary
--no-header suppress the "Generated by" header
-o, --output set the output directory for compiled JavaScript
-o, --output set the output path or path/filename for compiled JavaScript
-p, --print print out the compiled JavaScript
-r, --require require the given module before eval or REPL
-s, --stdio listen for and compile scripts over stdio