use anyhow::{Context as _, Result, bail}; use std::{ fs, io::{Read as _, Write as _}, }; fn main() -> Result<()> { let app = App::try_new()?; app.run() } struct App { filenames: Vec, is_terminal: bool, } impl App { fn try_new() -> Result { use std::io::IsTerminal as _; let is_terminal = std::io::stdout().is_terminal(); let mut args = std::env::args(); let _exe_name = args.next().context("How do we not have an exe name?")?; let mut filenames = vec![]; for arg in args { filenames.push(arg); } Ok(App { filenames, is_terminal, }) } fn run(&self) -> Result<()> { if self.filenames.is_empty() { return self.ls("."); } for filename in &self.filenames { let metadata = fs::metadata(filename)?; if metadata.is_dir() { self.ls(filename)?; } else { self.cat(filename)?; } } Ok(()) } fn cat(&self, filename: &str) -> Result<()> { let mut cat = Cat::try_new(filename)?; while let Some(buf) = cat.poll()? { std::io::stdout().write_all(buf)?; } Ok(()) } fn ls(&self, filename: &str) -> Result<()> { // Use `/usr/bin` just in case for some weird reason we're symlinked as ls, // we don't want to recurse into ourselves let mut command = std::process::Command::new("/usr/bin/ls"); if self.is_terminal { command.arg("--color=always"); } let status = command.arg(filename).status()?; if !status.success() { bail!("subprocess `ls` failed"); } Ok(()) } } struct Cat { f: fs::File, buf: Vec, } impl Cat { fn try_new(filename: &str) -> Result { let f = fs::File::open(filename)?; let buf = vec![0u8; 4_096]; Ok(Self { f, buf }) } fn poll(&mut self) -> Result> { let bytes_read = self.f.read(&mut self.buf)?; let buf = &self.buf[0..bytes_read]; if bytes_read == 0 { return Ok(None); } Ok(Some(buf)) } } #[cfg(test)] mod tests { use super::*; #[test] fn test() -> Result<()> { let dir = tempfile::tempdir()?; Ok(()) } }