'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; } }); };