import * as CF from '@efforting.tech/data/field-configuration-factories'; import { readFileSync } from 'node:fs'; import { inspect } from 'node:util'; import { Indention_Mode, Text_Settings } from '@efforting.tech/text/basic-tree'; // TODO: Move into string helper module function string_has_contents(str) { return /\S/.test(str); } function *indented_line_iterator(settings, text) { let line_no = settings.first_line; let index = 0; const { indention_tabulator_width } = settings; switch (settings.indention_mode) { case Indention_Mode.symbols.TABULATORS: { for (const line of text.matchAll(/^(\t*)(.*)$/gm)) { const [raw, tabs, remaining] = line; yield { raw, indent: tabs.length, line: remaining, line_no: line_no++, index: index++}; } break; } case Indention_Mode.symbols.SPACES: { for (const line of text.matchAll(/^([ ]*)(.*)$/gm)) { const [raw, spaces, remaining] = line; if ((spaces.length % indention_tabulator_width) !== 0) { throw new Error('Unaligned indention'); //TODO - proper error } yield { raw, indent: Math.floor(spaces.length / indention_tabulator_width), line: remaining, line_no: line_no++, index: index++}; } break; } default: throw new Error('Unsupported indention mode'); //TODO - proper error } } const ts = Text_Settings.load({ indention_mode: 'TABULATORS', }); const example_string = `branch1 leaf1 leaf2 branch2 sub-branch1 leaf3 leaf4 sub-branch2 leaf5 branch3 dual-indented `; class Text_Node { constructor(text_settings, line=undefined, indent=0, line_no=undefined, index=undefined, raw=undefined, parent=undefined) { Object.assign(this, { text_settings, line, indent, line_no, index, raw, parent, children: [] }); } } const root = new Text_Node(ts); // NOTE: This first Text_Node is not added to the tree, it serves as an initial cursor only. let current = new Text_Node(root.text_settings, undefined, 0, undefined, undefined, undefined, root); for (const line_info of indented_line_iterator(ts, example_string)) { // TODO: Implement other variants than inherit-from-previous const indent = string_has_contents(line_info.line) ? line_info.indent : current.indent; const delta_indent = indent - current.indent; if (delta_indent == 0) { const pending = new Text_Node(current.text_settings, undefined, current.indent, undefined, undefined, undefined, current.parent); // Partial insertion - same level if (current.parent) { current.parent.children.push(pending); } current = pending; } else if (delta_indent > 0) { for (let i=0; idelta_indent; i--) { current = current.parent; } const pending = new Text_Node(current.text_settings, undefined, current.indent, undefined, undefined, undefined, current.parent); // Partial insertion - same level if (current.parent) { current.parent.children.push(pending); } current = pending; } // Fill in partial insertion Object.assign(current, { line: line_info.line, line_no: line_info.line_no, index: line_info.index, raw: line_info.raw, }); } function debug_dump(node, level=0) { console.log(`${" ".repeat(level)}[${node.line_no ?? '-'}] ${inspect(node.line)}`); for (const child of node.children) { debug_dump(child, level+1); } } debug_dump(root); /* [-] undefined [1] 'branch1' [2] 'leaf1' [3] 'leaf2' [4] '' [5] 'branch2' [6] 'sub-branch1' [7] 'leaf3' [8] 'leaf4' [9] '' [10] '' [11] 'sub-branch2' [12] 'leaf5' [13] '' [14] 'branch3' [-] undefined [15] 'dual-indented' [16] '' [17] '' */