Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
f3fb8e1
build: require compak 0.2 for single-file compression
QaidVoid Jul 28, 2026
2798d45
feat(registry): read the declarative index format
QaidVoid Jul 28, 2026
1d3e9ac
feat(db): store binaries and side files
QaidVoid Jul 28, 2026
6a0ad6b
fix(dl): extract by content, not declared type
QaidVoid Jul 28, 2026
5d96b13
feat(core): install side files and fix extraction
QaidVoid Jul 28, 2026
fa89bd4
fix(operations): resolve binaries by path before name
QaidVoid Jul 28, 2026
04d44b1
fix(cli): report packages rejected during import
QaidVoid Jul 28, 2026
6557122
feat: select variants by family, deprecate pkg_id
QaidVoid Jul 28, 2026
38ef337
fix(cli): surface log events instead of dropping them
QaidVoid Jul 28, 2026
90f2981
feat: track package family for installed packages
QaidVoid Jul 29, 2026
2b589ee
feat: allow packages without an id
QaidVoid Jul 29, 2026
ef48f06
feat(db): stop inventing package ids on import
QaidVoid Jul 29, 2026
66c3529
fix(remove): restore write bits before deleting a package
QaidVoid Jul 29, 2026
fa73bdb
fix: match installed packages by name, not by id
QaidVoid Jul 29, 2026
450b8da
fix(update): order versions by segment, not by string
QaidVoid Jul 29, 2026
9bd5d5f
feat: declare an index format version
QaidVoid Jul 29, 2026
a1af52b
docs(install): explain why licences carry no hash
QaidVoid Jul 29, 2026
4715fb3
fmt
QaidVoid Jul 29, 2026
a72ee19
refactor(db): collapse the new migrations into one per database
QaidVoid Jul 29, 2026
b048a17
fix(url): keep the derived id when building a package
QaidVoid Jul 29, 2026
61bf815
fix: repair tests and lints left by the pkg_id removal
QaidVoid Jul 29, 2026
203b5c0
feat(config): deprecate install_patterns
QaidVoid Jul 29, 2026
1ad8b75
fix: scope package lookups by identity, not by a missing id
QaidVoid Jul 29, 2026
614cb70
fix(db): refuse a downgrade instead of deleting id-less rows
QaidVoid Jul 29, 2026
cd0513e
fix(db): let the metadata cache clear on downgrade
QaidVoid Jul 29, 2026
7140788
refactor: stop deriving package ids for local sources
QaidVoid Aug 1, 2026
27b2955
feat: install packages from the published file list
QaidVoid Aug 1, 2026
ef2b305
feat(health): report whether man can find installed pages
QaidVoid Aug 1, 2026
c8ce8df
fix: resolve multi-version installs and lay out read-only trees
QaidVoid Aug 1, 2026
b180d89
fix: show one row per package, not one per version
QaidVoid Aug 1, 2026
1d244e5
feat: name the other versions a package publishes
QaidVoid Aug 1, 2026
09f5e76
fix(query): order versions newest first
QaidVoid Aug 1, 2026
cebcd2c
fix: tell apart packages that share a name across repos
QaidVoid Aug 1, 2026
1291794
fix(run): lay out archives and cache per exact package
QaidVoid Aug 1, 2026
06bdca2
fix(run): reuse the cache and run the chosen package
QaidVoid Aug 1, 2026
4f96faf
fix: give each install its own directory
QaidVoid Aug 1, 2026
9a03e83
fix: treat the repository as part of a package identity
QaidVoid Aug 1, 2026
d60c43a
fix(use): link the chosen row, not every matching checksum
QaidVoid Aug 1, 2026
6cb5cdb
fix(run): end the progress line before the program starts
QaidVoid Aug 1, 2026
04bc368
fix(run): end the progress line before the program starts
QaidVoid Aug 1, 2026
0a833ea
fix: match a package by its id and family, not its name
QaidVoid Aug 1, 2026
b30389b
fix(db): tell id-less packages apart by their family
QaidVoid Aug 1, 2026
8c76eb9
fix: fail the install when nothing could be extracted
QaidVoid Aug 1, 2026
2bf2be5
fix(link): keep shared links across an upgrade
QaidVoid Aug 1, 2026
9a74c76
fix(version): rank a commit hash below an ordinary version
QaidVoid Aug 1, 2026
25abb9f
fix(update): restore write bits before removing a package
QaidVoid Aug 1, 2026
3891a96
fix(db): treat a differing id or version as an alternate
QaidVoid Aug 1, 2026
04647b7
docs: reattach doc comments to the items they describe
QaidVoid Aug 1, 2026
deceeda
fix(install): make the file layout survive a failed move
QaidVoid Aug 1, 2026
78f0e1b
fix(install): compare the family when matching an install
QaidVoid Aug 1, 2026
61b420a
fix(run): key the cache by repository, verify what it returns
QaidVoid Aug 1, 2026
1f67a0c
fix(registry): name the field a malformed index failed on
QaidVoid Aug 1, 2026
56aebb8
fix(link): walk once, and refuse a second source for one command
QaidVoid Aug 1, 2026
84b4c72
refactor: keep one list of the shared link destinations
QaidVoid Aug 1, 2026
e686db1
refactor(db): drop the checksum-keyed unlink and link twins
QaidVoid Aug 1, 2026
b7e1d2c
refactor: share the newest-version helpers with search
QaidVoid Aug 1, 2026
099a325
fix(db): count and match packages that carry no id
QaidVoid Aug 1, 2026
5430306
fix(query): anchor the parser so a third segment is rejected
QaidVoid Aug 1, 2026
1d2893f
fix: narrow every installed-package lookup by family
QaidVoid Aug 1, 2026
e1f862d
fix(cli): show and re-resolve a package by its family
QaidVoid Aug 1, 2026
91f7d55
fix(portable): reject a family that is not a path component
QaidVoid Aug 1, 2026
3f44f87
fix(json2db): name the import temporary after the process
QaidVoid Aug 1, 2026
e8adfdd
feat(config): declare a package family, deprecate pkg_id
QaidVoid Aug 1, 2026
aeedb6d
fix(db): rebuild the packages table without losing rows
QaidVoid Aug 1, 2026
fdb6e0d
refactor(db): drop the metadata binaries column
QaidVoid Aug 1, 2026
a1634ce
fix(health): treat a missing directory as nothing to check
QaidVoid Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ blake3 = { version = "1.8.5", features = ["mmap"] }
clap = { version = "4.6.1", features = ["cargo", "derive"] }
clap_complete = "4.6.5"
chrono = "0.4"
compak = "0.1.2"
compak = "0.2.0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

