yini-parser
Version:
Node.js parser for YINI — a clean, structured INI alternative with types, simple section nesting, comments, and strict mode.
186 lines (185 loc) • 9.48 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.constructFinalObject = void 0;
const env_1 = require("../config/env");
const print_1 = require("../utils/print");
/**
* Construct the final result of a JavaScript Object.
*/
const constructFinalObject = (syntaxTreeC, errorHandler) => {
(0, print_1.debugPrint)('-> constructFinalObject(..)');
const bulder = new Builder(syntaxTreeC, errorHandler);
if ((0, env_1.isDebug)()) {
console.log('Argument, syntaxTreeC:');
(0, print_1.printObject)(syntaxTreeC);
}
const jsObject = bulder.doCheckAndBuild();
(0, print_1.debugPrint)('<- About to leave constructFinalObject(..)');
if ((0, env_1.isDebug)()) {
console.log('Returning, jsObject:');
(0, print_1.printObject)(syntaxTreeC);
}
return jsObject;
};
exports.constructFinalObject = constructFinalObject;
class Builder {
constructor(syntaxTreeC, errorHandler) {
(0, print_1.debugPrint)('-> Builder: constructor(..)');
this.syntaxTreeC = syntaxTreeC;
this.errorHandler = errorHandler;
}
doCheckAndBuild() {
(0, print_1.debugPrint)('-> Builder: doCheckAndBuild(..)');
if (!this.syntaxTreeC) {
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
this.errorHandler.pushOrBail(null, 'Fatal-Error', 'SyntaxTreeC is undefined', 'This is most likely caused by an internal error somewhere. The process cannot recover fully from this, sorry.');
}
const fullSubTreeList = this.buildFullSubTrees(this.syntaxTreeC);
const jsObject = this.buildObjectFromList(fullSubTreeList);
return jsObject;
}
buildFullSubTrees(syntaxTreeC) {
(0, print_1.debugPrint)('-> Builder: buildFullSubTrees(..)');
(0, env_1.isDebug)() && console.log();
const fullSubTreeList = []; // List of FULL sub-trees.
// Current Working Full Sub-Tree (starting at level 1).
let workingFullSubTree = syntaxTreeC._syntaxTree[0]; // (!) Any tree MUST START at level 1.
(0, print_1.debugPrint)(`Setted new workingFullSubTree, from syntaxTreeC._syntaxTree[0]`);
const len = syntaxTreeC._syntaxTree.length;
for (let i = 1; i < len; i++) {
const currentChainC = syntaxTreeC._syntaxTree[i];
const level = currentChainC.originLevel;
const nestingIndex = level - 1; // For debugging purposes.
const chain = currentChainC.chain; // For debugging purposes.
(0, print_1.debugPrint)(`Got new chain from syntaxTreeC._syntaxTree[${i}] to be mounted onto parent...`);
(0, print_1.debugPrint)('* level: ' + level + ' (i=' + i + '), chain: ' + chain);
if (level === 1) {
(0, print_1.debugPrint)('HIT - Detected that currentChain starts with level 1');
fullSubTreeList.push(workingFullSubTree);
(0, print_1.debugPrint)('The workingFullSubTree is finished, pushed it to the list.');
workingFullSubTree = syntaxTreeC._syntaxTree[i]; // (!) The tree MUST START at level 1.
(0, print_1.debugPrint)(`Setted new workingFullSubTree, from syntaxTreeC._syntaxTree[${i}]`);
}
else {
(0, print_1.debugPrint)('About to mount currentChain onto workingFullSubTree at correct level...');
workingFullSubTree = this.mountChainOntoLevel(currentChainC, workingFullSubTree);
}
(0, print_1.debugPrint)();
}
fullSubTreeList.push(workingFullSubTree);
if ((0, env_1.isDebug)()) {
console.log();
console.log('--- fullSubTreeList: (list of FULL sub-trees.) -------');
(0, print_1.printObject)(fullSubTreeList);
console.log();
}
return fullSubTreeList;
}
mountChainOntoLevel(chainC, workingSubTree) {
(0, print_1.debugPrint)('-> Builder: mountChainOntoLevel(..)');
if ((0, env_1.isDebug)()) {
(0, print_1.printObject)(chainC);
}
if (chainC.originLevel > 1) {
// NOP
}
else {
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
this.errorHandler.pushOrBail(null, 'Fatal-Error', 'Internal-Error: Detected incorrect chain in mountChainOntoLevel(..), start section has level: ' +
chainC.originLevel, 'The (chain) must start with a section level higher than 1', '' + (0, print_1.printObject)(chainC));
}
if (workingSubTree.originLevel != 1) {
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
this.errorHandler.pushOrBail(null, 'Fatal-Error', 'Internal-Error: Detected incorrect full sub-tree in mountChainOntoLevel(..), start section has level: ' +
chainC.originLevel, 'A full sub-tree (chain) must start with a section at level 1', '' + (0, print_1.printObject)(chainC));
}
const chain = chainC.chain;
const targetLevel = chainC.originLevel;
if ((0, env_1.isDebug)()) {
(0, print_1.debugPrint)('Target level = ' + targetLevel);
(0, print_1.debugPrint)(`The chain to mount: (onto level: ${targetLevel})`);
(0, print_1.printObject)(chain);
(0, print_1.debugPrint)('--- workingFullSubTree: -------');
(0, print_1.debugPrint)('Before mounting onto workingSubTree.chain:');
(0, print_1.printObject)(workingSubTree.chain);
}
(0, print_1.debugPrint)('Mount currentChain onto workingFullSubTree.');
workingSubTree.chain = mountObjectAtLevel(workingSubTree.chain, chain, targetLevel, this.errorHandler);
if ((0, env_1.isDebug)()) {
(0, print_1.debugPrint)('After mounting onto workingSubTree.chain:');
(0, print_1.printObject)(workingSubTree.chain);
(0, print_1.debugPrint)('----------');
}
(0, print_1.debugPrint)('<- Builder: mountChainOntoLevel(..)');
return workingSubTree;
}
// Contruct the final JS object from the list of full sub-trees.
buildObjectFromList(fullSubTreeList) {
(0, print_1.debugPrint)('-> Builder: buildObjectFromList(..)');
const jsObject = {};
for (const chainC of fullSubTreeList) {
if (chainC.originLevel === 1) {
if ((0, env_1.isDebug)()) {
console.log('About to assign chainC.chain:');
console.log(chainC.chain);
}
Object.assign(jsObject, chainC.chain);
if ((0, env_1.isDebug)()) {
console.log('After, jsObject:');
console.log(jsObject);
}
}
else {
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
this.errorHandler.pushOrBail(null, 'Fatal-Error', 'Internal-Error: Detected incorrect full sub-tree in buildObjectFromList(..), start section has level: ' +
chainC.originLevel, 'A full sub-tree (chain) must start with a section at level 1', '' + (0, print_1.printObject)(chainC));
}
}
return jsObject;
}
}
/**
* Mounts objectDest onto the first object at the given depth (level is
* 1-based) in objectSrc.
* @return Returns a new object without mutating input objects.
*/
const mountObjectAtLevel = (objectSrc, objectDest, level, errorHandler) => {
// Deep copy to avoid mutating the input.
const result = JSON.parse(JSON.stringify(objectSrc));
let current = result;
let currentLevel = 1;
// Traverse down the first object path until the desired level.
while (currentLevel < level) {
// Get all keys in current object.
const keys = Object.keys(current);
// Find first key where value is a plain object (not array).
const nextKey = keys.find((key) => current[key] &&
typeof current[key] === 'object' &&
!Array.isArray(current[key]));
if (!nextKey) {
// Could not reach the specified depth.
return result;
}
current = current[nextKey];
currentLevel++;
}
(0, print_1.debugPrint)('--------');
(0, print_1.debugPrint)(' current = ' + (0, print_1.toPrettyJSON)(current));
const [firstKey] = Object.keys(objectDest);
(0, print_1.debugPrint)('objectDest = ' + firstKey);
if (Object.prototype.hasOwnProperty.call(current, firstKey)) {
//@todo Add metadata with line number, onto chainC, so can use line number in error reporting
(0, print_1.debugPrint)(`(!) sectionName already exist, name: "${firstKey}", in: `);
(0, print_1.debugPrint)((0, print_1.toPrettyJSON)(current));
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
errorHandler.pushOrBail(null, 'Syntax-Error', 'Section name already exists', 'Cannot redefine section name: "' +
firstKey +
'" at level ' +
currentLevel +
'.');
return current;
}
// Mount objectDest onto the object at the required level.
Object.assign(current, objectDest);
return result;
};