Sign in

    terminfo

    terminfo(5) terminal capability library in pure MoonBit: compiled binary format, source language, and tparm parameter expansion

    terminfo
    terminal
    curses
    cli
    ansi
    capability
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    7 hours ago
    Downloads
    2

    #terminfo

    A pure-MoonBit implementation of the terminfo(5) terminal capability format: the ncurses compiled binary format, the terminfo source language (terminfo.src), and a tparm(3x) parameter interpreter.

    ///|
    let entry = @terminfo.compile(@terminfo.parse_source(src), "adm3a")

    ///|
    let bytes = @terminfo.to_bytes(entry) // a compiled terminfo entry

    ///|
    let parsed = @terminfo.parse(bytes) // read it back, losslessly

    ///|
    let seq = @terminfo.tparm( // expand a capability value
    parsed.get_str("cup").unwrap(),
    [5, 10],
    )

    #API

    Three layers, all byte-exact so round-trips are lossless:

    #Compiled binary format (parse / to_bytes)

    Reads and writes ncurses compiled entries — both magic numbers (16-bit and 32-bit numbers), the extended storage section for user-defined capabilities, and entries that omit it entirely. Standard capabilities live in arrays aligned with the generated capability tables; unknown (user-defined) capabilities keep their names in ext_booleans / ext_numbers / ext_strings.

    #Source language (parse_source / compile / unescape_value)

    Parses the language that terminfo.src files are written in: name lines, comma-separated capability fields (name, name#N, name=value, name@), \E-style escapes and ^X control notation, and use= inheritance with cycle detection. compile resolves a full inheritance chain into a single Entry; earlier use= targets win, and name@ cancels inherited values. Capability fields accept long names (columns), two-character codes (cols), and termcap names (co).

    #Parameter expansion (tparm / strip_padding)

    Expands parameterized string capabilities per terminfo(5): %p1-%p9, %P/%g variables, %'c', %{nn}, %l, arithmetic/bitwise/logical operators, %i, %c, %d/%o/%x/%X with printf-style flags, %s, %%, and nested %? conditionals with %t/%e/%;. $<...> padding specs are timing information and are skipped (or removed with strip_padding).

    @terminfo.tparm(b"\x1b[%p1%{32}%+%c", [1]) // b"\x1b[!"
    @terminfo.tparm(b"%?%p1%tYES%eNO%;", [0]) // b"NO"

    #Capability tables

    caps.mbt is generated from the ncurses Caps table (MIT/X11 license), which is the source of truth for term.h. The order of each table defines the binary index of a capability in a compiled file and is fixed for SVr4 binary compatibility. Regenerate with:

    python scripts/gen_caps.py Caps > caps.mbt

    #CLI demo

    A small tput-like tool ships in cmd/main with a built-in table of common terminals:

    moon run cmd/main -- --term xterm-256color clear moon run cmd/main -- --term adm3a cup 5 10 moon run cmd/main -- --term linux setaf 4

    #Testing

    moon test # 31 tests: golden vectors from term(5), round-trips, tparm

    The golden adm3a entry quoted in the term(5) man page compiles to the exact bytes produced by tic, parses back, and round-trips byte-for-byte.

    ExpandError

    pub suberror ExpandError {
    BadPercent(String)
    StackUnderflow(String)
    Unbalanced(String)
    }

    Errors produced when expanding a parameterized capability string.

    ParseError

    pub suberror ParseError {
    BadMagic(Int)
    Truncated
    BadCount(String)
    BadOffset(String)
    CyclicUse(String)
    BadSyntax(String)
    }

    Errors produced while parsing or writing terminfo data.

    Entry

    pub struct Entry {
    names : Array[String]
    booleans : Array[Int]
    numbers : Array[Int]
    strings : Array[Bytes?]
    ext_booleans : Map[String, Int]
    ext_numbers : Map[String, Int]
    ext_strings : Map[String, Bytes]
    ext_canceled_strings : Array[String]
    }

    A parsed terminal description.

    Standard capabilities are stored in arrays aligned with bool_names, num_names and str_names, so parse/serialize round-trips are lossless even for capabilities this library does not know by name. Extended (user-defined) capabilities carry their own names and are stored in maps.

    String capabilities are raw byte strings, exactly as stored on disk: escape sequences and % parameter references are kept verbatim. Use tparm to expand the latter.

    Entry::get_bool

    fn Entry::get_bool(self : Entry, name : String) -> Bool?

    Look up a Boolean capability by long name (e.g. auto_right_margin) or two-character terminfo code (e.g. am). Returns None when the capability is absent or canceled.

    Entry::get_bool_code

    fn Entry::get_bool_code(self : Entry, code : String) -> Bool?

    Look up a Boolean capability by its two-character terminfo code (e.g. am).

    Entry::get_num

    fn Entry::get_num(self : Entry, name : String) -> Int?

    Look up a numeric capability by long name (e.g. columns) or code (e.g. cols). Returns None when absent or canceled.

    Entry::get_num_code

    fn Entry::get_num_code(self : Entry, code : String) -> Int?

    Look up a numeric capability by its two-character terminfo code (e.g. co).

    Entry::get_str

    fn Entry::get_str(self : Entry, name : String) -> Bytes?

    Look up a string capability by long name (e.g. cursor_address) or code (e.g. cup, sgr0). Returns None when absent or canceled. The value is returned verbatim: escapes resolved, % sequences and $<...> padding intact.

    Entry::get_str_code

    fn Entry::get_str_code(self : Entry, code : String) -> Bytes?

    Look up a string capability by its two-character terminfo code (e.g. AF).

    Entry::longname

    fn Entry::longname(self : Entry) -> String

    The verbose (last) name, as shown by infocmp -d style tools.

    Entry::names

    fn Entry::names(self : Entry) -> Array[String]

    Entry::primary_name

    fn Entry::primary_name(self : Entry) -> String

    The primary (first) terminal name.

    SourceEntry

    pub struct SourceEntry {
    names : Array[String]
    booleans : Map[String, Int]
    numbers : Map[String, Int]
    strings : Map[String, String]
    uses : Array[String]
    canceled : Array[String]
    }

    One entry parsed from terminfo source format (the terminfo.src language). Unlike a compiled Entry, capabilities are keyed by name directly and string values keep their source escapes (use unescape_value to expand them). Capability keys may be either long names (columns) or two-character codes (co); both resolve to the same binary index when compiling.

    bool_code_index

    let bool_code_index : Map[String, Int]

    Alternate names -> binary index lookup: the two-character terminfo code and the termcap name of each capability (e.g. cols/co for columns). Source entries may use any of the three spellings.

    bool_index

    let bool_index : Map[String, Int]

    Name -> binary index lookup for boolean capabilities.

    bool_names

    let bool_names : Array[String]

    Standard Boolean capability names, in BoolCodes order.

    compile

    fn compile(entries : Array[SourceEntry], name : String) -> Entry raise ParseError

    Compile the entry named name (and everything it inherits via use=) into an Entry ready for to_bytes. Capabilities of the entry itself override inherited ones; earlier use= targets win over later ones; name@ cancellations negate inherited values.

    empty

    fn empty() -> Entry

    An empty entry with standard-section arrays sized to the full capability tables. Used as a base when compiling source entries.

    num_code_index

    let num_code_index : Map[String, Int]

    Alternate names -> binary index lookup: the two-character terminfo code and the termcap name of each capability (e.g. cols/co for columns). Source entries may use any of the three spellings.

    num_index

    let num_index : Map[String, Int]

    Name -> binary index lookup for numeric capabilities.

    num_names

    let num_names : Array[String]

    Standard Numeric capability names, in NumCodes order.

    parse

    fn parse(bytes : Bytes) -> Entry raise ParseError

    Parse a compiled terminfo file (ncurses binary format, both magic numbers, with or without the extended storage section).

    parse_source

    fn parse_source(text : String) -> Array[SourceEntry] raise ParseError

    Parse a complete terminfo source file (the terminfo.src language) into its entries. Entries keep their raw source escapes; compile with compile to get binary-ready Entry values.

    str_code_index

    let str_code_index : Map[String, Int]

    Alternate names -> binary index lookup: the two-character terminfo code and the termcap name of each capability (e.g. cols/co for columns). Source entries may use any of the three spellings.

    str_index

    let str_index : Map[String, Int]

    Name -> binary index lookup for string capabilities.

    str_names

    let str_names : Array[String]

    Standard String capability names, in StrCodes order.

    strip_padding

    fn strip_padding(s : Bytes) -> Bytes

    Remove $<...> padding specifications from a capability value.

    Padding is timing information used by slow terminals; it is not part of the output sequence. tparm skips padding specs too, so this is only needed when emitting strings that contain no % sequences.

    to_bytes

    fn to_bytes(entry : Entry) -> Bytes raise ParseError

    Serialize an Entry to the compiled terminfo binary format.

    The output is canonical: the magic number is chosen from the widest number in the entry (0o432 for 16-bit numbers, 0o1036 for 32-bit), each section extends to its last non-absent capability, and extended capabilities are written in sorted name order.

    tparm

    fn tparm(fmt : Bytes, params : Array[Int], string_params? : Array[Bytes]) -> Bytes raise ExpandError

    Expand a parameterized terminal capability string.

    Implements the terminfo(5) stack machine: %p1-%p9, %P/%g variables (lower- and upper-case sets), %'c', %{nn}, %l, the arithmetic/bitwise/logical operators, %i, %c, %d/%o/%x/%X with printf-style flags (-+# and : for a literal -), %s, %%, and %? conditionals with %t/%e/%;. Padding specs ($<...>) are skipped, as they are timing information, not output.

    unescape_value

    fn unescape_value(raw : String) -> Bytes raise ParseError

    Expand the escape sequences of a source string value into raw bytes. Handles \E, \e, \n, \l, \r, \t, \b, \f, \s, \:, \^, \\, \,, \|, octal \NNN (up to 3 digits), and ^X control notation (^? is DEL). A backslash at end of line continues the value on the next line.