//! Arg parsing use super::*; use std::{ ffi::OsStr, path::{ Path, PathBuf, }, borrow::Cow, }; use tokio::{ sync::{ mpsc, }, }; use futures::{ stream::{self, Stream, BoxStream, StreamExt,}, }; /// Parsed command-line args #[derive(Debug)] pub struct Args { paths: Option>, } impl Args { /// Paths as an async stream /// /// # Non-immediate /// When input paths come from `stdin`, the output stream will be non-immediate. pub fn paths(&self) -> BoxStream<'_, Cow<'_, Path>> { if let Some(paths) = self.paths.as_ref() { stream::iter(paths.iter().map(|x| Cow::Borrowed(Path::new(x)))).boxed() } else { let (tx, rx) = mpsc::channel(128); tokio::spawn(async move { use tokio::io::{ self, AsyncReadExt, AsyncBufReadExt }; let mut stdin = { tokio::io::BufReader::new(io::stdin()) }; let mut buf = Vec::with_capacity(1024); loop { buf.clear(); use std::os::unix::prelude::*; let n = match stdin.read_until(b'\n', &mut buf).await { Ok(n) => n, Err(e) => { error!("paths: Failed to read input line: {}", e); break; }, }; trace!("paths: buffer: {:?}", &buf[..]); if n == 0 { trace!("paths: Stdin exhausted. Exiting."); break; } let path_bytes = &buf[..n]; let path_bytes = if path_bytes.len() == 1 { trace!("paths: Ignoring empty line. Yielding then continuing."); tokio::task::yield_now().await; continue; } else { &path_bytes[.. (path_bytes.len()-1)] }; let path = Path::new(OsStr::from_bytes(path_bytes)); trace!("Read path {:?}", path); if path.exists() { if tx.send(path.to_owned()).await.is_err() { trace!("paths: Stream dropped, cancelling stdin read."); break; } } } }); tokio_stream::wrappers::ReceiverStream::new(rx).map(|x| Cow::Owned(x)).boxed() } } } #[inline] pub fn parse_args() -> eyre::Result { //return Ok(Args { paths: None }); todo!("parse(std::env::args().skip(1))") } //TODO: fn parse(args: impl IntoIterator) -> eyre::Result