[CS2] Add #! support for executable scripts on Linux. (#3946)
* Add #! support for executable scripts on Linux. Pass arguments to executable script unchanged if using "#!/usr/bin/env coffee". (Previously, "./test.coffee -abck" would be turned into "-a -b -c -k", for example.) Fixes #1752. * refactor option parsing clean up parsing code and in the process fix oustanding bug where coffeescript modified arguments meant for an executable script * address comments * intermediate save * add note saying where OptionParser is used in coffee command * add some more work * fix flatten functions * refactor tests * make argument processing less confusing * add basic test * remove unused file * compilation now hangs * remove unnecessary changes * add tests!!! * add/fix some tests * clarify a test * fix helpers * fix opt parsing * fix infinite loop * make rule building easier to read * add tests for flag overlap * revamp argument parsing again and add more thorough testing * add tests, comment, clean unused method * address review comments * add test for direct invocation of shebang scripts * move shebang parsing test to separate file and check for browser * remove TODO * example backwards compatible warnings * add correct tests for warning 1 * add tests for warnings * commit output js libs and update docs * respond to review comments also add tests for help text * respond to review comments * fix example output * Rewrite argument parsing documentation to be more concise; add it to sidebar and body; add new output * Don’t mention deprecated syntax; clean up variable names
This commit is contained in:
parent
d287a798cc
commit
4e57ca6833
|
@ -306,7 +306,7 @@ td code {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2, h3 {
|
h2, h3, h4 {
|
||||||
margin-top: 1.3em;
|
margin-top: 1.3em;
|
||||||
margin-bottom: 0.6em;
|
margin-bottom: 0.6em;
|
||||||
font-family: 'Alegreya Sans';
|
font-family: 'Alegreya Sans';
|
||||||
|
@ -314,7 +314,7 @@ h2, h3 {
|
||||||
h2 {
|
h2 {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
h3, h2 time {
|
h3, h4, h2 time {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -734,6 +734,9 @@ textarea {
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="#breaking-changes-literate-coffeescript" class="nav-link" data-action="sidebar-nav">Literate CoffeeScript Parsing</a>
|
<a href="#breaking-changes-literate-coffeescript" class="nav-link" data-action="sidebar-nav">Literate CoffeeScript Parsing</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#breaking-changes-argument-parsing-and-shebang-lines" class="nav-link" data-action="sidebar-nav">Argument Parsing and <code>#!</code> Lines</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
@ -858,7 +861,7 @@ cubes = (function() {
|
||||||
<section id="coffeescript-2">
|
<section id="coffeescript-2">
|
||||||
<h2>CoffeeScript 2</h2>
|
<h2>CoffeeScript 2</h2>
|
||||||
<h3>What’s New In CoffeeScript 2?</h3>
|
<h3>What’s New In CoffeeScript 2?</h3>
|
||||||
<p>The biggest change in CoffeeScript 2 is that now the CoffeeScript compiler produces modern, ES2015+ JavaScript. A CoffeeScript <code>=></code> becomes an ES <code>=></code>, a CoffeeScript <code>class</code> becomes an ES <code>class</code> and so on. With the exception of modules (<code>import</code> and <code>export</code> statements), all the ES2015+ features that CoffeeScript supports can run natively in Node 7.6+, meaning that Node can run CoffeeScript’s output without any further processing required. You can <a href="http://coffeescript.org/v2/test.html">run the tests in your browser</a> to see if your browser can do the same; Chrome has supported all features since version 55.</p>
|
<p>The biggest change in CoffeeScript 2 is that now the CoffeeScript compiler produces modern, ES2015+ JavaScript. A CoffeeScript <code>=></code> becomes an ES <code>=></code>, a CoffeeScript <code>class</code> becomes an ES <code>class</code> and so on. With the exception of <a href="#modules">modules</a> (<code>import</code> and <code>export</code> statements) and <a href="#jsx">JSX</a>, all the ES2015+ features that CoffeeScript supports can run natively in Node 7.6+, meaning that Node can run CoffeeScript’s output without any further processing required. You can <a href="http://coffeescript.org/v2/test.html">run the tests in your browser</a> to see if your browser can do the same; Chrome has supported all features since version 55.</p>
|
||||||
<p>Support for ES2015+ syntax is important to ensure compatibility with frameworks that assume ES2015. Now that CoffeeScript compiles classes to the ES <code>class</code> keyword, it’s possible to <code>extend</code> an ES class; that wasn’t possible in CoffeeScript 1. Parity in how language features work is also important on its own; CoffeeScript “is just JavaScript,” and so things like <a href="#breaking-changes-default-values">function parameter default values</a> should behave the same in CoffeeScript as in JavaScript.</p>
|
<p>Support for ES2015+ syntax is important to ensure compatibility with frameworks that assume ES2015. Now that CoffeeScript compiles classes to the ES <code>class</code> keyword, it’s possible to <code>extend</code> an ES class; that wasn’t possible in CoffeeScript 1. Parity in how language features work is also important on its own; CoffeeScript “is just JavaScript,” and so things like <a href="#breaking-changes-default-values">function parameter default values</a> should behave the same in CoffeeScript as in JavaScript.</p>
|
||||||
<p>Many ES2015+ features have been backported to CoffeeScript 1.11 and 1.12, including <a href="#modules">modules</a>, <a href="#generator-iteration"><code>for…of</code></a>, and <a href="#tagged-template-literals">tagged template literals</a>. Major new features unique to CoffeeScript 2 are support for ES2017’s <a href="#async-functions">async functions</a> and for <a href="#jsx">JSX</a>. More details are in the <a href="#changelog">changelog</a>.</p>
|
<p>Many ES2015+ features have been backported to CoffeeScript 1.11 and 1.12, including <a href="#modules">modules</a>, <a href="#generator-iteration"><code>for…of</code></a>, and <a href="#tagged-template-literals">tagged template literals</a>. Major new features unique to CoffeeScript 2 are support for ES2017’s <a href="#async-functions">async functions</a> and for <a href="#jsx">JSX</a>. More details are in the <a href="#changelog">changelog</a>.</p>
|
||||||
<p>There are very few <a href="#breaking-changes">breaking changes from CoffeeScript 1.x to 2</a>; we hope the upgrade process is smooth for most projects.</p>
|
<p>There are very few <a href="#breaking-changes">breaking changes from CoffeeScript 1.x to 2</a>; we hope the upgrade process is smooth for most projects.</p>
|
||||||
|
@ -3244,7 +3247,7 @@ renderStarRating = function({rating, maxStars}) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
<p>Older plugins or forks of CoffeeScript supported JSX syntax and referred to it as CSX or CJSX. They also often used a <code>.cjsx</code> file extension, but this is no longer necessary; regalar <code>.coffee</code> will do.</p>
|
<p>Older plugins or forks of CoffeeScript supported JSX syntax and referred to it as CSX or CJSX. They also often used a <code>.cjsx</code> file extension, but this is no longer necessary; regular <code>.coffee</code> will do.</p>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
@ -3619,7 +3622,7 @@ f = function*() {
|
||||||
<blockquote class="uneditable-code-block"><pre><code class="language-coffee"><span class="class"><span class="keyword">class</span> <span class="title">B</span> <span class="keyword">extends</span> <span class="title">A</span></span>
|
<blockquote class="uneditable-code-block"><pre><code class="language-coffee"><span class="class"><span class="keyword">class</span> <span class="title">B</span> <span class="keyword">extends</span> <span class="title">A</span></span>
|
||||||
constructor: <span class="function">-></span> <span class="keyword">this</span> <span class="comment"># Throws a compiler error</span>
|
constructor: <span class="function">-></span> <span class="keyword">this</span> <span class="comment"># Throws a compiler error</span>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
</blockquote><p>ES2015 classes don’t allow bound (fat arrow) methods. The CoffeeScript compiler goes through some contortions to preserve support for them, but one thing that can’t be accomodated is calling a bound method before it is bound:</p>
|
</blockquote><p>ES2015 classes don’t allow bound (fat arrow) methods. The CoffeeScript compiler goes through some contortions to preserve support for them, but one thing that can’t be accommodated is calling a bound method before it is bound:</p>
|
||||||
<blockquote class="uneditable-code-block"><pre><code class="language-coffee"><span class="class"><span class="keyword">class</span> <span class="title">Base</span></span>
|
<blockquote class="uneditable-code-block"><pre><code class="language-coffee"><span class="class"><span class="keyword">class</span> <span class="title">Base</span></span>
|
||||||
constructor: <span class="function">-></span>
|
constructor: <span class="function">-></span>
|
||||||
@onClick() <span class="comment"># This works</span>
|
@onClick() <span class="comment"># This works</span>
|
||||||
|
@ -3787,7 +3790,7 @@ B = class B extends A {
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<section id="breaking-changes-jsx-and-the-less-than-and-greater-than-operators">
|
<section id="breaking-changes-jsx-and-the-less-than-and-greater-than-operators">
|
||||||
<h3>JSX and the <code><</code> and <code>></code> Operators</h3>
|
<h3>JSX and the <code><</code> and <code>></code> operators</h3>
|
||||||
<p>With the addition of <a href="#jsx">JSX</a>, the <code><</code> and <code>></code> characters serve as both the “less than” and “greater than” operators and as the delimiters for XML tags, like <code><div></code>. For best results, in general you should always wrap the operators in spaces to distinguish them from XML tags: <code>i < len</code>, not <code>i<len</code>. The compiler tries to be forgiving when it can be sure what you intend, but always putting spaces around the “less than” and “greater than” operators will remove ambiguity.</p>
|
<p>With the addition of <a href="#jsx">JSX</a>, the <code><</code> and <code>></code> characters serve as both the “less than” and “greater than” operators and as the delimiters for XML tags, like <code><div></code>. For best results, in general you should always wrap the operators in spaces to distinguish them from XML tags: <code>i < len</code>, not <code>i<len</code>. The compiler tries to be forgiving when it can be sure what you intend, but always putting spaces around the “less than” and “greater than” operators will remove ambiguity.</p>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
@ -3797,6 +3800,29 @@ B = class B extends A {
|
||||||
<p>Code blocks should also now maintain a consistent indentation level—so an indentation of one tab (or whatever you consider to be a tab stop, like 2 spaces or 4 spaces) should be treated as your code’s “left margin,” with all code in the file relative to that column.</p>
|
<p>Code blocks should also now maintain a consistent indentation level—so an indentation of one tab (or whatever you consider to be a tab stop, like 2 spaces or 4 spaces) should be treated as your code’s “left margin,” with all code in the file relative to that column.</p>
|
||||||
<p>Code blocks that you want to be part of the commentary, and not executed, must have at least one line (ideally the first line of the block) completely unindented.</p>
|
<p>Code blocks that you want to be part of the commentary, and not executed, must have at least one line (ideally the first line of the block) completely unindented.</p>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<section id="breaking-changes-argument-parsing-and-shebang-lines">
|
||||||
|
<h3>Argument parsing and shebang (<code>#!</code>) lines</h3>
|
||||||
|
<p>In CoffeeScript 1.x, <code>--</code> was required after the path and filename of the script to be run, but before any arguments passed to that script. This convention is now deprecated. So instead of:</p>
|
||||||
|
<blockquote class="uneditable-code-block"><pre><code class="language-bash">coffee [options] path/to/script.coffee -- [args]
|
||||||
|
</code></pre>
|
||||||
|
</blockquote><p>Now you would just type:</p>
|
||||||
|
<blockquote class="uneditable-code-block"><pre><code class="language-bash">coffee [options] path/to/script.coffee [args]
|
||||||
|
</code></pre>
|
||||||
|
</blockquote><p>The deprecated version will still work, but it will print a warning before running the script.</p>
|
||||||
|
<p>On non-Windows platforms, a <code>.coffee</code> file can be made executable by adding a shebang (<code>#!</code>) line at the top of the file and marking the file as executable. For example:</p>
|
||||||
|
<blockquote class="uneditable-code-block"><pre><code class="language-coffee"><span class="comment">#!/usr/bin/env coffee</span>
|
||||||
|
|
||||||
|
x = <span class="number">2</span> + <span class="number">2</span>
|
||||||
|
<span class="built_in">console</span>.log x
|
||||||
|
</code></pre>
|
||||||
|
</blockquote><p>If this were saved as <code>executable.coffee</code>, it could be made executable and run:</p>
|
||||||
|
<blockquote class="uneditable-code-block"><pre><code class="language-bash">▶ chmod +x ./executable.coffee
|
||||||
|
▶ ./executable.coffee
|
||||||
|
4
|
||||||
|
</code></pre>
|
||||||
|
</blockquote><p>In CoffeeScript 1.x, this used to fail when trying to pass arguments to the script. Some users on OS X worked around the problem by using <code>#!/usr/bin/env coffee --</code> as the first line of the file. That didn’t work on Linux, however, which cannot parse shebang lines with more than a single argument. While such scripts will still run on OS X, CoffeeScript will now display a warning before compiling or evaluating files that begin with a too-long shebang line. Now that CoffeeScript 2 supports passing arguments without needing <code>--</code>, we recommend simply changing the shebang lines in such scripts to just <code>#!/usr/bin/env coffee</code>.</p>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<section id="changelog">
|
<section id="changelog">
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
### Argument parsing and shebang (`#!`) lines
|
||||||
|
|
||||||
|
In CoffeeScript 1.x, `--` was required after the path and filename of the script to be run, but before any arguments passed to that script. This convention is now deprecated. So instead of:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
coffee [options] path/to/script.coffee -- [args]
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you would just type:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
coffee [options] path/to/script.coffee [args]
|
||||||
|
```
|
||||||
|
|
||||||
|
The deprecated version will still work, but it will print a warning before running the script.
|
||||||
|
|
||||||
|
On non-Windows platforms, a `.coffee` file can be made executable by adding a shebang (`#!`) line at the top of the file and marking the file as executable. For example:
|
||||||
|
|
||||||
|
```coffee
|
||||||
|
#!/usr/bin/env coffee
|
||||||
|
|
||||||
|
x = 2 + 2
|
||||||
|
console.log x
|
||||||
|
```
|
||||||
|
|
||||||
|
If this were saved as `executable.coffee`, it could be made executable and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
▶ chmod +x ./executable.coffee
|
||||||
|
▶ ./executable.coffee
|
||||||
|
4
|
||||||
|
```
|
||||||
|
|
||||||
|
In CoffeeScript 1.x, this used to fail when trying to pass arguments to the script. Some users on OS X worked around the problem by using `#!/usr/bin/env coffee --` as the first line of the file. That didn’t work on Linux, however, which cannot parse shebang lines with more than a single argument. While such scripts will still run on OS X, CoffeeScript will now display a warning before compiling or evaluating files that begin with a too-long shebang line. Now that CoffeeScript 2 supports passing arguments without needing `--`, we recommend simply changing the shebang lines in such scripts to just `#!/usr/bin/env coffee`.
|
|
@ -1,3 +1,3 @@
|
||||||
### JSX and the `<` and `>` Operators
|
### JSX and the `<` and `>` operators
|
||||||
|
|
||||||
With the addition of [JSX](#jsx), the `<` and `>` characters serve as both the “less than” and “greater than” operators and as the delimiters for XML tags, like `<div>`. For best results, in general you should always wrap the operators in spaces to distinguish them from XML tags: `i < len`, not `i<len`. The compiler tries to be forgiving when it can be sure what you intend, but always putting spaces around the “less than” and “greater than” operators will remove ambiguity.
|
With the addition of [JSX](#jsx), the `<` and `>` characters serve as both the “less than” and “greater than” operators and as the delimiters for XML tags, like `<div>`. For best results, in general you should always wrap the operators in spaces to distinguish them from XML tags: `i < len`, not `i<len`. The compiler tries to be forgiving when it can be sure what you intend, but always putting spaces around the “less than” and “greater than” operators will remove ambiguity.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
### What’s New In CoffeeScript 2?
|
### What’s New In CoffeeScript 2?
|
||||||
|
|
||||||
The biggest change in CoffeeScript 2 is that now the CoffeeScript compiler produces modern, ES2015+ JavaScript. A CoffeeScript `=>` becomes an ES `=>`, a CoffeeScript `class` becomes an ES `class` and so on. With the exception of modules (`import` and `export` statements), all the ES2015+ features that CoffeeScript supports can run natively in Node 7.6+, meaning that Node can run CoffeeScript’s output without any further processing required. You can [run the tests in your browser](http://coffeescript.org/v<%= majorVersion %>/test.html) to see if your browser can do the same; Chrome has supported all features since version 55.
|
The biggest change in CoffeeScript 2 is that now the CoffeeScript compiler produces modern, ES2015+ JavaScript. A CoffeeScript `=>` becomes an ES `=>`, a CoffeeScript `class` becomes an ES `class` and so on. With the exception of [modules](#modules) (`import` and `export` statements) and [JSX](#jsx), all the ES2015+ features that CoffeeScript supports can run natively in Node 7.6+, meaning that Node can run CoffeeScript’s output without any further processing required. You can [run the tests in your browser](http://coffeescript.org/v<%= majorVersion %>/test.html) to see if your browser can do the same; Chrome has supported all features since version 55.
|
||||||
|
|
||||||
Support for ES2015+ syntax is important to ensure compatibility with frameworks that assume ES2015. Now that CoffeeScript compiles classes to the ES `class` keyword, it’s possible to `extend` an ES class; that wasn’t possible in CoffeeScript 1. Parity in how language features work is also important on its own; CoffeeScript “is just JavaScript,” and so things like [function parameter default values](#breaking-changes-default-values) should behave the same in CoffeeScript as in JavaScript.
|
Support for ES2015+ syntax is important to ensure compatibility with frameworks that assume ES2015. Now that CoffeeScript compiles classes to the ES `class` keyword, it’s possible to `extend` an ES class; that wasn’t possible in CoffeeScript 1. Parity in how language features work is also important on its own; CoffeeScript “is just JavaScript,” and so things like [function parameter default values](#breaking-changes-default-values) should behave the same in CoffeeScript as in JavaScript.
|
||||||
|
|
||||||
|
|
|
@ -178,6 +178,9 @@
|
||||||
<section id="breaking-changes-literate-coffeescript">
|
<section id="breaking-changes-literate-coffeescript">
|
||||||
<%= htmlFor('breaking_changes_literate_coffeescript') %>
|
<%= htmlFor('breaking_changes_literate_coffeescript') %>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="breaking-changes-argument-parsing-and-shebang-lines">
|
||||||
|
<%= htmlFor('breaking_changes_argument_parsing_and_shebang_lines') %>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<section id="changelog">
|
<section id="changelog">
|
||||||
<%= htmlFor('changelog') %>
|
<%= htmlFor('changelog') %>
|
||||||
|
|
|
@ -286,7 +286,7 @@ td code {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2, h3 {
|
h2, h3, h4 {
|
||||||
margin-top: 1.3em;
|
margin-top: 1.3em;
|
||||||
margin-bottom: 0.6em;
|
margin-bottom: 0.6em;
|
||||||
font-family: 'Alegreya Sans';
|
font-family: 'Alegreya Sans';
|
||||||
|
@ -294,7 +294,7 @@ h2, h3 {
|
||||||
h2 {
|
h2 {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
h3, h2 time {
|
h3, h4, h2 time {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,9 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="#breaking-changes-literate-coffeescript" class="nav-link" data-action="sidebar-nav">Literate CoffeeScript Parsing</a>
|
<a href="#breaking-changes-literate-coffeescript" class="nav-link" data-action="sidebar-nav">Literate CoffeeScript Parsing</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#breaking-changes-argument-parsing-and-shebang-lines" class="nav-link" data-action="sidebar-nav">Argument Parsing and <code>#!</code> Lines</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Generated by CoffeeScript 2.0.0-beta3
|
// Generated by CoffeeScript 2.0.0-beta3
|
||||||
(function() {
|
(function() {
|
||||||
var Lexer, SourceMap, base64encode, compile, formatSourcePosition, getSourceMap, helpers, lexer, packageJson, parser, sourceMaps, sources, withPrettyErrors;
|
var Lexer, SourceMap, base64encode, checkShebangLine, compile, formatSourcePosition, getSourceMap, helpers, lexer, packageJson, parser, sourceMaps, sources, withPrettyErrors;
|
||||||
|
|
||||||
({Lexer} = require('./lexer'));
|
({Lexer} = require('./lexer'));
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@
|
||||||
options = extend({}, options);
|
options = extend({}, options);
|
||||||
generateSourceMap = options.sourceMap || options.inlineMap || (options.filename == null);
|
generateSourceMap = options.sourceMap || options.inlineMap || (options.filename == null);
|
||||||
filename = options.filename || '<anonymous>';
|
filename = options.filename || '<anonymous>';
|
||||||
|
checkShebangLine(filename, code);
|
||||||
sources[filename] = code;
|
sources[filename] = code;
|
||||||
if (generateSourceMap) {
|
if (generateSourceMap) {
|
||||||
map = new SourceMap;
|
map = new SourceMap;
|
||||||
|
@ -290,4 +291,18 @@
|
||||||
return `${err.toString()}\n${frames.join('\n')}\n`;
|
return `${err.toString()}\n${frames.join('\n')}\n`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
checkShebangLine = function(file, input) {
|
||||||
|
var args, firstLine, ref, rest;
|
||||||
|
firstLine = input.split(/$/m)[0];
|
||||||
|
rest = firstLine != null ? firstLine.match(/^#!\s*([^\s]+\s*)(.*)/) : void 0;
|
||||||
|
args = rest != null ? (ref = rest[2]) != null ? ref.split(/\s/).filter(function(s) {
|
||||||
|
return s !== '';
|
||||||
|
}) : void 0 : void 0;
|
||||||
|
if ((args != null ? args.length : void 0) > 1) {
|
||||||
|
console.error('The script to be run begins with a shebang line with more than one\nargument. This script will fail on platforms such as Linux which only\nallow a single argument.');
|
||||||
|
console.error(`The shebang line was: '${firstLine}' in file '${file}'`);
|
||||||
|
return console.error(`The arguments were: ${JSON.stringify(args)}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
return /^\.|~$/.test(file);
|
return /^\.|~$/.test(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
BANNER = 'Usage: coffee [options] path/to/script.coffee -- [args]\n\nIf called without options, `coffee` will run your script.';
|
BANNER = 'Usage: coffee [options] path/to/script.coffee [args]\n\nIf called without options, `coffee` will run your script.';
|
||||||
|
|
||||||
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 [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']];
|
||||||
|
|
||||||
|
@ -54,9 +54,20 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.run = function() {
|
exports.run = function() {
|
||||||
var i, len, literals, ref, replCliOpts, results, source;
|
var err, i, len, literals, ref, replCliOpts, results, source;
|
||||||
optionParser = buildCSOptionParser();
|
optionParser = buildCSOptionParser();
|
||||||
parseOptions();
|
try {
|
||||||
|
parseOptions();
|
||||||
|
} catch (error) {
|
||||||
|
err = error;
|
||||||
|
console.error(`option parsing error: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if ((!opts.doubleDashed) && (opts.arguments[1] === '--')) {
|
||||||
|
printWarn('coffee was invoked with \'--\' as the second positional argument, which is\nnow deprecated. To pass \'--\' as an argument to a script to run, put an\nadditional \'--\' before the path to your script.\n\n\'--\' will be removed from the argument list.');
|
||||||
|
printWarn(`The positional arguments were: ${JSON.stringify(opts.arguments)}`);
|
||||||
|
opts.arguments = [opts.arguments[0]].concat(opts.arguments.slice(2));
|
||||||
|
}
|
||||||
replCliOpts = {
|
replCliOpts = {
|
||||||
useGlobal: true
|
useGlobal: true
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,72 +1,52 @@
|
||||||
// Generated by CoffeeScript 2.0.0-beta3
|
// Generated by CoffeeScript 2.0.0-beta3
|
||||||
(function() {
|
(function() {
|
||||||
var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, buildRule, buildRules, normalizeArguments, repeat;
|
var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, buildRule, buildRules, normalizeArguments, repeat,
|
||||||
|
slice = [].slice;
|
||||||
|
|
||||||
({repeat} = require('./helpers'));
|
({repeat} = require('./helpers'));
|
||||||
|
|
||||||
exports.OptionParser = OptionParser = class OptionParser {
|
exports.OptionParser = OptionParser = class OptionParser {
|
||||||
constructor(rules, banner) {
|
constructor(ruleDeclarations, banner) {
|
||||||
this.banner = banner;
|
this.banner = banner;
|
||||||
this.rules = buildRules(rules);
|
this.rules = buildRules(ruleDeclarations);
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(args) {
|
parse(args) {
|
||||||
var arg, i, isOption, j, k, len, len1, matchedRule, options, originalArgs, pos, ref, rule, seenNonOptionArg, skippingArgument, value;
|
var argument, hasArgument, i, isList, len, name, options, positional, rules;
|
||||||
options = {
|
({rules, positional} = normalizeArguments(args, this.rules.flagDict));
|
||||||
arguments: []
|
options = {};
|
||||||
};
|
for (i = 0, len = rules.length; i < len; i++) {
|
||||||
skippingArgument = false;
|
({hasArgument, argument, isList, name} = rules[i]);
|
||||||
originalArgs = args;
|
if (hasArgument) {
|
||||||
args = normalizeArguments(args);
|
if (isList) {
|
||||||
for (i = j = 0, len = args.length; j < len; i = ++j) {
|
if (options[name] == null) {
|
||||||
arg = args[i];
|
options[name] = [];
|
||||||
if (skippingArgument) {
|
|
||||||
skippingArgument = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg === '--') {
|
|
||||||
pos = originalArgs.indexOf('--');
|
|
||||||
options.arguments = options.arguments.concat(originalArgs.slice(pos + 1));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
isOption = !!(arg.match(LONG_FLAG) || arg.match(SHORT_FLAG));
|
|
||||||
seenNonOptionArg = options.arguments.length > 0;
|
|
||||||
if (!seenNonOptionArg) {
|
|
||||||
matchedRule = false;
|
|
||||||
ref = this.rules;
|
|
||||||
for (k = 0, len1 = ref.length; k < len1; k++) {
|
|
||||||
rule = ref[k];
|
|
||||||
if (rule.shortFlag === arg || rule.longFlag === arg) {
|
|
||||||
value = true;
|
|
||||||
if (rule.hasArgument) {
|
|
||||||
skippingArgument = true;
|
|
||||||
value = args[i + 1];
|
|
||||||
}
|
|
||||||
options[rule.name] = rule.isList ? (options[rule.name] || []).concat(value) : value;
|
|
||||||
matchedRule = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
options[name].push(argument);
|
||||||
|
} else {
|
||||||
|
options[name] = argument;
|
||||||
}
|
}
|
||||||
if (isOption && !matchedRule) {
|
} else {
|
||||||
throw new Error(`unrecognized option: ${arg}`);
|
options[name] = true;
|
||||||
}
|
|
||||||
}
|
|
||||||
if (seenNonOptionArg || !isOption) {
|
|
||||||
options.arguments.push(arg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (positional[0] === '--') {
|
||||||
|
options.doubleDashed = true;
|
||||||
|
positional = positional.slice(1);
|
||||||
|
}
|
||||||
|
options.arguments = positional;
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
var j, len, letPart, lines, ref, rule, spaces;
|
var i, len, letPart, lines, ref, rule, spaces;
|
||||||
lines = [];
|
lines = [];
|
||||||
if (this.banner) {
|
if (this.banner) {
|
||||||
lines.unshift(`${this.banner}\n`);
|
lines.unshift(`${this.banner}\n`);
|
||||||
}
|
}
|
||||||
ref = this.rules;
|
ref = this.rules.ruleList;
|
||||||
for (j = 0, len = ref.length; j < len; j++) {
|
for (i = 0, len = ref.length; i < len; i++) {
|
||||||
rule = ref[j];
|
rule = ref[i];
|
||||||
spaces = 15 - rule.longFlag.length;
|
spaces = 15 - rule.longFlag.length;
|
||||||
spaces = spaces > 0 ? repeat(' ', spaces) : '';
|
spaces = spaces > 0 ? repeat(' ', spaces) : '';
|
||||||
letPart = rule.shortFlag ? rule.shortFlag + ', ' : ' ';
|
letPart = rule.shortFlag ? rule.shortFlag + ', ' : ' ';
|
||||||
|
@ -85,25 +65,45 @@
|
||||||
|
|
||||||
OPTIONAL = /\[(\w+(\*?))\]/;
|
OPTIONAL = /\[(\w+(\*?))\]/;
|
||||||
|
|
||||||
buildRules = function(rules) {
|
buildRules = function(ruleDeclarations) {
|
||||||
var j, len, results, tuple;
|
var flag, flagDict, i, j, len, len1, ref, rule, ruleList, tuple;
|
||||||
results = [];
|
ruleList = (function() {
|
||||||
for (j = 0, len = rules.length; j < len; j++) {
|
var i, len, results;
|
||||||
tuple = rules[j];
|
results = [];
|
||||||
if (tuple.length < 3) {
|
for (i = 0, len = ruleDeclarations.length; i < len; i++) {
|
||||||
tuple.unshift(null);
|
tuple = ruleDeclarations[i];
|
||||||
|
if (tuple.length < 3) {
|
||||||
|
tuple.unshift(null);
|
||||||
|
}
|
||||||
|
results.push(buildRule(...tuple));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
})();
|
||||||
|
flagDict = {};
|
||||||
|
for (i = 0, len = ruleList.length; i < len; i++) {
|
||||||
|
rule = ruleList[i];
|
||||||
|
ref = [rule.shortFlag, rule.longFlag];
|
||||||
|
for (j = 0, len1 = ref.length; j < len1; j++) {
|
||||||
|
flag = ref[j];
|
||||||
|
if (!(flag != null)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (flagDict[flag] != null) {
|
||||||
|
throw new Error(`flag ${flag} for switch ${rule.name} was already declared for switch ${flagDict[flag].name}`);
|
||||||
|
}
|
||||||
|
flagDict[flag] = rule;
|
||||||
}
|
}
|
||||||
results.push(buildRule(...tuple));
|
|
||||||
}
|
}
|
||||||
return results;
|
return {ruleList, flagDict};
|
||||||
};
|
};
|
||||||
|
|
||||||
buildRule = function(shortFlag, longFlag, description, options = {}) {
|
buildRule = function(shortFlag, longFlag, description) {
|
||||||
var match;
|
var match;
|
||||||
match = longFlag.match(OPTIONAL);
|
match = longFlag.match(OPTIONAL);
|
||||||
|
shortFlag = shortFlag != null ? shortFlag.match(SHORT_FLAG)[1] : void 0;
|
||||||
longFlag = longFlag.match(LONG_FLAG)[1];
|
longFlag = longFlag.match(LONG_FLAG)[1];
|
||||||
return {
|
return {
|
||||||
name: longFlag.substr(2),
|
name: longFlag.replace(/^--/, ''),
|
||||||
shortFlag: shortFlag,
|
shortFlag: shortFlag,
|
||||||
longFlag: longFlag,
|
longFlag: longFlag,
|
||||||
description: description,
|
description: description,
|
||||||
|
@ -112,23 +112,70 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
normalizeArguments = function(args) {
|
normalizeArguments = function(args, flagDict) {
|
||||||
var arg, j, k, l, len, len1, match, ref, result;
|
var arg, argIndex, flag, i, innerOpts, j, k, lastOpt, len, len1, multiFlags, multiOpts, needsArgOpt, positional, ref, rule, rules, singleRule, withArg;
|
||||||
args = args.slice(0);
|
rules = [];
|
||||||
result = [];
|
positional = [];
|
||||||
for (j = 0, len = args.length; j < len; j++) {
|
needsArgOpt = null;
|
||||||
arg = args[j];
|
for (argIndex = i = 0, len = args.length; i < len; argIndex = ++i) {
|
||||||
if (match = arg.match(MULTI_FLAG)) {
|
arg = args[argIndex];
|
||||||
ref = match[1].split('');
|
if (needsArgOpt != null) {
|
||||||
for (k = 0, len1 = ref.length; k < len1; k++) {
|
withArg = Object.assign({}, needsArgOpt.rule, {
|
||||||
l = ref[k];
|
argument: arg
|
||||||
result.push('-' + l);
|
});
|
||||||
|
rules.push(withArg);
|
||||||
|
needsArgOpt = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
multiFlags = (ref = arg.match(MULTI_FLAG)) != null ? ref[1].split('').map(function(flagName) {
|
||||||
|
return `-${flagName}`;
|
||||||
|
}) : void 0;
|
||||||
|
if (multiFlags != null) {
|
||||||
|
multiOpts = multiFlags.map(function(flag) {
|
||||||
|
var rule;
|
||||||
|
rule = flagDict[flag];
|
||||||
|
if (rule == null) {
|
||||||
|
throw new Error(`unrecognized option ${flag} in multi-flag ${arg}`);
|
||||||
|
}
|
||||||
|
return {rule, flag};
|
||||||
|
});
|
||||||
|
innerOpts = 2 <= multiOpts.length ? slice.call(multiOpts, 0, j = multiOpts.length - 1) : (j = 0, []), lastOpt = multiOpts[j++];
|
||||||
|
for (k = 0, len1 = innerOpts.length; k < len1; k++) {
|
||||||
|
({rule, flag} = innerOpts[k]);
|
||||||
|
if (rule.hasArgument) {
|
||||||
|
throw new Error(`cannot use option ${flag} in multi-flag ${arg} except as the last option, because it needs an argument`);
|
||||||
|
}
|
||||||
|
rules.push(rule);
|
||||||
|
}
|
||||||
|
if (lastOpt.rule.hasArgument) {
|
||||||
|
needsArgOpt = lastOpt;
|
||||||
|
} else {
|
||||||
|
rules.push(lastOpt.rule);
|
||||||
|
}
|
||||||
|
} else if ([LONG_FLAG, SHORT_FLAG].some(function(pat) {
|
||||||
|
return arg.match(pat) != null;
|
||||||
|
})) {
|
||||||
|
singleRule = flagDict[arg];
|
||||||
|
if (singleRule == null) {
|
||||||
|
throw new Error(`unrecognized option ${arg}`);
|
||||||
|
}
|
||||||
|
if (singleRule.hasArgument) {
|
||||||
|
needsArgOpt = {
|
||||||
|
rule: singleRule,
|
||||||
|
flag: arg
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
rules.push(singleRule);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.push(arg);
|
positional = args.slice(argIndex);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
if (needsArgOpt != null) {
|
||||||
|
throw new Error(`value required for ${needsArgOpt.flag}, but it was the last argument provided`);
|
||||||
|
}
|
||||||
|
return {rules, positional};
|
||||||
};
|
};
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
|
|
@ -73,6 +73,8 @@ exports.compile = compile = withPrettyErrors (code, options) ->
|
||||||
generateSourceMap = options.sourceMap or options.inlineMap or not options.filename?
|
generateSourceMap = options.sourceMap or options.inlineMap or not options.filename?
|
||||||
filename = options.filename or '<anonymous>'
|
filename = options.filename or '<anonymous>'
|
||||||
|
|
||||||
|
checkShebangLine filename, code
|
||||||
|
|
||||||
sources[filename] = code
|
sources[filename] = code
|
||||||
map = new SourceMap if generateSourceMap
|
map = new SourceMap if generateSourceMap
|
||||||
|
|
||||||
|
@ -295,3 +297,16 @@ Error.prepareStackTrace = (err, stack) ->
|
||||||
" at #{formatSourcePosition frame, getSourceMapping}"
|
" at #{formatSourcePosition frame, getSourceMapping}"
|
||||||
|
|
||||||
"#{err.toString()}\n#{frames.join '\n'}\n"
|
"#{err.toString()}\n#{frames.join '\n'}\n"
|
||||||
|
|
||||||
|
checkShebangLine = (file, input) ->
|
||||||
|
firstLine = input.split(/$/m)[0]
|
||||||
|
rest = firstLine?.match(/^#!\s*([^\s]+\s*)(.*)/)
|
||||||
|
args = rest?[2]?.split(/\s/).filter (s) -> s isnt ''
|
||||||
|
if args?.length > 1
|
||||||
|
console.error '''
|
||||||
|
The script to be run begins with a shebang line with more than one
|
||||||
|
argument. This script will fail on platforms such as Linux which only
|
||||||
|
allow a single argument.
|
||||||
|
'''
|
||||||
|
console.error "The shebang line was: '#{firstLine}' in file '#{file}'"
|
||||||
|
console.error "The arguments were: #{JSON.stringify args}"
|
||||||
|
|
|
@ -25,7 +25,7 @@ hidden = (file) -> /^\.|~$/.test file
|
||||||
|
|
||||||
# The help banner that is printed in conjunction with `-h`/`--help`.
|
# The help banner that is printed in conjunction with `-h`/`--help`.
|
||||||
BANNER = '''
|
BANNER = '''
|
||||||
Usage: coffee [options] path/to/script.coffee -- [args]
|
Usage: coffee [options] path/to/script.coffee [args]
|
||||||
|
|
||||||
If called without options, `coffee` will run your script.
|
If called without options, `coffee` will run your script.
|
||||||
'''
|
'''
|
||||||
|
@ -69,7 +69,22 @@ exports.buildCSOptionParser = buildCSOptionParser = ->
|
||||||
# `--` will be passed verbatim to your script as arguments in `process.argv`
|
# `--` will be passed verbatim to your script as arguments in `process.argv`
|
||||||
exports.run = ->
|
exports.run = ->
|
||||||
optionParser = buildCSOptionParser()
|
optionParser = buildCSOptionParser()
|
||||||
parseOptions()
|
try parseOptions()
|
||||||
|
catch err
|
||||||
|
console.error "option parsing error: #{err.message}"
|
||||||
|
process.exit 1
|
||||||
|
|
||||||
|
if (not opts.doubleDashed) and (opts.arguments[1] is '--')
|
||||||
|
printWarn '''
|
||||||
|
coffee was invoked with '--' as the second positional argument, which is
|
||||||
|
now deprecated. To pass '--' as an argument to a script to run, put an
|
||||||
|
additional '--' before the path to your script.
|
||||||
|
|
||||||
|
'--' will be removed from the argument list.
|
||||||
|
'''
|
||||||
|
printWarn "The positional arguments were: #{JSON.stringify opts.arguments}"
|
||||||
|
opts.arguments = [opts.arguments[0]].concat opts.arguments[2..]
|
||||||
|
|
||||||
# Make the REPL *CLI* use the global context so as to (a) be consistent with the
|
# Make the REPL *CLI* use the global context so as to (a) be consistent with the
|
||||||
# `node` REPL CLI and, therefore, (b) make packages that modify native prototypes
|
# `node` REPL CLI and, therefore, (b) make packages that modify native prototypes
|
||||||
# (such as 'colors' and 'sugar') work as expected.
|
# (such as 'colors' and 'sugar') work as expected.
|
||||||
|
|
|
@ -18,8 +18,8 @@ exports.OptionParser = class OptionParser
|
||||||
# [short-flag, long-flag, description]
|
# [short-flag, long-flag, description]
|
||||||
#
|
#
|
||||||
# Along with an optional banner for the usage help.
|
# Along with an optional banner for the usage help.
|
||||||
constructor: (rules, @banner) ->
|
constructor: (ruleDeclarations, @banner) ->
|
||||||
@rules = buildRules rules
|
@rules = buildRules ruleDeclarations
|
||||||
|
|
||||||
# Parse the list of arguments, populating an `options` object with all of the
|
# Parse the list of arguments, populating an `options` object with all of the
|
||||||
# specified options, and return it. Options after the first non-option
|
# specified options, and return it. Options after the first non-option
|
||||||
|
@ -28,36 +28,33 @@ exports.OptionParser = class OptionParser
|
||||||
# parsers that allow you to attach callback actions for every flag. Instead,
|
# parsers that allow you to attach callback actions for every flag. Instead,
|
||||||
# you're responsible for interpreting the options object.
|
# you're responsible for interpreting the options object.
|
||||||
parse: (args) ->
|
parse: (args) ->
|
||||||
options = arguments: []
|
# The CoffeeScript option parser is a little odd; options after the first
|
||||||
skippingArgument = no
|
# non-option argument are treated as non-option arguments themselves.
|
||||||
originalArgs = args
|
# Optional arguments are normalized by expanding merged flags into multiple
|
||||||
args = normalizeArguments args
|
# flags. This allows you to have `-wl` be the same as `--watch --lint`.
|
||||||
for arg, i in args
|
# Note that executable scripts with a shebang (`#!`) line should use the
|
||||||
if skippingArgument
|
# line `#!/usr/bin/env coffee`, or `#!/absolute/path/to/coffee`, without a
|
||||||
skippingArgument = no
|
# `--` argument after, because that will fail on Linux (see #3946).
|
||||||
continue
|
{rules, positional} = normalizeArguments args, @rules.flagDict
|
||||||
if arg is '--'
|
options = {}
|
||||||
pos = originalArgs.indexOf '--'
|
|
||||||
options.arguments = options.arguments.concat originalArgs[(pos + 1)..]
|
# The `argument` field is added to the rule instance non-destructively by
|
||||||
break
|
# `normalizeArguments`.
|
||||||
isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
|
for {hasArgument, argument, isList, name} in rules
|
||||||
# the CS option parser is a little odd; options after the first
|
if hasArgument
|
||||||
# non-option argument are treated as non-option arguments themselves
|
if isList
|
||||||
seenNonOptionArg = options.arguments.length > 0
|
options[name] ?= []
|
||||||
unless seenNonOptionArg
|
options[name].push argument
|
||||||
matchedRule = no
|
else
|
||||||
for rule in @rules
|
options[name] = argument
|
||||||
if rule.shortFlag is arg or rule.longFlag is arg
|
else
|
||||||
value = true
|
options[name] = true
|
||||||
if rule.hasArgument
|
|
||||||
skippingArgument = yes
|
if positional[0] is '--'
|
||||||
value = args[i + 1]
|
options.doubleDashed = yes
|
||||||
options[rule.name] = if rule.isList then (options[rule.name] or []).concat value else value
|
positional = positional[1..]
|
||||||
matchedRule = yes
|
|
||||||
break
|
options.arguments = positional
|
||||||
throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule
|
|
||||||
if seenNonOptionArg or not isOption
|
|
||||||
options.arguments.push arg
|
|
||||||
options
|
options
|
||||||
|
|
||||||
# Return the help text for this **OptionParser**, listing and describing all
|
# Return the help text for this **OptionParser**, listing and describing all
|
||||||
|
@ -65,7 +62,7 @@ exports.OptionParser = class OptionParser
|
||||||
help: ->
|
help: ->
|
||||||
lines = []
|
lines = []
|
||||||
lines.unshift "#{@banner}\n" if @banner
|
lines.unshift "#{@banner}\n" if @banner
|
||||||
for rule in @rules
|
for rule in @rules.ruleList
|
||||||
spaces = 15 - rule.longFlag.length
|
spaces = 15 - rule.longFlag.length
|
||||||
spaces = if spaces > 0 then repeat ' ', spaces else ''
|
spaces = if spaces > 0 then repeat ' ', spaces else ''
|
||||||
letPart = if rule.shortFlag then rule.shortFlag + ', ' else ' '
|
letPart = if rule.shortFlag then rule.shortFlag + ', ' else ' '
|
||||||
|
@ -75,26 +72,39 @@ exports.OptionParser = class OptionParser
|
||||||
# Helpers
|
# Helpers
|
||||||
# -------
|
# -------
|
||||||
|
|
||||||
# Regex matchers for option flags.
|
# Regex matchers for option flags on the command line and their rules.
|
||||||
LONG_FLAG = /^(--\w[\w\-]*)/
|
LONG_FLAG = /^(--\w[\w\-]*)/
|
||||||
SHORT_FLAG = /^(-\w)$/
|
SHORT_FLAG = /^(-\w)$/
|
||||||
MULTI_FLAG = /^-(\w{2,})/
|
MULTI_FLAG = /^-(\w{2,})/
|
||||||
|
# Matches the long flag part of a rule for an option with an argument. Not
|
||||||
|
# applied to anything in process.argv.
|
||||||
OPTIONAL = /\[(\w+(\*?))\]/
|
OPTIONAL = /\[(\w+(\*?))\]/
|
||||||
|
|
||||||
# Build and return the list of option rules. If the optional *short-flag* is
|
# Build and return the list of option rules. If the optional *short-flag* is
|
||||||
# unspecified, leave it out by padding with `null`.
|
# unspecified, leave it out by padding with `null`.
|
||||||
buildRules = (rules) ->
|
buildRules = (ruleDeclarations) ->
|
||||||
for tuple in rules
|
ruleList = for tuple in ruleDeclarations
|
||||||
tuple.unshift null if tuple.length < 3
|
tuple.unshift null if tuple.length < 3
|
||||||
buildRule tuple...
|
buildRule tuple...
|
||||||
|
flagDict = {}
|
||||||
|
for rule in ruleList
|
||||||
|
# `shortFlag` is null if not provided in the rule.
|
||||||
|
for flag in [rule.shortFlag, rule.longFlag] when flag?
|
||||||
|
if flagDict[flag]?
|
||||||
|
throw new Error "flag #{flag} for switch #{rule.name}
|
||||||
|
was already declared for switch #{flagDict[flag].name}"
|
||||||
|
flagDict[flag] = rule
|
||||||
|
|
||||||
|
{ruleList, flagDict}
|
||||||
|
|
||||||
# Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
|
# Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
|
||||||
# description of what the option does.
|
# description of what the option does.
|
||||||
buildRule = (shortFlag, longFlag, description, options = {}) ->
|
buildRule = (shortFlag, longFlag, description) ->
|
||||||
match = longFlag.match(OPTIONAL)
|
match = longFlag.match(OPTIONAL)
|
||||||
|
shortFlag = shortFlag?.match(SHORT_FLAG)[1]
|
||||||
longFlag = longFlag.match(LONG_FLAG)[1]
|
longFlag = longFlag.match(LONG_FLAG)[1]
|
||||||
{
|
{
|
||||||
name: longFlag.substr 2
|
name: longFlag.replace /^--/, ''
|
||||||
shortFlag: shortFlag
|
shortFlag: shortFlag
|
||||||
longFlag: longFlag
|
longFlag: longFlag
|
||||||
description: description
|
description: description
|
||||||
|
@ -102,14 +112,54 @@ buildRule = (shortFlag, longFlag, description, options = {}) ->
|
||||||
isList: !!(match and match[2])
|
isList: !!(match and match[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
# Normalize arguments by expanding merged flags into multiple flags. This allows
|
normalizeArguments = (args, flagDict) ->
|
||||||
# you to have `-wl` be the same as `--watch --lint`.
|
rules = []
|
||||||
normalizeArguments = (args) ->
|
positional = []
|
||||||
args = args[..]
|
needsArgOpt = null
|
||||||
result = []
|
for arg, argIndex in args
|
||||||
for arg in args
|
# If the previous argument given to the script was an option that uses the
|
||||||
if match = arg.match MULTI_FLAG
|
# next command-line argument as its argument, create copy of the option’s
|
||||||
result.push '-' + l for l in match[1].split ''
|
# rule with an `argument` field.
|
||||||
|
if needsArgOpt?
|
||||||
|
withArg = Object.assign {}, needsArgOpt.rule, {argument: arg}
|
||||||
|
rules.push withArg
|
||||||
|
needsArgOpt = null
|
||||||
|
continue
|
||||||
|
|
||||||
|
multiFlags = arg.match(MULTI_FLAG)?[1]
|
||||||
|
.split('')
|
||||||
|
.map (flagName) -> "-#{flagName}"
|
||||||
|
if multiFlags?
|
||||||
|
multiOpts = multiFlags.map (flag) ->
|
||||||
|
rule = flagDict[flag]
|
||||||
|
unless rule?
|
||||||
|
throw new Error "unrecognized option #{flag} in multi-flag #{arg}"
|
||||||
|
{rule, flag}
|
||||||
|
# Only the last flag in a multi-flag may have an argument.
|
||||||
|
[innerOpts..., lastOpt] = multiOpts
|
||||||
|
for {rule, flag} in innerOpts
|
||||||
|
if rule.hasArgument
|
||||||
|
throw new Error "cannot use option #{flag} in multi-flag #{arg} except
|
||||||
|
as the last option, because it needs an argument"
|
||||||
|
rules.push rule
|
||||||
|
if lastOpt.rule.hasArgument
|
||||||
|
needsArgOpt = lastOpt
|
||||||
|
else
|
||||||
|
rules.push lastOpt.rule
|
||||||
|
else if ([LONG_FLAG, SHORT_FLAG].some (pat) -> arg.match(pat)?)
|
||||||
|
singleRule = flagDict[arg]
|
||||||
|
unless singleRule?
|
||||||
|
throw new Error "unrecognized option #{arg}"
|
||||||
|
if singleRule.hasArgument
|
||||||
|
needsArgOpt = {rule: singleRule, flag: arg}
|
||||||
|
else
|
||||||
|
rules.push singleRule
|
||||||
else
|
else
|
||||||
result.push arg
|
# This is a positional argument.
|
||||||
result
|
positional = args[argIndex..]
|
||||||
|
break
|
||||||
|
|
||||||
|
if needsArgOpt?
|
||||||
|
throw new Error "value required for #{needsArgOpt.flag}, but it was the last
|
||||||
|
argument provided"
|
||||||
|
{rules, positional}
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
return unless require?
|
|
||||||
|
|
||||||
{buildCSOptionParser} = require '../lib/coffeescript/command'
|
|
||||||
|
|
||||||
optionParser = buildCSOptionParser()
|
|
||||||
|
|
||||||
sameOptions = (opts1, opts2, msg) ->
|
|
||||||
ownKeys = Object.keys(opts1).sort()
|
|
||||||
otherKeys = Object.keys(opts2).sort()
|
|
||||||
arrayEq ownKeys, otherKeys, msg
|
|
||||||
for k in ownKeys
|
|
||||||
arrayEq opts1[k], opts2[k], msg
|
|
||||||
yes
|
|
||||||
|
|
||||||
test "combined options are still split after initial file name", ->
|
|
||||||
argv = ['some-file.coffee', '-bc']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected = arguments: ['some-file.coffee', '-b', '-c']
|
|
||||||
sameOptions parsed, expected
|
|
||||||
|
|
||||||
argv = ['some-file.litcoffee', '-bc']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected = arguments: ['some-file.litcoffee', '-b', '-c']
|
|
||||||
sameOptions parsed, expected
|
|
||||||
|
|
||||||
argv = ['-c', 'some-file.coffee', '-bc']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected =
|
|
||||||
compile: yes
|
|
||||||
arguments: ['some-file.coffee', '-b', '-c']
|
|
||||||
sameOptions parsed, expected
|
|
||||||
|
|
||||||
argv = ['-bc', 'some-file.coffee', '-bc']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected =
|
|
||||||
bare: yes
|
|
||||||
compile: yes
|
|
||||||
arguments: ['some-file.coffee', '-b', '-c']
|
|
||||||
sameOptions parsed, expected
|
|
||||||
|
|
||||||
test "combined options are not split after a '--'", ->
|
|
||||||
argv = ['--', '-bc']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected = arguments: ['-bc']
|
|
||||||
sameOptions parsed, expected
|
|
||||||
|
|
||||||
argv = ['-bc', '--', '-bc']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected =
|
|
||||||
bare: yes
|
|
||||||
compile: yes
|
|
||||||
arguments: ['-bc']
|
|
||||||
sameOptions parsed, expected
|
|
||||||
|
|
||||||
test "options are not split after any '--'", ->
|
|
||||||
argv = ['--', '--', '-bc']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected = arguments: ['--', '-bc']
|
|
||||||
sameOptions parsed, expected
|
|
||||||
|
|
||||||
argv = ['--', 'some-file.coffee', '--', 'arg']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected = arguments: ['some-file.coffee', '--', 'arg']
|
|
||||||
sameOptions parsed, expected
|
|
||||||
|
|
||||||
argv = ['--', 'arg', 'some-file.coffee', '--', '-bc']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected = arguments: ['arg', 'some-file.coffee', '--', '-bc']
|
|
||||||
sameOptions parsed, expected
|
|
||||||
|
|
||||||
test "later '--' are removed", ->
|
|
||||||
argv = ['some-file.coffee', '--', '-bc']
|
|
||||||
parsed = optionParser.parse argv
|
|
||||||
expected = arguments: ['some-file.coffee', '-bc']
|
|
||||||
sameOptions parsed, expected
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
return unless require?
|
||||||
|
{buildCSOptionParser} = require '../lib/coffeescript/command'
|
||||||
|
|
||||||
|
optionParser = buildCSOptionParser()
|
||||||
|
|
||||||
|
sameOptions = (opts1, opts2, msg) ->
|
||||||
|
ownKeys = Object.keys(opts1).sort()
|
||||||
|
otherKeys = Object.keys(opts2).sort()
|
||||||
|
arrayEq ownKeys, otherKeys, msg
|
||||||
|
for k in ownKeys
|
||||||
|
arrayEq opts1[k], opts2[k], msg
|
||||||
|
yes
|
||||||
|
|
||||||
|
test "combined options are not split after initial file name", ->
|
||||||
|
argv = ['some-file.coffee', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected = arguments: ['some-file.coffee', '-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
argv = ['some-file.litcoffee', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected = arguments: ['some-file.litcoffee', '-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
argv = ['-c', 'some-file.coffee', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected =
|
||||||
|
compile: yes
|
||||||
|
arguments: ['some-file.coffee', '-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
argv = ['-bc', 'some-file.coffee', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected =
|
||||||
|
bare: yes
|
||||||
|
compile: yes
|
||||||
|
arguments: ['some-file.coffee', '-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
test "combined options are not split after a '--', which is discarded", ->
|
||||||
|
argv = ['--', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected =
|
||||||
|
doubleDashed: yes
|
||||||
|
arguments: ['-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
argv = ['-bc', '--', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected =
|
||||||
|
bare: yes
|
||||||
|
compile: yes
|
||||||
|
doubleDashed: yes
|
||||||
|
arguments: ['-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
test "options are not split after any '--'", ->
|
||||||
|
argv = ['--', '--', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected =
|
||||||
|
doubleDashed: yes
|
||||||
|
arguments: ['--', '-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
argv = ['--', 'some-file.coffee', '--', 'arg']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected =
|
||||||
|
doubleDashed: yes
|
||||||
|
arguments: ['some-file.coffee', '--', 'arg']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
argv = ['--', 'arg', 'some-file.coffee', '--', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected =
|
||||||
|
doubleDashed: yes
|
||||||
|
arguments: ['arg', 'some-file.coffee', '--', '-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
test "any non-option argument stops argument parsing", ->
|
||||||
|
argv = ['arg', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected = arguments: ['arg', '-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
test "later '--' are not removed", ->
|
||||||
|
argv = ['some-file.coffee', '--', '-bc']
|
||||||
|
parsed = optionParser.parse argv
|
||||||
|
expected = arguments: ['some-file.coffee', '--', '-bc']
|
||||||
|
sameOptions parsed, expected
|
||||||
|
|
||||||
|
test "throw on invalid options", ->
|
||||||
|
argv = ['-k']
|
||||||
|
throws -> optionParser.parse argv
|
||||||
|
|
||||||
|
argv = ['-ck']
|
||||||
|
throws (-> optionParser.parse argv), /multi-flag/
|
||||||
|
|
||||||
|
argv = ['-kc']
|
||||||
|
throws (-> optionParser.parse argv), /multi-flag/
|
||||||
|
|
||||||
|
argv = ['-oc']
|
||||||
|
throws (-> optionParser.parse argv), /needs an argument/
|
||||||
|
|
||||||
|
argv = ['-o']
|
||||||
|
throws (-> optionParser.parse argv), /value required/
|
||||||
|
|
||||||
|
argv = ['-co']
|
||||||
|
throws (-> optionParser.parse argv), /value required/
|
||||||
|
|
||||||
|
# Check if all flags in a multi-flag are recognized before checking if flags
|
||||||
|
# before the last need arguments.
|
||||||
|
argv = ['-ok']
|
||||||
|
throws (-> optionParser.parse argv), /unrecognized option/
|
||||||
|
|
||||||
|
test "has expected help text", ->
|
||||||
|
ok optionParser.help() is '''
|
||||||
|
|
||||||
|
Usage: coffee [options] path/to/script.coffee [args]
|
||||||
|
|
||||||
|
If called without options, `coffee` will run your script.
|
||||||
|
|
||||||
|
-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 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 pass options directly to the "node" binary
|
||||||
|
--no-header suppress the "Generated by" header
|
||||||
|
-o, --output set the output directory 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
|
||||||
|
-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
|
||||||
|
|
||||||
|
'''
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env coffee
|
||||||
|
|
||||||
|
process.stdout.write JSON.stringify(process.argv)
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env coffee --
|
||||||
|
|
||||||
|
process.stdout.write JSON.stringify(process.argv)
|
|
@ -0,0 +1,3 @@
|
||||||
|
#! /usr/bin/env coffee
|
||||||
|
|
||||||
|
process.stdout.write JSON.stringify(process.argv)
|
|
@ -0,0 +1,3 @@
|
||||||
|
#! /usr/bin/env coffee extra
|
||||||
|
|
||||||
|
process.stdout.write JSON.stringify(process.argv)
|
|
@ -0,0 +1,108 @@
|
||||||
|
return unless require?
|
||||||
|
|
||||||
|
path = require 'path'
|
||||||
|
{spawnSync, execFileSync} = require 'child_process'
|
||||||
|
|
||||||
|
# Get directory containing the compiled `coffee` executable and prepend it to
|
||||||
|
# the path so `#!/usr/bin/env coffee` resolves to our locally built file.
|
||||||
|
coffeeBinDir = path.dirname require.resolve('../bin/coffee')
|
||||||
|
patchedPath = "#{coffeeBinDir}:#{process.env.PATH}"
|
||||||
|
patchedEnv = Object.assign {}, process.env, {PATH: patchedPath}
|
||||||
|
|
||||||
|
shebangScript = require.resolve './importing/shebang.coffee'
|
||||||
|
initialSpaceScript = require.resolve './importing/shebang_initial_space.coffee'
|
||||||
|
extraArgsScript = require.resolve './importing/shebang_extra_args.coffee'
|
||||||
|
initialSpaceExtraArgsScript = require.resolve './importing/shebang_initial_space_extra_args.coffee'
|
||||||
|
|
||||||
|
test "parse arguments for shebang scripts correctly (on unix platforms)", ->
|
||||||
|
return if isWindows()
|
||||||
|
|
||||||
|
stdout = execFileSync shebangScript, ['-abck'], {env: patchedEnv}
|
||||||
|
expectedArgs = ['coffee', shebangScript, '-abck']
|
||||||
|
realArgs = JSON.parse stdout
|
||||||
|
arrayEq expectedArgs, realArgs
|
||||||
|
|
||||||
|
stdout = execFileSync initialSpaceScript, ['-abck'], {env: patchedEnv}
|
||||||
|
expectedArgs = ['coffee', initialSpaceScript, '-abck']
|
||||||
|
realArgs = JSON.parse stdout
|
||||||
|
arrayEq expectedArgs, realArgs
|
||||||
|
|
||||||
|
test "warn and remove -- if it is the second positional argument", ->
|
||||||
|
result = spawnSync 'coffee', [shebangScript, '--'], {env: patchedEnv}
|
||||||
|
stderr = result.stderr.toString()
|
||||||
|
arrayEq JSON.parse(result.stdout), ['coffee', shebangScript]
|
||||||
|
ok stderr.match /^coffee was invoked with '--'/m
|
||||||
|
posArgs = stderr.match(/^The positional arguments were: (.*)$/m)[1]
|
||||||
|
arrayEq JSON.parse(posArgs), [shebangScript, '--']
|
||||||
|
ok result.status is 0
|
||||||
|
|
||||||
|
result = spawnSync 'coffee', ['-b', shebangScript, '--'], {env: patchedEnv}
|
||||||
|
stderr = result.stderr.toString()
|
||||||
|
arrayEq JSON.parse(result.stdout), ['coffee', shebangScript]
|
||||||
|
ok stderr.match /^coffee was invoked with '--'/m
|
||||||
|
posArgs = stderr.match(/^The positional arguments were: (.*)$/m)[1]
|
||||||
|
arrayEq JSON.parse(posArgs), [shebangScript, '--']
|
||||||
|
ok result.status is 0
|
||||||
|
|
||||||
|
result = spawnSync(
|
||||||
|
'coffee', ['-b', shebangScript, '--', 'ANOTHER ONE'], {env: patchedEnv})
|
||||||
|
stderr = result.stderr.toString()
|
||||||
|
arrayEq JSON.parse(result.stdout), ['coffee', shebangScript, 'ANOTHER ONE']
|
||||||
|
ok stderr.match /^coffee was invoked with '--'/m
|
||||||
|
posArgs = stderr.match(/^The positional arguments were: (.*)$/m)[1]
|
||||||
|
arrayEq JSON.parse(posArgs), [shebangScript, '--', 'ANOTHER ONE']
|
||||||
|
ok result.status is 0
|
||||||
|
|
||||||
|
result = spawnSync(
|
||||||
|
'coffee', ['--', initialSpaceScript, 'arg'], {env: patchedEnv})
|
||||||
|
expectedArgs = ['coffee', initialSpaceScript, 'arg']
|
||||||
|
realArgs = JSON.parse result.stdout
|
||||||
|
arrayEq expectedArgs, realArgs
|
||||||
|
ok result.stderr.toString() is ''
|
||||||
|
ok result.status is 0
|
||||||
|
|
||||||
|
test "warn about non-portable shebang lines", ->
|
||||||
|
result = spawnSync 'coffee', [extraArgsScript, 'arg'], {env: patchedEnv}
|
||||||
|
stderr = result.stderr.toString()
|
||||||
|
arrayEq JSON.parse(result.stdout), ['coffee', extraArgsScript, 'arg']
|
||||||
|
ok stderr.match /^The script to be run begins with a shebang line with more than one/m
|
||||||
|
[_, firstLine, file] = stderr.match(/^The shebang line was: '([^']+)' in file '([^']+)'/m)
|
||||||
|
ok (firstLine is '#!/usr/bin/env coffee --')
|
||||||
|
ok (file is extraArgsScript)
|
||||||
|
args = stderr.match(/^The arguments were: (.*)$/m)[1]
|
||||||
|
arrayEq JSON.parse(args), ['coffee', '--']
|
||||||
|
ok result.status is 0
|
||||||
|
|
||||||
|
result = spawnSync 'coffee', [initialSpaceScript, 'arg'], {env: patchedEnv}
|
||||||
|
stderr = result.stderr.toString()
|
||||||
|
ok stderr is ''
|
||||||
|
arrayEq JSON.parse(result.stdout), ['coffee', initialSpaceScript, 'arg']
|
||||||
|
ok result.status is 0
|
||||||
|
|
||||||
|
result = spawnSync(
|
||||||
|
'coffee', [initialSpaceExtraArgsScript, 'arg'], {env: patchedEnv})
|
||||||
|
stderr = result.stderr.toString()
|
||||||
|
arrayEq JSON.parse(result.stdout), ['coffee', initialSpaceExtraArgsScript, 'arg']
|
||||||
|
ok stderr.match /^The script to be run begins with a shebang line with more than one/m
|
||||||
|
[_, firstLine, file] = stderr.match(/^The shebang line was: '([^']+)' in file '([^']+)'/m)
|
||||||
|
ok (firstLine is '#! /usr/bin/env coffee extra')
|
||||||
|
ok (file is initialSpaceExtraArgsScript)
|
||||||
|
args = stderr.match(/^The arguments were: (.*)$/m)[1]
|
||||||
|
arrayEq JSON.parse(args), ['coffee', 'extra']
|
||||||
|
ok result.status is 0
|
||||||
|
|
||||||
|
test "both warnings will be shown at once", ->
|
||||||
|
result = spawnSync(
|
||||||
|
'coffee', [initialSpaceExtraArgsScript, '--', 'arg'], {env: patchedEnv})
|
||||||
|
stderr = result.stderr.toString()
|
||||||
|
arrayEq JSON.parse(result.stdout), ['coffee', initialSpaceExtraArgsScript, 'arg']
|
||||||
|
ok stderr.match /^The script to be run begins with a shebang line with more than one/m
|
||||||
|
[_, firstLine, file] = stderr.match(/^The shebang line was: '([^']+)' in file '([^']+)'/m)
|
||||||
|
ok (firstLine is '#! /usr/bin/env coffee extra')
|
||||||
|
ok (file is initialSpaceExtraArgsScript)
|
||||||
|
args = stderr.match(/^The arguments were: (.*)$/m)[1]
|
||||||
|
arrayEq JSON.parse(args), ['coffee', 'extra']
|
||||||
|
ok stderr.match /^coffee was invoked with '--'/m
|
||||||
|
posArgs = stderr.match(/^The positional arguments were: (.*)$/m)[1]
|
||||||
|
arrayEq JSON.parse(posArgs), [initialSpaceExtraArgsScript, '--', 'arg']
|
||||||
|
ok result.status is 0
|
|
@ -1,18 +1,22 @@
|
||||||
# Option Parser
|
# Option Parser
|
||||||
# -------------
|
# -------------
|
||||||
|
|
||||||
# TODO: refactor option parser tests
|
|
||||||
|
|
||||||
# Ensure that the OptionParser handles arguments correctly.
|
# Ensure that the OptionParser handles arguments correctly.
|
||||||
return unless require?
|
return unless require?
|
||||||
{OptionParser} = require './../lib/coffeescript/optparse'
|
{OptionParser} = require './../lib/coffeescript/optparse'
|
||||||
|
|
||||||
opt = new OptionParser [
|
flags = [
|
||||||
['-r', '--required [DIR]', 'desc required']
|
['-r', '--required [DIR]', 'desc required']
|
||||||
['-o', '--optional', 'desc optional']
|
['-o', '--optional', 'desc optional']
|
||||||
['-l', '--list [FILES*]', 'desc list']
|
['-l', '--list [FILES*]', 'desc list']
|
||||||
]
|
]
|
||||||
|
|
||||||
|
banner = '''
|
||||||
|
banner text
|
||||||
|
'''
|
||||||
|
|
||||||
|
opt = new OptionParser flags, banner
|
||||||
|
|
||||||
test "basic arguments", ->
|
test "basic arguments", ->
|
||||||
args = ['one', 'two', 'three', '-r', 'dir']
|
args = ['one', 'two', 'three', '-r', 'dir']
|
||||||
result = opt.parse args
|
result = opt.parse args
|
||||||
|
@ -41,3 +45,50 @@ test "-- and interesting combinations", ->
|
||||||
eq undefined, result.optional
|
eq undefined, result.optional
|
||||||
eq undefined, result.required
|
eq undefined, result.required
|
||||||
arrayEq args[1..], result.arguments
|
arrayEq args[1..], result.arguments
|
||||||
|
|
||||||
|
test "throw if multiple flags try to use the same short or long name", ->
|
||||||
|
throws -> new OptionParser [
|
||||||
|
['-r', '--required [DIR]', 'required']
|
||||||
|
['-r', '--long', 'switch']
|
||||||
|
]
|
||||||
|
|
||||||
|
throws -> new OptionParser [
|
||||||
|
['-a', '--append [STR]', 'append']
|
||||||
|
['-b', '--append', 'append with -b short opt']
|
||||||
|
]
|
||||||
|
|
||||||
|
throws -> new OptionParser [
|
||||||
|
['--just-long', 'desc']
|
||||||
|
['--just-long', 'another desc']
|
||||||
|
]
|
||||||
|
|
||||||
|
throws -> new OptionParser [
|
||||||
|
['-j', '--just-long', 'desc']
|
||||||
|
['--just-long', 'another desc']
|
||||||
|
]
|
||||||
|
|
||||||
|
throws -> new OptionParser [
|
||||||
|
['--just-long', 'desc']
|
||||||
|
['-j', '--just-long', 'another desc']
|
||||||
|
]
|
||||||
|
|
||||||
|
test "outputs expected help text", ->
|
||||||
|
expectedBanner = '''
|
||||||
|
|
||||||
|
banner text
|
||||||
|
|
||||||
|
-r, --required desc required
|
||||||
|
-o, --optional desc optional
|
||||||
|
-l, --list desc list
|
||||||
|
|
||||||
|
'''
|
||||||
|
ok opt.help() is expectedBanner
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
''
|
||||||
|
' -r, --required desc required'
|
||||||
|
' -o, --optional desc optional'
|
||||||
|
' -l, --list desc list'
|
||||||
|
''
|
||||||
|
].join('\n')
|
||||||
|
ok new OptionParser(flags).help() is expected
|
||||||
|
|
|
@ -30,3 +30,5 @@ exports.eqJS = (input, expectedOutput, msg) ->
|
||||||
#{reset}#{expectedOutput}#{red}
|
#{reset}#{expectedOutput}#{red}
|
||||||
but instead it was:
|
but instead it was:
|
||||||
#{reset}#{actualOutput}#{red}"""
|
#{reset}#{actualOutput}#{red}"""
|
||||||
|
|
||||||
|
exports.isWindows = -> process.platform is 'win32'
|
||||||
|
|
Loading…
Reference in New Issue