import { PropertyOptions, ArgumentConfig, ArgumentOptions, CommandLineOption } from '../contracts'; import { isBoolean } from './options.helper'; export function createCommandLineConfig(config: ArgumentOptions): CommandLineOption[] { return Object.keys(config).map((key) => { const argConfig: any = config[key as keyof T]; const definition: PropertyOptions = typeof argConfig === 'object' ? argConfig : { type: argConfig }; return { name: key, ...definition }; }); } export function normaliseConfig(config: ArgumentConfig): ArgumentOptions { Object.keys(config).forEach((key) => { const argConfig: any = config[key as keyof T]; config[key as keyof T] = typeof argConfig === 'object' ? argConfig : { type: argConfig }; }); return config as ArgumentOptions; } export function mergeConfig( parsedConfig: Partial, parsedConfigWithoutDefaults: Partial, fileContent: Record, options: ArgumentOptions, jsonPath: keyof T | undefined, ): Partial { const configPath: string | undefined = jsonPath ? (parsedConfig[jsonPath] as any) : undefined; const configFromFile = resolveConfigFromFile(fileContent, configPath); if (configFromFile == null) { throw new Error(`Could not resolve config object from specified file and path`); } return { ...parsedConfig, ...applyTypeConversion(configFromFile, options), ...parsedConfigWithoutDefaults }; } function resolveConfigFromFile(configfromFile: any, configPath?: string): Partial> { if (configPath == null || configPath == '') { return configfromFile as Partial>; } const paths = configPath.split('.'); const key = paths.shift(); if (key == null) { return configfromFile; } const config = configfromFile[key]; return resolveConfigFromFile(config, paths.join('.')); } function applyTypeConversion( configfromFile: Partial>, options: ArgumentOptions, ): Partial { const transformedParams: Partial = {}; Object.keys(configfromFile).forEach((prop) => { const key = prop as keyof T; const argumentOptions = options[key]; if (argumentOptions == null) { return; } const fileValue = configfromFile[key]; if (argumentOptions.multiple || argumentOptions.lazyMultiple) { const fileArrayValue = Array.isArray(fileValue) ? fileValue : [fileValue]; transformedParams[key] = fileArrayValue.map((arrayValue) => convertType(arrayValue, argumentOptions), ) as any; } else { transformedParams[key] = convertType(fileValue, argumentOptions) as any; } }); return transformedParams; } function convertType(value: any, propOptions: PropertyOptions): any { if (propOptions.type.name === 'Boolean') { switch (value) { case 'true': return propOptions.type(true); case 'false': return propOptions.type(false); } } return propOptions.type(value); } type ArgsAndLastOption = { args: string[]; lastOption?: PropertyOptions }; type PartialAndLastOption = { partial: Partial; lastOption?: PropertyOptions; lastName?: Extract; }; const argNameRegExp = /^-{1,2}(\w+)(=(\w+))?$/; const booleanValue = ['1', '0', 'true', 'false']; /** * commandLineArgs throws an error if we pass aa value for a boolean arg as follows: * myCommand -a=true --booleanArg=false --otherArg true * this function removes these booleans so as to avoid errors from commandLineArgs * @param args * @param config */ export function removeBooleanValues(args: string[], config: ArgumentOptions): string[] { function removeBooleanArgs(argsAndLastValue: ArgsAndLastOption, arg: string): ArgsAndLastOption { const { argOptions, argValue } = getParamConfig(arg, config); const lastOption = argsAndLastValue.lastOption; if (lastOption != null && isBoolean(lastOption) && booleanValue.some((boolValue) => boolValue === arg)) { const args = argsAndLastValue.args.concat(); args.pop(); return { args }; } else if (argOptions != null && isBoolean(argOptions) && argValue != null) { return { args: argsAndLastValue.args }; } else { return { args: [...argsAndLastValue.args, arg], lastOption: argOptions }; } } return args.reduce(removeBooleanArgs, { args: [] }).args; } /** * Gets the values of any boolean arguments that were specified on the command line with a value * These arguments were removed by removeBooleanValues * @param args * @param config */ export function getBooleanValues(args: string[], config: ArgumentOptions): Partial { function getBooleanValues(argsAndLastOption: PartialAndLastOption, arg: string): PartialAndLastOption { const { argOptions, argName, argValue } = getParamConfig(arg, config); const lastOption = argsAndLastOption.lastOption; if (argOptions != null && isBoolean(argOptions) && argValue != null && argName != null) { argsAndLastOption.partial[argName] = convertType(argValue, argOptions) as any; } else if ( argsAndLastOption.lastName != null && lastOption != null && isBoolean(lastOption) && booleanValue.some((boolValue) => boolValue === arg) ) { argsAndLastOption.partial[argsAndLastOption.lastName] = convertType(arg, lastOption) as any; } return { partial: argsAndLastOption.partial, lastName: argName, lastOption: argOptions }; } return args.reduce>(getBooleanValues, { partial: {} }).partial; } function getParamConfig( arg: string, config: ArgumentOptions, ): { argName?: Extract; argOptions?: PropertyOptions; argValue?: string } { const regExpResult = argNameRegExp.exec(arg); if (regExpResult == null) { return {}; } const nameOrAlias = regExpResult[1]; for (const argName in config) { const argConfig = config[argName]; if (argName === nameOrAlias || argConfig.alias === nameOrAlias) { return { argOptions: argConfig as PropertyOptions, argName, argValue: regExpResult[3] }; } } return {}; }