this repo has no description

add chars and plain options

+781 -34
+59 -3
README.md
··· 1 1 # 🪾 `array-treeify` 2 2 3 - **Simple ASCII trees from arrays. For your terminal and console displays.** 3 + **Simple text trees from arrays using Unicode box-drawing characters. For your terminal and console displays.** 4 4 5 5 [![typescript](https://img.shields.io/badge/TypeScript-007ACC?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) 6 6 [![npm](https://img.shields.io/npm/v/array-treeify.svg)](https://www.npmjs.com/package/array-treeify) ··· 8 8 9 9 ## Overview 10 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. 11 + `array-treeify` transforms nested arrays into text trees with proper branching characters. Perfect for CLIs, debug outputs, or anywhere you need to visualize hierarchical data. 12 + 13 + ```typescript 14 + treeify([ 15 + 'Lumon Industries', 16 + [ 17 + 'Board of Directors', 18 + ['Natalie (Representative)'], 19 + 'Departments', 20 + [ 21 + 'Macrodata Refinement (Cobel)', 22 + ['Milchick', 'Mark S.', ['Dylan G.', 'Irving B.', 'Helly R.']], 23 + ], 24 + 'Other Departments', 25 + [ 26 + 'Optics & Design', 27 + 'Wellness Center', 28 + 'Mammalians Nurturable', 29 + 'Choreography and Merriment', 30 + ], 31 + ], 32 + ]) 33 + ``` 34 + 12 35 13 36 ``` 14 37 Lumon Industries ··· 37 60 ## Usage 38 61 39 62 ```typescript 40 - function treeify(input: TreeInput): string 63 + function treeify(input: TreeInput, options?: { 64 + chars?: TreeChars, // Custom characters for the tree 65 + plain?: boolean // Use plain whitespace instead of Unicode box-drawing characters 66 + }): string 41 67 ``` 42 68 43 69 `array-treeify` accepts a simple, intuitive array structure that's easy to build and manipulate: ··· 68 94 └─ Ambrose Eagan 69 95 */ 70 96 97 + // Using custom characters 98 + const resultCustomChars = treeify( 99 + eagan, 100 + { chars: { branch: '├• ', lastBranch: '└• ', pipe: '│ ', space: ' ' }, 101 + }) 102 + /* 103 + Kier Eagan 104 + ├• ... 105 + │ ├• ... 106 + │ └• Jame Eagan 107 + │ └• Helena Eagan 108 + └• Ambrose Eagan 109 + */ 110 + 111 + // Using plain whitespace characters 112 + console.log(treeify(eagan, { plain: true })) 113 + /* 114 + Kier Eagan 115 + ... 116 + ... 117 + Jame Eagan 118 + Helena Eagan 119 + Ambrose Eagan 120 + */ 121 + 71 122 // Nested example 72 123 const orgChart = [ 73 124 'Lumon Industries', ··· 109 160 ['root', ['child'], 'sibling', ['nephew', 'niece']] // 2 root nodes with children 110 161 ['root', ['child', ['grandchild']]] // Grandchildren 111 162 ``` 163 + 164 + ## Options 165 + 166 + - `chars`: Custom characters for the tree. Defaults to Unicode box-drawing characters. 167 + - `plain`: When true, uses plain whitespace characters instead of Unicode box-drawing characters. 112 168 113 169 ## License 114 170
+1 -1
biome.json
··· 7 7 }, 8 8 "files": { 9 9 "ignoreUnknown": false, 10 - "ignore": [] 10 + "ignore": ["package.json"] 11 11 }, 12 12 "formatter": { 13 13 "enabled": true,
+526 -1
package-lock.json
··· 11 11 "devDependencies": { 12 12 "@biomejs/biome": "1.9.4", 13 13 "@types/node": "^22.13.14", 14 - "typescript": "^5.3.3" 14 + "tsx": "^4.19.3", 15 + "typescript": "^5.8.2" 15 16 } 16 17 }, 17 18 "node_modules/@biomejs/biome": { ··· 178 179 "node": ">=14.21.3" 179 180 } 180 181 }, 182 + "node_modules/@esbuild/aix-ppc64": { 183 + "version": "0.25.2", 184 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", 185 + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", 186 + "cpu": [ 187 + "ppc64" 188 + ], 189 + "dev": true, 190 + "license": "MIT", 191 + "optional": true, 192 + "os": [ 193 + "aix" 194 + ], 195 + "engines": { 196 + "node": ">=18" 197 + } 198 + }, 199 + "node_modules/@esbuild/android-arm": { 200 + "version": "0.25.2", 201 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", 202 + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", 203 + "cpu": [ 204 + "arm" 205 + ], 206 + "dev": true, 207 + "license": "MIT", 208 + "optional": true, 209 + "os": [ 210 + "android" 211 + ], 212 + "engines": { 213 + "node": ">=18" 214 + } 215 + }, 216 + "node_modules/@esbuild/android-arm64": { 217 + "version": "0.25.2", 218 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", 219 + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", 220 + "cpu": [ 221 + "arm64" 222 + ], 223 + "dev": true, 224 + "license": "MIT", 225 + "optional": true, 226 + "os": [ 227 + "android" 228 + ], 229 + "engines": { 230 + "node": ">=18" 231 + } 232 + }, 233 + "node_modules/@esbuild/android-x64": { 234 + "version": "0.25.2", 235 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", 236 + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", 237 + "cpu": [ 238 + "x64" 239 + ], 240 + "dev": true, 241 + "license": "MIT", 242 + "optional": true, 243 + "os": [ 244 + "android" 245 + ], 246 + "engines": { 247 + "node": ">=18" 248 + } 249 + }, 250 + "node_modules/@esbuild/darwin-arm64": { 251 + "version": "0.25.2", 252 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", 253 + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", 254 + "cpu": [ 255 + "arm64" 256 + ], 257 + "dev": true, 258 + "license": "MIT", 259 + "optional": true, 260 + "os": [ 261 + "darwin" 262 + ], 263 + "engines": { 264 + "node": ">=18" 265 + } 266 + }, 267 + "node_modules/@esbuild/darwin-x64": { 268 + "version": "0.25.2", 269 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", 270 + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", 271 + "cpu": [ 272 + "x64" 273 + ], 274 + "dev": true, 275 + "license": "MIT", 276 + "optional": true, 277 + "os": [ 278 + "darwin" 279 + ], 280 + "engines": { 281 + "node": ">=18" 282 + } 283 + }, 284 + "node_modules/@esbuild/freebsd-arm64": { 285 + "version": "0.25.2", 286 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", 287 + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", 288 + "cpu": [ 289 + "arm64" 290 + ], 291 + "dev": true, 292 + "license": "MIT", 293 + "optional": true, 294 + "os": [ 295 + "freebsd" 296 + ], 297 + "engines": { 298 + "node": ">=18" 299 + } 300 + }, 301 + "node_modules/@esbuild/freebsd-x64": { 302 + "version": "0.25.2", 303 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", 304 + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", 305 + "cpu": [ 306 + "x64" 307 + ], 308 + "dev": true, 309 + "license": "MIT", 310 + "optional": true, 311 + "os": [ 312 + "freebsd" 313 + ], 314 + "engines": { 315 + "node": ">=18" 316 + } 317 + }, 318 + "node_modules/@esbuild/linux-arm": { 319 + "version": "0.25.2", 320 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", 321 + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", 322 + "cpu": [ 323 + "arm" 324 + ], 325 + "dev": true, 326 + "license": "MIT", 327 + "optional": true, 328 + "os": [ 329 + "linux" 330 + ], 331 + "engines": { 332 + "node": ">=18" 333 + } 334 + }, 335 + "node_modules/@esbuild/linux-arm64": { 336 + "version": "0.25.2", 337 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", 338 + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", 339 + "cpu": [ 340 + "arm64" 341 + ], 342 + "dev": true, 343 + "license": "MIT", 344 + "optional": true, 345 + "os": [ 346 + "linux" 347 + ], 348 + "engines": { 349 + "node": ">=18" 350 + } 351 + }, 352 + "node_modules/@esbuild/linux-ia32": { 353 + "version": "0.25.2", 354 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", 355 + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", 356 + "cpu": [ 357 + "ia32" 358 + ], 359 + "dev": true, 360 + "license": "MIT", 361 + "optional": true, 362 + "os": [ 363 + "linux" 364 + ], 365 + "engines": { 366 + "node": ">=18" 367 + } 368 + }, 369 + "node_modules/@esbuild/linux-loong64": { 370 + "version": "0.25.2", 371 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", 372 + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", 373 + "cpu": [ 374 + "loong64" 375 + ], 376 + "dev": true, 377 + "license": "MIT", 378 + "optional": true, 379 + "os": [ 380 + "linux" 381 + ], 382 + "engines": { 383 + "node": ">=18" 384 + } 385 + }, 386 + "node_modules/@esbuild/linux-mips64el": { 387 + "version": "0.25.2", 388 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", 389 + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", 390 + "cpu": [ 391 + "mips64el" 392 + ], 393 + "dev": true, 394 + "license": "MIT", 395 + "optional": true, 396 + "os": [ 397 + "linux" 398 + ], 399 + "engines": { 400 + "node": ">=18" 401 + } 402 + }, 403 + "node_modules/@esbuild/linux-ppc64": { 404 + "version": "0.25.2", 405 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", 406 + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", 407 + "cpu": [ 408 + "ppc64" 409 + ], 410 + "dev": true, 411 + "license": "MIT", 412 + "optional": true, 413 + "os": [ 414 + "linux" 415 + ], 416 + "engines": { 417 + "node": ">=18" 418 + } 419 + }, 420 + "node_modules/@esbuild/linux-riscv64": { 421 + "version": "0.25.2", 422 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", 423 + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", 424 + "cpu": [ 425 + "riscv64" 426 + ], 427 + "dev": true, 428 + "license": "MIT", 429 + "optional": true, 430 + "os": [ 431 + "linux" 432 + ], 433 + "engines": { 434 + "node": ">=18" 435 + } 436 + }, 437 + "node_modules/@esbuild/linux-s390x": { 438 + "version": "0.25.2", 439 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", 440 + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", 441 + "cpu": [ 442 + "s390x" 443 + ], 444 + "dev": true, 445 + "license": "MIT", 446 + "optional": true, 447 + "os": [ 448 + "linux" 449 + ], 450 + "engines": { 451 + "node": ">=18" 452 + } 453 + }, 454 + "node_modules/@esbuild/linux-x64": { 455 + "version": "0.25.2", 456 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", 457 + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", 458 + "cpu": [ 459 + "x64" 460 + ], 461 + "dev": true, 462 + "license": "MIT", 463 + "optional": true, 464 + "os": [ 465 + "linux" 466 + ], 467 + "engines": { 468 + "node": ">=18" 469 + } 470 + }, 471 + "node_modules/@esbuild/netbsd-arm64": { 472 + "version": "0.25.2", 473 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", 474 + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", 475 + "cpu": [ 476 + "arm64" 477 + ], 478 + "dev": true, 479 + "license": "MIT", 480 + "optional": true, 481 + "os": [ 482 + "netbsd" 483 + ], 484 + "engines": { 485 + "node": ">=18" 486 + } 487 + }, 488 + "node_modules/@esbuild/netbsd-x64": { 489 + "version": "0.25.2", 490 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", 491 + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", 492 + "cpu": [ 493 + "x64" 494 + ], 495 + "dev": true, 496 + "license": "MIT", 497 + "optional": true, 498 + "os": [ 499 + "netbsd" 500 + ], 501 + "engines": { 502 + "node": ">=18" 503 + } 504 + }, 505 + "node_modules/@esbuild/openbsd-arm64": { 506 + "version": "0.25.2", 507 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", 508 + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", 509 + "cpu": [ 510 + "arm64" 511 + ], 512 + "dev": true, 513 + "license": "MIT", 514 + "optional": true, 515 + "os": [ 516 + "openbsd" 517 + ], 518 + "engines": { 519 + "node": ">=18" 520 + } 521 + }, 522 + "node_modules/@esbuild/openbsd-x64": { 523 + "version": "0.25.2", 524 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", 525 + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", 526 + "cpu": [ 527 + "x64" 528 + ], 529 + "dev": true, 530 + "license": "MIT", 531 + "optional": true, 532 + "os": [ 533 + "openbsd" 534 + ], 535 + "engines": { 536 + "node": ">=18" 537 + } 538 + }, 539 + "node_modules/@esbuild/sunos-x64": { 540 + "version": "0.25.2", 541 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", 542 + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", 543 + "cpu": [ 544 + "x64" 545 + ], 546 + "dev": true, 547 + "license": "MIT", 548 + "optional": true, 549 + "os": [ 550 + "sunos" 551 + ], 552 + "engines": { 553 + "node": ">=18" 554 + } 555 + }, 556 + "node_modules/@esbuild/win32-arm64": { 557 + "version": "0.25.2", 558 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", 559 + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", 560 + "cpu": [ 561 + "arm64" 562 + ], 563 + "dev": true, 564 + "license": "MIT", 565 + "optional": true, 566 + "os": [ 567 + "win32" 568 + ], 569 + "engines": { 570 + "node": ">=18" 571 + } 572 + }, 573 + "node_modules/@esbuild/win32-ia32": { 574 + "version": "0.25.2", 575 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", 576 + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", 577 + "cpu": [ 578 + "ia32" 579 + ], 580 + "dev": true, 581 + "license": "MIT", 582 + "optional": true, 583 + "os": [ 584 + "win32" 585 + ], 586 + "engines": { 587 + "node": ">=18" 588 + } 589 + }, 590 + "node_modules/@esbuild/win32-x64": { 591 + "version": "0.25.2", 592 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", 593 + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", 594 + "cpu": [ 595 + "x64" 596 + ], 597 + "dev": true, 598 + "license": "MIT", 599 + "optional": true, 600 + "os": [ 601 + "win32" 602 + ], 603 + "engines": { 604 + "node": ">=18" 605 + } 606 + }, 181 607 "node_modules/@types/node": { 182 608 "version": "22.13.14", 183 609 "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", ··· 186 612 "license": "MIT", 187 613 "dependencies": { 188 614 "undici-types": "~6.20.0" 615 + } 616 + }, 617 + "node_modules/esbuild": { 618 + "version": "0.25.2", 619 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", 620 + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", 621 + "dev": true, 622 + "hasInstallScript": true, 623 + "license": "MIT", 624 + "bin": { 625 + "esbuild": "bin/esbuild" 626 + }, 627 + "engines": { 628 + "node": ">=18" 629 + }, 630 + "optionalDependencies": { 631 + "@esbuild/aix-ppc64": "0.25.2", 632 + "@esbuild/android-arm": "0.25.2", 633 + "@esbuild/android-arm64": "0.25.2", 634 + "@esbuild/android-x64": "0.25.2", 635 + "@esbuild/darwin-arm64": "0.25.2", 636 + "@esbuild/darwin-x64": "0.25.2", 637 + "@esbuild/freebsd-arm64": "0.25.2", 638 + "@esbuild/freebsd-x64": "0.25.2", 639 + "@esbuild/linux-arm": "0.25.2", 640 + "@esbuild/linux-arm64": "0.25.2", 641 + "@esbuild/linux-ia32": "0.25.2", 642 + "@esbuild/linux-loong64": "0.25.2", 643 + "@esbuild/linux-mips64el": "0.25.2", 644 + "@esbuild/linux-ppc64": "0.25.2", 645 + "@esbuild/linux-riscv64": "0.25.2", 646 + "@esbuild/linux-s390x": "0.25.2", 647 + "@esbuild/linux-x64": "0.25.2", 648 + "@esbuild/netbsd-arm64": "0.25.2", 649 + "@esbuild/netbsd-x64": "0.25.2", 650 + "@esbuild/openbsd-arm64": "0.25.2", 651 + "@esbuild/openbsd-x64": "0.25.2", 652 + "@esbuild/sunos-x64": "0.25.2", 653 + "@esbuild/win32-arm64": "0.25.2", 654 + "@esbuild/win32-ia32": "0.25.2", 655 + "@esbuild/win32-x64": "0.25.2" 656 + } 657 + }, 658 + "node_modules/fsevents": { 659 + "version": "2.3.3", 660 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 661 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 662 + "dev": true, 663 + "hasInstallScript": true, 664 + "license": "MIT", 665 + "optional": true, 666 + "os": [ 667 + "darwin" 668 + ], 669 + "engines": { 670 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 671 + } 672 + }, 673 + "node_modules/get-tsconfig": { 674 + "version": "4.10.0", 675 + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", 676 + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", 677 + "dev": true, 678 + "license": "MIT", 679 + "dependencies": { 680 + "resolve-pkg-maps": "^1.0.0" 681 + }, 682 + "funding": { 683 + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 684 + } 685 + }, 686 + "node_modules/resolve-pkg-maps": { 687 + "version": "1.0.0", 688 + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 689 + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 690 + "dev": true, 691 + "license": "MIT", 692 + "funding": { 693 + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 694 + } 695 + }, 696 + "node_modules/tsx": { 697 + "version": "4.19.3", 698 + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", 699 + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", 700 + "dev": true, 701 + "license": "MIT", 702 + "dependencies": { 703 + "esbuild": "~0.25.0", 704 + "get-tsconfig": "^4.7.5" 705 + }, 706 + "bin": { 707 + "tsx": "dist/cli.mjs" 708 + }, 709 + "engines": { 710 + "node": ">=18.0.0" 711 + }, 712 + "optionalDependencies": { 713 + "fsevents": "~2.3.3" 189 714 } 190 715 }, 191 716 "node_modules/typescript": {
+18 -12
package.json
··· 1 1 { 2 2 "name": "array-treeify", 3 3 "version": "0.1.2", 4 - "description": "", 5 - "keywords": [ 6 - "ascii", 7 - "tree", 8 - "treeify" 9 - ], 4 + "description": "Simple text tree diagrams from arrays.", 10 5 "license": "MIT", 11 6 "author": "tbeseda", 12 7 "repository": { ··· 17 12 "main": "dist/index.js", 18 13 "types": "dist/index.d.ts", 19 14 "files": [ 20 - "dist/index.js", 21 - "dist/index.d.ts" 15 + "dist/" 22 16 ], 23 17 "scripts": { 24 18 "lint": "biome check --write .", 25 19 "build": "rm -rf dist && tsc", 26 - "pretest": "npm run build", 27 - "test": "node --test", 20 + "test": "tsx --test", 28 21 "posttest": "npm run lint", 29 22 "prepublishOnly": "npm run build" 30 23 }, 31 24 "devDependencies": { 32 25 "@biomejs/biome": "1.9.4", 33 26 "@types/node": "^22.13.14", 34 - "typescript": "^5.3.3" 35 - } 27 + "tsx": "^4.19.3", 28 + "typescript": "^5.8.2" 29 + }, 30 + "keywords": [ 31 + "ascii", 32 + "unicode", 33 + "tree", 34 + "treeify", 35 + "diagram", 36 + "cli", 37 + "output", 38 + "pretty", 39 + "pretty print", 40 + "pretty print tree" 41 + ] 36 42 }
+63
src/index.test.ts
··· 131 131 console.log(`\n${treeify(input2)}`) 132 132 console.log(`\n${treeify(input3)}`) 133 133 }) 134 + 135 + test('plain option uses whitespace instead of Unicode characters', () => { 136 + const input: TreeInput = ['root', ['child1', 'child2', ['grandchild']]] 137 + const expected = `root 138 + child1 139 + child2 140 + grandchild` 141 + 142 + const result = treeify(input, { plain: true }) 143 + console.log('\nPlain whitespace tree:') 144 + console.log(result) 145 + assert.strictEqual(result, expected) 146 + }) 147 + 148 + test('custom characters can be provided', () => { 149 + const input: TreeInput = ['root', ['child1', 'child2']] 150 + const customChars = { 151 + branch: '├• ', 152 + lastBranch: '└• ', 153 + pipe: '│ ', 154 + space: ' ', 155 + } 156 + const expected = `root 157 + ├• child1 158 + └• child2` 159 + 160 + const result = treeify(input, { chars: customChars }) 161 + console.log('\nCustom character tree:') 162 + console.log(result) 163 + assert.strictEqual(result, expected) 164 + }) 165 + 166 + test('custom characters take precedence over plain option', () => { 167 + const input: TreeInput = ['root', ['child1', 'child2']] 168 + const customChars = { 169 + branch: '├• ', 170 + lastBranch: '└• ', 171 + pipe: '│ ', 172 + space: ' ', 173 + } 174 + const expected = `root 175 + ├• child1 176 + └• child2` 177 + 178 + const result = treeify(input, { plain: true, chars: customChars }) 179 + console.log('\nCustom characters with plain option:') 180 + console.log(result) 181 + assert.strictEqual(result, expected) 182 + }) 183 + 184 + test('first element must be a string', () => { 185 + assert.throws(() => treeify([null, ['child']] as unknown as TreeInput), { 186 + message: 'First element must be a string', 187 + }) 188 + assert.throws(() => treeify([1, ['child']] as unknown as TreeInput), { 189 + message: 'First element must be a string', 190 + }) 191 + }) 192 + 193 + test('empty or invalid input returns empty string', () => { 194 + assert.strictEqual(treeify([] as unknown as TreeInput), '') 195 + assert.strictEqual(treeify([undefined] as unknown as TreeInput), '') 196 + }) 134 197 })
+48 -16
src/index.ts
··· 16 16 */ 17 17 type FlexibleTreeInput = readonly (string | unknown[])[] 18 18 19 - const CHARS = { 20 - BRANCH: '├─ ', 21 - LAST_BRANCH: '└─ ', 22 - PIPE: '│ ', 23 - SPACE: ' ', 19 + type TreeChars = { 20 + branch: string 21 + lastBranch: string 22 + pipe: string 23 + space: string 24 + } 25 + 26 + /** 27 + * @description ASCII characters used to render the tree. 28 + */ 29 + const DEFAULT_CHARS: TreeChars = { 30 + branch: '├─ ', 31 + lastBranch: '└─ ', 32 + pipe: '│ ', 33 + space: ' ', 34 + } as const 35 + const SPACE = ' ' as const 36 + const EMPTY_CHARS: TreeChars = { 37 + branch: SPACE.repeat(DEFAULT_CHARS.branch.length), 38 + lastBranch: SPACE.repeat(DEFAULT_CHARS.lastBranch.length), 39 + pipe: SPACE.repeat(DEFAULT_CHARS.pipe.length), 40 + space: SPACE.repeat(DEFAULT_CHARS.space.length), 24 41 } as const 25 42 26 43 /** 27 - * @description Creates an ASCII tree representation from a nested array structure. 44 + * @description Creates a text tree representation from a nested array structure using Unicode box-drawing characters. 28 45 * 29 46 * The expected input format is a hierarchical structure where: 30 47 * - The first element must be a string (the root node) ··· 38 55 * - `['root', ['child1', ['grandchild1', 'grandchild2']]]` creates a root with nested children 39 56 * - `['root', ['childA', ['grandchildA'], 'childB']]` creates multiple branches 40 57 * 41 - * The output uses ASCII characters to visualize the tree structure. 58 + * The output uses Unicode box-drawing characters to visualize the tree structure. 42 59 * 43 60 * @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 61 + * @param options {Object} - An object containing optional configuration: 62 + * - `chars` {TreeChars} - Custom characters for the tree. Defaults to Unicode box-drawing characters. 63 + * - `plain` {boolean} - Whether to use plain whitespace characters instead of Unicode box-drawing characters. 64 + * 65 + * @returns {string} A string containing the tree representation 45 66 * 46 67 * @example 47 68 * treeify(['root', ['child1', 'child2', ['grandchild']]]) ··· 50 71 * // └─ child2 51 72 * // └─ grandchild 52 73 */ 53 - export function treeify(list: FlexibleTreeInput): string { 74 + export function treeify( 75 + list: FlexibleTreeInput, 76 + options?: { 77 + chars?: TreeChars 78 + plain?: boolean 79 + }, 80 + ): string { 54 81 if (!Array.isArray(list) || list.length === 0) return '' 55 82 if (list[0] === undefined) return '' 56 83 if (typeof list[0] !== 'string') 57 84 throw new Error('First element must be a string') 58 85 86 + let chars = DEFAULT_CHARS 87 + if (options?.plain) chars = EMPTY_CHARS 88 + if (options?.chars) chars = options.chars 89 + 59 90 const result: string[] = [] 60 91 61 92 result.push(list[0]) // first string is the root ··· 70 101 i++ 71 102 } else if (Array.isArray(node)) { 72 103 // array is the children of the previous item 73 - renderTreeNodes(node, '', result) 104 + renderTreeNodes(node, '', result, chars) 74 105 i++ 75 106 } else { 76 107 // idk. skip it. ··· 88 119 nodes: TreeNode[], 89 120 indent: string, 90 121 result: string[], 122 + chars: TreeChars, 91 123 ): void { 92 124 if (!Array.isArray(nodes) || nodes.length === 0) return 93 125 ··· 106 138 107 139 const isLast = !hasNextStringNode(nodes, childrenIndex + 1) 108 140 109 - const prefix = isLast ? CHARS.LAST_BRANCH : CHARS.BRANCH 141 + const prefix = isLast ? chars.lastBranch : chars.branch 110 142 result.push(indent + prefix + stringNode) 111 143 112 144 // children with increased indent 113 - const childIndent = indent + (isLast ? CHARS.SPACE : CHARS.PIPE) 114 - renderTreeNodes(arrayNode, childIndent, result) 145 + const childIndent = indent + (isLast ? chars.space : chars.pipe) 146 + renderTreeNodes(arrayNode, childIndent, result, chars) 115 147 116 148 // skip both the parent node and its children array 117 149 i += 2 118 150 } else if (typeof node === 'string') { 119 151 // string is simple. add it. 120 152 const isLast = !hasNextStringNode(nodes, i + 1) 121 - const prefix = isLast ? CHARS.LAST_BRANCH : CHARS.BRANCH 153 + const prefix = isLast ? chars.lastBranch : chars.branch 122 154 result.push(indent + prefix + node) 123 155 i++ 124 156 } else if (Array.isArray(node)) { 125 157 // (>_>) 126 158 const isLast = i === nodes.length - 1 127 - const childIndent = indent + (isLast ? CHARS.SPACE : CHARS.PIPE) 128 - renderTreeNodes(node, childIndent, result) 159 + const childIndent = indent + (isLast ? chars.space : chars.pipe) 160 + renderTreeNodes(node, childIndent, result, chars) 129 161 i++ 130 162 } else { 131 163 // (0_o)
+65
src/readme-examples.test.ts
··· 3 3 import { treeify } from './index.js' 4 4 5 5 describe('readme examples', () => { 6 + test('org chart example', () => { 7 + const orgChart = treeify([ 8 + 'Lumon Industries', 9 + [ 10 + 'Board of Directors', 11 + ['Natalie (Representative)'], 12 + 'Departments', 13 + [ 14 + 'Macrodata Refinement (Cobel)', 15 + ['Milchick', 'Mark S.', ['Dylan G.', 'Irving B.', 'Helly R.']], 16 + ], 17 + 'Other Departments', 18 + [ 19 + 'Optics & Design', 20 + 'Wellness Center', 21 + 'Mammalians Nurturable', 22 + 'Choreography and Merriment', 23 + ], 24 + ], 25 + ]) 26 + const expected = `Lumon Industries 27 + ├─ Board of Directors 28 + │ └─ Natalie (Representative) 29 + ├─ Departments 30 + │ └─ Macrodata Refinement (Cobel) 31 + │ ├─ Milchick 32 + │ └─ Mark S. 33 + │ ├─ Dylan G. 34 + │ ├─ Irving B. 35 + │ └─ Helly R. 36 + └─ Other Departments 37 + ├─ Optics & Design 38 + ├─ Wellness Center 39 + ├─ Mammalians Nurturable 40 + └─ Choreography and Merriment` 41 + 42 + console.log('\nOrg chart example:') 43 + console.log(orgChart) 44 + assert.strictEqual(orgChart, expected) 45 + }) 46 + 6 47 test('basic example', () => { 7 48 const eagan = [ 8 49 'Kier Eagan', ··· 19 60 console.log('\nBasic example:') 20 61 console.log(result) 21 62 assert.strictEqual(result, expected) 63 + 64 + const expectedPlain = `Kier Eagan 65 + ... 66 + ... 67 + Jame Eagan 68 + Helena Eagan 69 + Ambrose Eagan` 70 + const resultPlain = treeify(eagan, { plain: true }) 71 + console.log('\nBasic example (plain):') 72 + console.log(resultPlain) 73 + assert.strictEqual(resultPlain, expectedPlain) 74 + 75 + const expectedCustomChars = `Kier Eagan 76 + ├• ... 77 + │ ├• ... 78 + │ └• Jame Eagan 79 + │ └• Helena Eagan 80 + └• Ambrose Eagan` 81 + const resultCustomChars = treeify(eagan, { 82 + chars: { branch: '├• ', lastBranch: '└• ', pipe: '│ ', space: ' ' }, 83 + }) 84 + console.log('\nBasic example (custom chars):') 85 + console.log(resultCustomChars) 86 + assert.strictEqual(resultCustomChars, expectedCustomChars) 22 87 }) 23 88 24 89 test('nested example', () => {
+1 -1
tsconfig.json
··· 11 11 "forceConsistentCasingInFileNames": true, 12 12 "rootDir": "src" 13 13 }, 14 - "include": ["src"], 14 + "include": ["src/index.ts"], 15 15 "exclude": ["node_modules", "dist"] 16 16 }