eslintcc
Version:
ESLintCC is a ECMAScript/JavaScript tool that computes complexity of code by using ESLint
349 lines (308 loc) • 9.04 kB
JavaScript
function __escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
class CLArguments {
/**
* @name CLArguments.prefixPattern
* @type {RegExp}
* @default
*/
static get prefixPattern() {
return /^--?/
}
/**
* @name CLArguments.flagPrefix
* @type {string}
* @default
*/
static get flagPrefix() {
return '-'
}
/**
* @name CLArguments.optionPrefix
* @type {string}
* @default
*/
static get optionPrefix() {
return '--'
}
/**
* @function CLArguments.resolvePrefixPattern
* @param {string} [flagPrefix]
* @param {string} [optionPrefix]
* @returns {RegExp}
*/
static resolvePrefixPattern(flagPrefix, optionPrefix) {
if (flagPrefix || optionPrefix) {
flagPrefix = __escapeRegExp(flagPrefix || this.flagPrefix)
optionPrefix = __escapeRegExp(optionPrefix || this.optionPrefix)
if (flagPrefix.length < optionPrefix.length) {
return new RegExp(`^${optionPrefix}|^${flagPrefix}`)
} else {
return new RegExp(`^${flagPrefix}|^${optionPrefix}`)
}
} else {
return this.prefixPattern
}
}
/**
* @name CLArguments.setterPattern
* @type {RegExp}
* @default
*/
static get setterPattern() {
return /=/
}
/**
* @name CLArguments.setter
* @type {string}
* @default
*/
static get setter() {
return '='
}
/**
* @function CLArguments.resolveSetterPattern
* @param {string} [setter]
* @returns {RegExp}
*/
static resolveSetterPattern(setter) {
if (setter) {
return new RegExp(__escapeRegExp(setter))
} else {
return this.setterPattern
}
}
/**
* @typedef CLArgumentsOptions
* @property {RegExp} [prefixPattern=CLArguments.prefixPattern]
* @property {string} [flagPrefix=CLArguments.flagPrefix]
* @property {string} [optionPrefix=CLArguments.optionPrefix]
* @property {RegExp} [setterPattern=CLArguments.setterPattern]
* @property {string} [setter=CLArguments.setter]
* @property {{[x: string]: string}} [types={}]
* @property {{[x: string]: string|string[]}} [aliases={}]
*/
/**
* @function CLArguments.resolveCLAOptions
* @param {CLArgumentsOptions} claOptions
* @returns {CLArgumentsOptions}
*/
static resolveCLAOptions(claOptions = {}) {
claOptions.prefixPattern = claOptions.prefixPattern || this.resolvePrefixPattern(
claOptions.flagPrefix,
claOptions.optionPrefix
)
claOptions.flagPrefix = claOptions.flagPrefix || this.flagPrefix
claOptions.optionPrefix = claOptions.optionPrefix || this.optionPrefix
claOptions.setterPattern = claOptions.setterPattern || this.resolveSetterPattern(
claOptions.setter
)
claOptions.setter = claOptions.setter || this.setter
claOptions.types = claOptions.types || {}
claOptions.aliases = claOptions.aliases || {}
return claOptions
}
/**
* @function CLArguments.resolveArgumentName
* @param {string} name
* @param {{[x: string]: string|string[]}} aliases
* @returns {string}
*/
static resolveArgumentName(name, aliases) {
if (aliases) {
for (const [realName, alias] of Object.entries(aliases)) {
if (name === alias || (alias instanceof Array && ~alias.indexOf(name))) {
return realName
}
}
}
return name
}
/**
* @typedef CLSolvedArgument
* @property {string} [name]
* @property {string} value
* @property {string} [type]
* @property {boolean} [offset]
*/
/**
* @function CLArguments.resolveArgumentType
* @param {CLSolvedArgument} solvedArgument
* @param {{[x: string]: string}} types
* @returns {string}
*/
static resolveArgumentType({ name, value }, types = {}) {
let type = types[name]
if (!type) {
if (name && value) {
type = 'Option'
} else if (name) {
type = 'Flag'
} else {
type = 'Argument'
}
}
return type
}
/**
* @function CLArguments.resolveArgument
* @param {string} testName
* @param {string} [testValue]
* @param {CLArgumentsOptions} [claOptions]
* @returns {CLSolvedArgument}
*/
static resolveArgument(testName, testValue, claOptions) {
const { prefixPattern, setterPattern, aliases, types } = this.resolveCLAOptions(claOptions)
const result = {}
if (prefixPattern.test(testName)) {
const name = testName.replace(prefixPattern, '')
if (setterPattern.test(name)) {
const setter = name.replace(setterPattern, ' ').split(' ')
result.name = this.resolveArgumentName(setter[0], aliases)
/* eslint-disable prefer-destructuring */
result.value = setter[1]
result.offset = false
} else if (typeof testValue === 'undefined' || prefixPattern.test(testValue)) {
result.name = this.resolveArgumentName(name, aliases)
} else {
result.name = this.resolveArgumentName(name, aliases)
result.value = testValue
result.offset = true
}
} else {
result.value = testName
}
result.type = this.resolveArgumentType(result, types)
return result
}
/**
* @function CLArguments.setterTypeOption
* @param {CLParsedArguments} parsedArguments
* @param {CLSolvedArgument} solvedArgument
*/
static setterTypeOption({ options }, { name, value }) {
options[name] = value
}
/**
* @function CLArguments.setterTypeArray
* @param {CLParsedArguments} parsedArguments
* @param {CLSolvedArgument} solvedArgument
*/
static setterTypeArray({ options }, { name, value }) {
if (name in options) {
// @ts-ignore
options[name].push(value)
} else {
options[name] = [value]
}
}
/**
* @function CLArguments.setterTypeFlag
* @param {CLParsedArguments} parsedArguments
* @param {CLSolvedArgument} solvedArgument
*/
static setterTypeFlag({ flags, argv }, { name, value }) {
flags[name] = true
if (value) {
this.setterTypeArgument({ argv }, { value })
}
}
/**
* @function CLArguments.setterTypeArgument
* @param {CLParsedArguments} parsedArguments
* @param {CLSolvedArgument} solvedArgument
*/
static setterTypeArgument({ argv }, { value }) {
argv.push(value)
}
/**
* @typedef CLParsedArguments
* @property {{[x: string]: boolean}} [flags]
* @property {{[x: string]: string|string[]}} [options]
* @property {Array<string>} [argv]
*/
/**
* @function CLArguments.parse
* @param {string|Array<string>} [input]
* @param {CLArgumentsOptions} [claOptions]
* @returns {CLParsedArguments}
*/
static parse(input = [], claOptions = {}) {
const inputArgs = typeof input === 'string' ? input.split(' ').filter(Boolean) : input
const parsed = { flags: {}, options: {}, argv: [] }
for (let index = 0; index < inputArgs.length; index++) {
const solvedArgument = this.resolveArgument(
inputArgs[index], inputArgs[index + 1], claOptions
)
this['setterType' + solvedArgument.type](parsed, solvedArgument)
if (solvedArgument.offset) {
index++
}
}
return parsed
}
/**
* @function CLArguments.stringify
* @param {CLParsedArguments} parsedArguments
* @param {CLArgumentsOptions} claOptions
* @returns {string}
*/
static stringify(parsedArguments, claOptions) {
const { flagPrefix, optionPrefix, setter } = this.resolveCLAOptions(claOptions)
const argv = []
if ('flags' in parsedArguments) {
for (const [name] of Object.entries(parsedArguments.flags)) {
argv.push(flagPrefix + name)
}
}
if ('options' in parsedArguments) {
for (const [name, value] of Object.entries(parsedArguments.options)) {
argv.push(optionPrefix + name + setter + value)
}
}
if ('argv' in parsedArguments) {
argv.push(...parsedArguments.argv)
}
return argv.join(' ')
}
/**
* @class CLArguments
* @param {CLArgumentsOptions} claOptions
* @property {CLArgumentsOptions} claOptions
* @property {{[x: string]: boolean}} flags
* @property {{[x: string]: string}} options
* @property {Array<string>} argv
*/
constructor(claOptions) {
// @ts-ignore
this.claOptions = this.constructor.resolveCLAOptions(claOptions)
}
/**
* @function CLArguments#parse
* @param {string|Array<string>} [input]
* @returns {CLParsedArguments}
*/
parse(input) {
// @ts-ignore
return Object.assign(this, this.constructor.parse(input, this.claOptions))
}
/**
* @function CLArguments#stringify
* @returns {string}
*/
stringify() {
// @ts-ignore
return this.constructor.stringify(this, this.claOptions)
}
}
/**
* @function getProcessArgs
* @param {CLArgumentsOptions} claOptions
* @returns {CLParsedArguments}
*/
function getProcessArgs(claOptions) {
return new CLArguments(claOptions).parse(process.argv.slice(2))
}
exports.CLArguments = CLArguments
exports.getProcessArgs = getProcessArgs