209 lines
4.9 KiB
Rust
209 lines
4.9 KiB
Rust
use anyhow::{Context as _, Result, anyhow};
|
|
use camino::{Utf8Path, Utf8PathBuf};
|
|
use std::{
|
|
borrow::Cow,
|
|
fs,
|
|
io::{Read as _, Write as _},
|
|
rc::Rc,
|
|
};
|
|
|
|
fn main() -> Result<()> {
|
|
let mut app = App::try_new()?;
|
|
app.run()
|
|
}
|
|
|
|
struct App {
|
|
is_terminal: bool,
|
|
lk: Option<Lk>,
|
|
paths: Vec<Cow<'static, Utf8Path>>,
|
|
path_index: usize,
|
|
}
|
|
|
|
impl App {
|
|
fn try_new() -> Result<Self> {
|
|
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 paths = vec![];
|
|
for arg in args {
|
|
paths.push(Cow::Owned(Utf8PathBuf::from(arg)));
|
|
}
|
|
let paths = if paths.is_empty() {
|
|
vec![Cow::Borrowed(Utf8Path::new("."))]
|
|
} else {
|
|
paths
|
|
};
|
|
|
|
Ok(App {
|
|
is_terminal,
|
|
lk: None,
|
|
paths,
|
|
path_index: 0,
|
|
})
|
|
}
|
|
|
|
fn poll(&mut self) -> Result<Option<Rc<[u8]>>> {
|
|
loop {
|
|
if self.path_index >= self.paths.len() {
|
|
return Ok(None);
|
|
}
|
|
let path = &self.paths[self.path_index];
|
|
match self.lk.as_mut() {
|
|
Some(lk) => {
|
|
if let Some(buf) = lk.poll()? {
|
|
return Ok(Some(buf));
|
|
} else {
|
|
self.lk = None;
|
|
self.path_index += 1;
|
|
}
|
|
}
|
|
None => {
|
|
self.lk = Some(Lk::try_new(path)?);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
fn run(&mut self) -> Result<()> {
|
|
while let Some(buf) = self.poll()? {
|
|
std::io::stdout().write_all(&buf)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
enum Lk {
|
|
Cat(Cat),
|
|
Ls(Ls),
|
|
}
|
|
|
|
impl Lk {
|
|
fn try_new(path: &Utf8Path) -> Result<Self> {
|
|
let metadata = fs::metadata(path)?;
|
|
if metadata.is_dir() {
|
|
Self::try_new_ls(path)
|
|
} else {
|
|
Self::try_new_cat(path)
|
|
}
|
|
}
|
|
|
|
fn try_new_cat(path: &Utf8Path) -> Result<Self> {
|
|
Ok(Self::Cat(Cat::try_new(path)?))
|
|
}
|
|
|
|
fn try_new_ls(path: &Utf8Path) -> Result<Self> {
|
|
Ok(Self::Ls(Ls::try_new(path)?))
|
|
}
|
|
|
|
// I don't understand why, but using `Rc` here instead of `Cow` satisfies the borrow checker.
|
|
fn poll(&mut self) -> Result<Option<Rc<[u8]>>> {
|
|
match self {
|
|
Lk::Cat(x) => x.poll(),
|
|
Lk::Ls(x) => x.poll(),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Cat {
|
|
f: fs::File,
|
|
buf: Vec<u8>,
|
|
}
|
|
|
|
impl Cat {
|
|
fn try_new(path: &Utf8Path) -> Result<Self> {
|
|
let f = fs::File::open(path)?;
|
|
let buf = vec![0u8; 4_096];
|
|
Ok(Self { f, buf })
|
|
}
|
|
|
|
fn poll(&mut self) -> Result<Option<Rc<[u8]>>> {
|
|
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.into()))
|
|
}
|
|
}
|
|
|
|
struct Ls {
|
|
names: Vec<String>,
|
|
name_index: usize,
|
|
}
|
|
|
|
impl Ls {
|
|
fn try_new(path: &Utf8Path) -> Result<Self> {
|
|
let d = fs::read_dir(path)?;
|
|
let mut names = vec![];
|
|
for entry in d {
|
|
let entry = entry?;
|
|
names.push(
|
|
entry
|
|
.file_name()
|
|
.into_string()
|
|
.map_err(|_| anyhow!("name is not UTF-8"))?,
|
|
);
|
|
}
|
|
names.sort();
|
|
Ok(Self {
|
|
names,
|
|
name_index: 0,
|
|
})
|
|
}
|
|
|
|
fn poll(&mut self) -> Result<Option<Rc<[u8]>>> {
|
|
if self.name_index >= self.names.len() {
|
|
return Ok(None);
|
|
}
|
|
let name = &self.names[self.name_index];
|
|
self.name_index += 1;
|
|
Ok(Some(format!("{name}\n").into_bytes().into()))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::path::Path;
|
|
|
|
#[test]
|
|
fn test() -> Result<()> {
|
|
let dir = tempfile::tempdir()?;
|
|
let dir = dir.as_ref();
|
|
fs::write(dir.join("alfa.txt"), "alfa\n")?;
|
|
fs::write(dir.join("bravo.txt"), "bravo\n")?;
|
|
|
|
for (input, expected) in [
|
|
(vec![dir], b"alfa.txt\nbravo.txt\n".to_vec()),
|
|
(
|
|
vec![&dir.join("alfa.txt"), &dir.join("bravo.txt")],
|
|
b"alfa\nbravo\n".to_vec(),
|
|
),
|
|
] {
|
|
assert_eq!(lk(input)?, expected);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn lk(paths: Vec<&Path>) -> Result<Vec<u8>> {
|
|
let paths = paths
|
|
.iter()
|
|
.map(|p| Cow::Owned(Utf8PathBuf::from_path_buf(p.to_path_buf()).unwrap()))
|
|
.collect();
|
|
let mut app = App {
|
|
is_terminal: false,
|
|
lk: None,
|
|
paths,
|
|
path_index: 0,
|
|
};
|
|
let mut out = vec![];
|
|
while let Some(buf) = app.poll()? {
|
|
out.write_all(&buf)?;
|
|
}
|
|
Ok(out)
|
|
}
|
|
}
|