i3status-rs 0.36.0

A feature-rich and resource-friendly replacement for i3status, written in Rust.
Documentation
use regex::Regex;
use std::process::Stdio;
use tokio::process::Command;

use crate::blocks::prelude::*;
use crate::util::country_flag_from_iso_code;

use super::{Driver, Status};

pub struct NordVpnDriver {
    regex_country_code: Regex,
}

impl NordVpnDriver {
    pub async fn new() -> NordVpnDriver {
        NordVpnDriver {
            regex_country_code: Regex::new("^.*Hostname:\\s+([a-z]{2}).*$").unwrap(),
        }
    }

    async fn run_network_command(arg: &str) -> Result<()> {
        Command::new("nordvpn")
            .args([arg])
            .stdin(Stdio::null())
            .stdout(Stdio::null())
            .spawn()
            .error(format!("Problem running nordvpn command: {arg}"))?
            .wait()
            .await
            .error(format!("Problem running nordvpn command: {arg}"))?;
        Ok(())
    }

    async fn find_line(stdout: &str, needle: &str) -> Option<String> {
        stdout
            .lines()
            .find(|s| s.contains(needle))
            .map(|s| s.to_owned())
    }
}

#[async_trait]
impl Driver for NordVpnDriver {
    async fn get_status(&self) -> Result<Status> {
        let stdout = Command::new("nordvpn")
            .args(["status"])
            .output()
            .await
            .error("Problem running nordvpn command")?
            .stdout;

        let stdout = String::from_utf8(stdout).error("nordvpn produced non-UTF8 output")?;
        let line_status = Self::find_line(&stdout, "Status:").await;
        let line_country = Self::find_line(&stdout, "Country:").await;
        let line_country_flag = Self::find_line(&stdout, "Hostname:").await;
        if line_status.is_none() {
            return Ok(Status::Error(None));
        }
        let line_status = line_status.unwrap();

        if line_status.ends_with("Disconnected") {
            return Ok(Status::Disconnected { profile: None });
        } else if line_status.ends_with("Connected") {
            let country = line_country
                .map(|country_line| country_line.rsplit(": ").next().unwrap().to_string());
            let country_flag = match line_country_flag {
                Some(country_line_flag) => self
                    .regex_country_code
                    .captures_iter(&country_line_flag)
                    .last()
                    .map(|capture| capture[1].to_owned())
                    .map(|code| code.to_uppercase())
                    .map(|code| country_flag_from_iso_code(&code)),
                None => None,
            };
            return Ok(Status::Connected {
                country,
                country_flag,
                profile: None,
            });
        }
        Ok(Status::Error(None))
    }

    async fn toggle_connection(&self, status: &Status) -> Result<()> {
        match status {
            Status::Connected { .. } => Self::run_network_command("disconnect").await?,
            Status::Disconnected { .. } => Self::run_network_command("connect").await?,
            Status::Error(_) => (),
        }
        Ok(())
    }
}