Filemedium importancesource

treeify.ts

utils/treeify.ts

No strong subsystem tag
171
Lines
5033
Bytes
3
Exports
3
Imports
10
Keywords

What this is

This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.

Beginner explanation

This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.

How it is used

Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.

Expert explanation

Architecturally, this file intersects with general runtime concerns. It contains 171 lines, 3 detected imports, and 3 detected exports.

Important relationships

Detected exports

  • TreeNode
  • TreeifyOptions
  • treeify

Keywords

treecharcolorslinekeyscolorizenodelinespushcolorthemetypeof

Detected imports

  • figures
  • ../components/design-system/color.js
  • ./theme.js

Source notes

This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.

Open parent directory

Full source

import figures from 'figures'
import { color } from '../components/design-system/color.js'
import type { Theme, ThemeName } from './theme.js'

export type TreeNode = {
  [key: string]: TreeNode | string | undefined
}

export type TreeifyOptions = {
  showValues?: boolean
  hideFunctions?: boolean
  useColors?: boolean
  themeName?: ThemeName
  treeCharColors?: {
    treeChar?: keyof Theme // Color for tree characters (├ └ │)
    key?: keyof Theme // Color for property names
    value?: keyof Theme // Color for values
  }
}

type TreeCharacters = {
  branch: string
  lastBranch: string
  line: string
  empty: string
}

const DEFAULT_TREE_CHARS: TreeCharacters = {
  branch: figures.lineUpDownRight, // '├'
  lastBranch: figures.lineUpRight, // '└'
  line: figures.lineVertical, // '│'
  empty: ' ',
}

/**
 * Custom treeify implementation with Ink theme color support
 * Based on https://github.com/notatestuser/treeify
 */
export function treeify(obj: TreeNode, options: TreeifyOptions = {}): string {
  const {
    showValues = true,
    hideFunctions = false,
    themeName = 'dark',
    treeCharColors = {},
  } = options

  const lines: string[] = []
  const visited = new WeakSet<object>()

  function colorize(text: string, colorKey?: keyof Theme): string {
    if (!colorKey) return text
    return color(colorKey, themeName)(text)
  }

  function growBranch(
    node: TreeNode | string,
    prefix: string,
    _isLast: boolean,
    depth: number = 0,
  ): void {
    if (typeof node === 'string') {
      lines.push(prefix + colorize(node, treeCharColors.value))
      return
    }

    if (typeof node !== 'object' || node === null) {
      if (showValues) {
        const valueStr = String(node)
        lines.push(prefix + colorize(valueStr, treeCharColors.value))
      }
      return
    }

    // Check for circular references
    if (visited.has(node)) {
      lines.push(prefix + colorize('[Circular]', treeCharColors.value))
      return
    }
    visited.add(node)

    const keys = Object.keys(node).filter(key => {
      const value = node[key]
      if (hideFunctions && typeof value === 'function') return false
      return true
    })

    keys.forEach((key, index) => {
      const value = node[key]
      const isLastKey = index === keys.length - 1
      const nodePrefix = depth === 0 && index === 0 ? '' : prefix

      // Determine which tree character to use
      const treeChar = isLastKey
        ? DEFAULT_TREE_CHARS.lastBranch
        : DEFAULT_TREE_CHARS.branch
      const coloredTreeChar = colorize(treeChar, treeCharColors.treeChar)
      const coloredKey =
        key.trim() === '' ? '' : colorize(key, treeCharColors.key)

      let line =
        nodePrefix + coloredTreeChar + (coloredKey ? ' ' + coloredKey : '')

      // Check if we should add a colon (not for empty/whitespace keys)
      const shouldAddColon = key.trim() !== ''

      // Check for circular reference before recursing
      if (value && typeof value === 'object' && visited.has(value)) {
        const coloredValue = colorize('[Circular]', treeCharColors.value)
        lines.push(
          line + (shouldAddColon ? ': ' : line ? ' ' : '') + coloredValue,
        )
      } else if (value && typeof value === 'object' && !Array.isArray(value)) {
        lines.push(line)
        // Calculate the continuation prefix for nested items
        const continuationChar = isLastKey
          ? DEFAULT_TREE_CHARS.empty
          : DEFAULT_TREE_CHARS.line
        const coloredContinuation = colorize(
          continuationChar,
          treeCharColors.treeChar,
        )
        const nextPrefix = nodePrefix + coloredContinuation + ' '
        growBranch(value, nextPrefix, isLastKey, depth + 1)
      } else if (Array.isArray(value)) {
        // Handle arrays
        lines.push(
          line +
            (shouldAddColon ? ': ' : line ? ' ' : '') +
            '[Array(' +
            value.length +
            ')]',
        )
      } else if (showValues) {
        // Add value if showValues is true
        const valueStr =
          typeof value === 'function' ? '[Function]' : String(value)
        const coloredValue = colorize(valueStr, treeCharColors.value)
        line += (shouldAddColon ? ': ' : line ? ' ' : '') + coloredValue
        lines.push(line)
      } else {
        lines.push(line)
      }
    })
  }

  // Start growing the tree
  const keys = Object.keys(obj)
  if (keys.length === 0) {
    return colorize('(empty)', treeCharColors.value)
  }

  // Special case for single empty/whitespace string key
  if (
    keys.length === 1 &&
    keys[0] !== undefined &&
    keys[0].trim() === '' &&
    typeof obj[keys[0]] === 'string'
  ) {
    const firstKey = keys[0]
    const coloredTreeChar = colorize(
      DEFAULT_TREE_CHARS.lastBranch,
      treeCharColors.treeChar,
    )
    const coloredValue = colorize(obj[firstKey] as string, treeCharColors.value)
    return coloredTreeChar + ' ' + coloredValue
  }

  growBranch(obj, '', true)
  return lines.join('\n')
}