mod render;
mod shell_init;
mod types;
pub use render::humanize_bytes;
pub use shell_init::{
format_unix_ts, humanize_us, shell_init, shell_init_aggregate, shell_init_errors,
shell_init_filter, shell_init_history,
};
pub use types::{
AppProbeEntry, AppProbeView, DeploymentDisplayEntry, ProbeResult, ProbeSubcommandInfo,
ShellInitAggregateRow, ShellInitAggregateView, ShellInitErrorsView, ShellInitFilterRun,
ShellInitFilterTarget, ShellInitFilterView, ShellInitGroup, ShellInitHistoryRow,
ShellInitHistoryView, ShellInitRow, ShellInitView, TreeLine, DEFAULT_FILTER_RUNS,
DEFAULT_HISTORY_LIMIT, DEFAULT_SHOW_DATA_DIR_DEPTH, PROBE_SUBCOMMANDS,
};
use crate::packs::orchestration::ExecutionContext;
use crate::probe::{collect_data_dir_tree, collect_deployment_map};
use crate::Result;
pub fn summary(ctx: &ExecutionContext) -> Result<ProbeResult> {
Ok(ProbeResult::Summary {
data_dir: ctx.paths.data_dir().display().to_string(),
available: PROBE_SUBCOMMANDS.to_vec(),
})
}
pub fn deployment_map(ctx: &ExecutionContext) -> Result<ProbeResult> {
let raw = collect_deployment_map(ctx.fs.as_ref(), ctx.paths.as_ref())?;
let home = ctx.paths.home_dir();
let entries = raw
.into_iter()
.map(|e| render::into_display_entry(e, home))
.collect();
Ok(ProbeResult::DeploymentMap {
data_dir: ctx.paths.data_dir().display().to_string(),
map_path: ctx.paths.deployment_map_path().display().to_string(),
entries,
})
}
pub fn app(pack_name: &str, refresh: bool, ctx: &ExecutionContext) -> Result<ProbeResult> {
use std::collections::BTreeSet;
let pack_dir = crate::packs::orchestration::resolve_pack_dir_name(pack_name, ctx)
.unwrap_or_else(|_| {
if is_single_normal_path_component(pack_name) {
pack_name.to_string()
} else {
String::new()
}
});
let display_name = if pack_dir.is_empty() {
pack_name.to_string()
} else {
crate::packs::display_name_for(&pack_dir).to_string()
};
let pack_config = if pack_dir.is_empty() {
ctx.config_manager.root_config()?
} else {
match ctx
.config_manager
.config_for_pack(&ctx.paths.pack_path(&pack_dir))
{
Ok(c) => c,
Err(_) => ctx.config_manager.root_config()?,
}
};
let mut folders: Vec<(String, &'static str)> = Vec::new();
let mut seen: BTreeSet<String> = BTreeSet::new();
if let Some(alias) = pack_config.symlink.app_aliases.get(&display_name) {
if seen.insert(alias.clone()) {
folders.push((alias.clone(), "alias"));
}
}
let pack_path = ctx.paths.pack_path(&pack_dir);
if !pack_dir.is_empty() && ctx.fs.exists(&pack_path) {
if let Ok(entries) = ctx.fs.read_dir(&pack_path) {
for e in entries {
if e.is_dir
&& pack_config.symlink.force_app.iter().any(|f| f == &e.name)
&& seen.insert(e.name.clone())
{
folders.push((e.name.clone(), "force_app"));
}
}
let app_dir = pack_path.join("_app");
if ctx.fs.exists(&app_dir) {
if let Ok(children) = ctx.fs.read_dir(&app_dir) {
for e in children {
if e.is_dir && seen.insert(e.name.clone()) {
folders.push((e.name.clone(), "_app/"));
}
}
}
}
}
}
let macos = cfg!(target_os = "macos");
let app_support = ctx.paths.app_support_dir();
let cache_dir = ctx.paths.probes_brew_cache_dir();
if refresh && macos {
crate::probe::brew::invalidate_all_cache(&cache_dir, ctx.fs.as_ref());
}
let now = crate::probe::brew::now_secs_unix();
let folder_names: Vec<String> = folders.iter().map(|(f, _)| f.clone()).collect();
let matches = if macos {
crate::probe::brew::match_folders_to_installed_casks(
&folder_names,
ctx.command_runner.as_ref(),
&cache_dir,
now,
ctx.fs.as_ref(),
false,
)
} else {
crate::probe::brew::InstalledCaskMatches::default()
};
let mut entries: Vec<AppProbeEntry> = Vec::new();
let mut suggested: BTreeSet<String> = BTreeSet::new();
for (folder, source_rule) in &folders {
let target = app_support.join(folder);
let target_exists = ctx.fs.exists(&target);
let cask = matches.folder_to_token.get(folder).cloned();
let mut app_bundle = None;
let mut bundle_id = None;
if macos {
if let Some(token) = &cask {
if let Ok(Some(info)) = crate::probe::brew::info_cask(
token,
&cache_dir,
now,
ctx.fs.as_ref(),
ctx.command_runner.as_ref(),
) {
app_bundle = info.app_bundle_name();
if let Some(bundle_name) = &app_bundle {
let app_path = std::path::PathBuf::from("/Applications").join(bundle_name);
bundle_id = crate::probe::macos_native::bundle_id(
&app_path,
ctx.command_runner.as_ref(),
);
}
for plist in info.preferences_plists() {
suggested.insert(plist);
}
}
}
}
entries.push(AppProbeEntry {
folder: folder.clone(),
target_path: render::display_path(&target, ctx.paths.home_dir()),
target_exists,
source_rule: (*source_rule).into(),
cask,
app_bundle,
bundle_id,
});
}
Ok(ProbeResult::App(AppProbeView {
pack: display_name,
macos,
entries,
suggested_adoptions: suggested.into_iter().collect(),
}))
}
fn is_single_normal_path_component(value: &str) -> bool {
if value.is_empty() {
return false;
}
let mut comps = std::path::Path::new(value).components();
matches!(
(comps.next(), comps.next()),
(Some(std::path::Component::Normal(_)), None)
)
}
pub fn show_data_dir(ctx: &ExecutionContext, max_depth: usize) -> Result<ProbeResult> {
let tree = collect_data_dir_tree(ctx.fs.as_ref(), ctx.paths.as_ref(), max_depth)?;
let total_nodes = tree.count_nodes();
let total_size = tree.total_size();
let mut lines = Vec::new();
render::flatten_tree(&tree, "", true, &mut lines, true);
Ok(ProbeResult::ShowDataDir {
data_dir: ctx.paths.data_dir().display().to_string(),
lines,
total_nodes,
total_size,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn probe_result_deployment_map_serialises_with_kind_tag() {
let result = ProbeResult::DeploymentMap {
data_dir: "/d".into(),
map_path: "/d/deployment-map.tsv".into(),
entries: Vec::new(),
};
let json = serde_json::to_value(&result).unwrap();
assert_eq!(json["kind"], "deployment-map");
assert!(json["entries"].is_array());
}
#[test]
fn probe_result_show_data_dir_serialises_with_kind_tag() {
let result = ProbeResult::ShowDataDir {
data_dir: "/d".into(),
lines: Vec::new(),
total_nodes: 1,
total_size: 0,
};
let json = serde_json::to_value(&result).unwrap();
assert_eq!(json["kind"], "show-data-dir");
assert_eq!(json["total_nodes"], 1);
}
#[test]
fn probe_subcommands_list_matches_variants() {
let names: Vec<&str> = PROBE_SUBCOMMANDS.iter().map(|s| s.name).collect();
assert!(names.contains(&"deployment-map"));
assert!(names.contains(&"show-data-dir"));
}
}