infoterm 0.1.1

ncurses-compatible terminfo parsing library
Documentation
//! Terminal descriptions.

use std::collections::HashMap;

use thiserror::Error;

mod parser;

/// Error returned when parsing an entry fails.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
#[error("parsing entry failed at byte {position}: {kind}")]
pub struct ParseError {
    /// The position in the input data at which parsing failed.
    pub position: usize,
    /// The reason parsing failed.
    pub kind: ParseErrorKind,
}

/// Type storing the different reasons for why parsing can fail.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
pub enum ParseErrorKind {
    /// The file header's magic number is incorrect.
    #[error("invalid header magic number")]
    InvalidHeader,

    /// The parser unexpectedly hit the end of file.
    #[error("unexpected end of file")]
    UnexpectedEof,

    /// A terminal or capability name contains non-ascii characters.
    #[error("name field contains non-ascii characters")]
    NonAsciiName,

    /// A user-defined capability has no name.
    #[error("user-defined capability has no name name")]
    MissingName,

    /// A capability has an invalid value.
    #[error("invalid capability of type {0:?}")]
    InvalidField(FieldType),
}

/// The type of a terminfo capability. Used with [`ParseErrorKind`].
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FieldType {
    Boolean,
    Integer,
    String
}

/// Description of a terminal and its capabilities.
///
/// Note that while this type exposes terminal names and user-defined capability names as `str`s,
/// X/Open curses only specifies ISO 8859-1:1987 ("latin-1") as the encoding, and allows any
/// graphical character from that character set to be used for names. In practice however, files
/// only use ASCII, so the parser currently rejects non-ASCII in terminal and capability names.
pub struct Entry {
    pub(crate) names: Vec<String>,
    pub(crate) bools: Vec<bool>,
    pub(crate) ints: Vec<Option<i32>>,
    pub(crate) strs: Vec<Option<Vec<u8>>>,
    pub(crate) ext_bools: HashMap<String, bool>,
    pub(crate) ext_ints: HashMap<String, Option<i32>>,
    pub(crate) ext_strs: HashMap<String, Option<Vec<u8>>>,
}

impl Entry {
    /// Parse an `Entry` from a byte slice.
    pub fn parse(bytes: &[u8]) -> Result<Self, ParseError> {
        parser::parse(bytes)
    }

    /// Returns the name of this terminal, for example `xterm-256color`.
    pub fn name(&self) -> &str {
        &*self.names[0]
    }

    /// Returns an iterator over the aliases of this terminal.
    pub fn aliases(&self) -> impl Iterator<Item = &str> + '_ {
        let n = if self.names.len() < 3 {
            &[]
        } else {
            &self.names[1..self.names.len() - 1]
        };

        n.iter().map(std::ops::Deref::deref)
    }

    /// Returns the description of this terminal, for example `xterm with 256 colors`.
    pub fn description(&self) -> Option<&str> {
        self.names.last().map(std::ops::Deref::deref)
    }

    /// Returns the boolean capability with the given index.
    ///
    /// Returns `false` if the capability is not defined for this terminal.
    pub fn get_boolean(&self, n: usize) -> bool {
        self.bools.get(n).copied().unwrap_or_default()
    }

    /// Returns the integer capability with the given index.
    ///
    /// Returns `None` if the capability is not defined for this terminal.
    pub fn get_integer(&self, n: usize) -> Option<i32> {
        self.ints.get(n).copied().unwrap_or_default()
    }

    /// Returns the string capability with the given index.
    ///
    /// Returns `None` if the capability is not defined for this terminal.
    pub fn get_string(&self, n: usize) -> Option<&[u8]> {
        self.strs.get(n)?.as_deref()
    }

    /// Returns the user-defined boolean capability with the given name.
    ///
    /// Returns `false` if the capability is not defined for this terminal.
    pub fn get_user_boolean(&self, name: &str) -> bool {
        self.ext_bools.get(name).copied().unwrap_or_default()
    }

    /// Returns the user-defined integer capability with the given name.
    ///
    /// Returns `None` if the capability is not defined for this terminal.
    pub fn get_user_integer(&self, name: &str) -> Option<i32> {
        self.ext_ints.get(name).copied().unwrap_or_default()
    }

    /// Returns the user-defined string capability with the given name.
    ///
    /// Returns `None` if the capability is not defined for this terminal.
    pub fn get_user_string(&self, name: &str) -> Option<&[u8]> {
        self.ext_strs.get(name)?.as_deref()
    }

    /// Returns an iterator over all boolean capabilities of this terminal.
    pub fn booleans(&self) -> impl Iterator<Item = bool> + '_ {
        self.bools.iter().copied()
    }

    /// Returns an iterator over all integer capabilities of this terminal.
    pub fn integers(&self) -> impl Iterator<Item = Option<i32>> + '_ {
        self.ints.iter().copied()
    }

    /// Returns an iterator over all string capabilities of this terminal.
    pub fn strings(&self) -> impl Iterator<Item = Option<&[u8]>> + '_ {
        self.strs.iter().map(Option::as_deref)
    }

    /// Returns an iterator over all user-defined boolean capabilities and their names.
    pub fn user_booleans(&self) -> impl Iterator<Item = (&str, bool)> + '_ {
        self.ext_bools.iter().map(|(n, b)| (&**n, *b))
    }

    /// Returns an iterator over all user-defined integer capabilities and their names.
    pub fn user_integers(&self) -> impl Iterator<Item = (&str, Option<i32>)> + '_ {
        self.ext_ints.iter().map(|(n, b)| (&**n, *b))
    }

    /// Returns an iterator over all user-defined string capabilities and their names.
    pub fn user_strings(&self) -> impl Iterator<Item = (&str, Option<&[u8]>)> + '_ {
        self.ext_strs.iter().map(|(n, b)| (&**n, b.as_deref()))
    }
}