initial commit

main
Nicholas Hope 2023-09-17 16:11:44 -04:00
parent 717d74298f
commit b694e5f672
5 changed files with 645 additions and 0 deletions

344
Cargo.lock generated Normal file
View File

@ -0,0 +1,344 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anstream"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"
[[package]]
name = "anstyle-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "fud"
version = "0.1.0"
dependencies = [
"clap",
"rayon",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "libc"
version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "proc-macro2"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "fud"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.4.3", features = ["derive"] }
rayon = "1.7.0"

128
src/directory.rs Normal file
View File

@ -0,0 +1,128 @@
use std::path::{PathBuf, Component};
#[derive(Debug, Clone, Default)]
pub struct Directory {
pub name: String,
pub size: u64,
children: Vec<Directory>,
}
impl Directory {
pub fn new(name: String, size: u64) -> Self {
Self {
name,
size,
children: Vec::new(),
}
}
pub fn insert(&mut self, path: PathBuf, size: u64) {
let components =
path.components()
.filter_map(
|cmp| match cmp {
Component::Normal(os_str) => os_str.to_str(),
_ => None,
}
).map(ToOwned::to_owned);
let mut current = self;
for next in components {
if &next == &current.name {
continue;
}
let idx = if let Some(idx) = current.children.iter().position(|d| d.name == next) {
idx
} else {
current.children.push(Directory::new(next, size));
current.children.len()-1
};
current = &mut current.children[idx];
}
}
/// Recursive function to find the greaest file size
/// in this directory tree, used for formatting in the output
fn find_max_size(&self) -> u64 {
self.children.iter()
.map(|d| d.find_max_size())
.max()
.unwrap_or(self.size)
}
/// public-exposed print function, does the setup for the
/// real print function, `print2`
pub fn print(self) {
// fake print function to give the depth param
let max_size = self.find_max_size();
let tabwidth = max_size.to_string().len() + 2;
let mut stack = Vec::new();
self.print2(tabwidth as usize, &mut stack);
}
/// real print function that makes the tree
fn print2(self, tabwidth: usize, stack: &mut Vec<TreePart>) {
let indent = build_indent(stack);
println!("{:tabwidth$}{indent} {}", self.size.to_string(), self.name);
stack.push(TreePart::Edge);
let mut iter = self.children.into_iter().peekable();
while let Some(child) = iter.next() {
if iter.peek().is_none() {
let idx = stack.len()-1;
stack[idx] = TreePart::Corner;
};
child.print2(tabwidth, stack);
}
stack.pop();
}
}
impl Extend<(PathBuf, u64)> for Directory {
fn extend<T: IntoIterator<Item = (PathBuf, u64)>>(&mut self, iter: T) {
for (p, s) in iter {
self.insert(p, s);
}
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
enum TreePart {
/// Rightmost column, not the last in the directory
Edge,
/// Not rightmost, but dir not finished yet
Line,
/// Rightmost column in the last directory
Corner,
/// Not rightmost, but the dir has finished
Blank
}
impl TreePart {
/// convert to ascii art
pub fn display(&self) -> &'static str {
match self {
Self::Edge => "├──",
Self::Line => "",
Self::Corner => "└──",
Self::Blank => " ",
}
}
}
/// Construct the indent string for the given stack.
/// must be called at the top of the recursive function,
/// and does mutate the stack
fn build_indent(stack: &mut Vec<TreePart>) -> String {
let mut indent = String::new();
for (i, mut tp) in stack.iter().enumerate() {
if i < stack.len()-1 && tp == &TreePart::Edge {
tp = &TreePart::Line;
}
indent += tp.display();
}
// essentially, if the last time element on the stack was a corner,
// make it blank for all future prints
if let Some(last @ TreePart::Corner) = stack.last_mut() {
*last = TreePart::Blank;
}
indent
}

72
src/main.rs Normal file
View File

@ -0,0 +1,72 @@
use clap::Parser;
mod walk;
mod directory;
use walk::walk;
use std::fs::File;
use std::process::ExitCode;
#[derive(Parser, Debug, Clone)]
pub struct Args {
#[arg(
short, long, default_value_t = false,
help = "keep going if an error occurs",
long_help = "keep going if an error occurs (ex. unreadable subdirectories in a readable directory)"
)]
persistant: bool,
#[arg(
short, long,
help = "minimize output",
long_help = "like -t, but does not print \"total: \" before the summary or the newline after. It also surpresses all error messages",
default_value_t = false,
)]
minimal: bool,
#[arg(
short, long,
help = "only display the total size",
default_value_t = false,
)]
total_only: bool,
#[arg(value_parser = validate_path, default_value_t = String::from("."), help = "directory to begin search from")]
path: String,
}
fn validate_path(s: &str) -> Result<String, String> {
let here = File::open(s).map_err(|e| e.to_string())?;
let meta = here.metadata().map_err(|e| e.to_string())?;
if meta.is_dir() {
return Ok(s.to_owned())
} else if meta.is_file() {
return Err("this is a file (hint: use wc to view file sizes)".to_owned());
} else {
return Err("this is not a directory".to_owned());
}
}
fn main() -> ExitCode {
let args = Args::parse();
let dir_structure = match walk(args.clone()) {
Ok(dir) => dir,
Err(e) => return e,
};
if args.minimal {
print!("{}", dir_structure.size);
} else {
let size = dir_structure.size; // copy size before print consumes dir_structure
dir_structure.print();
// looks like "print the total unless asked to", but "total: " is to prevent
// the root from getting lost in massive outputs
if !args.total_only {
println!("total: {size}");
}
}
ExitCode::SUCCESS
}

