Added -z, -0, --nul, --delim <byte>, -I, --delim ifs: Set line seperator for stdin input and writing output to \0, or to a specific byte, or to the value of the IFS env var

Fortune for enumerate-ordered's current commit: Future blessing − 末吉
master
Avril 1 year ago
parent edc99301a1
commit 0334992225
Signed by: flanchan
GPG Key ID: 284488987C31F630

2
Cargo.lock generated

@ -97,7 +97,7 @@ dependencies = [
[[package]]
name = "enumerate-ordered"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"color-eyre",
"futures",

@ -2,7 +2,7 @@
name = "enumerate-ordered"
description = "Order files by their timestamps"
authors=["Avril (Flanchan) <avril@flanchan.moe>"]
version = "1.0.0"
version = "1.1.0"
edition = "2021"
license = "gpl3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

@ -1 +0,0 @@
Add `-0`, deliminate standard input reads and standard output writes by the ASCII nul (b'\0', 0u8) instead of a newline (b'\n')

@ -18,15 +18,32 @@ use futures::{
};
/// Parsed command-line args
#[derive(Debug, Default, Clone)]
#[derive(Debug, Clone)]
pub struct Args
{
pub delim: u8,
pub reverse: bool,
pub walker: walk::Config,
pub worker: work::Config,
paths: Option<Vec<PathBuf>>,
}
impl Default for Args
{
#[inline]
fn default() -> Self
{
Self {
delim: b'\n',
reverse: false,
walker: Default::default(),
worker: Default::default(),
paths: None,
}
}
}
impl Args
{
/// Paths as an async stream
@ -39,6 +56,7 @@ impl Args
stream::iter(paths.iter().map(|x| Cow::Borrowed(Path::new(x)))).boxed()
} else {
let (tx, rx) = mpsc::channel(128);
let read_chr = self.delim;
tokio::spawn(async move {
use tokio::io::{
self,
@ -52,7 +70,7 @@ impl Args
{
buf.clear();
use std::os::unix::prelude::*;
let n = match stdin.read_until(b'\n', &mut buf).await {
let n = match stdin.read_until(read_chr, &mut buf).await {
Ok(n) => n,
Err(e) => {
error!("paths: Failed to read input line: {}", e);
@ -69,7 +87,7 @@ impl Args
trace!("paths: Ignoring empty line. Yielding then continuing.");
tokio::task::yield_now().await;
continue;
} else if path_bytes[n-1] == b'\n' {
} else if path_bytes[n-1] == read_chr {
&path_bytes[.. (path_bytes.len()-1)]
} else {
path_bytes
@ -298,6 +316,44 @@ where I: Iterator<Item = String>
output.worker.by = work::OrderBy::ModifiedTime;
}
// -z, -0, --nul,
// -I, --delim <char>|ifs
'delim: {
output.delim = if input.is_any(args![b"z0", "nul"]) {
0u8
} else if input.is_any(args![b'I', "delim"]) {
fn read_ifs_as_byte() -> eyre::Result<u8>
{
let Some(ifs) = std::env::var_os("IFS") else {
return Err(eyre!("IFS env-var not set"));
};
use std::os::unix::prelude::*;
ifs.as_bytes().first().copied().ok_or(eyre!("IFS env-var empty"))
}
if input.is_long() {
let val = take!();
match val.as_bytes() {
[] => return Err(eyre!("Line seperator cannot be empty").with_suggestion(|| "--delim ifs|<byte>".header("Usage is"))),
b"ifs" | b"IFS" => {
read_ifs_as_byte().wrap_err(eyre!("Failed to read line seperator from IFS env var"))?
}
[de] => *de,
[de, rest @ ..] => {
warn!("Specified more than one byte for line seperator. Ignoring other {} bytes", rest.len());
*de
},
}
} else {
// Read IFS
read_ifs_as_byte().wrap_err(eyre!("Failed to read line seperator from IFS env var"))?
}
} else {
// No change
break 'delim;
};
};
// -n, --reverse
output.reverse = input.is_any(args![b'n', "reverse"]);

@ -81,6 +81,9 @@ where W: std::io::Write,
}
write_opt!("-r", "--recursive <limit>" => "Recursively sort input files, up to `<limit>` (set to 0 for infniite); if limit is not specified, recursion is infinite")?;
write_opt!("-z", "-0", "--nul" => "Seperate lines when reading/writing by the ascii NUL character (0) instead of a newline. This applies to reading input from `stdin` as well as writing output")?;
write_opt!("-I", "--delim ifs" => "Read the first byte of the IFS environment variable as the I/O line seperator.")?;
write_opt!("--delim <byte>" => "Use this user-provided byte as the I/O line seperator")?;
write_opt!("-a", "--atime" => "Sort by atime")?;
write_opt!("-c", "--ctime" => "Sort by ctime (default)")?;
write_opt!("-m", "--mtime" => "Sort by mtime")?;
@ -179,25 +182,27 @@ async fn main() -> eyre::Result<()> {
trace!("Writing ordered results to stdout... (buffered, sync, rev: {})", args.reverse);
#[inline]
fn operate_on<W: ?Sized, I>(stdout: &mut W, set: I) -> eyre::Result<()>
fn operate_on<W: ?Sized, I>(stdout: &mut W, set: I, delim: &[u8]) -> eyre::Result<()>
where W: Write,
I: IntoIterator<Item = work::FileInfo> + ExactSizeIterator + DoubleEndedIterator + std::iter::FusedIterator + 'static
{
for info in set
{
stdout.write_all(info.path().as_os_str().as_bytes())
.and_then(|_| stdout.write_all(&[b'\n']))
.and_then(|_| stdout.write_all(delim))
.wrap_err("Failed to write raw pathname for entry to stdout")
.with_context(|| format!("{:?}", info.path()).header("Pathname was"))?;
}
Ok(())
}
let delim = &[args.delim];
if args.reverse {
operate_on(&mut stdout, set.into_iter().rev())
operate_on(&mut stdout, set.into_iter().rev(), delim)
} else {
operate_on(&mut stdout, set.into_iter())
}.wrap_err("Abandoning output write due to failure")?;
operate_on(&mut stdout, set.into_iter(), delim)
}.wrap_err("Abandoning output write due to intermittent failure")?;
stdout.flush().wrap_err("Failed to flush buffered output to stdout")?;
Ok(())

Loading…
Cancel
Save