paru 1.11.0

Feature packed AUR helper
use crate::config::{Colors, Config};
use crate::download::cache_info_with_warnings;
use crate::exec;
use crate::fmt::{date, opt, print_indent};
use crate::util::split_repo_aur_info;

use alpm_utils::Targ;
use ansi_term::Style;
use anyhow::Error;
use raur::ArcPackage as Package;
use term_size::dimensions_stdout;
use tr::tr;
use unicode_width::UnicodeWidthStr;

pub async fn info(conf: &mut Config, verbose: bool) -> Result<i32, Error> {
    let targets = conf.targets.clone();
    let targets = targets.iter().map(Targ::from).collect::<Vec<_>>();

    let (repo, aur) = split_repo_aur_info(conf, &targets)?;
    let mut ret = 0;

    let aur = if !aur.is_empty() {
        let color = conf.color;
        let aur = aur.iter().map(|t| t.pkg).collect::<Vec<_>>();
        let warnings =
            cache_info_with_warnings(&conf.raur, &mut conf.cache, &aur, &conf.ignore).await?;
        for pkg in &warnings.missing {
            eprintln!(
                "{} {}",
                color.error.paint("error:"),
                tr!("package '{}' was not found", pkg,),
            );
        }
        ret = !warnings.missing.is_empty() as i32;
        warnings.pkgs
    } else {
        Vec::new()
    };

    if !repo.is_empty() {
        let targets = repo.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
        let mut args = conf.pacman_args();
        args.targets.clear();
        args.targets(targets.iter().map(|t| t.as_str()));
        ret |= exec::pacman(conf, &args)?.code();
    }

    if !aur.is_empty() {
        print_aur_info(conf, verbose, &aur)?;
    }

    Ok(ret)
}

fn longest() -> usize {
    [
        tr!("Repository"),
        tr!("Name"),
        tr!("Version"),
        tr!("Description"),
        tr!("Groups"),
        tr!("Licenses"),
        tr!("Provides"),
        tr!("Depends On"),
        tr!("Make Deps"),
        tr!("Check Deps"),
        tr!("Optional Deps"),
        tr!("Conflicts With"),
        tr!("Maintainer"),
        tr!("Votes"),
        tr!("Popularity"),
        tr!("First Submitted"),
        tr!("Last Modified"),
        tr!("Out Of Date"),
        tr!("ID"),
        tr!("Package Base ID"),
        tr!("Keywords"),
        tr!("Snapshot URL"),
        "URL".to_string(),
        "AUR URL".to_string(),
    ]
    .iter()
    .map(|s| s.width())
    .max()
    .unwrap()
}

pub fn print_aur_info(conf: &mut Config, verbose: bool, pkgs: &[Package]) -> Result<(), Error> {
    let len = longest() + 3;
    let color = conf.color;
    let cols = dimensions_stdout().map(|x| x.0);
    let print = |k: &str, v: &str| print(color, len, cols, k, v);
    let print_list = |k: &str, v: &[_]| print_list(color, len, cols, k, v);
    let no = tr!("No");

    for pkg in pkgs {
        print(&tr!("Repository"), "aur");
        print(&tr!("Name"), &pkg.name);
        print(&tr!("Version"), &pkg.version);
        print(&tr!("Description"), &opt(&pkg.description));
        print("URL", &opt(&pkg.url));
        print(
            "AUR URL",
            conf.aur_url
                .join(&format!("packages/{}", pkg.package_base))?
                .as_str(),
        );
        print_list(&tr!("Groups"), &pkg.groups);
        print_list(&tr!("Licenses"), &pkg.license);
        print_list(&tr!("Provides"), &pkg.provides);
        print_list(&tr!("Depends On"), &pkg.depends);
        print_list(&tr!("Make Deps"), &pkg.make_depends);
        print_list(&tr!("Check Deps"), &pkg.check_depends);
        print_list(&tr!("Optional Deps"), &pkg.opt_depends);
        print_list(&tr!("Conflicts With"), &pkg.conflicts);
        print(&tr!("Maintainer"), &opt(&pkg.maintainer));
        print(&tr!("Votes"), &pkg.num_votes.to_string());
        print(&tr!("Popularity"), &pkg.popularity.to_string());
        print(&tr!("First Submitted"), &date(pkg.first_submitted));
        print(&tr!("Last Modified"), &date(pkg.last_modified));
        print(
            &tr!("Out Of Date"),
            pkg.out_of_date.map(date).as_deref().unwrap_or(no.as_str()),
        );

        if verbose {
            print(&tr!("ID"), &pkg.id.to_string());
            print(&tr!("Package Base ID"), &pkg.package_base_id.to_string());
            print_list(&tr!("Keywords"), &pkg.keywords);
            print(
                &tr!("Snapshot URL"),
                conf.aur_url.join(&pkg.url_path)?.as_str(),
            );
        }

        println!();
    }

    Ok(())
}

pub fn print(color: Colors, indent: usize, cols: Option<usize>, k: &str, v: &str) {
    print_info(color, false, indent, cols, k, v.split_whitespace());
}

fn print_list(color: Colors, indent: usize, cols: Option<usize>, k: &str, v: &[String]) {
    if v.is_empty() {
        print(color, indent, cols, k, &tr!("None"));
    } else {
        print_info(color, true, indent, cols, k, v.iter().map(|s| s.as_str()));
    }
}

fn print_info<'a>(
    color: Colors,
    list: bool,
    indent: usize,
    cols: Option<usize>,
    key: &str,
    value: impl IntoIterator<Item = &'a str>,
) {
    let mut prefix = key.to_string();
    for _ in 0..indent - prefix.width() - 2 {
        prefix.push(' ');
    }
    prefix.push_str(": ");
    print!("{}", color.field.paint(&prefix));

    let sep = if list { "  " } else { " " };
    print_indent(Style::new(), indent, indent, cols, sep, value)
}