Skip to main content

AnsiML - Markup Language for ANSI Colors

AnsiML (ANSI Markup Language) uses a tree representation for inserting ANSI colors into strings. This allows for a strict separation between text and colors and thus semantic operations on formatted text. AnsiML comes with a functional, statically typed implementation in TypeScript for Deno.

deno.land mod deno.land doc tag MIT license CI Code coverage

Motivation

ANSI colors are special character sequences for adding colors to console output. In Deno you can use the ANSI colors module to directly add these codes to a piece of text. Yet, once text contains colors, changing it becomes difficult. The complexity of implementing operations on formatted text increases significantly when ANSI colors are included directly in the text. AnsiML solves this with an intermediate representation that keeps colors separate from the actual text.

Scenarios

  • You want to print ANSI formatted text with a maximum of 80 columns. If you do not parse the ANSI colors in the text while calculating the positions of the line breaks, the lines will end up shorter than they need to be.

  • You want to print syntax-highlighted source code with line numbers to the console. If you insert the line numbers into the text, syntax-highlighting for multi-line comments or string might be broken by the colors for the line numbers. But if you add the line numbers before syntax-highlighting, the highlighter will not able to parse the code.

Documentation

AnsiML exposes a TypeScript API for Deno. It represents AnsiML as a tree of terminal string values (leaf nodes) and non-terminal Node objects (internal nodes).

mod.ts

The Command type represents an ANSI color command as an array. The first element of this array is the name of a function in the ANSI colors module. The remaining elements are function arguments, for example an RGB color code:

import { Command } from "https://deno.land/x/ansiml/mod.ts";

// Simple command.
const bold: Command = ["bold"];

// Command with an argument.
const blue: Command = ["rgb24", 0x88CCFF];

// Commands are strongly typed.
// This is not a command since it has a typo.
const invalid = ["bodl"];

The Node type associates a Command array with raw text. It is either a plain string, or a tree node with children and commands. The following example defines bold, blue text:

import { Node } from "https://deno.land/x/ansiml/mod.ts";

// Strings are leaf nodes in AnsiML.
const text: Node = "some text";

// This is an internal node in AnsiML.
// The commands apply to all children.
const internal: Node = {
  commands: [["bold"], ["rgb24", 0x88CCFF]],
  children: [text],
};

The stringify function converts a Node into a string with ANSI colors. It is used to render AnsiML:

import { stringify } from "https://deno.land/x/ansiml/mod.ts";

// Render text with ANSI colors.
const ansi = stringify({
  commands: [["bold"], ["rgb24", 0x88CCFF]],
  children: ["some text"],
});

// Print bold, blue text.
console.log(ansi);

ansi.ts

The Ansi type and object both define and implement the available ANSI color commands. It defines a subset of the properties exported by the ANSI colors module:

import { Ansi } from "https://deno.land/x/ansiml/ansi.ts";

// Print bold text.
console.log(Ansi.bold("some text"));

// Print blue text.
console.log(Ansi.rgb24("some text", 0x88CCFF));

demo.ts

The demo.ts module defines a simple AnsiML example that can be run with deno:

deno run https://deno.land/x/ansiml/demo.ts

Related Work

lgram is used as the core of AnsiML.

That’s it!