0.0.0-alpha.3 • Published 6 months ago

@suin/printree v0.0.0-alpha.3

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

printree

A flexible TypeScript library for rendering tree structures as ASCII art.

Installation

npm install @suin/printree

Features

  • 🌳 Render any tree structure as ASCII art
  • šŸŽØ Fully customizable node formatting
  • šŸ·ļø Support for labeled nodes
  • šŸ“Š Optional node indices and children count
  • šŸ“ Perfect for directory trees, JSON AST, and more
  • šŸ’Ŗ Written in TypeScript with full type safety

Usage

Basic Example

import { format } from "@suin/printree";

// Define your tree node types
type Node = Parent | Leaf;
type Parent = { kind: string; children: Node[] };
type Leaf = { kind: string; value: string };

// Create your tree
const tree: Node = {
  kind: "root",
  children: [
    {
      kind: "node",
      children: [
        { kind: "leaf", value: "Hello" },
        { kind: "leaf", value: "World!" },
      ],
    },
  ],
};

// Format it!
const result = format(tree, {
  mapping: {
    getChildren: node => "children" in node ? node.children : undefined,
    toText: node => {
      let text = node.kind;
      if ("value" in node) {
        text += `: ${JSON.stringify(node.value)}`;
      }
      return text;
    },
  },
});

console.log(result);

Output:

root
└─ node
   ā”œā”€ leaf: "Hello"
   └─ leaf: "World!"

Directory Tree Example

type Entry = Directory | File;
type Directory = { type: "directory"; name: string; children: Entry[] };
type File = { type: "file"; name: string };

const tree: Entry = {
  type: "directory",
  name: ".",
  children: [
    {
      type: "directory",
      name: "src",
      children: [
        { type: "file", name: "index.ts" },
        { type: "file", name: "utils.ts" },
      ],
    },
    {
      type: "directory",
      name: "test",
      children: [
        { type: "file", name: "index.test.ts" },
        { type: "file", name: "utils.test.ts" },
      ],
    },
  ],
};

const result = format(tree, {
  mapping: {
    getChildren: node => node.type === "directory" ? node.children : undefined,
    toText: node => node.name,
  },
});

console.log(result);

Output:

.
ā”œā”€ src
│  ā”œā”€ index.ts
│  └─ utils.ts
└─ test
   ā”œā”€ index.test.ts
   └─ utils.test.ts

JSON AST Example

type Node = ObjectNode | ArrayNode | ValueNode;
type ObjectNode = { type: "Object"; properties: Record<string, Node> };
type ArrayNode = { type: "Array"; elements: Node[] };
type ValueNode = { type: "Scalar"; value: string };

const ast: Node = {
  type: "Object",
  properties: {
    hello: { type: "Scalar", value: "world" },
    nested: {
      type: "Object",
      properties: {
        array: {
          type: "Array",
          elements: [
            { type: "Scalar", value: "one" },
            { type: "Scalar", value: "two" },
          ],
        },
      },
    },
  },
};

const result = format(ast, {
  mapping: {
    getChildren: node => {
      if (node.type === "Array") {
        return node.elements;
      }
      if (node.type === "Object") {
        return Object.entries(node.properties).map(([name, node]) => ({
          name,
          node,
        }));
      }
      return undefined;
    },
    toText: (node, { name }) => {
      const propertyName = name ? `${name}: ` : "";
      if (node.type === "Scalar") {
        return `${propertyName}${JSON.stringify(node.value)}`;
      }
      return `${propertyName}${node.type}`;
    },
  },
});

console.log(result);

Output:

Object
ā”œā”€ hello: "world"
└─ nested: Object
   └─ array: Array
      ā”œā”€ "one"
      └─ "two"

Advanced Features

Node Indices

const result = format(tree, {
  mapping: {
    getChildren: node => "children" in node ? node.children : undefined,
    toText: (node, { index }) => {
      let text = node.kind;
      if ("value" in node) {
        text += `: ${JSON.stringify(node.value)}`;
      }
      return index !== undefined ? `${index} ${text}` : text;
    },
  },
});

Output:

0 root
ā”œā”€ 0 node
│  ā”œā”€ 0 leaf: "Hello"
│  └─ 1 leaf: "World!"
└─ 1 node
   ā”œā”€ 0 leaf: "Bonjour"
   └─ 1 leaf: "le monde!"

Children Count

const result = format(tree, {
  mapping: {
    getChildren: node => "children" in node ? node.children : undefined,
    toText: (node, { children }) => {
      let text = node.kind;
      if (children) {
        text += `[${children.length}]`;
      }
      if ("value" in node) {
        text += `: ${JSON.stringify(node.value)}`;
      }
      return text;
    },
  },
});

Output:

root[2]
ā”œā”€ node[2]
│  ā”œā”€ leaf: "Hello"
│  └─ leaf: "World!"
└─ node[2]
   ā”œā”€ leaf: "Bonjour"
   └─ leaf: "le monde!"

API Reference

format<T>(input: Input<T>, options: Options<T>): string

Formats a tree structure into a human-readable ASCII tree representation.

Options

interface Options<T> {
  /** Mapping configuration for transforming the input tree */
  readonly mapping: Mapping<T>;
  /** Optional rendering configuration for customizing the output appearance */
  readonly rendering?: undefined | RenderingOptions;
}

interface Mapping<T> {
  /** Get children of a node (return undefined for leaf nodes) */
  getChildren(node: T): undefined | Children<T>;
  /** Generate text representation of a node */
  toText(node: T, context: Context<T>): string;
}

interface Context<T> {
  /** Optional name for labeled nodes */
  readonly name?: undefined | string;
  /** Index of the node among its siblings */
  readonly index: number;
  /** Children of the node if any */
  readonly children?: undefined | ReadonlyArray<T | ReadonlyArray<T>>;
}

Architecture

Data Flow

flowchart LR
    input["Input Tree<br>(Any Structure)"]
    transformed["Transformed Tree<br>(Rendering Data model)"]
    rendered["Rendered Tree<br>(ASCII Art String)"]

    input -- transform --> transformed
    transformed -- render --> rendered

Module Structure

classDiagram
    class format {
        +format<T>(input, options)
    }
    class transform {
        +transform<T>(input, mapping)
    }
    class render {
        +render(input, options?)
    }

    format ..> transform : uses
    format ..> render : uses

License

MIT

Author

suin