use std::collections::HashMap;
use std::fs;
use color_eyre::Result;
use serde_json::{Value, json};
use crate::models::Package;
use crate::models::dirs;
use crate::models::PACKAGE_CACHE;
pub fn show_package_info(
all_args: &[String],
show_files: bool,
show_scripts: bool,
show_store_path: bool,
) -> Result<()> {
crate::repo::sync_channel_metadata()?;
crate::io::load_installed_packages()?;
let (package_specs, filters) = parse_args_and_filters(all_args);
if package_specs.is_empty() {
println!("No package specifications provided");
return Ok(());
}
for package_spec in package_specs {
process_package_spec(
&package_spec,
&filters,
show_store_path,
show_scripts,
show_files,
)?;
}
Ok(())
}
fn parse_args_and_filters(all_args: &[String]) -> (Vec<String>, HashMap<String, String>) {
let mut package_specs = Vec::new();
let mut filters = HashMap::new();
let valid_filter_keys = ["version", "arch", "summary", "maintainer", "section", "priority", "homepage"];
for arg in all_args {
if let Some(equals_pos) = arg.find('=') {
let before_equals = &arg[..equals_pos];
let mut paren_depth = 0;
for ch in before_equals.chars() {
if ch == '(' {
paren_depth += 1;
} else if ch == ')' {
paren_depth -= 1;
}
}
let is_inside_parens = paren_depth > 0;
if is_inside_parens {
package_specs.push(arg.clone());
continue;
}
if let Some((key, val)) = arg.split_once('=') {
if valid_filter_keys.contains(&key) {
filters.insert(key.to_string(), val.to_string());
} else {
package_specs.push(arg.clone());
}
} else {
package_specs.push(arg.clone());
}
} else {
package_specs.push(arg.clone());
}
}
(package_specs, filters)
}
fn process_package_spec(
package_spec: &str,
filters: &HashMap<String, String>,
show_store_path: bool,
show_scripts: bool,
show_files: bool,
) -> Result<()> {
let parts: Vec<&str> = package_spec.split("__").collect();
let is_pkgkey = parts.len() == 3 && !parts[0].is_empty() && !parts[1].is_empty() && !parts[2].is_empty();
let mut packages = Vec::new();
if is_pkgkey {
match crate::mmio::map_pkgkey2package(package_spec) {
Ok(package) => {
packages.push(package);
}
Err(_) => {
println!("No packages found matching '{}'", package_spec);
return Ok(());
}
}
} else {
packages = crate::package_cache::map_pkgname2packages(package_spec)?;
if packages.is_empty() {
let provider_pkgnames = crate::mmio::map_provide2pkgnames(package_spec)?;
for provider_pkgname in provider_pkgnames {
let mut provider_packages = crate::package_cache::map_pkgname2packages(&provider_pkgname)?;
packages.append(&mut provider_packages);
}
}
}
let mut dedup_map = HashMap::new();
for pkg in packages {
dedup_map.insert(pkg.pkgkey.clone(), pkg);
}
packages = dedup_map.into_values().collect();
if !filters.is_empty() {
packages.retain(|pkg| apply_filters(pkg, filters));
}
if packages.is_empty() {
println!("No packages found matching '{}'", package_spec);
return Ok(());
}
for package in packages {
process_single_package(
&package,
show_store_path,
show_scripts,
show_files,
)?;
}
Ok(())
}
fn apply_filters(package: &Package, filters: &HashMap<String, String>) -> bool {
for (key, expected_value) in filters {
let actual_value = match key.as_str() {
"version" => &package.version,
"arch" => &package.arch,
"summary" => &package.summary,
"maintainer" => &package.maintainer,
"section" => package.section.as_deref().unwrap_or(""),
"priority" => package.priority.as_deref().unwrap_or(""),
"homepage" => &package.homepage,
_ => "",
};
if actual_value != expected_value {
return false;
}
}
true
}
fn process_single_package(
package: &Package,
show_store_path: bool,
show_scripts: bool,
show_files: bool,
) -> Result<()> {
let is_installed = PACKAGE_CACHE.installed_packages.read().unwrap().contains_key(&package.pkgkey);
if show_store_path {
show_store_path_info(package, is_installed)?;
return Ok(());
}
if show_scripts {
show_scripts_info(package, is_installed)?;
return Ok(());
}
if show_files {
show_files_info(package, is_installed)?;
return Ok(());
}
show_comprehensive_info(package, is_installed)?;
Ok(())
}
fn show_store_path_info(
package: &Package,
is_installed: bool,
) -> Result<()> {
if is_installed {
let installed = PACKAGE_CACHE.installed_packages.read().unwrap();
if let Some(installed_info) = installed.get(&package.pkgkey) {
let store_path = dirs().epkg_store.join(&installed_info.pkgline);
println!("{}", store_path.display());
}
} else {
println!("Package {} is not installed", package.pkgkey);
}
Ok(())
}
fn show_scripts_info(
package: &Package,
is_installed: bool,
) -> Result<()> {
if !is_installed {
println!("Package {} is not installed", package.pkgkey);
return Ok(());
}
let installed = PACKAGE_CACHE.installed_packages.read().unwrap();
if let Some(installed_info) = installed.get(&package.pkgkey) {
let scripts_path = crate::dirs::path_join(
&dirs().epkg_store.join(&installed_info.pkgline),
&["info", "install"],
);
if scripts_path.exists() {
if let Ok(entries) = fs::read_dir(&scripts_path) {
let entries: Vec<_> = entries.collect();
if !entries.is_empty() {
println!("Install scriptlets for {}:", package.pkgkey);
}
for entry_result in entries {
if let Ok(entry) = entry_result {
let file_path = entry.path();
if file_path.is_file() {
println!("=== {} ===", file_path.file_name().unwrap().to_string_lossy());
if let Ok(content) = fs::read_to_string(&file_path) {
println!("{}", content);
}
println!();
}
}
}
}
} else {
println!("No install scripts found for {}", package.pkgkey);
}
}
Ok(())
}
fn show_files_info(
package: &Package,
is_installed: bool,
) -> Result<()> {
if !is_installed {
println!("Package {} is not installed", package.pkgkey);
return Ok(());
}
let installed = PACKAGE_CACHE.installed_packages.read().unwrap();
if let Some(installed_info) = installed.get(&package.pkgkey) {
let filelist_path = crate::dirs::path_join(
&dirs().epkg_store.join(&installed_info.pkgline),
&["info", "filelist.txt"],
);
if filelist_path.exists() {
println!("Files for {}:", package.pkgkey);
if let Ok(content) = fs::read_to_string(&filelist_path) {
print!("{}", content);
}
} else {
println!("No filelist found for {}", package.pkgkey);
}
}
Ok(())
}
fn package_to_fields(package: &Package) -> std::collections::HashMap<String, String> {
let mut package_fields = std::collections::HashMap::new();
let package_json = json!(package);
if let Value::Object(map) = package_json {
for (key, value) in map.iter() {
if value.is_null() ||
(value.is_array() && value.as_array().unwrap().is_empty()) ||
(value.is_object() && value.as_object().unwrap().is_empty()) {
continue;
}
let formatted_value = match value {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
Value::Array(arr) => {
let strings: Vec<String> = arr.iter()
.filter_map(|v| {
if v.is_string() {
Some(v.as_str().unwrap().to_string())
} else {
Some(v.to_string())
}
})
.collect();
strings.join(", ")
},
Value::Object(_) => value.to_string(),
Value::Null => continue,
};
if formatted_value.is_empty() {
continue;
}
package_fields.insert(key.clone(), formatted_value);
}
}
package_fields
}
fn add_installation_info(
package_fields: &mut std::collections::HashMap<String, String>,
package: &Package,
is_installed: bool,
) {
if is_installed {
package_fields.insert("status".to_string(), "Installed".to_string());
let installed = PACKAGE_CACHE.installed_packages.read().unwrap();
if let Some(installed_info) = installed.get(&package.pkgkey) {
let store_path = dirs().epkg_store.join(&installed_info.pkgline);
package_fields.insert("storePath".to_string(), store_path.display().to_string());
package_fields.insert("dependDepth".to_string(), installed_info.depend_depth.to_string());
package_fields.insert("installTime".to_string(), installed_info.install_time.to_string());
if installed_info.ebin_exposure {
package_fields.insert("ebin".to_string(), "true".to_string());
}
}
} else {
package_fields.insert("status".to_string(), "Available".to_string());
}
}
fn show_comprehensive_info(
package: &Package,
is_installed: bool,
) -> Result<()> {
let mut package_fields = package_to_fields(package);
add_installation_info(&mut package_fields, package, is_installed);
let formatted_output = crate::store::format_package_fields(&package_fields);
print!("{}", formatted_output);
println!();
Ok(())
}