that's fine for now
parent
4363fa1f19
commit
94d4845409
|
@ -2,11 +2,44 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.97"
|
version = "1.0.97"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.74"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
|
@ -54,6 +87,12 @@ dependencies = [
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.31.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.171"
|
version = "0.2.171"
|
||||||
|
@ -75,6 +114,30 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.36.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.1"
|
version = "1.21.1"
|
||||||
|
@ -87,6 +150,12 @@ version = "5.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
|
|
@ -12,7 +12,7 @@ name = "lk"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.97" }
|
anyhow = { version = "1.0.97", features = ["backtrace"] }
|
||||||
camino = "1.1.9"
|
camino = "1.1.9"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
101
src/main.rs
101
src/main.rs
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
fs,
|
fs,
|
||||||
|
@ -14,7 +15,7 @@ fn main() -> Result<()> {
|
||||||
struct App {
|
struct App {
|
||||||
is_terminal: bool,
|
is_terminal: bool,
|
||||||
lk: Option<Lk>,
|
lk: Option<Lk>,
|
||||||
paths: Vec<Cow<'static, str>>,
|
paths: Vec<Cow<'static, Utf8Path>>,
|
||||||
path_index: usize,
|
path_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,15 +28,20 @@ impl App {
|
||||||
let _exe_name = args.next().context("How do we not have an exe name?")?;
|
let _exe_name = args.next().context("How do we not have an exe name?")?;
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
for arg in args {
|
for arg in args {
|
||||||
paths.push(arg.into());
|
paths.push(Cow::Owned(Utf8PathBuf::from(arg)));
|
||||||
}
|
}
|
||||||
let paths = if paths.is_empty() {
|
let paths = if paths.is_empty() {
|
||||||
vec![".".into()]
|
vec![Cow::Borrowed(Utf8Path::new("."))]
|
||||||
} else {
|
} else {
|
||||||
paths
|
paths
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(App { is_terminal, lk: None, paths, path_index: 0 })
|
Ok(App {
|
||||||
|
is_terminal,
|
||||||
|
lk: None,
|
||||||
|
paths,
|
||||||
|
path_index: 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(&mut self) -> Result<Option<Rc<[u8]>>> {
|
fn poll(&mut self) -> Result<Option<Rc<[u8]>>> {
|
||||||
|
@ -45,11 +51,13 @@ impl App {
|
||||||
}
|
}
|
||||||
let path = &self.paths[self.path_index];
|
let path = &self.paths[self.path_index];
|
||||||
match self.lk.as_mut() {
|
match self.lk.as_mut() {
|
||||||
Some(lk) => if let Some(buf) = lk.poll()? {
|
Some(lk) => {
|
||||||
return Ok(Some(buf));
|
if let Some(buf) = lk.poll()? {
|
||||||
} else {
|
return Ok(Some(buf));
|
||||||
self.lk = None;
|
} else {
|
||||||
self.path_index += 1;
|
self.lk = None;
|
||||||
|
self.path_index += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.lk = Some(Lk::try_new(path)?);
|
self.lk = Some(Lk::try_new(path)?);
|
||||||
|
@ -72,7 +80,7 @@ enum Lk {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Lk {
|
impl Lk {
|
||||||
fn try_new(path: &str) -> Result<Self> {
|
fn try_new(path: &Utf8Path) -> Result<Self> {
|
||||||
let metadata = fs::metadata(path)?;
|
let metadata = fs::metadata(path)?;
|
||||||
if metadata.is_dir() {
|
if metadata.is_dir() {
|
||||||
Self::try_new_ls(path)
|
Self::try_new_ls(path)
|
||||||
|
@ -81,14 +89,15 @@ impl Lk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_new_cat(path: &str) -> Result<Self> {
|
fn try_new_cat(path: &Utf8Path) -> Result<Self> {
|
||||||
Ok(Self::Cat(Cat::try_new(path)?))
|
Ok(Self::Cat(Cat::try_new(path)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_new_ls(path: &str) -> Result<Self> {
|
fn try_new_ls(path: &Utf8Path) -> Result<Self> {
|
||||||
Ok(Self::Ls(Ls::try_new(path)?))
|
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]>>> {
|
fn poll(&mut self) -> Result<Option<Rc<[u8]>>> {
|
||||||
match self {
|
match self {
|
||||||
Lk::Cat(x) => x.poll(),
|
Lk::Cat(x) => x.poll(),
|
||||||
|
@ -103,7 +112,7 @@ struct Cat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cat {
|
impl Cat {
|
||||||
fn try_new(path: &str) -> Result<Self> {
|
fn try_new(path: &Utf8Path) -> Result<Self> {
|
||||||
let f = fs::File::open(path)?;
|
let f = fs::File::open(path)?;
|
||||||
let buf = vec![0u8; 4_096];
|
let buf = vec![0u8; 4_096];
|
||||||
Ok(Self { f, buf })
|
Ok(Self { f, buf })
|
||||||
|
@ -120,34 +129,80 @@ impl Cat {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Ls {
|
struct Ls {
|
||||||
d: fs::ReadDir,
|
names: Vec<String>,
|
||||||
|
name_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ls {
|
impl Ls {
|
||||||
fn try_new(path: &str) -> Result<Self> {
|
fn try_new(path: &Utf8Path) -> Result<Self> {
|
||||||
let d = fs::read_dir(path)?;
|
let d = fs::read_dir(path)?;
|
||||||
Ok(Self { d })
|
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]>>> {
|
fn poll(&mut self) -> Result<Option<Rc<[u8]>>> {
|
||||||
let Some(entry) = self.d.next() else {
|
if self.name_index >= self.names.len() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
}
|
||||||
let entry = entry?;
|
let name = &self.names[self.name_index];
|
||||||
let path = entry.file_name();
|
self.name_index += 1;
|
||||||
let path = path.to_str().context("file name is not valid Unicode")?;
|
Ok(Some(format!("{name}\n").into_bytes().into()))
|
||||||
Ok(Some(format!("{path}\n").into_bytes().into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() -> Result<()> {
|
fn test() -> Result<()> {
|
||||||
let dir = tempfile::tempdir()?;
|
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(())
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue