Compare commits

..

No commits in common. "7a35f4ac3ecd33665b3f8d3f933129a935eb11b7" and "6b0731a6f1bb7cab2507d1d121cd0e11f675d2ff" have entirely different histories.

4 changed files with 53 additions and 84 deletions

View file

@ -1,11 +1,11 @@
[package] [package]
name = "find-images" name = "find-images"
version = "0.1.1" version = "0.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
chrono = "0.4" chrono = "0.4"
shlex = "1.3" shlex = "1.2"
structopt = "0.3" structopt = "0.3"

View file

@ -3,4 +3,4 @@ Find images (sort by last modified by default).
Useful for piping into sxiv/nsxiv when hidden subfiles/directories are significant. Useful for piping into sxiv/nsxiv when hidden subfiles/directories are significant.
## Find non-images ## Find non-images
Obtain non-image files by giving any extensions to `--extensions` obtain non-image files by giving any extension to `--extensions`

Binary file not shown.

View file

@ -5,9 +5,6 @@ use crate::structopt::StructOpt;
use std::collections::HashSet; use std::collections::HashSet;
use std::io::Write; use std::io::Write;
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use std::os::unix::ffi::OsStrExt;
#[derive(StructOpt)] #[derive(StructOpt)]
#[structopt(about="Recursively get images")] #[structopt(about="Recursively get images")]
@ -29,7 +26,7 @@ struct CLIArguments {
quote: bool, quote: bool,
#[structopt(short, long, value_name="EXT", #[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<String>, extensions: Vec<String>,
#[structopt(value_name="TARGET", #[structopt(value_name="TARGET",
@ -37,8 +34,6 @@ struct CLIArguments {
targets: Vec<String>, targets: Vec<String>,
} }
fn main() -> Result<(), Box<dyn std::error::Error>>{ fn main() -> Result<(), Box<dyn std::error::Error>>{
let args = { let args = {
let mut args = CLIArguments::from_args(); let mut args = CLIArguments::from_args();
@ -49,89 +44,64 @@ fn main() -> Result<(), Box<dyn std::error::Error>>{
}; };
let valid_extensions: HashSet<&str> = if args.extensions.is_empty() { 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 { } else {
args.extensions.iter().map(|s| s.as_str()).collect() args.extensions.iter().map(|s| s.as_str()).collect()
}; };
let mut registry = Registry::new(valid_extensions); let mut registry: Vec<PathBuf> = 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 { if !args.no_sort {
registry.sort_by_modified(); registry.sort_by_key(|entry| {
} entry.metadata().ok().and_then(|meta| meta.modified().ok()).unwrap_or_else(
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(
|| std::time::SystemTime::UNIX_EPOCH, || std::time::SystemTime::UNIX_EPOCH,
) )
}); });
} }
pub fn populate(&mut self, source_paths: impl Iterator<Item=PathBuf>, dohidden: bool) { let stdout = std::io::stdout();
for path in source_paths { let mut stdout_buffer = std::io::BufWriter::new(stdout.lock());
if path.is_file() {
if let Ok(metadata) = std::fs::metadata(&path) { // intentionally not symlink_metadata if args.null {
self.add_file(path, metadata); 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 path.is_dir() { } else {
self.add_dir(path, dohidden); 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() )?; } }
}
} }
fn add_file(&mut self, path: PathBuf, metadata: std::fs::Metadata) { Ok(())
}
fn register_file_if_image(registry: &mut Vec<PathBuf>, path: PathBuf, valid_extensions: &HashSet<&str>) {
if let Some(osstr_ext) = path.extension() { if let Some(osstr_ext) = path.extension() {
match osstr_ext.to_str() { match osstr_ext.to_str() {
Some(ext) => if self.valid_extensions.contains(ext) { Some(ext) => {
self.registry.push((metadata, path)); if valid_extensions.contains(ext) {
registry.push(path);
}
}, },
None => eprintln!( None => eprintln!(
"Cannot read non-utf-8 file extension: {} on {}", "Cannot read non-utf-8 file extension: {} on {}",
shlex::try_quote(&osstr_ext.to_string_lossy()).unwrap(), shlex::quote(&osstr_ext.to_string_lossy()),
shlex::try_quote(&path.to_string_lossy()).unwrap(), shlex::quote(&path.to_string_lossy())
) )
} }
} }
} }
fn add_dir(&mut self, path: PathBuf, dohidden: bool) { fn register_dir(registry: &mut Vec<PathBuf>, path: PathBuf, valid_extensions: &HashSet<&str>, dohidden: bool) {
if let Ok(entries) = std::fs::read_dir(path) { if let Ok(entries) = std::fs::read_dir(path) {
for path in entries.filter_map(|e| e.ok() ).map(|e| e.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 !dohidden && path.file_name().map(|name| name.to_string_lossy().starts_with('.')).unwrap_or(false) {
continue continue
} }
if let Ok(metadata) = std::fs::symlink_metadata(&path) { if let Ok(metadata) = std::fs::symlink_metadata(&path) {
@ -139,10 +109,9 @@ impl<'a> Registry<'a> {
continue continue
} }
if path.is_file() { if path.is_file() {
self.add_file(path, metadata) register_file_if_image(registry, path, valid_extensions);
} else if path.is_dir() { } else if path.is_dir() {
self.add_dir(path, dohidden); register_dir(registry, path, valid_extensions, dohidden);
}
} }
} }
} }