{repeat} = require './helpers'
{repeat} = require './helpers'
一个简单的 OptionParser 类,用于从命令行解析选项标志。使用方法如下
parser = new OptionParser switches, helpBanner
options = parser.parse process.argv
第一个非选项被认为是文件(和文件选项)列表的开始,所有后续参数将保持未解析。
coffee
命令使用 OptionParser 的实例来解析其在 src/command.coffee
中的命令行参数。
exports.OptionParser = class OptionParser
constructor: (ruleDeclarations, @banner) ->
@rules = buildRules ruleDeclarations
解析参数列表,用所有指定的选项填充 options
对象,并返回它。第一个非选项参数之后的选项将被视为参数。options.arguments
将是一个包含剩余参数的数组。这比许多允许您为每个标志附加回调操作的选项解析器更简单的 API。相反,您负责解释选项对象。
parse: (args) ->
CoffeeScript 选项解析器有点奇怪;第一个非选项参数之后的选项将被视为非选项参数本身。可选参数通过将合并的标志扩展为多个标志来进行规范化。这允许您将 -wl
与 --watch --lint
相同。请注意,带有 shebang(#!
)行的可执行脚本应使用 #!/usr/bin/env coffee
或 #!/absolute/path/to/coffee
行,后面不要带 --
参数,因为这将在 Linux 上失败(参见 #3946)。
{rules, positional} = normalizeArguments args, @rules.flagDict
options = {}
argument
字段由 normalizeArguments
非破坏性地添加到规则实例中。
for {hasArgument, argument, isList, name} in rules
if hasArgument
if isList
options[name] ?= []
options[name].push argument
else
options[name] = argument
else
options[name] = true
if positional[0] is '--'
options.doubleDashed = yes
positional = positional[1..]
options.arguments = positional
options
返回此 OptionParser 的帮助文本,列出并描述所有有效选项,用于 --help
等。
help: ->
lines = []
lines.unshift "#{@banner}\n" if @banner
for rule in @rules.ruleList
spaces = 15 - rule.longFlag.length
spaces = if spaces > 0 then repeat ' ', spaces else ''
letPart = if rule.shortFlag then rule.shortFlag + ', ' else ' '
lines.push ' ' + letPart + rule.longFlag + spaces + rule.description
"\n#{ lines.join('\n') }\n"
命令行上选项标志及其规则的正则表达式匹配器。
LONG_FLAG = /^(--\w[\w\-]*)/
SHORT_FLAG = /^(-\w)$/
MULTI_FLAG = /^-(\w{2,})/
匹配具有参数的选项的规则的 long flag 部分。不应用于 process.argv 中的任何内容。
OPTIONAL = /\[(\w+(\*?))\]/
构建并返回选项规则列表。如果可选的 short-flag 未指定,则通过用 null
填充将其省略。
buildRules = (ruleDeclarations) ->
ruleList = for tuple in ruleDeclarations
tuple.unshift null if tuple.length < 3
buildRule tuple...
flagDict = {}
for rule in ruleList
shortFlag
如果在规则中未提供,则为 null。
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}
从 -o
short flag、--output [DIR]
long flag 和选项功能的描述构建规则。
buildRule = (shortFlag, longFlag, description) ->
match = longFlag.match(OPTIONAL)
shortFlag = shortFlag?.match(SHORT_FLAG)[1]
longFlag = longFlag.match(LONG_FLAG)[1]
{
name: longFlag.replace /^--/, ''
shortFlag: shortFlag
longFlag: longFlag
description: description
hasArgument: !!(match and match[1])
isList: !!(match and match[2])
}
normalizeArguments = (args, flagDict) ->
rules = []
positional = []
needsArgOpt = null
for arg, argIndex in args
如果之前传递给脚本的参数是使用下一个命令行参数作为其参数的选项,则创建选项规则的副本,其中包含 argument
字段。
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}
只有多标志中的最后一个标志可能具有参数。
[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
这是一个位置参数。
positional = args[argIndex..]
break
if needsArgOpt?
throw new Error "value required for #{needsArgOpt.flag}, but it was the last
argument provided"
{rules, positional}