From 814d37030e2d27ed75d6598c9d6ec00d427f196d Mon Sep 17 00:00:00 2001 From: sharkdp Date: Sat, 10 Feb 2018 15:19:53 +0100 Subject: [PATCH] Implement -F/--fixed-strings/--literal --- README.md | 3 ++- doc/fd.1 | 3 +++ src/app.rs | 9 +++++++++ src/main.rs | 11 +++++++++-- tests/tests.rs | 30 ++++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 54ac02e..2025528 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,7 @@ FLAGS: --no-ignore-vcs Do not respect .gitignore files -s, --case-sensitive Case-sensitive search (default: smart case) -i, --ignore-case Case-insensitive search (default: smart case) + -F, --fixed-strings Treat the pattern as a literal string -a, --absolute-path Show absolute instead of relative paths -L, --follow Follow symbolic links -p, --full-path Search full path (default: file-/dirname only) @@ -222,7 +223,7 @@ OPTIONS: -d, --max-depth Set maximum search depth (default: none) -t, --type ... Filter by type: f(ile), d(irectory), (sym)l(ink) -e, --extension ... Filter by file extension - -x, --exec ... Execute a command for each search result + -x, --exec Execute a command for each search result -E, --exclude ... Exclude entries that match the given glob pattern -c, --color When to use colors: never, *auto*, always -j, --threads Set number of threads to use for searching & executing diff --git a/doc/fd.1 b/doc/fd.1 index 50083c2..613edc9 100644 --- a/doc/fd.1 +++ b/doc/fd.1 @@ -47,6 +47,9 @@ Perform a case-sensitive search (default: smart case). .B \-i, \-\-ignore\-case Perform a case-insensitive search (default: smart case). .TP +.B \-F, \-\-fixed\-strings +Treat the pattern as a literal string instead of a regular expression. +.TP .B \-a, \-\-absolute\-path Show absolute instead of relative paths. .TP diff --git a/src/app.rs b/src/app.rs index 4c255d0..25771cc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -61,6 +61,12 @@ pub fn build_app() -> App<'static, 'static> { .short("i") .overrides_with("case-sensitive"), ) + .arg( + arg("fixed-strings") + .long("fixed-strings") + .short("F") + .alias("literal"), + ) .arg(arg("absolute-path").long("absolute-path").short("a")) .arg(arg("follow").long("follow").short("L").alias("dereference")) .arg(arg("full-path").long("full-path").short("p")) @@ -153,6 +159,9 @@ fn usage() -> HashMap<&'static str, Help> { , "Case-insensitive search (default: smart case)" , "Perform a case-insensitive search. By default, fd uses case-insensitive searches, \ unless the pattern contains an uppercase character (smart case)."); + doc!(h, "fixed-strings" + , "Treat the pattern as a literal string" + , "Treat the pattern as a literal string instead of a regular expression."); doc!(h, "absolute-path" , "Show absolute instead of relative paths" , "Shows the full path starting from the root as opposed to relative paths."); diff --git a/src/main.rs b/src/main.rs index bd714f1..bff1766 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,10 +81,17 @@ fn main() { .collect(); } + // Treat pattern as literal string if '--fixed-strings' is used + let pattern_regex = if matches.is_present("fixed-strings") { + regex::escape(pattern) + } else { + String::from(pattern) + }; + // The search will be case-sensitive if the command line flag is set or // if the pattern has an uppercase character (smart case). let case_sensitive = !matches.is_present("ignore-case") - && (matches.is_present("case-sensitive") || pattern_has_uppercase_char(pattern)); + && (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex)); let colored_output = match matches.value_of("color") { Some("always") => true, @@ -169,7 +176,7 @@ fn main() { .unwrap_or_else(|| vec![]), }; - match RegexBuilder::new(pattern) + match RegexBuilder::new(&pattern_regex) .case_insensitive(!config.case_sensitive) .dot_matches_new_line(true) .build() diff --git a/tests/tests.rs b/tests/tests.rs index 2e7e2b8..4259c1a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -791,3 +791,33 @@ fn assert_exec_output(exec_style: &str) { te.assert_output(&["e1", exec_style, "printf", "%s.%s\n"], "e1 e2."); } } + +/// Literal search (--fixed-strings) +#[test] +fn test_fixed_strings() { + let dirs = &["test1", "test2"]; + let files = &["test1/a.foo", "test1/a_foo", "test2/Download (1).tar.gz"]; + let te = TestEnv::new(dirs, files); + + // Regex search, dot is treated as "any character" + te.assert_output( + &["a.foo"], + "test1/a.foo + test1/a_foo", + ); + + // Literal search, dot is treated as character + te.assert_output(&["--fixed-strings", "a.foo"], "test1/a.foo"); + + // Regex search, parens are treated as group + te.assert_output(&["download (1)"], ""); + + // Literal search, parens are treated as characters + te.assert_output( + &["--fixed-strings", "download (1)"], + "test2/Download (1).tar.gz", + ); + + // Combine with --case-sensitive + te.assert_output(&["--fixed-strings", "--case-sensitive", "download (1)"], ""); +}