use crate::pkg::{local, resolve, types};
use crate::utils;
use anyhow::{Result, anyhow};
use colored::*;
use std::fs;
fn print_dependency_group(group: &types::DependencyGroup, indent: usize) {
let prefix = " ".repeat(indent * 2);
let mut count = 0;
let required = group.required();
if !required.is_empty() {
for dep in required {
println!("{}- {} (required)", prefix, dep);
count += 1;
}
}
let options = group.options();
if !options.is_empty() {
for opt_group in options {
println!(
"{}{}: {} (choose {})",
prefix,
opt_group.name.bold(),
opt_group.desc,
if opt_group.all { "any" } else { "one" }
);
for dep in &opt_group.depends {
println!("{} - {}", prefix, dep);
count += 1;
}
}
}
let optional = group.optional();
if !optional.is_empty() {
for dep in optional {
println!("{}- {} (optional)", prefix, dep);
count += 1;
}
}
if let types::DependencyGroup::Complex(complex) = group
&& let Some(subs) = &complex.sub_packages
{
for (sub_name, sub_group) in subs {
println!("{}{}:", prefix, sub_name.bold().cyan());
print_dependency_group(sub_group, indent + 1);
count += 1;
}
}
if count == 0 {
println!("{}- {}", prefix, "None".italic());
}
}
pub fn run(source: &str, raw: bool, purl: bool) -> Result<()> {
let mut source_str = source.trim().to_string();
if purl {
println!(
"{} Fetching PURL package '{}'...",
"::".bold().blue(),
source_str
);
source_str = crate::pkg::purl::fetch_and_store_purl_package(&source_str)?;
}
let source = source_str.as_str();
let resolved_source = resolve::resolve_source(source, None, false, false)?;
if raw {
let content = fs::read_to_string(&resolved_source.path)?;
println!("{content}");
return Ok(());
}
let mut pkg: types::Package = crate::pkg::lua::parser::parse_lua_package(
resolved_source.path.to_str().ok_or_else(|| {
anyhow!(
"Path contains invalid UTF-8 characters: {:?}",
resolved_source.path
)
})?,
None,
None,
false,
)?;
if let Some(repo_name) = resolved_source.repo_name {
pkg.repo = repo_name;
}
pkg.version = Some(
resolve::get_default_version(&pkg, resolved_source.registry_handle.as_deref())
.unwrap_or_else(|_| "N/A".to_string()),
);
let request = resolve::parse_source_string(source)?;
let installed_manifest = match find_installed_manifest(&request) {
Ok(manifest) => manifest,
Err(e) => {
eprintln!("Warning: could not check installation status: {}", e);
None
}
};
print_beautiful(&pkg, installed_manifest.as_ref());
Ok(())
}
fn find_installed_manifest(
request: &crate::pkg::resolve::PackageRequest,
) -> Result<Option<types::InstallManifest>> {
let mut candidates = Vec::new();
for scope in [
types::Scope::User,
types::Scope::System,
types::Scope::Project,
] {
candidates.extend(local::find_installed_manifests_matching(request, scope)?);
}
if candidates.is_empty() {
return Ok(None);
}
let package_name = if let Some(sub) = &request.sub_package {
format!("{}:{}", request.name, sub)
} else {
request.name.clone()
};
Ok(Some(
crate::cmd::installed_select::choose_installed_manifest(&package_name, &candidates, false)?,
))
}
fn print_beautiful(
pkg: &crate::pkg::types::Package,
installed_manifest: Option<&types::InstallManifest>,
) {
let mut version_display = if pkg.epoch > 0 {
format!("{}:", pkg.epoch)
} else {
String::new()
};
version_display.push_str(pkg.version.as_deref().unwrap_or("N/A"));
if pkg.revision != "1" {
version_display = format!("{}-{}", version_display, pkg.revision);
}
let repo_display = if pkg.repo.starts_with("git/") {
format!("#git@{}", &pkg.repo[4..])
} else {
pkg.repo.clone()
};
println!(
"{} {} - {}",
pkg.name.bold().green(),
version_display.dimmed(),
repo_display
);
if let Some(website) = &pkg.website {
println!("Website: {}", website.cyan().underline());
}
if !pkg.git.is_empty() {
println!("Git Repo: {}", pkg.git.cyan().underline());
}
println!("{}", pkg.description);
if let Some(subs) = &pkg.sub_packages {
println!("{}: {}", "Sub-packages".bold(), subs.join(", "));
if let Some(main_subs) = &pkg.main_subs {
println!("{}: {}", "Main sub-packages".bold(), main_subs.join(", "));
}
}
if let Some(manifest) = installed_manifest {
let status_text = if let Some(sub) = &manifest.sub_package {
format!("Installed ({})", sub)
} else {
"Installed".to_string()
};
let installed_version_display = if manifest.revision != "1" {
format!("{}-{}", manifest.version, manifest.revision)
} else {
manifest.version.clone()
};
println!(
"{}: {} ({})",
"Status".bold(),
status_text.green(),
installed_version_display
);
} else {
println!("{}: {}", "Status".bold(), "Not Installed".red());
}
if !pkg.license.is_empty() {
println!("{}: {}", "License".bold(), pkg.license);
utils::check_license(&pkg.license);
}
let mut maintainer_line = format!(
"{}: {} <{}>",
"Maintainer".bold(),
pkg.maintainer.name,
pkg.maintainer.email
);
if let Some(website) = &pkg.maintainer.website {
maintainer_line.push_str(&format!(" - {}", website.cyan().underline()));
}
println!("{}", maintainer_line);
if let Some(author) = &pkg.author {
let mut author_line = format!("{}: {}", "Author".bold(), author.name);
if let Some(email) = &author.email {
author_line.push_str(&format!(" <{}>", email));
}
if let Some(website) = &author.website {
author_line.push_str(&format!(" - {}", website.cyan().underline()));
}
println!("{}", author_line);
}
let type_display = match pkg.package_type {
crate::pkg::types::PackageType::Package => "Package",
crate::pkg::types::PackageType::Collection => "Collection",
crate::pkg::types::PackageType::App => "App",
crate::pkg::types::PackageType::Extension => "Extension",
};
println!("{}: {}", "Type".bold(), type_display);
let scope_display = match pkg.scope {
crate::pkg::types::Scope::User => "User",
crate::pkg::types::Scope::System => "System",
crate::pkg::types::Scope::Project => "Project",
};
println!("{}: {}", "Scope".bold(), scope_display);
if !pkg.tags.is_empty() {
println!("{}: {}", "Tags".bold(), pkg.tags.join(", "));
}
if let Some(bins) = &pkg.bins
&& !bins.is_empty()
{
println!("{}: {}", "Provides".bold(), bins.join(", ").green());
}
if let Some(conflicts) = &pkg.conflicts
&& !conflicts.is_empty()
{
println!("{}: {}", "Conflicts".bold(), conflicts.join(", ").red());
}
if pkg.package_type == crate::pkg::types::PackageType::Package {
println!("{}: {}", "Available types".bold(), pkg.types.join(", "));
}
if let Some(service) = &pkg.service {
println!("\n{}:", "Service".bold());
println!(" Run: {}", service.run.cyan());
println!(
" Run at load: {}",
if service.run_at_load {
"Yes".green()
} else {
"No".yellow()
}
);
}
if let Some(deps) = &pkg.dependencies {
println!("\n{}:", "Dependencies".bold());
if let Some(runtime) = &deps.runtime {
println!(" Runtime:");
print_dependency_group(runtime, 2);
}
if let Some(build_deps) = &deps.build {
println!(" Build Dependencies:");
match build_deps {
types::BuildDependencies::Group(group) => {
print_dependency_group(group, 2);
}
types::BuildDependencies::Typed(typed_build_deps) => {
for (name, group) in &typed_build_deps.types {
println!(" {}:", name.cyan());
print_dependency_group(group, 3);
}
}
}
}
if let Some(test_deps) = &deps.test {
println!(" Test Dependencies:");
print_dependency_group(test_deps, 2);
}
}
}