Initial impl of size constraints.

This adds find-style size constraints with + or - indicating greater
or less than, a numerical size, and a unit
This commit is contained in:
Steve Pentland 2018-04-22 19:38:10 -04:00 committed by David Peter
parent fea85dbfb3
commit 0207c1371e
4 changed files with 112 additions and 7 deletions

View File

@ -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 <+-><NUM><UN>.\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
}

View File

@ -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::<u64>().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<PathBuf>,
/// The given constraints on the size of returned files
pub size_constraints: Vec<SizeFilter>,
}
/// Print error message to stderr and exit with status `1`.

View File

@ -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<SizeFilter> = 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)

View File

@ -248,6 +248,16 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
}
}
// 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()),