,
+}
+impl Directory {
+ #[inline(always)]
+ pub const fn size(&self) -> u64 {
+ self.size
+ }
+
+ #[inline(always)]
+ pub fn path(&self) -> &Path {
+ self.name.as_ref()
+ }
+
+ pub fn new(path: P) -> Result
+ where
+ P: AsRef
+ {
+ let path = path.as_ref();
+ let name = path.canonicalize()?.file_name().unwrap().into();
+
+ let dir = match read_dir(&path) {
+ Ok(dir) => dir,
+ Err(io_error) => match io_error.kind() {
+ ErrorKind::NotADirectory => return Ok(Self {
+ name,
+ size: path.metadata()?.len(),
+ children: Vec::new()
+ }),
+ _ => return Err(io_error),
+ }
+ };
+
+ let mut size = 0;
+ let mut children = Vec::new();
+ for entry in dir {
+ let child = Directory::new(entry?.path())?;
+ size += child.size;
+ children.push(child);
+ }
+
+ Ok(Self{
+ name,
+ size,
+ children,
+ })
+ }
+
+ pub fn display(self) -> String {
+ // since self.size is definitionally the greatest value, the tab length
+ // is just the length of self.len, plus two for a tab width
+ let tab_size = self.size.to_string().len() + 2;
+ self.vectorise().iter().map(|e| e.to_string(tab_size) + "\n").collect()
+ }
+
+ /// TODO: make not recursive, take &self if possible,
+ /// and maybe write directly to stdout to not use so much mem
+ fn vectorise(self) -> Vec {
+ let mut result = Vec::new();
+
+ result.push(TreeEntry(Vec::new(), self.name.display().to_string(), self.size));
+
+ let mut new_entry_part = TreePart::First;
+ let mut continue_part = TreePart::Wait;
+
+ let len = self.children.len();
+
+ for (idx, child) in self.children.into_iter().enumerate() {
+ if idx+1 == len {
+ new_entry_part = TreePart::Last;
+ continue_part = TreePart::Blank;
+ }
+
+ let subtree = child.vectorise();
+
+ for mut item in subtree {
+ if item.0.len() == 0 {
+ item.0.push(new_entry_part);
+ } else {
+ item.0.push(continue_part);
+ }
+ result.push(item);
+ }
+ }
+
+ result
+ }
+}
+
+#[derive(Debug)]
+struct TreeEntry(Vec, String, u64);
+impl TreeEntry {
+ fn to_string(&self, tab_size: usize) -> String {
+ let mut result = format!("{: &str {
+ match self {
+ Self::First => "├──",
+ Self::Wait => "│ ",
+ Self::Last => "└──",
+ Self::Blank => " ",
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..22ad14b
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,104 @@
+#![feature(io_error_more, fs_try_exists)]
+
+use clap::Parser;
+use clap::ArgAction;
+
+mod directory;
+use directory::Directory;
+
+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",
+ conflicts_with = "total_only",
+ default_value_t = false,
+ )]
+ minimal: bool,
+
+ #[arg(
+ short, long,
+ help = "only display the total size",
+ conflicts_with = "minimal",
+ default_value_t = false,
+ )]
+ total_only: bool,
+
+ #[arg(
+ short='2', long,
+ help = "print sizes in powers of 1024",
+ default_value_t = false,
+ conflicts_with = "si"
+ )]
+ base_two: bool,
+
+ #[arg(
+ short='0', long,
+ help = "print sizes in powers of 1000",
+ default_value_t = false,
+ conflicts_with = "base_two"
+ )]
+ si: bool,
+
+ #[arg(
+ value_parser = validate_path,
+ help = "directories to summate",
+ action = ArgAction::Append,
+ num_args = 1..
+ )]
+ path: Vec,
+}
+
+fn validate_path(s: &str) -> Result {
+ // try to access it's metadata, since that is what is used
+ // to get its length
+ std::fs::metadata(s)
+ .map(|_| s.to_string())
+ .map_err(|e| e.to_string())
+}
+
+fn main() -> ExitCode {
+ let args = Args::parse();
+
+ let mut total = 0;
+ for path in args.path {
+ let dir_structure = match Directory::new(path) {
+ Ok(ds) => ds,
+ Err(e) => {
+ if !args.minimal {
+ eprintln!("hb: {e}");
+ }
+ return ExitCode::FAILURE;
+ }
+ };
+
+ total += dir_structure.size();
+
+ if !args.minimal {
+ if args.total_only {
+ println!("{}: {}", dir_structure.path().to_str().unwrap(), dir_structure.size());
+ } else {
+ print!("{}", dir_structure.display());
+ }
+ }
+ }
+
+ if args.total_only {
+ println!("total: {total}");
+ }
+ else if args.minimal {
+ print!("{total}");
+ }
+
+ ExitCode::SUCCESS
+}
\ No newline at end of file
diff --git a/src/walk.rs b/src/walk.rs
new file mode 100644
index 0000000..3b561b3
--- /dev/null
+++ b/src/walk.rs
@@ -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 {
+ 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::make(args.path, total);
+ fs.extend(rx);
+
+ Ok(fs)
+}
+
+fn total_entry(entry: DirEntry, args: &Args, printer: &Sender<(PathBuf, u64)>) -> Result {
+ 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(accum: Result, this: Result) -> Result {
+ // 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),
+ }
+}
\ No newline at end of file