statusline 0.23.1

Simple and fast bash PS1 line with useful features
use crate::{Block, Color, Environment, Icon, IconMode, Pretty, Style, WithStyle as _};
use std::{
    ffi::OsStr,
    fs::File,
    io::{BufRead as _, BufReader},
    path::{Path, PathBuf},
};

pub struct Venv {
    name: String,
    version: String,
}

super::register_block!(Venv);

impl Block for Venv {
    fn new(_: &Environment) -> Option<Self> {
        let path = PathBuf::from(std::env::var("VIRTUAL_ENV").ok()?);
        Some(Venv {
            name: venv_name(&path).to_owned(),
            version: venv_ver(&path)
                .unwrap_or_default()
                .unwrap_or("<sys?>".to_owned()),
        })
    }
}

impl Pretty for Venv {
    fn pretty(&self, f: &mut std::fmt::Formatter, mode: IconMode) -> std::fmt::Result {
        f.with_style(Color::YELLOW, Style::empty(), |f| {
            write!(f, "[{} {}|{}]", self.icon(mode), self.version, self.name)
        })
    }
}

impl Icon for Venv {
    fn icon(&self, mode: IconMode) -> &'static str {
        use IconMode::*;
        match &mode {
            Text => "py",
            Icons | MinimalIcons => "",
        }
    }
}

fn venv_name(path: &Path) -> &str {
    path.ancestors()
        .filter_map(Path::file_name)
        .filter_map(OsStr::to_str)
        .find(|name| !["venv", "env", "virtualenv"].contains(name))
        .map_or("<venv>", |name| {
            ["venv", "virtualenv", "env", "-", "_"]
                .iter()
                .fold(name, |s, suf| s.strip_suffix(suf).unwrap_or(s))
        })
}

fn venv_ver(path: &Path) -> std::io::Result<Option<String>> {
    Ok(BufReader::new(File::open(path.join("pyvenv.cfg"))?)
        .lines()
        .find_map(|line| {
            Some(
                line.ok()?
                    .strip_prefix("version")?
                    .trim_start_matches(' ')
                    .strip_prefix('=')?
                    .trim_start_matches(' ')
                    .to_owned(),
            )
        }))
}