npm.io
0.2.0 • Published yesterday

@rnx-kit/tools-formatting

Licence
MIT
Version
0.2.0
Deps
0
Size
40 kB
Vulns
0
Weekly
0
Stars
1.7K

@rnx-kit/tools-formatting

Build npm version

THIS TOOL IS EXPERIMENTAL — USE WITH CAUTION

Provides light-weight, zero-dependency, formatting utilities for console (or log-file) output.

Motivation

Lightweight and centralized formatting utilities.

Installation

yarn add @rnx-kit/tools-formatting --dev

or if you're using npm

npm add --save-dev @rnx-kit/tools-formatting

Usage

Text metrics

getTextMetrics measures line count and the terminal width of the widest line using JavaScript's Unicode code-point iterator, without first splitting or stripping the input. The iterator yields individual character strings rather than constructing a stripped or normalized copy of the input:

import { getTextMetrics, getTextOutput } from "@rnx-kit/tools-formatting";

getTextMetrics("\x1b[31mhello\x1b[0m\nworld");
// => { lineCount: 2, width: 5 }

getTextOutput(["first\r\nsecond", ""]);
// => { lineCount: 3, width: 6, lines: ["first", "second", ""] }

Both functions accept a string or an array of strings. Array entries and embedded LF/CRLF separators contribute separate lines; empty and trailing lines are preserved. Omitted text is one empty line, while an empty array has zero lines. getTextOutput calls getTextMetrics, then splits the original text into lines without removing ANSI styling or other content.

ANSI escape sequences, control characters, combining marks, and zero-width formatting characters do not contribute width. Wide/fullwidth characters and emoji sequences supported by Node's Unicode data occupy two columns; ambiguous-width characters occupy one. Tabs and cursor movement are ignored, not expanded or simulated, so these are text measurements rather than a terminal emulator.

Table formatting

The formatAsTable utility can format any 2D data array into a bordered table:

import { formatAsTable } from "@rnx-kit/tools-formatting";

const table = formatAsTable(
  [
    ["parse", 12, 1],
    ["bundle", 450, 1],
  ],
  {
    columns: [
      { label: "operation", align: "left" },
      { label: "total (ms)", align: "right", digits: 0, localeFmt: true },
      { label: "calls", align: "right" },
    ],
    sort: [1],
  }
);
console.log(table);
Tree formatting

formatAsTree assembles a header and a list of pre-formatted rows into a tree-shaped string. It is a pure formatter — no buffering, no console output, no styling — so callers stay in control of how rows are produced and styled.

import { formatAsTree } from "@rnx-kit/tools-formatting";

const report = formatAsTree("Found problems in package.json", [
  'missing field "license"',
  "version is invalid",
  "homepage is empty",
]);
console.log(report);
// Found problems in package.json
// ├── missing field "license"
// ├── version is invalid
// └── homepage is empty

Rows that contain \n are expanded into multiple output lines, with the trunk character preserved on continuation lines so the structure stays readable:

formatAsTree("Type errors", [
  'src/foo.ts:12\n  Type "string" is not assignable to type "number"',
  'src/bar.ts:7\n  Cannot find name "baz"',
]);
// Type errors
// ├── src/foo.ts:12
// │     Type "string" is not assignable to type "number"
// └── src/bar.ts:7
//       Cannot find name "baz"

For terminals without unicode support, set asciiOnly:

formatAsTree("Header", ["a", "b"], { asciiOnly: true });
// Header
// +-- a
// `-- b

Or supply a fully custom set of branch characters via treeParts:

formatAsTree("Header", ["a", "b"], {
  treeParts: {
    row: ["* ", "  "], // [first-line prefix, multi-line continuation]
    last: ["> ", "  "],
  },
});

indent (number of spaces or a literal string) is prepended to every row line, including continuations, while leaving the header column flush:

formatAsTree("Header", ["a", "b"], { indent: 2 });
// Header
//   ├── a
//   └── b

The result never has a trailing newline, so it composes cleanly with whatever emits it.

Path shortening

shortenPath truncates file paths for display, keeping the most significant trailing segments and replacing the rest with an ellipsis. This is useful for tables or logs where long absolute paths waste space.

import { shortenPath } from "@rnx-kit/tools-formatting";

shortenPath(
  "/Users/me/dev/rnx-kit/packages/metro-resolver-symlinks/src/resolver.ts"
);
// => ".../metro-resolver-symlinks/src/resolver.ts"

By default it keeps 3 path segments. If the segment at the cut boundary is a known source directory (src, lib, dist, bin), it keeps one extra segment so the parent package name stays visible:

shortenPath("/Users/me/dev/rnx-kit/packages/my-package/src/utils/helpers.ts");
// => ".../my-package/src/utils/helpers.ts"  (4 segments)

Short paths are returned unchanged when shortening would not save space. The segment count can be customized:

shortenPath("/a/b/c/d/e.ts", 2);
// => ".../d/e.ts"

API Reference

Functions
Function Description
formatAsTable(data, opts?) Format a 2D data array into a bordered ASCII table.
formatAsTree(header, rows, opts?) Format a header and a list of rows into a tree-shaped string.
getTextMetrics(text?) Measure line count and maximum terminal width using Unicode code-point iteration.
getTextOutput(text?) Return text metrics and LF/CRLF-split lines, preserving styling and empty lines.
shortenPath(path, segs?) Shorten a file path to the last segs segments (default 3), with ... prefix.
TextMetrics and TextOutput
Field Type Description
lineCount number Number of lines, including empty and trailing lines.
width number Terminal width of the widest line.
lines string[] Original text split into lines (TextOutput only).
TableOptions
Field Type Default Description
columns (string | ColumnOptions)[] auto Column labels or configuration objects.
sort number[] none Column indices to sort by, in precedence order.
showIndex boolean false Show a row index column.
asciiOnly boolean false Use ASCII-only border characters instead of unicode box drawing.
tableParts TableViewParts -- Fully override the border characters. Takes precedence over asciiOnly.
noColors boolean false Strip ANSI styling from output.
ColumnOptions
Field Type Default Description
label string auto Column header label.
format (value) => string -- Convert a cell value to a string. Defaults to String.
digits number -- Fixed decimal places for numeric values.
localeFmt boolean false Use locale number formatting.
align "left"|"right"|"center" "left" Cell text alignment.
maxWidth number -- Maximum column width (truncates with ...).
style StyleValue | function -- ANSI style or custom formatter.
TreeFormattingOptions
Field Type Default Description
asciiOnly boolean false Use ASCII-only branch characters (+-- / `-- ) instead of unicode.
treeParts TreeViewParts -- Fully override the branch characters. Takes precedence over asciiOnly.
indent number | string none Prepend this many spaces (number) or this exact string to every row line.
TreeViewParts

Describes the branch glyphs used for each row as [first-line prefix, continuation prefix]. The continuation prefix is used when a row's text contains \n. All four prefixes should have the same width.

Field Type Description
row [string, string] Prefixes for any non-last row (e.g. ["├── ", "│ "]).
last [string, string] Prefixes for the final row (e.g. ["└── ", " "]).