1use std::fmt::Write;
4use std::fs;
5
6use anyhow::{Result, anyhow};
7use colored::Colorize;
8
9use crate::pkg::{local, resolve, types};
10use crate::utils;
11
12fn print_dependency_group(group: &types::DependencyGroup, indent: usize) {
14 let prefix = " ".repeat(indent * 2);
15 let mut count = 0;
16
17 let required = group.required();
18 if !required.is_empty() {
19 for dep in required {
20 println!("{prefix}- {dep} (required)");
21 count += 1;
22 }
23 }
24
25 let options = group.options();
26 if !options.is_empty() {
27 for opt_group in options {
28 println!(
29 "{}{}: {} (choose {})",
30 prefix,
31 opt_group.name.bold(),
32 opt_group.desc,
33 if opt_group.all { "any" } else { "one" }
34 );
35 for dep in &opt_group.depends {
36 println!("{prefix} - {dep}");
37 count += 1;
38 }
39 }
40 }
41
42 let optional = group.optional();
43 if !optional.is_empty() {
44 for dep in optional {
45 println!("{prefix}- {dep} (optional)");
46 count += 1;
47 }
48 }
49
50 if let types::DependencyGroup::Complex(complex) = group
51 && let Some(subs) = &complex.sub_packages
52 {
53 for (sub_name, sub_group) in subs {
54 println!("{}{}:", prefix, sub_name.bold().cyan());
55 print_dependency_group(sub_group, indent + 1);
56 count += 1;
57 }
58 }
59
60 if count == 0 {
61 println!("{}- {}", prefix, "None".italic());
62 }
63}
64
65pub fn run(source: &str, raw: bool, purl: bool) -> Result<()> {
78 let mut source_str = source.trim().to_string();
79 if purl {
80 println!(
81 "{} Fetching PURL package '{}'...",
82 "::".bold().blue(),
83 source_str
84 );
85 source_str =
86 crate::pkg::purl::fetch_and_store_purl_package(&source_str)?;
87 }
88 let source = source_str.as_str();
89 let resolved_source = resolve::resolve_source(source, None, false, false)?;
90
91 if raw {
92 let content = fs::read_to_string(&resolved_source.path)?;
93 println!("{content}");
94 return Ok(());
95 }
96 let mut pkg: types::Package = crate::pkg::lua::parser::parse_lua_package(
97 resolved_source.path.to_str().ok_or_else(|| {
98 anyhow!(
99 "Path contains invalid UTF-8 characters: {}",
100 resolved_source.path.display()
101 )
102 })?,
103 None,
104 None,
105 false
106 )?;
107 if let Some(repo_name) = resolved_source.repo_name {
108 pkg.repo = repo_name;
109 }
110 pkg.version = Some(
111 resolve::get_default_version(
112 &pkg,
113 resolved_source.registry_handle.as_deref()
114 )
115 .unwrap_or_else(|_| "N/A".to_string())
116 );
117
118 let request = resolve::parse_source_string(source)?;
119 let installed_manifest = match find_installed_manifest(&request) {
120 Ok(manifest) => manifest,
121 Err(e) => {
122 eprintln!("Warning: could not check installation status: {e}");
123 None
124 }
125 };
126
127 print_beautiful(&pkg, installed_manifest.as_ref());
128 Ok(())
129}
130
131fn find_installed_manifest(
133 request: &crate::pkg::resolve::PackageRequest
134) -> Result<Option<types::InstallManifest>> {
135 let mut candidates = Vec::new();
136 for scope in [
137 types::Scope::User,
138 types::Scope::System,
139 types::Scope::Project
140 ] {
141 candidates
142 .extend(local::find_installed_manifests_matching(request, scope)?);
143 }
144
145 if candidates.is_empty() {
146 return Ok(None);
147 }
148
149 let package_name = if let Some(sub) = &request.sub_package {
150 format!("{}:{}", request.name, sub)
151 } else {
152 request.name.clone()
153 };
154 Ok(Some(
155 crate::cmd::installed_select::choose_installed_manifest(
156 &package_name,
157 &candidates,
158 false
159 )?
160 ))
161}
162
163fn print_beautiful(
165 pkg: &crate::pkg::types::Package,
166 installed_manifest: Option<&types::InstallManifest>
167) {
168 let mut version_display = if pkg.epoch > 0 {
169 format!("{}:", pkg.epoch)
170 } else {
171 String::new()
172 };
173
174 version_display.push_str(pkg.version.as_deref().unwrap_or("N/A"));
175
176 if pkg.revision != "1" {
177 version_display = format!("{}-{}", version_display, pkg.revision);
178 }
179
180 let repo_display = if pkg.repo.starts_with("git/") {
181 format!("#git@{}", &pkg.repo[4..])
182 } else {
183 pkg.repo.clone()
184 };
185
186 println!(
187 "{} {} - {}",
188 pkg.name.bold().green(),
189 version_display.dimmed(),
190 repo_display
191 );
192 if let Some(website) = &pkg.website {
193 println!("Website: {}", website.cyan().underline());
194 }
195 if !pkg.git.is_empty() {
196 println!("Git Repo: {}", pkg.git.cyan().underline());
197 }
198 println!("{}", pkg.description);
199
200 if let Some(subs) = &pkg.sub_packages {
201 println!("{}: {}", "Sub-packages".bold(), subs.join(", "));
202 if let Some(main_subs) = &pkg.main_subs {
203 println!(
204 "{}: {}",
205 "Main sub-packages".bold(),
206 main_subs.join(", ")
207 );
208 }
209 }
210
211 if let Some(manifest) = installed_manifest {
212 let status_text = if let Some(sub) = &manifest.sub_package {
213 format!("Installed ({sub})")
214 } else {
215 "Installed".to_string()
216 };
217
218 let installed_version_display = if manifest.revision == "1" {
219 manifest.version.clone()
220 } else {
221 format!("{}-{}", manifest.version, manifest.revision)
222 };
223
224 println!(
225 "{}: {} ({})",
226 "Status".bold(),
227 status_text.green(),
228 installed_version_display
229 );
230 } else {
231 println!("{}: {}", "Status".bold(), "Not Installed".red());
232 }
233
234 if !pkg.license.is_empty() {
235 println!("{}: {}", "License".bold(), pkg.license);
236 utils::check_license(&pkg.license);
237 }
238
239 let mut maintainer_line = format!(
240 "{}: {} <{}>",
241 "Maintainer".bold(),
242 pkg.maintainer.name,
243 pkg.maintainer.email
244 );
245 if let Some(website) = &pkg.maintainer.website {
246 let _ = write!(maintainer_line, " - {}", website.cyan().underline());
247 }
248 println!("{maintainer_line}");
249
250 if let Some(author) = &pkg.author {
251 let mut author_line = format!("{}: {}", "Author".bold(), author.name);
252 if let Some(email) = &author.email {
253 let _ = write!(author_line, " <{email}>");
254 }
255 if let Some(website) = &author.website {
256 let _ = write!(author_line, " - {}", website.cyan().underline());
257 }
258 println!("{author_line}");
259 }
260
261 let type_display = match pkg.package_type {
262 crate::pkg::types::PackageType::Package => "Package",
263 crate::pkg::types::PackageType::Collection => "Collection",
264 crate::pkg::types::PackageType::App => "App",
265 crate::pkg::types::PackageType::Extension => "Extension"
266 };
267 println!("{}: {}", "Type".bold(), type_display);
268
269 let scope_display = match pkg.scope {
270 crate::pkg::types::Scope::User => "User",
271 crate::pkg::types::Scope::System => "System",
272 crate::pkg::types::Scope::Project => "Project"
273 };
274 println!("{}: {}", "Scope".bold(), scope_display);
275
276 if !pkg.tags.is_empty() {
277 println!("{}: {}", "Tags".bold(), pkg.tags.join(", "));
278 }
279
280 if let Some(bins) = &pkg.bins
281 && !bins.is_empty()
282 {
283 println!("{}: {}", "Provides".bold(), bins.join(", ").green());
284 }
285
286 if let Some(conflicts) = &pkg.conflicts
287 && !conflicts.is_empty()
288 {
289 println!("{}: {}", "Conflicts".bold(), conflicts.join(", ").red());
290 }
291
292 if pkg.package_type == crate::pkg::types::PackageType::Package {
293 println!("{}: {}", "Available types".bold(), pkg.types.join(", "));
294 }
295
296 if let Some(service) = &pkg.service {
297 println!("\n{}:", "Service".bold());
298 println!(" Run: {}", service.run.cyan());
299 println!(
300 " Run at load: {}",
301 if service.run_at_load {
302 "Yes".green()
303 } else {
304 "No".yellow()
305 }
306 );
307 }
308
309 if let Some(deps) = &pkg.dependencies {
310 println!("\n{}:", "Dependencies".bold());
311
312 if let Some(runtime) = &deps.runtime {
313 println!(" Runtime:");
314 print_dependency_group(runtime, 2);
315 }
316
317 if let Some(build_deps) = &deps.build {
318 println!(" Build Dependencies:");
319 match build_deps {
320 types::BuildDependencies::Group(group) => {
321 print_dependency_group(group, 2);
322 }
323 types::BuildDependencies::Typed(typed_build_deps) => {
324 for (name, group) in &typed_build_deps.types {
325 println!(" {}:", name.cyan());
326 print_dependency_group(group, 3);
327 }
328 }
329 }
330 }
331
332 if let Some(test_deps) = &deps.test {
333 println!(" Test Dependencies:");
334 print_dependency_group(test_deps, 2);
335 }
336 }
337}