flk 0.6.3

A CLI tool for managing flake.nix devShell environments
Documentation
//! # Search Command Handler
//!
//! Search nixpkgs for packages using the nix-versions tool.

use anyhow::{bail, Context, Result};
use colored::Colorize;

use crate::nix::{check_nix_available, run_nix_command};
use flk::flake::interfaces::profiles::Package;
use flk::flake::parsers::packages::extract_packages_from_output;
use flk::utils::visual::{display_list, display_table, with_spinner};

/// Search nixpkgs for packages matching the query.
///
/// # Arguments
///
/// * `query` - Search term (supports wildcards)
/// * `limit` - Maximum number of results to display
///
/// # Returns
///
/// `true` if packages were found, `false` otherwise.
pub fn run_search(query: &str, limit: usize) -> Result<bool> {
    println!(
        "{} Searching nixpkgs for: {}",
        "".blue().bold(),
        query.green()
    );
    let search_query = format!("*{}*", query);

    if !check_nix_available() {
        bail!("Nix command is not available, is it installed on the system?");
    }

    // Use nix search command with JSON output
    let (stdout, _, _) = with_spinner("Searching packages...", || {
        run_nix_command(&[
            "run",
            "github:vic/nix-versions",
            &search_query,
            "--",
            "--one",
        ])
        .context("Failed to execute nix search. Is nix installed?")
    })?;

    let packages: Vec<Package> =
        extract_packages_from_output(&stdout).context("Failed to parse nix search output")?;

    if packages.is_empty() {
        println!(
            "{} No packages found for query '{}'",
            "".red().bold(),
            query
        );
        return Ok(false);
    }

    println!(
        "\n{} Found {} package(s) (showing {}):\n",
        "".green().bold(),
        packages.len(),
        packages.len()
    );

    println!("{}", display_list(&packages[..packages.len().min(limit)]));

    Ok(true)
}

/// Get detailed version information for a specific package.
///
/// Shows all available versions of a package in nixpkgs.
pub fn run_deep_search(package: &str) -> Result<()> {
    println!(
        "{} Getting details for: {}",
        "".blue().bold(),
        package.green()
    );

    if !check_nix_available() {
        bail!("Nix is not available!")
    }
    let (stdout, _, _) = with_spinner("Searching packages...", || {
        run_nix_command(&["run", "github:vic/nix-versions", package, "--", "--all"])
            .context("Failed to execute nix search. Is nix installed?")
    })?;

    let packages: Vec<Package> =
        extract_packages_from_output(&stdout).context("Failed to parse nix search output")?;
    if packages.is_empty() {
        println!("{} No packages found for '{}'", "".red().bold(), package);
        return Ok(());
    }

    println!("\n{}", "Search Results:".bold().underline());
    println!("{}", display_table(&packages));

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::nix::with_nix_runner;

    // nix-versions prints a header line plus `name version system description`.
    const SAMPLE_OUTPUT: &str = "\
Name Version System Description
ripgrep 14.1.0 x86_64-linux Fast grep replacement
ripgrep-all 0.10.0 x86_64-linux Grep across many file formats
";

    #[test]
    fn run_search_returns_true_when_packages_found() {
        with_nix_runner(
            |args| {
                assert_eq!(args[0], "run");
                assert_eq!(args[1], "github:vic/nix-versions");
                assert_eq!(args[2], "*ripgrep*");
                Ok((SAMPLE_OUTPUT.into(), String::new(), true))
            },
            || {
                let found = run_search("ripgrep", 10).unwrap();
                assert!(found);
            },
        );
    }

    #[test]
    fn run_search_returns_false_when_no_packages() {
        with_nix_runner(
            |_| {
                Ok((
                    "Name Version System Description\n".into(),
                    String::new(),
                    true,
                ))
            },
            || {
                let found = run_search("nope-nothing-here", 10).unwrap();
                assert!(!found);
            },
        );
    }

    #[test]
    fn run_search_propagates_nix_failure() {
        with_nix_runner(
            |_| Err(anyhow::anyhow!("nix exploded")),
            || {
                let err = run_search("anything", 5).unwrap_err();
                assert!(err.to_string().contains("nix search"));
            },
        );
    }

    #[test]
    fn run_deep_search_handles_empty_results() {
        with_nix_runner(
            |args| {
                assert_eq!(
                    args,
                    &["run", "github:vic/nix-versions", "ripgrep", "--", "--all"]
                );
                Ok((
                    "Name Version System Description\n".into(),
                    String::new(),
                    true,
                ))
            },
            || run_deep_search("ripgrep").unwrap(),
        );
    }
}