UNPKG

yini-cli

Version:

CLI for parsing and validating YINI config files: type-safe values, nested sections, comments, minimal syntax noise, and optional strict mode.

218 lines (210 loc) 7.53 kB
import assert from 'node:assert'; import { exit } from 'node:process'; import YINI from 'yini-parser'; const IS_DEBUG = false; // For local debugging purposes, etc. // ------------------------------------------------------------------------- export const validateFile = (file, commandOptions = {}) => { let parsedResult = undefined; let isCatchedError = true; const parseOptions = { strictMode: commandOptions.strict ?? false, // failLevel: 'errors', failLevel: commandOptions.force ? 'ignore-errors' : 'errors', // failLevel: 'ignore-errors', includeMetadata: true, includeDiagnostics: true, silent: true, }; try { parsedResult = YINI.parseFile(file, parseOptions); isCatchedError = false; } catch (err) { isCatchedError = true; } let metadata = null; let errors = 0; let warnings = 0; let notices = 0; let infos = 0; if (!isCatchedError && parsedResult?.meta) { metadata = parsedResult?.meta; assert(metadata); // Make sure there is metadata! // printObject(metadata, true) assert(metadata.diagnostics); const diag = metadata.diagnostics; errors = diag.errors.errorCount; warnings = diag.warnings.warningCount; notices = diag.notices.noticeCount; infos = diag.infos.infoCount; } IS_DEBUG && console.log(); IS_DEBUG && console.log('isCatchedError = ' + isCatchedError); IS_DEBUG && console.log('TEMP OUTPUT'); IS_DEBUG && console.log('isCatchedError = ' + isCatchedError); IS_DEBUG && console.log(' errors = ' + errors); IS_DEBUG && console.log('warnings = ' + warnings); IS_DEBUG && console.log(' notices = ' + notices); IS_DEBUG && console.log(' infor = ' + infos); IS_DEBUG && console.log('metadata = ' + metadata); IS_DEBUG && console.log('includeMetadata = ' + metadata?.diagnostics?.effectiveOptions.includeMetadata); IS_DEBUG && console.log('commandOptions.report = ' + commandOptions?.report); IS_DEBUG && console.log(); if (!commandOptions.silent && !isCatchedError) { if (commandOptions.report) { if (!metadata) { console.error('Internal Error: No meta data found'); } assert(metadata); // Make sure there is metadata! console.log(); console.log(formatToReport(file, metadata).trim()); } if (commandOptions.details) { if (!metadata) { console.error('Internal Error: No meta data found'); } assert(metadata); // Make sure there is metadata! console.log(); printDetailsOnAllIssue(file, metadata); } } //state returned: // - passed (no errors/warnings), // - finished (with warnings, no errors) / or - passed with warnings // - failed (errors), if (isCatchedError) { errors = 1; } // console.log() if (errors) { // red ✖ console.error(formatToStatus('Failed', errors, warnings, notices, infos)); exit(1); } else if (warnings) { // yellow ⚠️ console.warn(formatToStatus('Passed-with-Warnings', errors, warnings, notices, infos)); exit(0); } else { // green ✔ console.log(formatToStatus('Passed', errors, warnings, notices, infos)); exit(0); } }; const formatToStatus = (statusType, errors, warnings, notices, infos) => { const totalMsgs = errors + warnings + notices + infos; let str = ``; switch (statusType) { case 'Passed': str = '✔ Validation passed'; break; case 'Passed-with-Warnings': str = '⚠️ Validation finished'; break; case 'Failed': str = '✖ Validation failed'; break; } str += ` (${errors} errors, ${warnings} warnings, ${totalMsgs} total messages)`; return str; }; // --- Format to a Report -------------------------------------------------------- //@todo format parsed.meta to report as /* - Produce a summary-level validation report. - Output is structured and concise (e.g. JSON or table-like). - Focus on counts, pass/fail, severity summary. Example: Validation report for config.yini: Errors: 3 Warnings: 1 Notices: 0 Result: INVALID */ const formatToReport = (fileWithPath, metadata) => { // console.log('formatToReport(..)') // printObject(metadata) // console.log() assert(metadata.diagnostics); const diag = metadata.diagnostics; const issuesCount = diag.errors.errorCount + diag.warnings.warningCount + diag.notices.noticeCount + diag.infos.infoCount; const str = `Validation Report ================= File "${fileWithPath}" Issues: ${issuesCount} Summary ------- Mode: ${metadata.mode} Strict: ${metadata.mode === 'strict'} Errors: ${diag.errors.errorCount} Warnings: ${diag.warnings.warningCount} Notices: ${diag.notices.noticeCount} Infos: ${diag.infos.infoCount} Stats ----- Line Count: ${metadata.source.lineCount} Section Count: ${metadata.structure.sectionCount} Member Count: ${metadata.structure.memberCount} Nesting Depth: ${metadata.structure.maxDepth} Has @YINI: ${metadata.source.hasYiniMarker} Has /END: ${metadata.source.hasDocumentTerminator} Byte Size: ${metadata.source.sourceType === 'inline' ? 'n/a' : metadata.source.byteSize + ' bytes'} `; return str; }; // ------------------------------------------------------------------------- // --- Format to a Details -------------------------------------------------------- //@todo format parsed.meta to details as /* - Show full detailed validation messages. - Output includes line numbers, columns, error codes, and descriptive text. - Useful for debugging YINI files. Example: Error at line 5, column 9: Unexpected '/END' — expected <EOF> Warning at line 10, column 3: Section level skipped (0 → 2) Notice at line 1: Unused @yini directive */ const printDetailsOnAllIssue = (fileWithPath, metadata) => { // console.log('printDetails(..)') // printObject(metadata) // console.log(toPrettyJSON(metadata)) // console.log() assert(metadata.diagnostics); const diag = metadata.diagnostics; console.log('Details'); console.log('-------'); console.log(); const errors = diag.errors.payload; printIssues('Error ', 'E', errors); const warnings = diag.warnings.payload; printIssues('Warning', 'W', warnings); const notices = diag.notices.payload; printIssues('Notice ', 'N', notices); const infos = diag.infos.payload; printIssues('Info ', 'I', infos); return; }; // ------------------------------------------------------------------------- const printIssues = (typeLabel, prefix, issues) => { const leftPadding = ' '; issues.forEach((iss, i) => { const id = '#' + prefix + '-0' + (i + 1); // const id: string = '' + prefix + '-0' + (i+1) + ':' let str = `${typeLabel} [${id}]:\n`; str += leftPadding + `At line ${iss.line}, column ${iss.column}: ${iss.message}`; if (iss.advice) str += '\n' + leftPadding + iss.advice; if (iss.hint) str += '\n' + leftPadding + iss.hint; console.log(str); console.log(); }); };