UNPKG

enolib

Version:

The eno standard library

280 lines (223 loc) 7.4 kB
const { DOCUMENT, FIELD, FIELDSET, FIELDSET_ENTRY, LIST, LIST_ITEM, MULTILINE_FIELD_BEGIN, SECTION } = require('../constants.js'); // TODO: Better simple lastIn() / lastMissingIn() utility function usage to get m...n range for tagging? const DISPLAY = Symbol('Display Line'); const EMPHASIZE = Symbol('Emphasize Line'); const INDICATE = Symbol('Indicate Line'); const OMISSION = Symbol('Insert Omission'); const QUESTION = Symbol('Question Line'); class Reporter { constructor(context) { this._context = context; this._index = new Array(this._context._lineCount); this._snippet = new Array(this._context._lineCount); this._buildIndex() } _buildIndex() { const indexComments = element => { if(element.hasOwnProperty('comments')) { for(const comment of element.comments) { this._index[comment.line] = comment; } } }; const traverse = section => { for(const element of section.elements) { indexComments(element); this._index[element.line] = element; if(element.type === SECTION) { traverse(element); } else if(element.type === FIELD) { if(element.hasOwnProperty('continuations')) { for(const continuation of element.continuations) { this._index[continuation.line] = continuation; } } } else if(element.type === MULTILINE_FIELD_BEGIN) { // Missing when reporting an unterminated multiline field if(element.hasOwnProperty('end')) { this._index[element.end.line] = element.end; } for(const line of element.lines) { this._index[line.line] = line; } } else if(element.type === LIST) { if(element.hasOwnProperty('items')) { for(const item of element.items) { indexComments(item); this._index[item.line] = item; for(const continuation of item.continuations) { this._index[continuation.line] = continuation; } } } } else if(element.type === FIELDSET) { if(element.hasOwnProperty('entries')) { for(const entry of element.entries) { indexComments(entry); this._index[entry.line] = entry; for(const continuation of entry.continuations) { this._index[continuation.line] = continuation; } } } } } } traverse(this._context._document); for(const meta of this._context._meta) { this._index[meta.line] = meta; } } _tagContinuations(element, tag) { let scanLine = element.line + 1; if(element.continuations.length === 0) return scanLine; for(const continuation of element.continuations) { while(scanLine < continuation.line) { this._snippet[scanLine] = tag; scanLine++; } this._snippet[continuation.line] = tag; scanLine++; } return scanLine; } _tagContinuables(element, collection, tag) { let scanLine = element.line + 1; if(element[collection].length === 0) return scanLine; for(const continuable of element[collection]) { while(scanLine < continuable.line) { this._snippet[scanLine] = tag; scanLine++; } this._snippet[continuable.line] = tag; scanLine = this._tagContinuations(continuable, tag); } return scanLine; } _tagChildren(element, tag) { if(element.type === FIELD || element.type === LIST_ITEM || element.type === FIELDSET_ENTRY) { return this._tagContinuations(element, tag); } else if(element.type === LIST) { return this._tagContinuables(element, 'items', tag); } else if(element.type === FIELDSET) { return this._tagContinuables(element, 'entries', tag); } else if(element.type === MULTILINE_FIELD_BEGIN) { for(const line of element.lines) { this._snippet[line.line] = tag; } if(element.hasOwnProperty('end')) { this._snippet[element.end.line] = tag; return element.end.line + 1; } else if(element.lines.length > 0) { return element.lines[element.lines.length - 1].line + 1; } else { return element.line + 1; } } else if(element.type === SECTION) { return this._tagSection(element, tag); } } _tagSection(section, tag, recursive = true) { let scanLine = section.line + 1; for(const element of section.elements) { while(scanLine < element.line) { this._snippet[scanLine] = tag; scanLine++; } if(!recursive && element.type === SECTION) break; this._snippet[element.line] = tag; scanLine = this._tagChildren(element, tag); } return scanLine; } indicateLine(element) { this._snippet[element.line] = INDICATE; return this; } questionLine(element) { this._snippet[element.line] = QUESTION; return this; } reportComments(element) { this._snippet[element.line] = INDICATE; for(const comment of element.comments) { this._snippet[comment.line] = EMPHASIZE; } return this; } reportElement(element) { this._snippet[element.line] = EMPHASIZE; this._tagChildren(element, INDICATE); return this; } reportElements(elements) { for(const element of elements) { this._snippet[element.line] = EMPHASIZE; this._tagChildren(element, INDICATE); } return this; } reportLine(instruction) { this._snippet[instruction.line] = EMPHASIZE; return this; } reportMultilineValue(element) { for(const line of element.lines) { this._snippet[line.line] = EMPHASIZE; } return this; } reportMissingElement(parent) { if(parent.type !== DOCUMENT) { this._snippet[parent.line] = INDICATE; } if(parent.type === SECTION) { this._tagSection(parent, QUESTION, false); } else { this._tagChildren(parent, QUESTION); } return this; } snippet() { if(this._snippet.every(line => line === undefined)) { for(let line = 0; line < this._snippet.length; line++) { this._snippet[line] = QUESTION; } } else { // TODO: Possibly better algorithm for this for(const [line, tag] of this._snippet.entries()) { if(tag !== undefined) continue; // TODO: Prevent out of bounds access if(this._snippet[line + 2] !== undefined && this._snippet[line + 2] !== DISPLAY || this._snippet[line - 2] !== undefined && this._snippet[line - 2] !== DISPLAY || this._snippet[line + 1] !== undefined && this._snippet[line + 1] !== DISPLAY || this._snippet[line - 1] !== undefined && this._snippet[line - 1] !== DISPLAY) { this._snippet[line] = DISPLAY; } else if(this._snippet[line + 3] !== undefined && this._snippet[line + 3] !== DISPLAY) { this._snippet[line] = OMISSION; } } if(this._snippet[this._snippet.length - 1] === undefined) { this._snippet[this._snippet.length - 1] = OMISSION; } } return this._print(); } } exports.DISPLAY = DISPLAY; exports.EMPHASIZE = EMPHASIZE; exports.INDICATE = INDICATE; exports.OMISSION = OMISSION; exports.QUESTION = QUESTION; exports.Reporter = Reporter;