-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Print Derived Configuration Report (bcoe#517)
feat: print derived config variables feat: print derived config variables in json test: 12 new unit tests to support features test: 1 skipped unit test for discovered bug in yargs with reports param
- Loading branch information
Showing
11 changed files
with
1,287 additions
and
546 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,289 @@ | ||
const parser = require('yargs-parser') | ||
|
||
/** | ||
* Function: printConfig | ||
* | ||
* @param {Object} yargs: instance of populated yargs object. | ||
* @param {Function} hideInstrumenteeArgs: Callback defined in lib/parse-args. | ||
* @returns {undefined} | ||
* | ||
* Entry point for print config logic from lib/parse-args.js file. | ||
* Kills process at the end of execution. | ||
* | ||
*/ | ||
function printConfig (yargs, hideInstrumenteeArgs) { | ||
const argv = process.argv.slice(2) | ||
const checkArgs = parser(argv) | ||
|
||
let shouldPrint = false | ||
|
||
if (Object.keys(checkArgs).includes('print-config')) { | ||
// checkArgs['print-config'] could contain a boolean or a string | ||
// representing a boolean. | ||
if (typeof checkArgs['print-config'] === 'boolean') { | ||
shouldPrint = checkArgs['print-config'] | ||
} else if (typeof checkArgs['print-config'] === 'string') { | ||
shouldPrint = JSON.parse(checkArgs['print-config']) | ||
} | ||
} | ||
|
||
if (shouldPrint) { | ||
const args = yargs.parse(hideInstrumenteeArgs()) | ||
const cmdExecuted = 'c8 ' + argv.join(' ') | ||
const cleanArgs = cleanUpArgumentObject(args) | ||
|
||
if (args.printConfigFormat === 'text') { | ||
printConfigText(cleanArgs, cmdExecuted) | ||
} else if (checkArgs.printConfigFormat === 'json') { | ||
const jsonYargs = JSON.stringify(cleanArgs, 2) | ||
console.log(jsonYargs) | ||
} | ||
|
||
process.exit() | ||
} | ||
} | ||
|
||
/** | ||
* Function: cleanUpArgumentObject | ||
* | ||
* @param {Object} args: key/value pairs of configuration options | ||
* generated by yargs.parse(). | ||
* @returns {Object} - Clone of args with duplicated data removed. | ||
* | ||
* This function exclude duplicate values that have different keys. | ||
* Additionally, scrubs convenience key values. | ||
* | ||
* For example: args['temp-directory'] and args['tempDirectory'] | ||
* are essentially the same variable. | ||
* | ||
*/ | ||
function cleanUpArgumentObject (args) { | ||
const argsToPrint = {} | ||
|
||
const keysToIterate = Object.keys(args).filter(v => { | ||
return (!['_', '$0'].includes(v) && v.length > 1) | ||
}) | ||
|
||
const camelCaseKeys = keysToIterate.filter(x => { | ||
return [...x.matchAll(/([A-Z])/g)].length > 0 | ||
}) | ||
|
||
keysToIterate.forEach(v => { | ||
if (camelCaseKeys.includes(v)) { | ||
// Derive Kebab Case string from Camel Case Key string | ||
const newKey = v.replace(/([A-Z])/g, '-$1').toLowerCase() | ||
// If the Kebab Case key is not assigned a value | ||
if (!args[newKey]) { | ||
// Then assigned it the Camel Case Variable | ||
argsToPrint[newKey] = args[v] | ||
} | ||
} else { | ||
// Just keep the value. Either Kebab case or otherwise | ||
argsToPrint[v] = args[v] | ||
} | ||
// Not sure if we will hit this scenario | ||
// should we throw an error? | ||
}) | ||
|
||
return argsToPrint | ||
} | ||
|
||
/** | ||
* Function: printConfigText | ||
* | ||
* @param {Object} argsv: sanitized configuration option object. | ||
* @param {String} cmdExecuted: the string representing the | ||
* command for c8 that passed to the cli. | ||
* @returns {undefined} | ||
* | ||
* Todo: | ||
* 1. Address gap with a unit test | ||
* | ||
*/ | ||
function printConfigText (argsv, cmdExecuted) { | ||
// Todo: gap in branch coverage | ||
const configFilePath = argsv instanceof Object && | ||
Object.keys(argsv).includes('config') | ||
? argsv.config | ||
: '' | ||
|
||
const banner = printConfigBanner(cmdExecuted, configFilePath) | ||
const table = printConfigTable(argsv) | ||
|
||
console.log(banner) | ||
console.log(table) | ||
} | ||
|
||
/** | ||
* Function: printConfigBanner | ||
* | ||
* @param {String} cmdExecuted: the string representing the | ||
* command for c8 that passed to the cli. | ||
* @param {String} configFilePath: the absolute path to | ||
* the configuration file that was loaded. | ||
* @returns {String} - the banner string to print. | ||
* | ||
* Todo: Should I center this using the process.stdout.columns | ||
* variable? | ||
*/ | ||
function printConfigBanner (cmdExecuted, configFilePath) { | ||
return String.raw` | ||
/* ________/\\\\\\\\\_ _____/\\\\\\\\\____ */ | ||
/* _____/\\\////////__ ___/\\\///////\\\__ */ | ||
/* ___/\\\/___________ __\/\\\_____\/\\\__ */ | ||
/* __/\\\_____________ __\///\\\\\\\\\/___ */ | ||
/* _\/\\\_____________ ___/\\\///////\\\__ */ | ||
/* _\//\\\____________ __/\\\______\//\\\_ */ | ||
/* __\///\\\__________ _\//\\\______/\\\__ */ | ||
/* ____\////\\\\\\\\\_ __\///\\\\\\\\\/___ */ | ||
/* _______\/////////__ ____\/////////_____ */ | ||
Command Issued: ${cmdExecuted} | ||
Config File Loaded: ${configFilePath} | ||
Derived Configuration from CLI options and configuration file | ||
------------------------------------------------------------------------------ | ||
`.replace(/\n{1} +/g, '\n') | ||
} | ||
|
||
/** | ||
* Function: printConfigTable | ||
* | ||
* @param {Object} args: An object of config params processed by | ||
* cleanUpArgumentObject function. | ||
* @returns {String} - A string representing the current configuration. | ||
* | ||
*/ | ||
function printConfigTable (args) { | ||
let output = '' | ||
const headerPadding = 10 | ||
const headerColWidth = tableCalcHeaderWidth(args) + headerPadding | ||
|
||
Object.keys(args).forEach(v => { | ||
const headerText = v | ||
const value = args[v] | ||
output += printConfigTableRow(headerText, value, headerColWidth) | ||
}) | ||
|
||
return output | ||
} | ||
|
||
/** | ||
* Function: tableCalcHeaderWidth | ||
* | ||
* @param {Object} args: An object of config params processed by | ||
* cleanUpArgumentObject function. | ||
* @returns {number} - An integer representing the max length of | ||
* all keys assigned to the args object. | ||
* | ||
*/ | ||
function tableCalcHeaderWidth (args) { | ||
return Object.keys(args) | ||
.map(v => String(v).length) | ||
.reduce((p, c) => { | ||
return (p >= c) ? p : c | ||
}) | ||
} | ||
|
||
/** | ||
* printConfigTableRow | ||
* | ||
* @param {String} header: a key in the arguments object. | ||
* @param {any} value: a value in the arguments object | ||
* @param {any} headerColWidth: max length of keys in | ||
* arguments object plus padding. | ||
* @returns {any} - A rendered row of config table | ||
* | ||
*/ | ||
function printConfigTableRow (header, value, headerColWidth) { | ||
const { valueMargin, headerMargin } = | ||
tableCalcRowMargin(headerColWidth, header) | ||
|
||
const val = formatPrintVariable(value, valueMargin) | ||
const output = String(header) + ':' + headerMargin + val + '\n' | ||
|
||
return output | ||
} | ||
|
||
/** | ||
* tableCalcRowMargin | ||
* | ||
* @param {number} headerWidth: The width of the header column. | ||
* @param {String} headerText: The value of the header column. | ||
* @returns {OBject} - An object containing whitespace string | ||
* padding for the value and header columns | ||
* | ||
*/ | ||
function tableCalcRowMargin (headerWidth, headerText) { | ||
const whiteSpaceString = (numOfSpace) => { | ||
let space = '' | ||
for (let i = 0; i < numOfSpace; i++) space += ' ' | ||
return space | ||
} | ||
|
||
const rowHeaderLength = headerWidth - headerText.length | ||
const rowHeaderMargin = whiteSpaceString(rowHeaderLength) | ||
|
||
const rowValueMargin = whiteSpaceString(headerWidth) | ||
|
||
return { | ||
valueMargin: rowValueMargin, | ||
headerMargin: rowHeaderMargin | ||
} | ||
} | ||
|
||
/** | ||
* Function: formatPrintVariable | ||
* | ||
* @param {any} variable: the variable to format. | ||
* @param {String} space: a string containing a variable | ||
* amount of blank spaces. | ||
* @returns {String} - string representation of the variable. | ||
* | ||
* | ||
*/ | ||
function formatPrintVariable (variable, space) { | ||
let value | ||
|
||
if (Array.isArray(variable) && variable.length > 0) { | ||
value = stringifyObject(variable, space, ']') | ||
} else if (typeof variable === 'object' && Object.keys(variable).length > 0) { | ||
value = stringifyObject(variable, space, '}') | ||
} else if (typeof variable === 'string' && variable) { | ||
value = "'" + variable + "'" | ||
} else if (typeof variable === 'string' && !variable) { | ||
value = "''" | ||
} else { | ||
value = variable | ||
} | ||
|
||
return value | ||
} | ||
|
||
/** | ||
* Function: stringifyObject | ||
* | ||
* @param {any} variable: the variable to format. | ||
* @param {String} space: string containing a variable | ||
* amount of blank spaces. | ||
* @param {String} closingChar: single string character | ||
* either a ']' or a '}'. | ||
* @returns {String} - string representation of the variable. | ||
* | ||
*/ | ||
function stringifyObject (variable, space, closingChar) { | ||
const closeReg = new RegExp('\n' + closingChar, 'g') | ||
const out = JSON.stringify(variable, null, '\t\t\t\t ') | ||
.replace(closeReg, '\n' + space + ' ' + closingChar) | ||
|
||
return out | ||
} | ||
|
||
module.exports = { | ||
printConfig, | ||
formatPrintVariable, | ||
cleanUpArgumentObject | ||
} |
Oops, something went wrong.