knott 0.1.4

Fast Rust package manager helper for Arch Linux repos and the AUR
Documentation
use crate::aur::AurClient;
use crate::cli::Options;
use crate::download::{build_path, sync_aur_base};
use crate::localdb::LocalDb;
use crate::output;
use crate::pacman::Toolchain;
use crate::resolver;
use crate::upgrade;

use anyhow::Result;
use std::collections::HashSet;
use std::io::{self, Write};

pub async fn sync(
    options: &Options,
    tools: &Toolchain,
    aur: &AurClient,
    targets: &[String],
    refresh: bool,
    sysupgrade: bool,
) -> Result<i32> {
    if (refresh || sysupgrade) && options.mode.repo() {
        let code = tools
            .system_upgrade(refresh, sysupgrade, options.no_confirm, options.dry_run)
            .await?;
        if code != 0 {
            return Ok(code);
        }
    }

    if targets.is_empty() {
        if sysupgrade && options.mode.aur() {
            let updates = upgrade::aur_update_targets(options, tools, aur).await?;
            if updates.is_empty() {
                return Ok(0);
            }

            println!(":: AUR packages to upgrade: {}", display_list(&updates));
            if !options.no_confirm && !confirm("Proceed with AUR upgrade? [Y/n] ")? {
                return Ok(0);
            }

            return install_targets(options, tools, aur, &updates).await;
        }

        return Ok(0);
    }

    install_targets(options, tools, aur, targets).await
}

async fn install_targets(
    options: &Options,
    tools: &Toolchain,
    aur: &AurClient,
    targets: &[String],
) -> Result<i32> {
    let mut repo_targets = Vec::new();
    let mut aur_targets = Vec::new();

    for target in targets {
        if options.mode.repo()
            && !matches!(options.mode, crate::cli::TargetMode::Aur)
            && tools.repo_has_package(target).await
        {
            repo_targets.push(target.clone());
            continue;
        }

        if options.mode.aur() {
            aur_targets.push(target.clone());
        } else {
            repo_targets.push(target.clone());
        }
    }

    if !repo_targets.is_empty() {
        let code = tools
            .install_repo_packages(
                &repo_targets,
                options.needed,
                options.no_confirm,
                false,
                options.dry_run,
            )
            .await?;
        if code != 0 {
            return Ok(code);
        }
    }

    if aur_targets.is_empty() {
        return Ok(0);
    }

    let local = LocalDb::load_default().unwrap_or_else(|_| LocalDb::empty());
    let plan = resolver::resolve(options, tools, aur, &local, &aur_targets).await?;

    if !plan.repo_deps.is_empty() {
        println!(
            ":: Repository dependencies: {}",
            display_list(&plan.repo_deps)
        );
    }
    if !plan.aur.is_empty() {
        println!(
            ":: AUR build order: {}",
            plan.aur
                .iter()
                .map(|pkg| output::inline(&pkg.name))
                .collect::<Vec<_>>()
                .join(" ")
        );
    }
    for dep in &plan.unresolved {
        eprintln!(
            "warning: dependency '{dep}' was not resolved up front; makepkg may still resolve it",
            dep = output::inline(dep)
        );
    }

    if !options.no_confirm && !confirm("Proceed with installation? [Y/n] ")? {
        return Ok(0);
    }

    if !plan.repo_deps.is_empty() {
        let code = tools
            .install_repo_packages(
                &plan.repo_deps,
                true,
                options.no_confirm,
                true,
                options.dry_run,
            )
            .await?;
        if code != 0 {
            return Ok(code);
        }
    }

    let mut built_bases = HashSet::new();
    for pkg in &plan.aur {
        if !built_bases.insert(pkg.base().to_string()) {
            continue;
        }

        let path = build_path(tools, pkg.base());
        let code = sync_aur_base(options, tools, aur, pkg.base(), &path).await?;
        if code != 0 {
            return Ok(code);
        }

        let code = tools
            .build_install_aur(
                &path,
                options.needed,
                options.no_confirm,
                options.no_check,
                options.dry_run,
            )
            .await?;
        if code != 0 {
            return Ok(code);
        }
    }

    Ok(0)
}

fn display_list(values: &[String]) -> String {
    values
        .iter()
        .map(|value| output::inline(value))
        .collect::<Vec<_>>()
        .join(" ")
}

fn confirm(prompt: &str) -> Result<bool> {
    print!("{prompt}");
    io::stdout().flush()?;

    let mut input = String::new();
    io::stdin().read_line(&mut input)?;
    let input = input.trim();
    Ok(input.is_empty() || input.eq_ignore_ascii_case("y") || input.eq_ignore_ascii_case("yes"))
}