{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,})/
比對具有參數的選項規則的長旗標部分。不套用於 process.argv 中的任何項目。
OPTIONAL = /\[(\w+(\*?))\]/
建立並傳回選項規則清單。如果未指定選用的 短旗標,請透過使用 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
短旗標、--output [DIR]
長旗標以及選項功能說明建立規則。
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}