auraline 0.5.0

utility that renders information into a string suitable to be embedded in the shell prompt.
use colored::ColoredString;
use futures::future::join_all;
use std::future::Future;
use std::io::stdout;
use tokio::task::JoinError;

use crate::git::git_ahead_behind_icon;
use crate::git::git_branch_icon;
use crate::git::git_branch_name;
use crate::git::git_commit_name;
use crate::git::git_describe;
use crate::git::git_stash_counter;
use crate::git::git_status_icon;
use crate::git::git_worktree;
use crate::netns::net_namespace;

use crate::color::ColorizeExt;
use crate::Options;

use std::io::Write;

macro_rules! item {
    ($e:expr) => {
        tokio::spawn(async move { $e.map(Into::into) })
    };
    ($e:expr, $c:expr) => {{
        let color = $c.clone();
        tokio::spawn(async move { $e.color(color) })
    }};
}

pub async fn build_prompt(opts: Options) -> Result<Vec<ColoredString>, JoinError> {
    with_path(&opts.path, async move {
        let prompt = [
            item! { net_namespace().await, opts.theme },
            item! { git_branch_icon().await },
            item! { git_status_icon().await, opts.theme },
            item! { git_stash_counter().await },
            item! { git_worktree().await.bold() },
            item! { git_branch_name().await.bold(), opts.theme },
            item! { git_commit_name(opts.fast).await.bold() },
            item! { git_describe(opts.fast).await.bold() },
            item! { git_ahead_behind_icon().await },
        ];

        let parts = join_all(prompt).await;

        Ok(parts
            .into_iter()
            .collect::<Result<Vec<_>, _>>()?
            .iter()
            .filter_map(|x| x.clone())
            .collect::<Vec<_>>())
    })
    .await
}

pub async fn print_prompt(parts: Vec<ColoredString>) -> Result<(), JoinError> {
    colored::control::set_override(true);
    let stdout = stdout();
    let mut handle = stdout.lock();

    for (i, part) in parts.iter().enumerate() {
        write!(handle, "{}", part).unwrap();
        if i < parts.len() - 1 {
            write!(handle, " ").unwrap();
        }
    }

    Ok(())
}

async fn with_path<F>(path: &Option<String>, action: F) -> Result<Vec<ColoredString>, JoinError>
where
    F: Future<Output = Result<Vec<ColoredString>, JoinError>>,
{
    match path {
        Some(p) => {
            let cur = std::env::current_dir().unwrap();
            std::env::set_current_dir(p).expect("could not change directory");
            let result = action.await;
            std::env::set_current_dir(cur).expect("could not change directory");
            result
        }
        None => action.await,
    }
}