UNPKG

capitano

Version:

Powerful, non opitionated command line parser for serious applications

159 lines (118 loc) 4.56 kB
_ = 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)