parallel-disk-usage 0.21.1

Highly parallelized, blazing fast directory tree analyzer
Documentation
#![cfg(feature = "cli")]
#![cfg(feature = "json")]

pub mod _utils;
pub use _utils::*;

use command_extra::CommandExtra;
use parallel_disk_usage::{
    bytes_format::BytesFormat,
    data_tree::DataTree,
    fs_tree_builder::FsTreeBuilder,
    get_size::GetApparentSize,
    hardlink::HardlinkIgnorant,
    json_data::{JsonData, JsonTree, SchemaVersion},
    reporter::{ErrorOnlyReporter, ErrorReport},
    size::Bytes,
    visualizer::{BarAlignment, ColumnWidthDistribution, Direction, Visualizer},
};
use pipe_trait::Pipe;
use pretty_assertions::assert_eq;
use std::{
    convert::TryInto,
    io::Write,
    process::{Command, Stdio},
};

type SampleName = String;
type SampleSize = Bytes;
type SampleJsonTree = JsonTree<SampleSize>;
type SampleTree = DataTree<SampleName, SampleSize>;

fn sample_tree() -> SampleTree {
    let dir = |name: &'static str, children: Vec<SampleTree>| {
        SampleTree::dir(name.to_string(), 1024.into(), children)
    };
    let file =
        |name: &'static str, size: u64| SampleTree::file(name.to_string(), Bytes::from(size));
    dir(
        "root",
        vec![
            file("foo", 2530),
            file("bar", 52),
            dir(
                "hello",
                vec![dir("world", vec![file("hello", 45), file("world", 54)])],
            ),
            dir("empty dir", vec![]),
            dir(
                "directory with a really long name",
                vec![dir(
                    "subdirectory with a really long name",
                    vec![file("file with a really long name", 475)],
                )],
            ),
        ],
    )
    .into_par_sorted(|left, right| left.size().cmp(&right.size()).reverse())
}

#[test]
fn json_output() {
    let workspace = SampleWorkspace::default();
    let actual = Command::new(PDU)
        .with_current_dir(&workspace)
        .with_arg("--json-output")
        .with_arg("--quantity=apparent-size")
        .with_arg("--min-ratio=0")
        .with_arg(&workspace)
        .with_stdin(Stdio::null())
        .with_stdout(Stdio::piped())
        .with_stderr(Stdio::piped())
        .output()
        .expect("spawn command")
        .pipe(stdout_text)
        .pipe_as_ref(serde_json::from_str::<JsonData>)
        .expect("parse stdout as JsonData")
        .body
        .pipe(TryInto::<SampleJsonTree>::try_into)
        .expect("extract reflection")
        .tree
        .pipe(sanitize_tree_reflection);
    dbg!(&actual);
    let builder = FsTreeBuilder {
        root: workspace.to_path_buf(),
        size_getter: GetApparentSize,
        hardlinks_recorder: &HardlinkIgnorant,
        reporter: &ErrorOnlyReporter::new(ErrorReport::SILENT),
        max_depth: 10,
    };
    let expected = builder
        .pipe(DataTree::<_, Bytes>::from)
        .into_reflection()
        .par_convert_names_to_utf8()
        .expect("convert all names from raw strings to UTF-8")
        .pipe(sanitize_tree_reflection);
    dbg!(&expected);
    assert_eq!(actual, expected);
}

#[test]
fn json_input() {
    let json_tree = JsonTree {
        tree: sample_tree().into_reflection(),
        shared: Default::default(),
    };
    let json_data = JsonData {
        schema_version: SchemaVersion,
        binary_version: None,
        body: json_tree.into(),
    };
    let json = serde_json::to_string_pretty(&json_data).expect("convert sample tree to JSON");
    eprintln!("JSON: {json}\n");
    let workspace = Temp::new_dir().expect("create temporary directory");
    let mut child = Command::new(PDU)
        .with_current_dir(&workspace)
        .with_arg("--json-input")
        .with_arg("--bytes-format=metric")
        .with_arg("--total-width=100")
        .with_arg("--max-depth=10")
        .with_stdin(Stdio::piped())
        .with_stdout(Stdio::piped())
        .with_stderr(Stdio::piped())
        .spawn()
        .expect("spawn command");
    child
        .stdin
        .as_mut()
        .expect("get stdin of child process")
        .write_all(json.as_bytes())
        .expect("write JSON string to child process's stdin");
    let actual = child
        .wait_with_output()
        .expect("wait for output of child process")
        .pipe(stdout_text);
    let actual = actual.trim_end();
    eprintln!("ACTUAL:\n{actual}\n");

    let visualizer = Visualizer {
        data_tree: &sample_tree(),
        bytes_format: BytesFormat::MetricUnits,
        direction: Direction::BottomUp,
        bar_alignment: BarAlignment::Left,
        column_width_distribution: ColumnWidthDistribution::total(100),
    };
    let expected = format!("{visualizer}");
    let expected = expected.trim_end();
    eprintln!("EXPECTED:\n{expected}\n");

    assert_eq!(actual, expected);
}

#[test]
fn json_output_json_input() {
    let workspace = SampleWorkspace::default();

    let mut json_output = Command::new(PDU)
        .with_current_dir(&workspace)
        .with_arg("--json-output")
        .with_arg("--quantity=apparent-size")
        .with_arg(&workspace)
        .with_stdin(Stdio::null())
        .with_stdout(Stdio::piped())
        .with_stderr(Stdio::piped())
        .spawn()
        .expect("spawn command with --json-output");
    let actual = Command::new(PDU)
        .with_current_dir(&workspace)
        .with_arg("--json-input")
        .with_arg("--bytes-format=metric")
        .with_arg("--total-width=100")
        .with_arg("--max-depth=10")
        .with_stdin(
            json_output
                .stdout
                .take()
                .expect("get stdout of command with --json-output")
                .into(),
        )
        .with_stdout(Stdio::piped())
        .with_stderr(Stdio::piped())
        .output()
        .expect("spawn command with --json-input")
        .pipe(stdout_text);
    eprintln!("ACTUAL:\n{actual}\n");

    let expected = Command::new(PDU)
        .with_current_dir(&workspace)
        .with_arg("--bytes-format=metric")
        .with_arg("--total-width=100")
        .with_arg("--max-depth=10")
        .with_arg("--quantity=apparent-size")
        .with_arg(&workspace)
        .with_stdin(Stdio::piped())
        .with_stdout(Stdio::piped())
        .with_stderr(Stdio::piped())
        .output()
        .expect("spawn command for expected")
        .pipe(stdout_text);
    eprintln!("EXPECTED:\n{expected}\n");

    assert_eq!(actual, expected);

    let json_output_status = json_output
        .wait()
        .expect("wait for the command with --json-output to terminate");
    assert!(json_output_status.success());
}