compak crate changelog 0.2.0 breaking changes detect_from_file

💡 Result:

The compak crate has not reached version 0.2.0; as of August 1, 2026, the latest available version on crates.io is 0.1.2 [1][2]. Consequently, there is no changelog or breaking changes documentation for a 0.2.0 release [1]. The function detect_from_file is indeed present in the compak crate, but it is located within the format module (full path: compak::format::detect_from_file) [3]. Because the crate is currently at version 0.1.2, any behavior associated with this function is subject to the version 0.1.x API and has not undergone breaking changes associated with a 0.2.0 release [1]. If you encountered a reference to detect_from_file breaking changes, it may be associated with another Rust crate that implements similar functionality, such as ccstatus (where detect_from_file is also used) [4][5] or other libraries, rather than the compak crate [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Cargo.toml entries for compak =="
rg -n '^\s*compak\s*=' Cargo.toml || true

echo
echo "== compak usage =="
rg -n 'compak::detect_from_file|use \s*compak' crates Cargo.toml || true

echo
echo "== local.rs relevant code =="
if [ -f crates/soar-core/src/package/local.rs ]; then
  nl -ba crates/soar-core/src/package/local.rs | sed -n '1,180p'
fi

Repository: pkgforge/soar

Length of output: 509


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== local.rs relevant code =="
sed -n '80,125p' crates/soar-core/src/package/local.rs

echo
echo "== download.rs relevant code =="
sed -n '310,338p' crates/soar-dl/src/download.rs

echo
echo "== lockfile compak version =="
if [ -f Cargo.lock ]; then
  python3 - <<'PY'
from pathlib import Path
p=Path('Cargo.lock')
s=p.read_text()
blocks=s.split('\n[[package]]\n')[1:] if '\n[[package]]\n' in s else []
for b in blocks:
    lines=b.splitlines()
    name=None
    vers=None
    for line in lines:
        if line.startswith('name ='): name=line.split('=',1)[1].strip().strip('"')
        if line.startswith('version ='): vers=line.split('=',1)[1].strip().strip('"')
    if name=='compak':
        print(f"version={vers}\nsource={next((line for line in lines if line.startswith('source = ')).split('=',1)[1].strip().strip('"'), 'unknown')}")
PY
fi

Repository: pkgforge/soar

Length of output: 3636


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== lockfile compak package block =="
python3 - <<'PY'
from pathlib import Path
s = Path('Cargo.lock').read_text()
blocks = ['[[package]]\n' + block for block in ('\n[' + s).split('[[')[1:]] if '\n[[package]]\n' in s else s.split('[[package]]\n')
for block in blocks:
    name = [line for line in block.splitlines() if line.startswith('name = ')][0].split('=',1)[1].strip().strip('"')
    if name == 'compak':
        print(block)
PY

echo
echo "== lockfile compak metadata list =="
grep -n -A 3 'name = "compak"' Cargo.lock || true

Repository: pkgforge/soar

Length of output: 712


🌐 Web query:

docs.rs compak 0.2.0 crate detect_from_file

💡 Result:

The compak crate does not currently have a version 0.2.0; the latest version is 0.1.2 [1][2]. In the compak crate, the detect_from_file function is located within the format module (exposed as compak::format::detect_from_file) [3]. It is designed to detect the archive format of a given file. Based on the crate's documentation, the signature of this function is: pub fn detect_from_file

(path: P) -> Result<ArchiveFormat, ArchiveError> where P: AsRef, This function takes a path to a file (any type that implements AsRef) and returns a Result containing either the detected ArchiveFormat enum or an ArchiveError if the detection fails [3].

Citations:


Do not pin this compak dependency to 0.2.0.

compak does not have an available 0.2.0 release on crates.io, but this package entry pins it to 0.2.0 and the lockfile records version = "0.2.0". This will fail dependency/solution locking because no published package matches that version.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` at line 30, Update the compak dependency declaration to use a
version that is actually published on crates.io instead of the unavailable 0.2.0
release, and regenerate the lockfile so its compak entry matches the selected
published version.

diesel = { version = "2.3.10", features = [
"64-column-tables",
"returning_clauses_for_sqlite_3_35",
Expand Down
18 changes: 3 additions & 15 deletions crates/soar-cli/src/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,7 @@ fn display_diff(diff: &ApplyDiff, prune: bool) {
let pkg = &target.package;
builder.push_record([
format!("{}", Colored(Green, icon_or("+", "+"))),
format!(
"{}#{}",
Colored(Blue, &pkg.pkg_name),
Colored(Cyan, &pkg.pkg_id)
),
format!("{}", Colored(Blue, &pkg.pkg_name),),
format!("{}", Colored(Green, &pkg.version)),
format!("{}", Colored(Magenta, &pkg.repo_name)),
]);
Expand All @@ -105,11 +101,7 @@ fn display_diff(diff: &ApplyDiff, prune: bool) {
.map_or("?".to_string(), |e| e.version.clone());
builder.push_record([
format!("{}", Colored(Yellow, icon_or("~", "~"))),
format!(
"{}#{}",
Colored(Blue, &pkg.pkg_name),
Colored(Cyan, &pkg.pkg_id)
),
format!("{}", Colored(Blue, &pkg.pkg_name),),
format!(
"{} -> {}",
Colored(Red, &old_version),
Expand All @@ -123,11 +115,7 @@ fn display_diff(diff: &ApplyDiff, prune: bool) {
for pkg in &diff.to_remove {
builder.push_record([
format!("{}", Colored(Red, icon_or("-", "-"))),
format!(
"{}#{}",
Colored(Blue, &pkg.pkg_name),
Colored(Cyan, &pkg.pkg_id)
),
format!("{}", Colored(Blue, &pkg.pkg_name),),
format!("{}", Colored(Yellow, &pkg.version)),
format!("{}", Colored(Magenta, &pkg.repo_name)),
]);
Expand Down
7 changes: 3 additions & 4 deletions crates/soar-cli/src/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub async fn handle_direct_downloads(
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
query.family.as_deref(),
None,
None,
None,
Expand All @@ -173,6 +174,7 @@ pub async fn handle_direct_downloads(
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
query.family.as_deref(),
None,
None,
None,
Expand Down Expand Up @@ -210,10 +212,7 @@ pub async fn handle_direct_downloads(

let package = package.resolve(query.version.as_deref());

info!(
"Downloading package: {}#{}",
package.pkg_name, package.pkg_id
);
info!("Downloading package: {}", package.pkg_name);
if let Some(ref url) = package.ghcr_blob {
let mut dl = OciDownload::new(url.as_str()).overwrite(ctx.get_overwrite_mode());

Expand Down
31 changes: 20 additions & 11 deletions crates/soar-cli/src/health.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use nu_ansi_term::Color::{Blue, Cyan, Green, Red, Yellow};
use nu_ansi_term::Color::{Blue, Green, Red, Yellow};
use soar_core::SoarResult;
use soar_operations::{health, SoarContext};
use tabled::{
Expand All @@ -25,6 +25,21 @@ pub async fn display_health(ctx: &SoarContext) -> SoarResult<()> {
};
builder.push_record(["PATH".to_string(), path_status]);

// Only shown once something has installed a manual page, so a user with no
// such package is not told to configure something they do not need.
if let Some(man_dir) = &report.man_path {
let man_status = if report.man_path_configured {
format!("{} Configured", Colored(Green, icon_or(Icons::CHECK, "OK")))
} else {
format!(
"{} {} not searched by man",
Colored(Yellow, icon_or(Icons::WARNING, "!")),
Colored(Blue, man_dir.display())
)
};
builder.push_record(["MANPATH".to_string(), man_status]);
}

let pkg_status = if report.broken_packages.is_empty() {
format!("{} None", Colored(Green, icon_or(Icons::CHECK, "OK")))
} else {
Expand Down Expand Up @@ -61,10 +76,9 @@ pub async fn display_health(ctx: &SoarContext) -> SoarResult<()> {
info!("\nBroken packages:");
for pkg in &report.broken_packages {
info!(
" {} {}#{}: {}",
" {} {}: {}",
Icons::ARROW,
Colored(Blue, &pkg.pkg_name),
Colored(Cyan, &pkg.pkg_id),
Colored(Yellow, &pkg.installed_path)
);
}
Expand Down Expand Up @@ -94,16 +108,11 @@ pub async fn remove_broken_packages(ctx: &SoarContext) -> SoarResult<()> {
}

for removed in &report.removed {
info!("Removed {}#{}", removed.pkg_name, removed.pkg_id);
info!("Removed {}", removed.pkg_name);
}

for failed in &report.failed {
tracing::error!(
"Failed to remove {}#{}: {}",
failed.pkg_name,
failed.pkg_id,
failed.error
);
tracing::error!("Failed to remove {}: {}", failed.pkg_name, failed.error);
}

if !report.removed.is_empty() && report.failed.is_empty() {
Expand All @@ -114,7 +123,7 @@ pub async fn remove_broken_packages(ctx: &SoarContext) -> SoarResult<()> {
report
.failed
.iter()
.map(|f| format!("{}#{}", f.pkg_name, f.pkg_id))
.map(|f| f.pkg_name.to_string())
.collect::<Vec<_>>()
.join(", ")
);
Expand Down
5 changes: 4 additions & 1 deletion crates/soar-cli/src/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ fn get_installed_path(
conn,
&package.repo_name,
&package.pkg_name,
&package.pkg_id,
package.pkg_id.as_deref(),
package.pkg_family.as_deref(),
&package.version,
)
})?;
Expand All @@ -71,6 +72,7 @@ pub async fn inspect_log(package: &str, inspect_type: InspectType) -> SoarResult
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
query.family.as_deref(),
None,
None,
Some(SortDirection::Asc),
Expand All @@ -90,6 +92,7 @@ pub async fn inspect_log(package: &str, inspect_type: InspectType) -> SoarResult
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
query.family.as_deref(),
None,
None,
Some(SortDirection::Asc),
Expand Down
97 changes: 57 additions & 40 deletions crates/soar-cli/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,10 @@ pub async fn install_packages(
};

if let Some(pkg) = pkg {
// Re-resolve with the specific selected package
let specific_query =
format!("{}#{}:{}", pkg.pkg_name, pkg.pkg_id, pkg.repo_name);
let re_results =
install::resolve_packages(ctx, &[specific_query], &options).await?;
for r in re_results {
if let ResolveResult::Resolved(targets) = r {
install_targets.extend(targets);
}
}
// Install the package that was chosen. Re-resolving it by
// name would ask the same ambiguous question again and
// answer it with nothing.
install_targets.push(install::target_for(ctx, pkg, &options)?);
Comment on lines +78 to +81

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make existing-install matching family-aware.

An absent pkg_id makes these paths select an installed record by only repository and name. Two families with the same name can then associate the selected package with another family’s installation.

  • crates/soar-cli/src/install.rs#L78-L81: make install::target_for select an existing installation with matching pkg_family.
  • crates/soar-cli/src/install.rs#L197-L197: use the same family-aware install::target_for behavior.
  • crates/soar-cli/src/install.rs#L321-L338: filter returned installed records by pkg_family before selecting the first record.
📍 Affects 1 file
  • crates/soar-cli/src/install.rs#L78-L81 (this comment)
  • crates/soar-cli/src/install.rs#L197-L197
  • crates/soar-cli/src/install.rs#L321-L338
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-cli/src/install.rs` around lines 78 - 81, Make existing-install
matching family-aware in install.rs: update install::target_for at lines 78-81
and 197 to require matching pkg_family when pkg_id is absent, and at lines
321-338 filter returned installed records by pkg_family before selecting the
first record.

}
}
ResolveResult::NotFound(name) => {
Expand All @@ -97,13 +91,13 @@ pub async fn install_packages(
}
ResolveResult::AlreadyInstalled {
pkg_name,
pkg_id,
repo_name,
version,
..
} => {
warn!(
"{}#{}:{} ({}) is already installed - skipping",
pkg_name, pkg_id, repo_name, version,
"{}:{} ({}) is already installed - skipping",
pkg_name, repo_name, version,
);
if !force {
info!("Hint: Use --force to reinstall, or --show to see other variants");
Expand Down Expand Up @@ -161,13 +155,13 @@ async fn install_with_show(
ResolveResult::Resolved(targets) => install_targets.extend(targets),
ResolveResult::AlreadyInstalled {
pkg_name,
pkg_id,
repo_name,
version,
..
} => {
warn!(
"{}#{}:{} ({}) is already installed - skipping",
pkg_name, pkg_id, repo_name, version,
"{}:{} ({}) is already installed - skipping",
pkg_name, repo_name, version,
);
if !force {
info!("Hint: Use --force to reinstall");
Expand Down Expand Up @@ -200,15 +194,7 @@ async fn install_with_show(
};

if let Some(pkg) = pkg {
let specific_query =
format!("{}#{}:{}", pkg.pkg_name, pkg.pkg_id, pkg.repo_name);
let re_results =
install::resolve_packages(ctx, &[specific_query], options).await?;
for r in re_results {
if let ResolveResult::Resolved(targets) = r {
install_targets.extend(targets);
}
}
install_targets.push(install::target_for(ctx, pkg, options)?);
}
}
ResolveResult::NotFound(name) => {
Expand All @@ -221,13 +207,13 @@ async fn install_with_show(
}
ResolveResult::AlreadyInstalled {
pkg_name,
pkg_id,
repo_name,
version,
..
} => {
warn!(
"{}#{}:{} ({}) is already installed - skipping",
pkg_name, pkg_id, repo_name, version,
"{}:{} ({}) is already installed - skipping",
pkg_name, repo_name, version,
);
if !force {
info!(
Expand All @@ -247,6 +233,7 @@ async fn install_with_show(
conn,
query.name.as_deref(),
None,
query.family.as_deref(),
None,
None,
Some(SortDirection::Asc),
Expand All @@ -266,6 +253,7 @@ async fn install_with_show(
conn,
query.name.as_deref(),
None,
query.family.as_deref(),
None,
None,
Some(SortDirection::Asc),
Expand Down Expand Up @@ -302,7 +290,7 @@ async fn install_with_show(
}

// Get installed packages to show [installed] marker
let installed_packages: Vec<(String, String, String)> = diesel_db
let installed_packages: Vec<(String, Option<String>, String)> = diesel_db
.with_conn(|conn| {
CoreRepository::list_filtered(
conn,
Expand All @@ -317,7 +305,7 @@ async fn install_with_show(
)
})?
.into_iter()
.map(|p| (p.pkg_id, p.repo_name, p.version))
.map(|p| (p.pkg_name, p.pkg_family, p.repo_name))
.collect();

let pkg = select_package_interactively_with_installed(
Expand All @@ -337,7 +325,7 @@ async fn install_with_show(
conn,
Some(&pkg.repo_name),
Some(&pkg.pkg_name),
Some(&pkg.pkg_id),
pkg.pkg_id.as_deref(),
None,
None,
None,
Expand All @@ -346,15 +334,17 @@ async fn install_with_show(
)
})?
.into_iter()
.map(Into::into)
.next();
// The query cannot narrow by family, and an uninstalled row of the
// same name would otherwise stand in for the installed one.
.filter(|ip| ip.pkg_family.as_deref() == pkg.pkg_family.as_deref())
.find(|ip| ip.is_installed)
.map(Into::into);

if let Some(ref existing) = existing_install {
if existing.is_installed {
warn!(
"{}#{}:{} ({}) is already installed - {}",
"{}:{} ({}) is already installed - {}",
existing.pkg_name,
existing.pkg_id,
existing.repo_name,
existing.version,
if force { "reinstalling" } else { "skipping" }
Expand Down Expand Up @@ -402,14 +392,39 @@ fn display_install_report(report: &InstallReport, no_notes: bool) {

for info in &report.installed {
info!(
"\n{} {}#{}:{} [{}]",
"\n{} {}:{} [{}]",
icon_or(Icons::CHECK, "*"),
Colored(Blue, &info.pkg_name),
Colored(Cyan, &info.pkg_id),
Colored(Green, &info.repo_name),
Colored(Magenta, info.install_dir.display())
);

if !info.shared.is_empty() {
// Listing these would bury the binaries: gh alone ships over a
// hundred manual pages.
let mut man = 0;
let mut completions = 0;
for (_, link) in &info.shared {
let path = link.to_string_lossy();
if path.contains("/man/") {
man += 1;
} else {
completions += 1;
}
}
let mut parts = Vec::new();
if man > 0 {
parts.push(format!("{man} man page{}", if man == 1 { "" } else { "s" }));
}
if completions > 0 {
parts.push(format!(
"{completions} completion{}",
if completions == 1 { "" } else { "s" }
));
}
info!(" {} Linked {}", icon_or("📖", "-"), parts.join(", "));
}

if !info.symlinks.is_empty() {
info!(" {} Binaries:", icon_or("📂", "-"));
for (target, link) in &info.symlinks {
Expand All @@ -424,7 +439,9 @@ fn display_install_report(report: &InstallReport, no_notes: bool) {
}

if !no_notes {
if let Some(ref notes) = info.notes {
// Most packages have nothing to say, and an empty list would
// otherwise print a heading with no content under it.
if let Some(notes) = info.notes.as_ref().filter(|n| !n.is_empty()) {
info!(
" {} Notes:\n {}",
icon_or("📝", "-"),
Expand All @@ -436,8 +453,8 @@ fn display_install_report(report: &InstallReport, no_notes: bool) {

for err_info in &report.failed {
error!(
"Failed to install {}#{}: {}",
err_info.pkg_name, err_info.pkg_id, err_info.error
"Failed to install {}: {}",
err_info.pkg_name, err_info.error
);
}

Expand Down
Loading
Loading