terminfokit 0.1.0

Terminfo parsing, compilation, formatting, database access, and expansion
Documentation
// SPDX-FileCopyrightText: 2026 Yasunobu Sakashita
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use std::env;
use std::fmt::Write as _;
use std::fs;
use std::path::PathBuf;

fn parameters(specification: &str) -> String {
    if specification == "-" {
        return "&[]".to_owned();
    }
    let values = specification
        .split(',')
        .map(|value| match value {
            "n" => "ParameterType::Number",
            "b" => "ParameterType::Bytes",
            _ => panic!("unknown parameter type {value:?}"),
        })
        .collect::<Vec<_>>()
        .join(", ");
    format!("&[{values}]")
}

fn main() {
    println!("cargo:rerun-if-changed=capabilities.tsv");
    let input = fs::read_to_string("capabilities.tsv").expect("read capabilities.tsv");
    let mut groups = [Vec::new(), Vec::new(), Vec::new()];
    for (line_no, line) in input.lines().enumerate() {
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let fields: Vec<_> = line.split('\t').collect();
        assert_eq!(fields.len(), 6, "capabilities.tsv:{}", line_no + 1);
        let group = match fields[0] {
            "bool" => 0,
            "num" => 1,
            "str" => 2,
            _ => panic!("unknown capability kind on line {}", line_no + 1),
        };
        groups[group].push((fields[1], fields[2], fields[3], fields[4], fields[5]));
    }

    let mut output = String::from("// @generated by build.rs from capabilities.tsv\n");
    for (group, type_name) in groups.iter().zip(["BooleanCap", "NumericCap", "StringCap"]) {
        writeln!(
            output,
            "/// Generated fixed-index capability identifier.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]\npub struct {type_name}(u16);"
        )
        .unwrap();
        writeln!(output, "impl {type_name} {{").unwrap();
        writeln!(
            output,
            "    /// Number of slots in this binary section.\n    pub const COUNT: usize = {};",
            group.len()
        )
        .unwrap();
        for (index, (long, short, _, _, _)) in group.iter().enumerate() {
            writeln!(
                output,
                "    /// `{short}` (`{long}`).\n    pub const {}: Self = Self({index});",
                long.to_uppercase(),
            )
            .unwrap();
        }
        writeln!(
            output,
            "    /// All identifiers in binary-slot order.\n    pub const ALL: &'static [Self] = &["
        )
        .unwrap();
        for (long, _, _, _, _) in group {
            writeln!(output, "        Self::{},", long.to_uppercase()).unwrap();
        }
        writeln!(output, "    ];").unwrap();
        writeln!(
            output,
            "    pub(crate) const fn index(self) -> usize {{ self.0 as usize }}"
        )
        .unwrap();
        writeln!(output, "    /// Returns names, parameter types, and version metadata.\n    pub fn metadata(self) -> &'static CapabilityMetadata {{ &{}_METADATA[self.index()] }}", type_name.to_uppercase()).unwrap();
        writeln!(
            output,
            "    /// Returns the terminfo short name.\n    pub fn short_name(self) -> &'static str {{ self.metadata().short }}"
        )
        .unwrap();
        writeln!(
            output,
            "    /// Returns the descriptive terminfo name.\n    pub fn long_name(self) -> &'static str {{ self.metadata().long }}"
        )
        .unwrap();
        writeln!(
            output,
            "    /// Returns the historical termcap code when defined.\n    pub fn termcap_name(self) -> Option<&'static str> {{ self.metadata().termcap }}"
        )
        .unwrap();
        writeln!(output, "    pub(crate) fn from_index(index: usize) -> Option<Self> {{ Self::ALL.get(index).copied() }}").unwrap();
        writeln!(output, "    pub(crate) fn find_any(name: &str) -> Option<Self> {{ {}_METADATA.iter().position(|m| m.matches_any(name)).and_then(Self::from_index) }}", type_name.to_uppercase()).unwrap();
        writeln!(output, "    /// Finds the first exact name in one namespace.\n    pub fn lookup(namespace: NameNamespace, name: &str) -> Option<Self> {{ {}_METADATA.iter().position(|m| m.name(namespace) == Some(name)).and_then(Self::from_index) }}", type_name.to_uppercase()).unwrap();
        writeln!(output, "}}\n").unwrap();
        writeln!(
            output,
            "/// Metadata in compiled binary-slot order.\npub static {}_METADATA: [CapabilityMetadata; {}] = [",
            type_name.to_uppercase(),
            group.len()
        )
        .unwrap();
        for (long, short, termcap, parameter_specification, version) in group {
            let termcap = if *termcap == "-" {
                "None".to_owned()
            } else {
                format!("Some({termcap:?})")
            };
            let parameters = parameters(parameter_specification);
            let introduced = match *version {
                "system-v" => "CapabilityVersion::SystemV",
                _ => panic!("unknown capability version {version:?}"),
            };
            writeln!(
                output,
                "    CapabilityMetadata {{ long: {long:?}, short: {short:?}, termcap: {termcap}, parameters: {parameters}, introduced: {introduced} }},"
            )
            .unwrap();
        }
        writeln!(output, "];\n").unwrap();
    }

    let out = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR"));
    fs::write(out.join("caps_generated.rs"), output).expect("write generated capability table");
}