You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
4.4 KiB
JavaScript

'use strict';
const chalk = require("chalk");
const repeatString = require("repeat-string");
const createEventEmitter = require("create-event-emitter");
const findNestedProperties = require("./find-nested-properties");
module.exports = function createWalker(tree, logging = false) {
let properties = findNestedProperties(tree);
return createEventEmitter({
tree: tree,
done: false,
currentLevel: 0,
currentProperty: properties[0],
currentPropertyIndex: 0,
currentStack: [ tree ],
currentIndex: 0,
currentNode: tree,
properties: [ null ],
indexes: [ null ],
propertyIndexes: [ null ],
stacks: [ [tree] ],
nodes: [ tree ], // FIXME: This might be redundant?
_padding: function _padding() {
return repeatString(" ", this.currentLevel);
},
_debugStatus: function () {
return `(level: ${this.currentLevel}, property: ${this.currentProperty.property}, index: ${this.currentIndex})`;
},
getPath: function getPath() {
let path = new Array(this.currentLevel * 2);
for (let i = 1; i <= this.currentLevel; i++) {
path[(i-1) * 2] = this.properties[i][this.propertyIndexes[i]].property;
path[(i-1) * 2 + 1] = this.indexes[i];
}
return path;
},
step: function step() {
if (this.done !== true) {
let node = this.currentStack[this.currentIndex];
if (logging) {
console.log(this._padding(), chalk.bold.red(`Node encountered: ${node.type}`), this._debugStatus());
}
this.emit("enterNode", node);
this.levelDown(node);
} else {
throw new Error("Walker reached end");
}
},
nextIndex: function nextIndex() {
this.emit("exitNode", this.currentStack[this.currentIndex]);
if (this.currentIndex < this.currentStack.length - 1) {
this.updateIndex(this.currentIndex + 1);
if (logging) {
console.log(this._padding(), chalk.bold.blue(`New index: ${this.currentIndex}`), this._debugStatus());
}
} else {
this.nextProperty();
}
},
nextProperty: function nextProperty() {
if (this.currentPropertyIndex < this.properties[this.currentLevel].length - 1) {
this.updateIndex(0);
this.updatePropertyIndex(this.currentPropertyIndex + 1);
if (logging) {
console.log(this._padding(), chalk.bold.yellow(`New property: ${this.currentProperty.property}`), this._debugStatus());
console.log(this._padding(), chalk.bold.blue(`New index: ${this.currentIndex}`), this._debugStatus());
}
} else {
this.levelUp();
}
},
levelUp: function levelUp() {
if (this.currentLevel > 1) {
this.currentLevel -= 1;
this.currentNode = this.nodes[this.currentLevel];
this.updateIndex(this.indexes[this.currentLevel]);
this.updatePropertyIndex(this.propertyIndexes[this.currentLevel]);
if (logging) {
console.log(this._padding(), chalk.bold.green(`New level: ${this.currentLevel}`), this._debugStatus());
}
this.nextIndex();
} else {
this.emit("exitNode", tree);
this.done = true;
}
},
levelDown: function levelDown(node) {
let nestedProperties = findNestedProperties(node);
if (nestedProperties.length > 0) {
this.currentLevel += 1;
this.nodes[this.currentLevel] = node;
this.currentNode = node;
this.properties[this.currentLevel] = nestedProperties;
this.updateIndex(0);
this.updatePropertyIndex(0);
if (logging) {
console.log(this._padding(), chalk.bold.green(`New level: ${this.currentLevel}`), this._debugStatus());
console.log(this._padding(), chalk.bold.yellow(`New property: ${this.currentProperty.property}`), this._debugStatus());
console.log(this._padding(), chalk.bold.blue(`New index: ${this.currentIndex}`), this._debugStatus());
}
} else {
this.nextIndex();
}
},
updateIndex: function updateIndex(index) {
this.currentIndex = index;
this.indexes[this.currentLevel] = index;
},
updatePropertyIndex: function updatePropertyIndex(index) {
this.currentPropertyIndex = index;
this.propertyIndexes[this.currentLevel] = index;
this.currentProperty = this.properties[this.currentLevel][index];
let newStack;
if (this.currentProperty.type === "array") {
newStack = this.currentNode[this.currentProperty.property];
} else if (this.currentProperty.type === "node") {
newStack = [ this.currentNode[this.currentProperty.property] ];
} else {
throw new Error("Unrecognized property type");
}
this.stacks[this.currentLevel] = newStack;
this.currentStack = newStack;
}
});
};