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 MullvadDriver {
    regex_country_code: Regex,
}

impl MullvadDriver {
    pub async fn new() -> MullvadDriver {
        MullvadDriver {
            regex_country_code: Regex::new("Connected to ([a-z]{2}).*, ([A-Z][a-z]*).*\n").unwrap(),
        }
    }

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

        if code.success() {
            Ok(())
        } else {
            Err(Error::new(format!(
                "mullvad command failed with nonzero status: {code:?}"
            )))
        }
    }
}

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

        let status = String::from_utf8(stdout).error("mullvad produced non-UTF8 output")?;

        if status.contains("Disconnected") {
            return Ok(Status::Disconnected { profile: None });
        } else if status.contains("Connected") {
            let (country_flag, country) = self
                .regex_country_code
                .captures_iter(&status)
                .next()
                .map(|capture| {
                    let country_code = capture[1].to_uppercase();
                    let country = capture[2].to_owned();
                    let country_flag = country_flag_from_iso_code(&country_code);
                    (Some(country_flag), Some(country))
                })
                .unwrap_or((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(())
    }
}