diff --git a/lib/coffeescript/command.js b/lib/coffeescript/command.js index 0f7ba90329..3526c5dda2 100644 --- a/lib/coffeescript/command.js +++ b/lib/coffeescript/command.js @@ -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); }; diff --git a/src/command.coffee b/src/command.coffee index fc0b318d42..d9e62627e5 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -32,25 +32,25 @@ BANNER = ''' # 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'] + ['-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'] + ['-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. @@ -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`. diff --git a/test/argument_parsing.coffee b/test/argument_parsing.coffee index c82a3136e9..2ed4fdb8c7 100644 --- a/test/argument_parsing.coffee +++ b/test/argument_parsing.coffee @@ -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