diff --git a/src/app.rs b/src/app.rs index ec06a1f..01e60ff 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,7 +5,6 @@ // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. - use std::collections::HashMap; use clap::{App, AppSettings, Arg}; @@ -16,10 +15,10 @@ struct Help { } macro_rules! doc { - ($map:expr, $name:expr, $short:expr) => { + ($map: expr, $name: expr, $short: expr) => { doc!($map, $name, $short, $short) }; - ($map:expr, $name:expr, $short:expr, $long:expr) => { + ($map: expr, $name: expr, $short: expr, $long: expr) => { $map.insert( $name, Help { @@ -146,6 +145,15 @@ pub fn build_app() -> App<'static, 'static> { .takes_value(true) .value_name("num"), ) + .arg( + arg("size") + .long("size") + .short("S") + .takes_value(true) + .number_of_values(1) + .allow_hyphen_values(true) + .multiple(true), + ) .arg( arg("max-buffer-time") .long("max-buffer-time") @@ -254,6 +262,18 @@ fn usage() -> HashMap<&'static str, Help> { doc!(h, "rg-alias-hidden-ignore" , "Alias for no-ignore and/or hidden" , "Alias for no-ignore ('u') and no-ignore and hidden ('uu')"); - + doc!(h, "size" + , "Limit results based on the size of files." + , "Limit results based on the size of files using the format <+->.\n \ + '+': file size must be greater than this\n \ + '-': file size must be less than this\n \ + 'NUM': The numeric size (e.g. 500)\n \ + 'UN': The units for NUM.\n\ + Allowed unit values:\n \ + 'b' or 'B': bytes\n \ + 'k' or 'K': kilobytes\n \ + 'm' or 'M': megabytes\n \ + 'g' or 'G': gigabytes\n \ + 't' or 'T': terabytes"); h } diff --git a/src/internal.rs b/src/internal.rs index 8d07836..023afc5 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -14,10 +14,14 @@ use std::time; use exec::CommandTemplate; use lscolors::LsColors; -use regex::RegexSet; +use regex::{Regex, RegexSet}; use regex_syntax::hir::Hir; use regex_syntax::Parser; +lazy_static! { + static ref SIZE_CAPTURES: Regex = { Regex::new(r"^(\+|-)(\d+)([a-zA-Z]{1,2})$").unwrap() }; +} + /// Whether or not to show pub struct FileTypes { pub files: bool, @@ -37,6 +41,58 @@ impl Default for FileTypes { } } +enum SizeLimitType { + Max, + Min, +} + +pub struct SizeFilter { + size: u64, + limit_type: SizeLimitType, +} + +impl SizeFilter { + pub fn is_within(&self, size: u64) -> bool { + match self.limit_type { + SizeLimitType::Max => size <= self.size, + SizeLimitType::Min => size >= self.size, + } + } +} + +const KILO: u64 = 1024; +const MEGA: u64 = KILO * 1024; +const GIGA: u64 = MEGA * 1024; +const TERA: u64 = GIGA * 1024; + +impl<'a> From<&'a str> for SizeFilter { + /// Create the `SizeFilter` from the given `&str`. + /// It is imperative that the incoming value has been validated for + /// proper format. + fn from(s: &str) -> Self { + let captures = SIZE_CAPTURES.captures(s).unwrap(); + let limit = match captures.get(1).map_or("+", |m| m.as_str()) { + "+" => SizeLimitType::Min, + _ => SizeLimitType::Max, + }; + + let quantity = captures.get(2).unwrap().as_str().parse::().unwrap(); + + let multiplier = match &captures.get(3).map_or("m", |m| m.as_str()).to_lowercase()[..] { + "k" => KILO, + "m" => MEGA, + "g" => GIGA, + "t" => TERA, + _ => 1, // Any we don't understand we'll just say the number of bytes + }; + + SizeFilter { + size: quantity * multiplier, + limit_type: limit, + } + } +} + /// Configuration options for *fd*. pub struct FdOptions { /// Whether the search is case-sensitive or case-insensitive. @@ -96,6 +152,9 @@ pub struct FdOptions { /// A list of custom ignore files. pub ignore_files: Vec, + + /// The given constraints on the size of returned files + pub size_constraints: Vec, } /// Print error message to stderr and exit with status `1`. diff --git a/src/main.rs b/src/main.rs index 69479c4..973d656 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,12 +34,17 @@ use std::sync::Arc; use std::time; use atty::Stream; -use regex::{RegexBuilder, RegexSetBuilder}; +use regex::{RegexBuilder, RegexSetBuilder, Regex}; use exec::CommandTemplate; -use internal::{error, pattern_has_uppercase_char, transform_args_with_exec, FdOptions, FileTypes}; +use internal::{error, pattern_has_uppercase_char, transform_args_with_exec, FdOptions, FileTypes, + SizeFilter}; use lscolors::LsColors; +lazy_static! { + static ref VALIDATE_SIZE: Regex = { Regex::new(r"^[\+-]{1}\d+[bBkKmMgGTt]{1,2}$").unwrap() }; +} + fn main() { let checked_args = transform_args_with_exec(env::args_os()); let matches = app::build_app().get_matches_from(checked_args); @@ -132,6 +137,16 @@ fn main() { let command = matches.values_of("exec").map(CommandTemplate::new); + let size_limits: Vec = matches + .values_of("size") + .map(|v| v.map(|sf| { + if !VALIDATE_SIZE.is_match(sf) { + error(&format!("Error: {} is not a valid size constraint.", sf)); + } + sf.into() + }).collect()) + .unwrap_or_else(|| vec![]); + let config = FdOptions { case_sensitive, search_full_path: matches.is_present("full-path"), @@ -195,6 +210,7 @@ fn main() { .values_of("ignore-file") .map(|vs| vs.map(PathBuf::from).collect()) .unwrap_or_else(|| vec![]), + size_constraints: size_limits, }; match RegexBuilder::new(&pattern_regex) diff --git a/src/walk.rs b/src/walk.rs index 25630d8..16b0bae 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -248,6 +248,16 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc, config: Arc) { } } + // Filter out unwanted sizes if it is a file and we have been given size constraints. + if entry_path.is_file() && config.size_constraints.len() > 0 { + if let Ok(metadata) = entry_path.metadata() { + let file_size = metadata.len(); + if config.size_constraints.iter().any(|sc| !sc.is_within(file_size)) { + return ignore::WalkState::Continue; + } + } + } + let search_str_o = if config.search_full_path { match fshelper::path_absolute_form(entry_path) { Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),