this repo has no description

still refining macrodata

tbeseda.com 924c8a67

+758
+5
.gitignore
··· 1 + node_modules/ 2 + dist/ 3 + .DS_Store 4 + *.log 5 + scratch/
+115
README.md
··· 1 + # 🪾 `array-treeify` 2 + 3 + **Simple ASCII trees from arrays. For your terminal and console displays.** 4 + 5 + [![typescript](https://img.shields.io/badge/TypeScript-007ACC?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) 6 + [![npm](https://img.shields.io/npm/v/array-treeify.svg)](https://www.npmjs.com/package/array-treeify) 7 + [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/tbeseda/array-treeify/blob/main/LICENSE) 8 + 9 + ## Overview 10 + 11 + `array-treeify` transforms nested arrays into beautiful ASCII trees with proper branching characters. Perfect for CLIs, debug outputs, or anywhere you need to visualize hierarchical data. 12 + 13 + ``` 14 + Lumon Industries 15 + ├─ Board of Directors 16 + │ └─ Natalie (Representative) 17 + ├─ Departments 18 + │ └─ Macrodata Refinement (Cobel) 19 + │ ├─ Milchick 20 + │ └─ Mark S. 21 + │ ├─ Dylan G. 22 + │ ├─ Irving B. 23 + │ └─ Helly R. 24 + └─ Other Departments 25 + ├─ Optics & Design 26 + ├─ Wellness Center 27 + ├─ Mammalians Nurturable 28 + └─ Choreography and Merriment 29 + ``` 30 + 31 + ## Installation 32 + 33 + ```bash 34 + npm install array-treeify 35 + ``` 36 + 37 + ## Usage 38 + 39 + ```typescript 40 + function treeify(input: TreeInput): string 41 + ``` 42 + 43 + `array-treeify` accepts a simple, intuitive array structure that's easy to build and manipulate: 44 + 45 + ```typescript 46 + import {treeify} from 'array-treeify' 47 + 48 + // Basic example 49 + const eagan = [ 50 + 'Kier Eagan', 51 + [ 52 + '...', 53 + [ 54 + '...', 55 + 'Jame Eagan', 56 + ['Helena Eagan'] 57 + ], 58 + ], 59 + 'Ambrose Eagan' 60 + ] 61 + console.log(treeify(eagan)) 62 + /* 63 + Kier Eagan 64 + ├─ ... 65 + | ├─ ... 66 + | └─ Jame Eagan 67 + | └─ Helena Eagan 68 + └─ Ambrose Eagan 69 + */ 70 + 71 + // Nested example 72 + const orgChart = [ 73 + 'Lumon Industries', 74 + [ 75 + 'Board of Directors', 76 + ['Natalie (Representative)'], 77 + 'Department Heads', 78 + [ 79 + 'Cobel (MDR)', 80 + ['Milchick', 'Mark S.', ['Dylan G.', 'Irving B.', 'Helly R.']] 81 + ] 82 + ] 83 + ] 84 + console.log(treeify(orgChart)) 85 + /* 86 + Lumon Industries 87 + ├─ Board of Directors 88 + │ └─ Natalie (Representative) 89 + └─ Department Heads 90 + └─ Cobel (MDR) 91 + ├─ Milchick 92 + └─ Mark S. 93 + ├─ Dylan G. 94 + ├─ Irving B. 95 + └─ Helly R. 96 + */ 97 + ``` 98 + 99 + ## Input Format 100 + 101 + The `treeify` function accepts arrays with the following structure: 102 + 103 + 1. First element must be a string (the root node) 104 + 2. Subsequent elements can be strings (nodes at same level) or arrays (children of previous node) 105 + 3. Arrays can be nested to any depth 106 + 107 + ```typescript 108 + ['root', 'sibling', ['child1', 'child2']] // Root with 2 children 109 + ['root', ['child'], 'sibling', ['nephew', 'niece']] // 2 root nodes with children 110 + ['root', ['child', ['grandchild']]] // Grandchildren 111 + ``` 112 + 113 + ## License 114 + 115 + MIT © tbeseda
+31
biome.json
··· 1 + { 2 + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 + "vcs": { 4 + "enabled": false, 5 + "clientKind": "git", 6 + "useIgnoreFile": false 7 + }, 8 + "files": { 9 + "ignoreUnknown": false, 10 + "ignore": [] 11 + }, 12 + "formatter": { 13 + "enabled": true, 14 + "indentStyle": "space" 15 + }, 16 + "organizeImports": { 17 + "enabled": true 18 + }, 19 + "linter": { 20 + "enabled": true, 21 + "rules": { 22 + "recommended": true 23 + } 24 + }, 25 + "javascript": { 26 + "formatter": { 27 + "quoteStyle": "single", 28 + "semicolons": "asNeeded" 29 + } 30 + } 31 + }
+213
package-lock.json
··· 1 + { 2 + "name": "array-treeify", 3 + "version": "0.1.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "array-treeify", 9 + "version": "0.1.0", 10 + "license": "MIT", 11 + "devDependencies": { 12 + "@biomejs/biome": "1.9.4", 13 + "@types/node": "^22.13.14", 14 + "typescript": "^5.3.3" 15 + } 16 + }, 17 + "node_modules/@biomejs/biome": { 18 + "version": "1.9.4", 19 + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", 20 + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", 21 + "dev": true, 22 + "hasInstallScript": true, 23 + "license": "MIT OR Apache-2.0", 24 + "bin": { 25 + "biome": "bin/biome" 26 + }, 27 + "engines": { 28 + "node": ">=14.21.3" 29 + }, 30 + "funding": { 31 + "type": "opencollective", 32 + "url": "https://opencollective.com/biome" 33 + }, 34 + "optionalDependencies": { 35 + "@biomejs/cli-darwin-arm64": "1.9.4", 36 + "@biomejs/cli-darwin-x64": "1.9.4", 37 + "@biomejs/cli-linux-arm64": "1.9.4", 38 + "@biomejs/cli-linux-arm64-musl": "1.9.4", 39 + "@biomejs/cli-linux-x64": "1.9.4", 40 + "@biomejs/cli-linux-x64-musl": "1.9.4", 41 + "@biomejs/cli-win32-arm64": "1.9.4", 42 + "@biomejs/cli-win32-x64": "1.9.4" 43 + } 44 + }, 45 + "node_modules/@biomejs/cli-darwin-arm64": { 46 + "version": "1.9.4", 47 + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", 48 + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", 49 + "cpu": [ 50 + "arm64" 51 + ], 52 + "dev": true, 53 + "license": "MIT OR Apache-2.0", 54 + "optional": true, 55 + "os": [ 56 + "darwin" 57 + ], 58 + "engines": { 59 + "node": ">=14.21.3" 60 + } 61 + }, 62 + "node_modules/@biomejs/cli-darwin-x64": { 63 + "version": "1.9.4", 64 + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", 65 + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", 66 + "cpu": [ 67 + "x64" 68 + ], 69 + "dev": true, 70 + "license": "MIT OR Apache-2.0", 71 + "optional": true, 72 + "os": [ 73 + "darwin" 74 + ], 75 + "engines": { 76 + "node": ">=14.21.3" 77 + } 78 + }, 79 + "node_modules/@biomejs/cli-linux-arm64": { 80 + "version": "1.9.4", 81 + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", 82 + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", 83 + "cpu": [ 84 + "arm64" 85 + ], 86 + "dev": true, 87 + "license": "MIT OR Apache-2.0", 88 + "optional": true, 89 + "os": [ 90 + "linux" 91 + ], 92 + "engines": { 93 + "node": ">=14.21.3" 94 + } 95 + }, 96 + "node_modules/@biomejs/cli-linux-arm64-musl": { 97 + "version": "1.9.4", 98 + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", 99 + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", 100 + "cpu": [ 101 + "arm64" 102 + ], 103 + "dev": true, 104 + "license": "MIT OR Apache-2.0", 105 + "optional": true, 106 + "os": [ 107 + "linux" 108 + ], 109 + "engines": { 110 + "node": ">=14.21.3" 111 + } 112 + }, 113 + "node_modules/@biomejs/cli-linux-x64": { 114 + "version": "1.9.4", 115 + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", 116 + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", 117 + "cpu": [ 118 + "x64" 119 + ], 120 + "dev": true, 121 + "license": "MIT OR Apache-2.0", 122 + "optional": true, 123 + "os": [ 124 + "linux" 125 + ], 126 + "engines": { 127 + "node": ">=14.21.3" 128 + } 129 + }, 130 + "node_modules/@biomejs/cli-linux-x64-musl": { 131 + "version": "1.9.4", 132 + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", 133 + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", 134 + "cpu": [ 135 + "x64" 136 + ], 137 + "dev": true, 138 + "license": "MIT OR Apache-2.0", 139 + "optional": true, 140 + "os": [ 141 + "linux" 142 + ], 143 + "engines": { 144 + "node": ">=14.21.3" 145 + } 146 + }, 147 + "node_modules/@biomejs/cli-win32-arm64": { 148 + "version": "1.9.4", 149 + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", 150 + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", 151 + "cpu": [ 152 + "arm64" 153 + ], 154 + "dev": true, 155 + "license": "MIT OR Apache-2.0", 156 + "optional": true, 157 + "os": [ 158 + "win32" 159 + ], 160 + "engines": { 161 + "node": ">=14.21.3" 162 + } 163 + }, 164 + "node_modules/@biomejs/cli-win32-x64": { 165 + "version": "1.9.4", 166 + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", 167 + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", 168 + "cpu": [ 169 + "x64" 170 + ], 171 + "dev": true, 172 + "license": "MIT OR Apache-2.0", 173 + "optional": true, 174 + "os": [ 175 + "win32" 176 + ], 177 + "engines": { 178 + "node": ">=14.21.3" 179 + } 180 + }, 181 + "node_modules/@types/node": { 182 + "version": "22.13.14", 183 + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", 184 + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", 185 + "dev": true, 186 + "license": "MIT", 187 + "dependencies": { 188 + "undici-types": "~6.20.0" 189 + } 190 + }, 191 + "node_modules/typescript": { 192 + "version": "5.8.2", 193 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 194 + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 195 + "dev": true, 196 + "license": "Apache-2.0", 197 + "bin": { 198 + "tsc": "bin/tsc", 199 + "tsserver": "bin/tsserver" 200 + }, 201 + "engines": { 202 + "node": ">=14.17" 203 + } 204 + }, 205 + "node_modules/undici-types": { 206 + "version": "6.20.0", 207 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 208 + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 209 + "dev": true, 210 + "license": "MIT" 211 + } 212 + } 213 + }
+25
package.json
··· 1 + { 2 + "name": "array-treeify", 3 + "version": "0.1.0", 4 + "description": "", 5 + "keywords": ["ascii", "tree", "treeify"], 6 + "license": "MIT", 7 + "author": "tbeseda", 8 + "type": "module", 9 + "main": "dist/index.js", 10 + "types": "dist/index.d.ts", 11 + "files": ["dist"], 12 + "scripts": { 13 + "lint": "biome check --write .", 14 + "build": "tsc", 15 + "pretest": "npm run build", 16 + "test": "node --test", 17 + "posttest": "npm run lint", 18 + "prepublishOnly": "npm run build" 19 + }, 20 + "devDependencies": { 21 + "@biomejs/biome": "1.9.4", 22 + "@types/node": "^22.13.14", 23 + "typescript": "^5.3.3" 24 + } 25 + }
+185
src/index.test.ts
··· 1 + import assert from 'node:assert' 2 + import { describe, test } from 'node:test' 3 + import { type TreeInput, treeify } from './index.js' 4 + 5 + describe('treeify utility', () => { 6 + test('basic tree with root and children', () => { 7 + const input: TreeInput = ['root', ['child1', 'child2', 'child3']] 8 + const expected = `root 9 + ├─ child1 10 + ├─ child2 11 + └─ child3` 12 + 13 + const result = treeify(input) 14 + console.log('\nBasic tree with root and children:') 15 + console.log(result) 16 + assert.strictEqual(result, expected) 17 + }) 18 + 19 + test('tree with nested children', () => { 20 + const input: TreeInput = [ 21 + 'foo', 22 + ['bar', 'baz', 'corge', ['qux', ['qui', 'grault', 'garply']]], 23 + ] 24 + const expected = `foo 25 + ├─ bar 26 + ├─ baz 27 + └─ corge 28 + └─ qux 29 + ├─ qui 30 + ├─ grault 31 + └─ garply` 32 + 33 + const result = treeify(input) 34 + console.log('\nTree with nested children:') 35 + console.log(result) 36 + assert.strictEqual(result, expected) 37 + }) 38 + 39 + test('deeply nested tree', () => { 40 + const input: TreeInput = [ 41 + 'root', 42 + [ 43 + 'level1-1', 44 + [ 45 + 'level2-1', 46 + 'level2-2', 47 + ['level3-1', 'level3-2', ['level4-1', 'level4-2']], 48 + ], 49 + 'level1-2', 50 + ], 51 + ] 52 + const expected = `root 53 + ├─ level1-1 54 + │ ├─ level2-1 55 + │ └─ level2-2 56 + │ ├─ level3-1 57 + │ └─ level3-2 58 + │ ├─ level4-1 59 + │ └─ level4-2 60 + └─ level1-2` 61 + 62 + const result = treeify(input) 63 + console.log('\nDeeply nested tree:') 64 + console.log(result) 65 + assert.strictEqual(result, expected) 66 + }) 67 + 68 + test('tree with multiple deep branches', () => { 69 + const input: TreeInput = [ 70 + 'root', 71 + [ 72 + 'branch1', 73 + ['leaf2-1', ['leaf3-1', 'leaf3-2']], 74 + 'branch2', 75 + ['leaf2-1', ['leaf3-1', ['leaf4-1', 'leaf4-2']]], 76 + 'leaf1', 77 + ], 78 + ] 79 + const expected = `root 80 + ├─ branch1 81 + │ └─ leaf2-1 82 + │ ├─ leaf3-1 83 + │ └─ leaf3-2 84 + ├─ branch2 85 + │ └─ leaf2-1 86 + │ └─ leaf3-1 87 + │ ├─ leaf4-1 88 + │ └─ leaf4-2 89 + └─ leaf1` 90 + 91 + const result = treeify(input) 92 + console.log('\nTree with multiple deep branches:') 93 + console.log(result) 94 + assert.strictEqual(result, expected) 95 + }) 96 + 97 + test('empty array returns empty string', () => { 98 + assert.strictEqual(treeify([] as unknown as TreeInput), '') 99 + }) 100 + 101 + test('array with only a root returns only the root', () => { 102 + assert.strictEqual(treeify(['root'] as TreeInput), 'root') 103 + }) 104 + 105 + test('multiple strings at root level', () => { 106 + const input: TreeInput = ['root', 'second', ['child1', 'child2']] 107 + const expected = `root 108 + second 109 + ├─ child1 110 + └─ child2` 111 + 112 + const result = treeify(input) 113 + console.log('\nMultiple strings at root level:') 114 + console.log(result) 115 + assert.strictEqual(result, expected) 116 + }) 117 + 118 + test('consumer-friendly without type annotations', () => { 119 + // These don't need type annotations anymore - TypeScript can infer them 120 + const input1 = ['root', ['child1', 'child2']] 121 + const input2 = ['parent', 'sibling', ['child1', 'child2']] 122 + const input3 = ['top', ['middle', ['bottom']]] 123 + 124 + // All of these should work without type errors 125 + assert.ok(treeify(input1).includes('root')) 126 + assert.ok(treeify(input2).includes('sibling')) 127 + assert.ok(treeify(input3).includes('middle')) 128 + 129 + console.log('\nConsumer examples without type annotations:') 130 + console.log(treeify(input1)) 131 + console.log(`\n${treeify(input2)}`) 132 + console.log(`\n${treeify(input3)}`) 133 + }) 134 + }) 135 + 136 + describe('readme examples', () => { 137 + test('basic example', () => { 138 + const eagan = [ 139 + 'Kier Eagan', 140 + ['...', ['...', 'Jame Eagan', ['Helena Eagan']]], 141 + 'Ambrose Eagan', 142 + ] 143 + const expected = `Kier Eagan 144 + ├─ ... 145 + | ├─ ... 146 + | └─ Jame Eagan 147 + | └─ Helena Eagan 148 + └─ Ambrose Eagan` 149 + 150 + const result = treeify(eagan) 151 + console.log('\nBasic example:') 152 + console.log(result) 153 + assert.strictEqual(result, expected) 154 + }) 155 + 156 + test('nested example', () => { 157 + const orgChart = [ 158 + 'Lumon Industries', 159 + [ 160 + 'Board of Directors', 161 + ['Natalie (Representative)'], 162 + 'Department Heads', 163 + [ 164 + 'Cobel (MDR)', 165 + ['Milchick', 'Mark S.', ['Dylan G.', 'Irving B.', 'Helly R.']], 166 + ], 167 + ], 168 + ] 169 + const expected = `Lumon Industries 170 + ├─ Board of Directors 171 + │ └─ Natalie (Representative) 172 + └─ Department Heads 173 + └─ Cobel (MDR) 174 + ├─ Milchick 175 + └─ Mark S. 176 + ├─ Dylan G. 177 + ├─ Irving B. 178 + └─ Helly R.` 179 + 180 + const result = treeify(orgChart) 181 + console.log('\nNested example:') 182 + console.log(result) 183 + assert.strictEqual(result, expected) 184 + }) 185 + })
+168
src/index.ts
··· 1 + /** 2 + * Represents a node in the tree structure. 3 + * Can be either a string (a leaf node) or an array of TreeNodes (a branch with children). 4 + */ 5 + type TreeNode = string | TreeNode[] 6 + 7 + /** 8 + * The strict tree input format. Must start with a string. 9 + * This type is exported for testing purposes and advanced usage. 10 + */ 11 + export type TreeInput = [string, ...Array<string | TreeNode[]>] 12 + 13 + /** 14 + * Flexible input type that accepts any array. 15 + * Runtime validation ensures the first element is a string. 16 + */ 17 + type FlexibleTreeInput = readonly (string | unknown[])[] 18 + 19 + const CHARS = { 20 + BRANCH: '├─ ', 21 + LAST_BRANCH: '└─ ', 22 + PIPE: '│ ', 23 + SPACE: ' ', 24 + } as const 25 + 26 + /** 27 + * @description Creates an ASCII tree representation from a nested array structure. 28 + * 29 + * The expected input format is a hierarchical structure where: 30 + * - The first element must be a string (the root node) 31 + * - String elements represent nodes at the current level 32 + * - Array elements following a string represent the children of the previous node 33 + * - Nested arrays create deeper levels in the tree 34 + * 35 + * Examples of supported formats: 36 + * - `['root', ['child1', 'child2', 'child3']]` creates a root with three children 37 + * - `['root', 'second', ['child1', 'child2']]` creates multiple root nodes with children 38 + * - `['root', ['child1', ['grandchild1', 'grandchild2']]]` creates a root with nested children 39 + * - `['root', ['childA', ['grandchildA'], 'childB']]` creates multiple branches 40 + * 41 + * The output uses ASCII characters to visualize the tree structure. 42 + * 43 + * @param list {FlexibleTreeInput} - An array representing the tree structure. First element must be a string. 44 + * @returns {string} A string containing the ASCII tree representation 45 + * 46 + * @example 47 + * treeify(['root', ['child1', 'child2', ['grandchild']]]) 48 + * // root 49 + * // ├─ child1 50 + * // └─ child2 51 + * // └─ grandchild 52 + */ 53 + export function treeify(list: FlexibleTreeInput): string { 54 + if (!Array.isArray(list) || list.length === 0) return '' 55 + if (list[0] === undefined) return '' 56 + if (typeof list[0] !== 'string') 57 + throw new Error('First element must be a string') 58 + 59 + const result: string[] = [] 60 + 61 + result.push(list[0]) // first string is the root 62 + 63 + let i = 1 64 + while (i < list.length) { 65 + const node = list[i] 66 + 67 + if (typeof node === 'string') { 68 + // add strings here 69 + result.push(node) 70 + i++ 71 + } else if (Array.isArray(node)) { 72 + // array is the children of the previous item 73 + renderTreeNodes(node, '', result) 74 + i++ 75 + } else { 76 + // idk. skip it. 77 + i++ 78 + } 79 + } 80 + 81 + return result.join('\n') 82 + } 83 + 84 + /** 85 + * @description Renders tree nodes with appropriate ASCII indentation and branching 86 + */ 87 + function renderTreeNodes( 88 + nodes: TreeNode[], 89 + indent: string, 90 + result: string[], 91 + ): void { 92 + if (!Array.isArray(nodes) || nodes.length === 0) return 93 + 94 + const parentNodeIndices = findParentNodeIndices(nodes) 95 + 96 + let i = 0 97 + while (i < nodes.length) { 98 + const node = nodes[i] 99 + const isParentNode = parentNodeIndices.has(i) 100 + 101 + if (isParentNode) { 102 + const parentIndex = i 103 + const childrenIndex = i + 1 104 + const stringNode = nodes[parentIndex] as string 105 + const arrayNode = nodes[childrenIndex] as TreeNode[] 106 + 107 + const isLast = !hasNextStringNode(nodes, childrenIndex + 1) 108 + 109 + const prefix = isLast ? CHARS.LAST_BRANCH : CHARS.BRANCH 110 + result.push(indent + prefix + stringNode) 111 + 112 + // children with increased indent 113 + const childIndent = indent + (isLast ? CHARS.SPACE : CHARS.PIPE) 114 + renderTreeNodes(arrayNode, childIndent, result) 115 + 116 + // skip both the parent node and its children array 117 + i += 2 118 + } else if (typeof node === 'string') { 119 + // string is simple. add it. 120 + const isLast = !hasNextStringNode(nodes, i + 1) 121 + const prefix = isLast ? CHARS.LAST_BRANCH : CHARS.BRANCH 122 + result.push(indent + prefix + node) 123 + i++ 124 + } else if (Array.isArray(node)) { 125 + // (>_>) 126 + const isLast = i === nodes.length - 1 127 + const childIndent = indent + (isLast ? CHARS.SPACE : CHARS.PIPE) 128 + renderTreeNodes(node, childIndent, result) 129 + i++ 130 + } else { 131 + // (0_o) 132 + i++ 133 + } 134 + } 135 + } 136 + 137 + /** 138 + * @description Locate parent nodes in the array to handle nesting. 139 + */ 140 + function findParentNodeIndices(nodes: TreeNode[]): Set<number> { 141 + const parentNodeIndices = new Set<number>() 142 + 143 + for (let i = 0; i < nodes.length; i++) { 144 + if ( 145 + typeof nodes[i] === 'string' && 146 + i + 1 < nodes.length && 147 + Array.isArray(nodes[i + 1]) 148 + ) { 149 + parentNodeIndices.add(i) 150 + } 151 + } 152 + 153 + return parentNodeIndices 154 + } 155 + 156 + /** 157 + * @description 158 + * Determines if there's another string node after the given index. 159 + * Used to decide if the current node is the last at its level. 160 + */ 161 + function hasNextStringNode(nodes: TreeNode[], startIndex: number): boolean { 162 + for (let i = startIndex; i < nodes.length; i++) { 163 + if (typeof nodes[i] === 'string') { 164 + return true 165 + } 166 + } 167 + return false 168 + }
+16
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "module": "ES2020", 5 + "moduleResolution": "node", 6 + "declaration": true, 7 + "outDir": "./dist", 8 + "strict": true, 9 + "esModuleInterop": true, 10 + "skipLibCheck": true, 11 + "forceConsistentCasingInFileNames": true, 12 + "rootDir": "src" 13 + }, 14 + "include": ["src"], 15 + "exclude": ["node_modules", "dist"] 16 + }