Skip to content

Commit 3dd4582

Browse files
[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
1 parent 892c469 commit 3dd4582

File tree

3 files changed

+100
-82
lines changed

3 files changed

+100
-82
lines changed

lib/coffeescript/command.js

+37-34
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/command.coffee

+62-47
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,25 @@ BANNER = '''
3232

3333
# The list of all the valid option flags that `coffee` knows how to handle.
3434
SWITCHES = [
35-
['-b', '--bare', 'compile without a top-level function wrapper']
36-
['-c', '--compile', 'compile to JavaScript and save as .js files']
37-
['-e', '--eval', 'pass a string from the command line as input']
38-
['-h', '--help', 'display this help message']
39-
['-i', '--interactive', 'run an interactive CoffeeScript REPL']
40-
['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling']
41-
['-m', '--map', 'generate source map and save as .js.map files']
42-
['-M', '--inline-map', 'generate source map and include it directly in output']
43-
['-n', '--nodes', 'print out the parse tree that the parser produces']
44-
[ '--nodejs [ARGS]', 'pass options directly to the "node" binary']
45-
[ '--no-header', 'suppress the "Generated by" header']
46-
['-o', '--output [DIR]', 'set the output directory for compiled JavaScript']
47-
['-p', '--print', 'print out the compiled JavaScript']
35+
['-b', '--bare', 'compile without a top-level function wrapper']
36+
['-c', '--compile', 'compile to JavaScript and save as .js files']
37+
['-e', '--eval', 'pass a string from the command line as input']
38+
['-h', '--help', 'display this help message']
39+
['-i', '--interactive', 'run an interactive CoffeeScript REPL']
40+
['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling']
41+
['-m', '--map', 'generate source map and save as .js.map files']
42+
['-M', '--inline-map', 'generate source map and include it directly in output']
43+
['-n', '--nodes', 'print out the parse tree that the parser produces']
44+
[ '--nodejs [ARGS]', 'pass options directly to the "node" binary']
45+
[ '--no-header', 'suppress the "Generated by" header']
46+
['-o', '--output [PATH]', 'set the output path or path/filename for compiled JavaScript']
47+
['-p', '--print', 'print out the compiled JavaScript']
4848
['-r', '--require [MODULE*]', 'require the given module before eval or REPL']
49-
['-s', '--stdio', 'listen for and compile scripts over stdio']
50-
['-l', '--literate', 'treat stdio as literate style coffeescript']
51-
['-t', '--tokens', 'print out the tokens that the lexer/rewriter produce']
52-
['-v', '--version', 'display the version number']
53-
['-w', '--watch', 'watch scripts for changes and rerun commands']
49+
['-s', '--stdio', 'listen for and compile scripts over stdio']
50+
['-l', '--literate', 'treat stdio as literate style coffeescript']
51+
['-t', '--tokens', 'print out the tokens that the lexer/rewriter produce']
52+
['-v', '--version', 'display the version number']
53+
['-w', '--watch', 'watch scripts for changes and rerun commands']
5454
]
5555

5656
# Top-level objects shared by all the functions.
@@ -102,7 +102,19 @@ exports.run = ->
102102
process.argv = process.argv[0..1].concat literals
103103
process.argv[0] = 'coffee'
104104

105-
opts.output = path.resolve opts.output if opts.output
105+
if opts.output
106+
outputBasename = path.basename opts.output
107+
if '.' in outputBasename and
108+
outputBasename not in ['.', '..'] and
109+
not helpers.ends(opts.output, path.sep)
110+
# An output filename was specified, e.g. `/dist/scripts.js`.
111+
opts.outputFilename = outputBasename
112+
opts.outputPath = path.resolve path.dirname opts.output
113+
else
114+
# An output path was specified, e.g. `/dist`.
115+
opts.outputFilename = null
116+
opts.outputPath = path.resolve opts.output
117+
106118
if opts.join
107119
opts.join = path.resolve opts.join
108120
console.error '''
@@ -167,7 +179,7 @@ compilePath = (source, topLevel, base) ->
167179
code = fs.readFileSync source
168180
catch err
169181
if err.code is 'ENOENT' then return else throw err
170-
compileScript(source, code.toString(), base)
182+
compileScript source, code.toString(), base
171183
else
172184
notSources[source] = yes
173185

@@ -182,43 +194,46 @@ findDirectoryIndex = (source) ->
182194
process.exit 1
183195

184196
# Compile a single source script, containing the given code, according to the
185-
# requested options. If evaluating the script directly sets `__filename`,
197+
# requested options. If evaluating the script directly, set `__filename`,
186198
# `__dirname` and `module.filename` to be correct relative to the script's path.
187199
compileScript = (file, input, base = null) ->
188-
o = opts
189200
options = compileOptions file, base
190201
try
191-
t = task = {file, input, options}
202+
task = {file, input, options}
192203
CoffeeScript.emit 'compile', task
193-
if o.tokens
194-
printTokens CoffeeScript.tokens t.input, t.options
195-
else if o.nodes
196-
printLine CoffeeScript.nodes(t.input, t.options).toString().trim()
197-
else if o.run
204+
if opts.tokens
205+
printTokens CoffeeScript.tokens task.input, task.options
206+
else if opts.nodes
207+
printLine CoffeeScript.nodes(task.input, task.options).toString().trim()
208+
else if opts.run
198209
CoffeeScript.register()
199-
CoffeeScript.eval opts.prelude, t.options if opts.prelude
200-
CoffeeScript.run t.input, t.options
201-
else if o.join and t.file isnt o.join
202-
t.input = helpers.invertLiterate t.input if helpers.isLiterate file
203-
sourceCode[sources.indexOf(t.file)] = t.input
210+
CoffeeScript.eval opts.prelude, task.options if opts.prelude
211+
CoffeeScript.run task.input, task.options
212+
else if opts.join and task.file isnt opts.join
213+
task.input = helpers.invertLiterate task.input if helpers.isLiterate file
214+
sourceCode[sources.indexOf(task.file)] = task.input
204215
compileJoin()
205216
else
206-
compiled = CoffeeScript.compile t.input, t.options
207-
t.output = compiled
208-
if o.map
209-
t.output = compiled.js
210-
t.sourceMap = compiled.v3SourceMap
217+
compiled = CoffeeScript.compile task.input, task.options
218+
task.output = compiled
219+
if opts.map
220+
task.output = compiled.js
221+
task.sourceMap = compiled.v3SourceMap
211222

212223
CoffeeScript.emit 'success', task
213-
if o.print
214-
printLine t.output.trim()
215-
else if o.compile or o.map
216-
writeJs base, t.file, t.output, options.jsPath, t.sourceMap
224+
if opts.print
225+
printLine task.output.trim()
226+
else if opts.compile or opts.map
227+
saveTo = if opts.outputFilename and sources.length is 1
228+
path.join opts.outputPath, opts.outputFilename
229+
else
230+
options.jsPath
231+
writeJs base, task.file, task.output, saveTo, task.sourceMap
217232
catch err
218233
CoffeeScript.emit 'failure', err, task
219234
return if CoffeeScript.listeners('failure').length
220235
message = err?.stack or "#{err}"
221-
if o.watch
236+
if opts.watch
222237
printLine message + '\x07'
223238
else
224239
printWarn message
@@ -352,12 +367,12 @@ silentUnlink = (path) ->
352367
outputPath = (source, base, extension=".js") ->
353368
basename = helpers.baseFileName source, yes, useWinPathSep
354369
srcDir = path.dirname source
355-
if not opts.output
356-
dir = srcDir
370+
dir = unless opts.outputPath
371+
srcDir
357372
else if source is base
358-
dir = opts.output
373+
opts.outputPath
359374
else
360-
dir = path.join opts.output, path.relative base, srcDir
375+
path.join opts.outputPath, path.relative base, srcDir
361376
path.join dir, basename + extension
362377

363378
# Recursively mkdir, like `mkdir -p`.

test/argument_parsing.coffee

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

0 commit comments

Comments
 (0)