Skip to content

Commit

Permalink
Print Derived Configuration Report
Browse files Browse the repository at this point in the history
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
mcknasty committed Jan 20, 2024
1 parent bf3073b commit 60c7925
Show file tree
Hide file tree
Showing 15 changed files with 1,019 additions and 546 deletions.
9 changes: 9 additions & 0 deletions c8-ascii-art.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* ________/\\\\\\\\\_ _____/\\\\\\\\\____ */
/* _____/\\\////////__ ___/\\\///////\\\__ */
/* ___/\\\/___________ __\/\\\_____\/\\\__ */
/* __/\\\_____________ __\///\\\\\\\\\/___ */
/* _\/\\\_____________ ___/\\\///////\\\__ */
/* _\//\\\____________ __/\\\______\//\\\_ */
/* __\///\\\__________ _\//\\\______/\\\__ */
/* ____\////\\\\\\\\\_ __\///\\\\\\\\\/___ */
/* _______\/////////__ ____\/////////_____ */
140 changes: 139 additions & 1 deletion lib/parse-args.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,17 @@ function buildYargs (withCommands = false) {
describe: 'supplying --merge-async will merge all v8 coverage reports asynchronously and incrementally. ' +
'This is to avoid OOM issues with Node.js runtime.'
})
.options('print-config', {
default: false,
type: 'boolean',
describe: 'Print the derived configuration between command line parameters and loaded configuration file'
})
.options('print-config-format', {
default: 'text',
type: 'string',
describe: 'Format to print the configuration in. Accepted formats are either text or json'
})
.pkgConf('c8')
.demandCommand(1)
.check((argv) => {
if (!argv.tempDirectory) {
argv.tempDirectory = resolve(argv.reportsDir, 'tmp')
Expand All @@ -181,6 +190,38 @@ function buildYargs (withCommands = false) {
// }
// })

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 commandExecutedReference = 'c8 ' + argv.join(' ')
const args = yargs.parse(hideInstrumenteeArgs())
const cleanArgs = cleanUpArgumentArray(args)

if (args.printConfigFormat === 'text') {
printConfigText(cleanArgs, commandExecutedReference)
} else if (checkArgs.printConfigFormat === 'json') {
const jsonYargs = JSON.stringify(cleanArgs, 2)
console.log(jsonYargs)
}

process.exit()
}

yargs.demandCommand(1)

const checkCoverage = require('./commands/check-coverage')
const report = require('./commands/report')
if (withCommands) {
Expand Down Expand Up @@ -217,6 +258,103 @@ function hideInstrumenteeArgs () {
return argv
}

// Todo: Take a look at Optimizing this function
// exclude certain temporary values and duplicates from printing
// the same variable will be included twice with different keys
// for example args['temp-directory'] && args['tempDirectory']
// are essentially the same variable
function cleanUpArgumentArray (args) {
const argsToPrint = {}
Object.keys(args).forEach(v => {
if (v && v.length > 1 && v !== '_' && v !== '$0') {
// See if we are dealing with a Camel Case key string
const matches = [...v.matchAll(/([A-Z])/g)]
if (matches.length > 0) {
matches.forEach(m => {
// Derive Kebab Case string from Camel Case Key string
const newKey = m.input.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 if (!args[v]) {
// Other wise assign keep the Kebab Case key value
argsToPrint[newKey] = args[newKey]
}
})
} else {
// Just keep the value. Either Kebab case or otherwise
argsToPrint[v] = args[v]
}
}
})

return argsToPrint
}

function printConfigText (argsv, commandExecutedReference) {
const banner = readFileSync('./c8-ascii-art.txt', 'utf-8')
console.log('\n\n')
console.log(banner)
console.log('\n\n')
console.log('Command Issued: ' + commandExecutedReference)
console.log('Config File Loaded: ' + argsv.config + '\n\n\n')
console.log('Derived Configuration from CLI options and configuration file')
console.log('------------------------------------------------------------------------------')

const addSpace = (numOfSpace) => {
let space = ''
for (let i = 0; i < numOfSpace; i++) space += ' '
const s = space
return s
}

// Including some formatting variables
// for spacing to make the output more readable
let output = ''
let spaceLength = Object.keys(argsv)
.map(v => String(v).length)
.reduce((p, c) => {
return (p >= c) ? p : c
})
spaceLength += 10

// For each configuration value, print pretty
Object.keys(argsv).forEach(v => {
const fillSpace = addSpace(spaceLength - v.length)
const enumSpace = addSpace(spaceLength)
const value = formatPrintVariable(argsv[v], enumSpace)
output += String(v) + ':' + fillSpace + value + '\n'
})
console.log(output)
}

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 (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 = {
buildYargs,
hideInstrumenterArgs,
Expand Down

0 comments on commit 60c7925

Please sign in to comment.