diff --git a/userspace/sysutils/Cargo.toml b/userspace/sysutils/Cargo.toml index b8660ebb..1063ca9a 100644 --- a/userspace/sysutils/Cargo.toml +++ b/userspace/sysutils/Cargo.toml @@ -81,6 +81,10 @@ path = "src/echo.rs" name = "ls" path = "src/ls.rs" +[[bin]] +name = "tree" +path = "src/tree.rs" + [[bin]] name = "mv" path = "src/mv.rs" diff --git a/userspace/sysutils/src/tree.rs b/userspace/sysutils/src/tree.rs new file mode 100644 index 00000000..5340a236 --- /dev/null +++ b/userspace/sysutils/src/tree.rs @@ -0,0 +1,90 @@ +use std::{ + fs, io, + path::{Path, PathBuf}, + process::ExitCode, +}; + +use clap::Parser; + +fn list>(path: P, depth: usize, last_mask: u64) -> io::Result<()> { + fn indent(depth: usize, mask: u64) { + for i in 0..depth + 1 { + if i < depth { + if (1 << i) & mask == 0 { + print!("│ "); + } else { + print!(" "); + } + } else if (1 << i) & mask == 0 { + print!("├─"); + } else { + print!("└─"); + } + } + } + + let mut entries = vec![]; + let dir = fs::read_dir(path)?; + for entry in dir { + let Ok((ty, entry)) = entry.and_then(|e| Ok((e.file_type()?, e))) else { + entries.push(None); + continue; + }; + + let name = entry.file_name().into_string().unwrap(); + + if name == "." || name == ".." { + continue; + } + + entries.push(Some((name, entry.path(), ty.is_dir()))); + } + + entries.sort_by(|a, b| { + let (Some((a, _, _)), Some((b, _, _))) = (a.as_ref(), b.as_ref()) else { + return Ord::cmp(&a.is_none(), &b.is_none()); + }; + + Ord::cmp(a, b) + }); + + let len = entries.len(); + for (i, entry) in entries.into_iter().enumerate() { + let last_bit = ((i == len - 1) as u64) << depth; + indent(depth, last_mask | last_bit); + + let Some((name, path, dir)) = entry else { + println!(""); + continue; + }; + + println!("{name}"); + + if dir { + list(path, depth + 1, last_mask | last_bit).ok(); + } + } + + Ok(()) +} + +#[derive(Debug, Parser)] +struct Args { + path: Option, +} + +fn run>(path: P) -> io::Result<()> { + list(path, 0, 0) +} + +fn main() -> ExitCode { + let args = Args::parse(); + let path = args.path.unwrap_or_else(|| PathBuf::from(".")); + match run(&path) { + Ok(()) => ExitCode::SUCCESS, + Err(error) => { + eprintln!("{}: {}", path.display(), error); + ExitCode::FAILURE + } + } +} diff --git a/xtask/src/build/userspace.rs b/xtask/src/build/userspace.rs index b2be21a0..2dcc9dfc 100644 --- a/xtask/src/build/userspace.rs +++ b/xtask/src/build/userspace.rs @@ -58,6 +58,7 @@ const PROGRAMS: &[(&str, &str)] = &[ ("sysmon", "bin/sysmon"), ("top", "bin/top"), ("touch", "bin/touch"), + ("tree", "bin/tree"), ("tst", "bin/tst"), ("view", "bin/view"), // netutils