diff --git a/Cargo.toml b/Cargo.toml index 65494e2..4b68c78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "find-images" -version = "0.1.1" +version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] chrono = "0.4" -shlex = "1.3" +shlex = "1.2" structopt = "0.3" diff --git a/README.md b/README.md index f2e17d1..35e0447 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,4 @@ Find images (sort by last modified by default). Useful for piping into sxiv/nsxiv when hidden subfiles/directories are significant. ## Find non-images -Obtain non-image files by giving any extensions to `--extensions` +obtain non-image files by giving any extension to `--extensions` diff --git a/prebuilt-x86-64-linux/find-images b/prebuilt-x86-64-linux/find-images index 9feaa0c..82a7267 100755 Binary files a/prebuilt-x86-64-linux/find-images and b/prebuilt-x86-64-linux/find-images differ diff --git a/src/main.rs b/src/main.rs index d3a0bb6..96e2172 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,9 +5,6 @@ use crate::structopt::StructOpt; use std::collections::HashSet; use std::io::Write; use std::path::{Path,PathBuf}; -use std::os::unix::ffi::OsStrExt; - - #[derive(StructOpt)] #[structopt(about="Recursively get images")] @@ -29,7 +26,7 @@ struct CLIArguments { quote: bool, #[structopt(short, long, value_name="EXT", - help="File extensions to filter for (default: dpx exr gif heic jpeg jpg png svg tiff webp)")] + help="File extensions to filter for (default: jpg jpeg png webp gif heic tiff dpx exr svg)")] extensions: Vec, #[structopt(value_name="TARGET", @@ -37,8 +34,6 @@ struct CLIArguments { targets: Vec, } - - fn main() -> Result<(), Box>{ let args = { let mut args = CLIArguments::from_args(); @@ -49,100 +44,74 @@ fn main() -> Result<(), Box>{ }; let valid_extensions: HashSet<&str> = if args.extensions.is_empty() { - ["dpx", "exr", "gif", "heic", "jpeg", "jpg", "png", "svg", "tiff", "webp"].into_iter().collect() + ["jpg", "jpeg", "png", "webp", "gif", "heic", "tiff", "dpx", "exr", "svg"].into_iter().collect() } else { args.extensions.iter().map(|s| s.as_str()).collect() }; - let mut registry = Registry::new(valid_extensions); + let mut registry: Vec = Vec::new(); - registry.populate(args.targets.into_iter().map(|target| Path::new(&target).to_path_buf() ), args.dohidden); + args.targets.into_iter().map(|target| Path::new(&target).to_path_buf() ).for_each( + |path| if path.is_file() { + register_file_if_image(&mut registry, path, &valid_extensions); + } else if path.is_dir() { + register_dir(&mut registry, path, &valid_extensions, args.dohidden); + } + ); if !args.no_sort { - registry.sort_by_modified(); - } - - let stdout = std::io::stdout(); - let mut stdout_buffer = std::io::BufWriter::new(stdout.lock()); - registry.write_all(&mut stdout_buffer, args.null, args.quote)?; - - Ok(()) -} - - - -struct Registry<'a> { - registry: Vec<(std::fs::Metadata, PathBuf)>, - valid_extensions: HashSet<&'a str> -} -impl<'a> Registry<'a> { - pub fn new(valid_extensions: HashSet<&'a str>) -> Self { - Self { registry: Vec::new(), valid_extensions } - } - - pub fn write_all(&self, writer: &mut impl Write, separator_null: bool, quote: bool) -> std::io::Result<()> { - let sep = if separator_null { '\0' } else { '\n' }; - for (_, file) in &self.registry { - if quote { - writer.write_all(&shlex::bytes::try_quote(&file.as_os_str().as_bytes()).unwrap())?; - } else { - writer.write_all(file.as_os_str().as_bytes())?; - } - write!(writer, "{}", sep)?; - } - Ok(()) - } - - pub fn sort_by_modified(&mut self) { - self.registry.sort_by_key(|(meta,_)| { - meta.modified().ok().unwrap_or_else( + registry.sort_by_key(|entry| { + entry.metadata().ok().and_then(|meta| meta.modified().ok()).unwrap_or_else( || std::time::SystemTime::UNIX_EPOCH, ) }); } - pub fn populate(&mut self, source_paths: impl Iterator, dohidden: bool) { - for path in source_paths { - if path.is_file() { - if let Ok(metadata) = std::fs::metadata(&path) { // intentionally not symlink_metadata - self.add_file(path, metadata); + let stdout = std::io::stdout(); + let mut stdout_buffer = std::io::BufWriter::new(stdout.lock()); + + if args.null { + if args.quote { for file in registry { write!(stdout_buffer, "{}\0", shlex::quote(&file.to_string_lossy()))?; } } + else { for file in registry { write!(stdout_buffer, "{}\0", &file.to_string_lossy() )?; } } + } else { + if args.quote { for file in registry { writeln!(stdout_buffer, "{}", shlex::quote(&file.to_string_lossy()))?; } } + else { for file in registry { writeln!(stdout_buffer, "{}", &file.to_string_lossy() )?; } } + } + + Ok(()) +} + +fn register_file_if_image(registry: &mut Vec, path: PathBuf, valid_extensions: &HashSet<&str>) { + if let Some(osstr_ext) = path.extension() { + match osstr_ext.to_str() { + Some(ext) => { + if valid_extensions.contains(ext) { + registry.push(path); } - } else if path.is_dir() { - self.add_dir(path, dohidden); - } + }, + None => eprintln!( + "Cannot read non-utf-8 file extension: {} on {}", + shlex::quote(&osstr_ext.to_string_lossy()), + shlex::quote(&path.to_string_lossy()) + ) } } +} - fn add_file(&mut self, path: PathBuf, metadata: std::fs::Metadata) { - if let Some(osstr_ext) = path.extension() { - match osstr_ext.to_str() { - Some(ext) => if self.valid_extensions.contains(ext) { - self.registry.push((metadata, path)); - }, - None => eprintln!( - "Cannot read non-utf-8 file extension: {} on {}", - shlex::try_quote(&osstr_ext.to_string_lossy()).unwrap(), - shlex::try_quote(&path.to_string_lossy()).unwrap(), - ) +fn register_dir(registry: &mut Vec, path: PathBuf, valid_extensions: &HashSet<&str>, dohidden: bool) { + if let Ok(entries) = std::fs::read_dir(path) { + for path in entries.filter_map(|e| e.ok() ).map(|e| e.path() ) { + if !dohidden && path.file_name().map(|name| name.to_string_lossy().starts_with('.')).unwrap_or(false) { + continue } - } - } - - fn add_dir(&mut self, path: PathBuf, dohidden: bool) { - if let Ok(entries) = std::fs::read_dir(path) { - for path in entries.filter_map(|e| e.ok() ).map(|e| e.path() ) { - if !dohidden && path.file_name().map(|name| name.to_string_lossy().starts_with('.')).unwrap_or(true) { // this unwraps to None if the file_name is .. or is root / (neither of which would happen in this scenario) + if let Ok(metadata) = std::fs::symlink_metadata(&path) { + if metadata.file_type().is_symlink() { continue } - if let Ok(metadata) = std::fs::symlink_metadata(&path) { - if metadata.file_type().is_symlink() { - continue - } - if path.is_file() { - self.add_file(path, metadata) - } else if path.is_dir() { - self.add_dir(path, dohidden); - } + if path.is_file() { + register_file_if_image(registry, path, valid_extensions); + } else if path.is_dir() { + register_dir(registry, path, valid_extensions, dohidden); } } }