capitano
Version:
Powerful, non opitionated command line parser for serious applications
159 lines (118 loc) • 4.56 kB
text/coffeescript
_ = require('lodash')
async = require('async')
Parameter = require('./parameter')
settings = require('./settings')
parse = require('./parse')
utils = require('./utils')
isLastOne = (parameters, predicate) ->
lastParameter = _.last(parameters)
return predicate(lastParameter)
appearedMoreThanOnce = (parameters, predicate) ->
filteredParameters = _.filter(parameters, predicate)
return filteredParameters.length > 1
module.exports = class Signature
constructor: (signature) ->
if not signature? or not _.isString(signature)
throw new Error('Missing or invalid signature')
@parameters = []
_.forEach parse.split(signature), (word) =>
@_addParameter(word)
if @allowsStdin()
isStdin = (parameter) ->
return parameter.allowsStdin()
if appearedMoreThanOnce(@parameters, isStdin)
throw new Error('Signature can only contain one stdin parameter')
if not isLastOne(@parameters, isStdin)
throw new Error('The stdin parameter should be the last one')
if @hasVariadicParameters()
isVariadic = (parameter) ->
return parameter.isVariadic()
if appearedMoreThanOnce(@parameters, isVariadic)
throw new Error('Signature can only contain one variadic parameter')
if not isLastOne(@parameters, isVariadic)
throw new Error('The variadic parameter should be the last one')
_addParameter: (word) ->
parameter = new Parameter(word)
@parameters.push(parameter)
hasParameters: ->
return _.some @parameters, (parameter) ->
return not parameter.isWord()
hasVariadicParameters: ->
return _.some @parameters, (parameter) ->
return parameter.isVariadic()
allowsStdin: ->
return _.some @parameters, (parameter) ->
return parameter.allowsStdin()
toString: ->
result = []
for parameter in @parameters
result.push(parameter.toString())
return result.join(' ')
isWildcard: ->
return _.every [
@parameters.length is 1
@parameters[0].toString() is settings.signatures.wildcard
]
# TODO: There should be a better way to implement
# this algorithm without duplicating a big chunk of
# compileParameters().
# Maybe there should be a third function that these
# two algorithms share?
# Related to this issue, if a command accepts input from stdin
# matches() calls compileParameters() thus causing compileParameters()
# to be called twice per execution, one for testing the match command
# and another one to actually do the real compilation.
# Stdin can be grabbed once, so the result is grabbed by matches()
# and when compileParameters() tries to fetch stdin again, there's
# nothing else to retrieve.
matches: (command, callback) ->
@compileParameters command, (error) ->
return callback(true) if not error?
if _.startsWith(error.message, 'Missing')
return callback(true)
return callback(false)
, false
compileParameters: (command, callback, performStdin = true) ->
commandWords = parse.split(command)
comparison = _.zip(@parameters, commandWords)
result = {}
return callback(null, result) if @isWildcard()
async.eachSeries comparison, (item, done) =>
parameter = item[0]
word = item[1]
if not parameter?
return callback(new Error('Signature dismatch'))
parameterValue = parameter.getValue()
if parameter.allowsStdin() and not word?
# Used to prevent matches() to retrieve the
# input from stdin.
# TODO: This should be unnecessary once matches()
# do not calls compileParameters() anymore.
return callback(null, result) if not performStdin
return utils.getStdin (stdin) ->
if parameter.isRequired() and not stdin?
return callback(new Error("Missing #{parameterValue}"))
if stdin?
result[parameterValue] = stdin
return callback(null, result)
if not parameter.matches(word)
if parameter.isRequired()
return callback(new Error("Missing #{parameterValue}"))
return callback(new Error("#{parameterValue} does not match #{word}"))
if parameter.isVariadic()
parameterIndex = _.indexOf(@parameters, parameter)
value = _.slice(commandWords, parameterIndex).join(' ')
if parameter.isOptional() and _.isEmpty(value)
return callback(null, result)
result[parameterValue] = value
return callback(null, result)
if not parameter.isWord() and word?
if /^\d+$/.test(word)
result[parameterValue] = _.parseInt(word)
result[parameterValue + '_raw'] = word
else
result[parameterValue] = word
return done()
, (error) ->
return callback(error) if error?
return callback(null, result)