pub mod filter;
mod format;
pub mod json;
pub mod largest;
pub mod ndjson;
pub mod summary;
pub mod tree;
use std::collections::HashMap;
use std::io::Write;
use std::path::Path;
use crate::entry::Entry;
use crate::scanner::{HardlinkPolicy, ScanCounts};
pub struct OutputConfig<'a> {
pub max_depth: Option<usize>,
pub top: Option<usize>,
pub scan_root: &'a Path,
pub counts: &'a ScanCounts,
pub hardlinks: HardlinkPolicy,
pub filter: &'a filter::Filter,
}
pub(crate) fn select_top(children: &[Entry], top: Option<usize>) -> (Vec<&Entry>, usize, u64) {
let refs: Vec<&Entry> = children.iter().collect();
select_top_refs(&refs, top)
}
pub(crate) fn select_top_refs<'a>(
refs: &[&'a Entry],
top: Option<usize>,
) -> (Vec<&'a Entry>, usize, u64) {
match top {
None => (refs.to_vec(), 0, 0),
Some(n) if n >= refs.len() => (refs.to_vec(), 0, 0),
Some(n) => {
let mut by_size: Vec<(usize, u64)> =
refs.iter().enumerate().map(|(i, e)| (i, e.size)).collect();
by_size.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
by_size.truncate(n);
let keep: std::collections::BTreeSet<usize> = by_size.iter().map(|&(i, _)| i).collect();
let mut kept: Vec<&Entry> = Vec::with_capacity(keep.len());
let mut dropped_size: u64 = 0;
for (i, e) in refs.iter().enumerate() {
if keep.contains(&i) {
kept.push(*e);
} else {
dropped_size += e.size;
}
}
let dropped_count = refs.len() - kept.len();
(kept, dropped_count, dropped_size)
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum OutputMode {
Tree,
Json,
Ndjson,
Summary,
Largest {
n: usize,
format: largest::LargestFormat,
},
}
pub(crate) const WIRE_VERSION: u32 = 2;
pub(crate) fn hardlinks_label(p: HardlinkPolicy) -> &'static str {
match p {
HardlinkPolicy::CountOnce => "count-once",
HardlinkPolicy::CountEach => "count-each",
}
}
pub(crate) fn child_relative_path(parent: &str, name: &str) -> String {
if parent == "." {
name.to_string()
} else {
format!("{parent}/{name}")
}
}
pub(crate) fn scan_root_for_wire(scan_root: &Path) -> String {
scan_root.display().to_string().replace('\\', "/")
}
pub(crate) fn is_zero_u64(n: &u64) -> bool {
*n == 0
}
pub(crate) type SubtreeCounts = HashMap<*const Entry, (u64, u64)>;
pub(crate) fn precompute_subtree_counts(entry: &Entry) -> SubtreeCounts {
let mut map: SubtreeCounts = HashMap::new();
walk_counts(entry, &mut map);
map
}
fn walk_counts(entry: &Entry, map: &mut SubtreeCounts) -> (u64, u64) {
let mut files = if entry.is_dir() { 0 } else { 1 };
let mut dirs = if entry.is_dir() { 1 } else { 0 };
if let Some(children) = entry.children() {
for child in children {
let (cfc, cdc) = walk_counts(child, map);
files += cfc;
dirs += cdc;
}
}
map.insert(entry as *const Entry, (files, dirs));
(files, dirs)
}
pub fn render(
entry: &Entry,
config: &OutputConfig,
mode: OutputMode,
out: &mut impl Write,
) -> anyhow::Result<()> {
match mode {
OutputMode::Tree => tree::write(entry, config, out)?,
OutputMode::Json => json::write(entry, config, out)?,
OutputMode::Ndjson => ndjson::write(entry, config, out)?,
OutputMode::Summary => summary::write(entry, config, out)?,
OutputMode::Largest { n, format } => largest::write(entry, config, n, format, out)?,
}
Ok(())
}