91
src/walk.rs Normal file
View File

@ -0,0 +1,91 @@
use rayon::prelude::*;
use std::sync::mpsc::{Sender, channel};
use std::fs::{read_dir, DirEntry};
use std::io::Error;
use std::path::PathBuf;
use std::process::ExitCode;
use crate::Args;
use crate::directory::Directory;
pub fn walk<'a>(args: Args) -> Result<Directory, ExitCode> {
let (tx, rx) = channel();
let mut total = 0;
for entry in read_dir(&args.path).unwrap() {
let entry = match entry {
Ok(e) => e,
Err(e) => {
if !args.minimal {
eprintln!("unable to open {}: {e}", args.path);
}
if args.persistant {
continue;
} else {
return Err(ExitCode::FAILURE);
}
},
};
total += match total_entry(entry, &args, &tx) {
Ok(t) => t,
Err((path, error)) => {
if !args.minimal {
eprintln!("error opening {}: {error}", path.display());
}
if args.persistant {
continue;
} else {
return Err(ExitCode::FAILURE);
}
},
};
}
// drop this to close the channel, so that into_iter() can end
drop(tx);
let mut fs = Directory::new(args.path, total);
fs.extend(rx);
Ok(fs)
}
fn total_entry(entry: DirEntry, args: &Args, printer: &Sender<(PathBuf, u64)>) -> Result<u64, (PathBuf, Error)> {
let path = entry.path();
match path.read_dir() {
Ok(dir) => {
let result = dir.par_bridge()
.filter_map(Result::ok)
.map(|entry| total_entry(entry, args, printer))
.reduce(|| Ok(0), reduce_once);
if let Ok(size) = result {
if !args.minimal && !args.total_only {
let _ = printer.send((path, size));
}
}
return result;
},
Err(_) if path.is_file() => {
let size = unsafe { path.metadata().unwrap_unchecked() }.len();
if !args.minimal && !args.total_only {
let _ = printer.send((path, size));
}
return Ok(size);
},
Err(e) => Err((path, e)),
}
}
fn reduce_once<E>(accum: Result<u64, E>, this: Result<u64, E>) -> Result<u64, E> {
// reduction function for total_entry():
// short-circuit Errs, propagate Oks
// generic bc I'm lazy
match (accum, this) {
(Ok(n1), Ok(n2)) => Ok(n1 + n2),
(Err(e), _) | (_, Err(e)) => Err(e),
}
}