From c0dac45fe10cd8cc7bf1bfcf14acbd4e024bd224 Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Wed, 25 Jan 2012 19:47:03 -0500 Subject: [PATCH] OptionParser and related tests needed a cleanup The object returned from OptionParser::parse no longer has a `literals` property. It was pretty arbitrary, anyway. --- lib/coffee-script/command.js | 6 ++-- lib/coffee-script/optparse.js | 48 ++++++++++++++++---------------- src/command.coffee | 5 ++-- src/optparse.coffee | 52 +++++++++++++++++------------------ test/option_parser.coffee | 37 ++++++++++++++++--------- 5 files changed, 80 insertions(+), 68 deletions(-) diff --git a/lib/coffee-script/command.js b/lib/coffee-script/command.js index fa9f2680..4923f393 100644 --- a/lib/coffee-script/command.js +++ b/lib/coffee-script/command.js @@ -43,7 +43,7 @@ optionParser = null; exports.run = function() { - var source, _i, _len, _results; + var literals, source, _i, _len, _results; parseOptions(); if (opts.nodejs) return forkNode(); if (opts.help) return usage(); @@ -56,8 +56,8 @@ if (opts.stdio) return compileStdio(); if (opts["eval"]) return compileScript(null, sources[0]); if (!sources.length) return require('./repl'); - if (opts.run) opts.literals = sources.splice(1).concat(opts.literals); - process.argv = process.argv.slice(0, 2).concat(opts.literals); + literals = opts.run ? sources.splice(1) : []; + process.argv = process.argv.slice(0, 2).concat(literals); process.argv[0] = 'coffee'; process.execPath = require.main.filename; _results = []; diff --git a/lib/coffee-script/optparse.js b/lib/coffee-script/optparse.js index 58b9e4c2..ae6afa5e 100644 --- a/lib/coffee-script/optparse.js +++ b/lib/coffee-script/optparse.js @@ -12,10 +12,9 @@ } OptionParser.prototype.parse = function(args) { - var arg, i, isOption, matchedRule, options, originalArgs, pos, rule, skippingArgument, value, _i, _j, _len, _len2, _ref; + var arg, i, isOption, matchedRule, options, originalArgs, pos, rule, seenNonOptionArg, skippingArgument, value, _i, _j, _len, _len2, _ref; options = { - "arguments": [], - literals: [] + "arguments": [] }; skippingArgument = false; originalArgs = args; @@ -28,29 +27,32 @@ } if (arg === '--') { pos = originalArgs.indexOf('--'); - options["arguments"] = [originalArgs[1 + pos]]; - options.literals = originalArgs.slice(2 + pos); + options["arguments"] = options["arguments"].concat(originalArgs.slice(pos + 1)); break; } isOption = !!(arg.match(LONG_FLAG) || arg.match(SHORT_FLAG)); - matchedRule = false; - _ref = this.rules; - for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) { - rule = _ref[_j]; - if (rule.shortFlag === arg || rule.longFlag === arg) { - value = rule.hasArgument ? (skippingArgument = true, args[i + 1]) : true; - options[rule.name] = rule.isList ? (options[rule.name] || []).concat(value) : value; - matchedRule = true; - break; + seenNonOptionArg = options["arguments"].length > 0; + if (!seenNonOptionArg) { + matchedRule = false; + _ref = this.rules; + for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) { + rule = _ref[_j]; + 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; + } + } + if (isOption && !matchedRule) { + throw new Error("unrecognized option: " + arg); } } - if (isOption && !matchedRule) { - throw new Error("unrecognized option: " + arg); - } - if (!isOption) { - options["arguments"] = originalArgs.slice(originalArgs.indexOf(arg)); - break; - } + if (seenNonOptionArg || !isOption) options["arguments"].push(arg); } return options; }; @@ -74,9 +76,9 @@ })(); - LONG_FLAG = /^(--\w[\w\-]+)/; + LONG_FLAG = /^(--\w[\w\-]*)/; - SHORT_FLAG = /^(-\w)/; + SHORT_FLAG = /^(-\w)$/; MULTI_FLAG = /^-(\w{2,})/; diff --git a/src/command.coffee b/src/command.coffee index 374eea5c..330218fc 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -69,9 +69,8 @@ exports.run = -> return compileStdio() if opts.stdio return compileScript null, sources[0] if opts.eval return require './repl' unless sources.length - if opts.run - opts.literals = sources.splice(1).concat opts.literals - process.argv = process.argv[0..1].concat opts.literals + literals = if opts.run then sources.splice 1 else [] + process.argv = process.argv[0..1].concat literals process.argv[0] = 'coffee' process.execPath = require.main.filename for source in sources diff --git a/src/optparse.coffee b/src/optparse.coffee index d03ae356..3bed118c 100644 --- a/src/optparse.coffee +++ b/src/optparse.coffee @@ -17,14 +17,13 @@ exports.OptionParser = class OptionParser @rules = buildRules rules # Parse the list of arguments, populating an `options` object with all of the - # specified options, and return it. `options.arguments` will be an array - # containing the remaining non-option arguments. `options.literals` will be - # an array of options that are meant to be passed through directly to the - # executing script. This is a simpler API than many option parsers that allow - # you to attach callback actions for every flag. Instead, you're responsible - # for interpreting the options object. + # specified options, and return it. Options after the first non-option + # argument are treated as arguments. `options.arguments` will be an array + # containing the remaining arguments. This is a simpler API than many option + # parsers that allow you to attach callback actions for every flag. Instead, + # you're responsible for interpreting the options object. parse: (args) -> - options = arguments: [], literals: [] + options = arguments: [] skippingArgument = no originalArgs = args args = normalizeArguments args @@ -34,25 +33,26 @@ exports.OptionParser = class OptionParser continue if arg is '--' pos = originalArgs.indexOf '--' - options.arguments = [originalArgs[1 + pos]] - options.literals = originalArgs[(2 + pos)..] + options.arguments = options.arguments.concat originalArgs[(pos + 1)..] break isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG)) - matchedRule = no - for rule in @rules - if rule.shortFlag is arg or rule.longFlag is arg - value = if rule.hasArgument - skippingArgument = yes - args[i + 1] - else - true - options[rule.name] = if rule.isList then (options[rule.name] or []).concat value else value - matchedRule = yes - break - throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule - if not isOption - options.arguments = originalArgs[(originalArgs.indexOf arg)..] - break + # the CS option parser is a little odd; options after the first + # non-option argument are treated as non-option arguments themselves + seenNonOptionArg = options.arguments.length > 0 + unless seenNonOptionArg + matchedRule = no + for rule in @rules + if rule.shortFlag is arg or rule.longFlag is arg + value = true + if rule.hasArgument + skippingArgument = yes + value = args[i + 1] + options[rule.name] = if rule.isList then (options[rule.name] or []).concat value else value + matchedRule = yes + break + throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule + if seenNonOptionArg or not isOption + options.arguments.push arg options # Return the help text for this **OptionParser**, listing and describing all @@ -71,8 +71,8 @@ exports.OptionParser = class OptionParser # ------- # Regex matchers for option flags. -LONG_FLAG = /^(--\w[\w\-]+)/ -SHORT_FLAG = /^(-\w)/ +LONG_FLAG = /^(--\w[\w\-]*)/ +SHORT_FLAG = /^(-\w)$/ MULTI_FLAG = /^-(\w{2,})/ OPTIONAL = /\[(\w+(\*?))\]/ diff --git a/test/option_parser.coffee b/test/option_parser.coffee index 835f33af..b3fd81cc 100644 --- a/test/option_parser.coffee +++ b/test/option_parser.coffee @@ -13,20 +13,31 @@ opt = new OptionParser [ ['-l', '--list [FILES*]', 'desc list'] ] -result = opt.parse ['one', 'two', 'three', '-r', 'dir'] +test "basic arguments", -> + args = ['one', 'two', 'three', '-r', 'dir'] + result = opt.parse args + arrayEq args, result.arguments + eq undefined, result.required -eq 5, result.arguments.length -eq '-r', result.arguments[3] +test "boolean and parameterised options", -> + result = opt.parse ['--optional', '-r', 'folder', 'one', 'two'] + ok result.optional + eq 'folder', result.required + arrayEq ['one', 'two'], result.arguments -result = opt.parse ['--optional', '-r', 'folder', 'one', 'two'] +test "list options", -> + result = opt.parse ['-l', 'one.txt', '-l', 'two.txt', 'three'] + arrayEq ['one.txt', 'two.txt'], result.list + arrayEq ['three'], result.arguments -ok result.optional -eq 'folder', result.required -eq 'one two', result.arguments.join ' ' - -result = opt.parse ['-l', 'one.txt', '-l', 'two.txt', 'three'] - -ok result.list instanceof Array -ok result.list.join(' ') is 'one.txt two.txt' -ok result.arguments.join(' ') is 'three' +test "-- and interesting combinations", -> + result = opt.parse ['-o','-r','a','-r','b','-o','--','-a','b','--c','d'] + arrayEq ['-a', 'b', '--c', 'd'], result.arguments + ok result.optional + eq 'b', result.required + args = ['--','-o','a','-r','c','-o','--','-a','arg0','-b','arg1'] + result = opt.parse args + eq undefined, result.optional + eq undefined, result.required + arrayEq args[1..], result.arguments