navi 2.24.0

An interactive cheatsheet tool for the command-line
Documentation
use crate::prelude::*;
use crossterm::style;
use crossterm::terminal;

use std::process::Command;

const FALLBACK_WIDTH: u16 = 80;

fn width_with_shell_out() -> Result<u16> {
    let output = if cfg!(target_os = "macos") {
        Command::new("stty")
            .arg("-f")
            .arg("/dev/stderr")
            .arg("size")
            .stderr(Stdio::inherit())
            .output()?
    } else {
        Command::new("stty")
            .arg("size")
            .arg("-F")
            .arg("/dev/stderr")
            .stderr(Stdio::inherit())
            .output()?
    };

    if let Some(0) = output.status.code() {
        let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty");
        let mut data = stdout.split_whitespace();
        data.next();
        return data
            .next()
            .expect("Not enough data")
            .parse::<u16>()
            .map_err(|_| anyhow!("Invalid width"));
    }

    Err(anyhow!("Invalid status code"))
}

pub fn width() -> u16 {
    if let Ok((w, _)) = terminal::size() {
        w
    } else {
        width_with_shell_out().unwrap_or(FALLBACK_WIDTH)
    }
}

pub fn parse_ansi(ansi: &str) -> Option<style::Color> {
    style::Color::parse_ansi(&format!("5;{ansi}"))
}

#[derive(Debug, Clone)]
pub struct Color(pub style::Color);

impl FromStr for Color {
    type Err = &'static str;

    fn from_str(ansi: &str) -> Result<Self, Self::Err> {
        if let Some(c) = parse_ansi(ansi) {
            Ok(Color(c))
        } else {
            Err("Invalid color")
        }
    }
}