mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-04-24 17:12:16 +02:00
Add --sort-files flag.
When used, parallelism is disabled but the results are sorted by file path. Closes #263
This commit is contained in:
parent
95cea77625
commit
b65a8c353b
7
doc/rg.1
7
doc/rg.1
@ -355,6 +355,13 @@ This is overridden by either \-\-case\-sensitive or \-\-ignore\-case.
|
|||||||
.RS
|
.RS
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B \-\-sort\-files
|
||||||
|
Sort results by file path.
|
||||||
|
Note that this currently disables all parallelism and runs search in a
|
||||||
|
single thread.
|
||||||
|
.RS
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
.B \-j, \-\-threads \f[I]ARG\f[]
|
.B \-j, \-\-threads \f[I]ARG\f[]
|
||||||
The number of threads to use.
|
The number of threads to use.
|
||||||
0 means use the number of logical CPUs (capped at 6).
|
0 means use the number of logical CPUs (capped at 6).
|
||||||
|
@ -239,6 +239,10 @@ Project home page: https://github.com/BurntSushi/ripgrep
|
|||||||
Search case sensitively otherwise. This is overridden by either
|
Search case sensitively otherwise. This is overridden by either
|
||||||
--case-sensitive or --ignore-case.
|
--case-sensitive or --ignore-case.
|
||||||
|
|
||||||
|
--sort-files
|
||||||
|
: Sort results by file path. Note that this currently
|
||||||
|
disables all parallelism and runs search in a single thread.
|
||||||
|
|
||||||
-j, --threads *ARG*
|
-j, --threads *ARG*
|
||||||
: The number of threads to use. 0 means use the number of logical CPUs
|
: The number of threads to use. 0 means use the number of logical CPUs
|
||||||
(capped at 6). [default: 0]
|
(capped at 6). [default: 0]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::ffi::OsStr;
|
use std::cmp;
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::{self, FileType, Metadata};
|
use std::fs::{self, FileType, Metadata};
|
||||||
use std::io;
|
use std::io;
|
||||||
@ -324,16 +325,30 @@ impl DirEntryRaw {
|
|||||||
/// path is skipped.
|
/// path is skipped.
|
||||||
/// * Sixth, if the path has made it this far then it is yielded in the
|
/// * Sixth, if the path has made it this far then it is yielded in the
|
||||||
/// iterator.
|
/// iterator.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct WalkBuilder {
|
pub struct WalkBuilder {
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
ig_builder: IgnoreBuilder,
|
ig_builder: IgnoreBuilder,
|
||||||
parents: bool,
|
parents: bool,
|
||||||
max_depth: Option<usize>,
|
max_depth: Option<usize>,
|
||||||
follow_links: bool,
|
follow_links: bool,
|
||||||
|
sorter: Option<Arc<Fn(&OsString, &OsString) -> cmp::Ordering + 'static>>,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for WalkBuilder {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("WalkBuilder")
|
||||||
|
.field("paths", &self.paths)
|
||||||
|
.field("ig_builder", &self.ig_builder)
|
||||||
|
.field("parents", &self.parents)
|
||||||
|
.field("max_depth", &self.max_depth)
|
||||||
|
.field("follow_links", &self.follow_links)
|
||||||
|
.field("threads", &self.threads)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WalkBuilder {
|
impl WalkBuilder {
|
||||||
/// Create a new builder for a recursive directory iterator for the
|
/// Create a new builder for a recursive directory iterator for the
|
||||||
/// directory given.
|
/// directory given.
|
||||||
@ -348,6 +363,7 @@ impl WalkBuilder {
|
|||||||
parents: true,
|
parents: true,
|
||||||
max_depth: None,
|
max_depth: None,
|
||||||
follow_links: false,
|
follow_links: false,
|
||||||
|
sorter: None,
|
||||||
threads: 0,
|
threads: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -356,6 +372,7 @@ impl WalkBuilder {
|
|||||||
pub fn build(&self) -> Walk {
|
pub fn build(&self) -> Walk {
|
||||||
let follow_links = self.follow_links;
|
let follow_links = self.follow_links;
|
||||||
let max_depth = self.max_depth;
|
let max_depth = self.max_depth;
|
||||||
|
let cmp = self.sorter.clone();
|
||||||
let its = self.paths.iter().map(move |p| {
|
let its = self.paths.iter().map(move |p| {
|
||||||
if p == Path::new("-") {
|
if p == Path::new("-") {
|
||||||
(p.to_path_buf(), None)
|
(p.to_path_buf(), None)
|
||||||
@ -365,6 +382,10 @@ impl WalkBuilder {
|
|||||||
if let Some(max_depth) = max_depth {
|
if let Some(max_depth) = max_depth {
|
||||||
wd = wd.max_depth(max_depth);
|
wd = wd.max_depth(max_depth);
|
||||||
}
|
}
|
||||||
|
if let Some(ref cmp) = cmp {
|
||||||
|
let cmp = cmp.clone();
|
||||||
|
wd = wd.sort_by(move |a, b| cmp(a, b));
|
||||||
|
}
|
||||||
(p.to_path_buf(), Some(WalkEventIter::from(wd)))
|
(p.to_path_buf(), Some(WalkEventIter::from(wd)))
|
||||||
}
|
}
|
||||||
}).collect::<Vec<_>>().into_iter();
|
}).collect::<Vec<_>>().into_iter();
|
||||||
@ -533,6 +554,20 @@ impl WalkBuilder {
|
|||||||
self.ig_builder.git_exclude(yes);
|
self.ig_builder.git_exclude(yes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a function for sorting directory entries.
|
||||||
|
///
|
||||||
|
/// If a compare function is set, the resulting iterator will return all
|
||||||
|
/// paths in sorted order. The compare function will be called to compare
|
||||||
|
/// names from entries from the same directory using only the name of the
|
||||||
|
/// entry.
|
||||||
|
///
|
||||||
|
/// Note that this is not used in the parallel iterator.
|
||||||
|
pub fn sort_by<F>(&mut self, cmp: F) -> &mut WalkBuilder
|
||||||
|
where F: Fn(&OsString, &OsString) -> cmp::Ordering + 'static {
|
||||||
|
self.sorter = Some(Arc::new(cmp));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Walk is a recursive directory iterator over file paths in one or more
|
/// Walk is a recursive directory iterator over file paths in one or more
|
||||||
|
@ -158,6 +158,7 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
|
|||||||
.arg(flag("replace").short("r").value_name("ARG").takes_value(true))
|
.arg(flag("replace").short("r").value_name("ARG").takes_value(true))
|
||||||
.arg(flag("case-sensitive").short("s"))
|
.arg(flag("case-sensitive").short("s"))
|
||||||
.arg(flag("smart-case").short("S"))
|
.arg(flag("smart-case").short("S"))
|
||||||
|
.arg(flag("sort-files"))
|
||||||
.arg(flag("threads")
|
.arg(flag("threads")
|
||||||
.short("j").value_name("ARG").takes_value(true)
|
.short("j").value_name("ARG").takes_value(true)
|
||||||
.validator(validate_number))
|
.validator(validate_number))
|
||||||
@ -426,6 +427,10 @@ lazy_static! {
|
|||||||
"Searches case insensitively if the pattern is all lowercase. \
|
"Searches case insensitively if the pattern is all lowercase. \
|
||||||
Search case sensitively otherwise. This is overridden by \
|
Search case sensitively otherwise. This is overridden by \
|
||||||
either -s/--case-sensitive or -i/--ignore-case.");
|
either -s/--case-sensitive or -i/--ignore-case.");
|
||||||
|
doc!(h, "sort-files",
|
||||||
|
"Sort results by file path. Implies --threads=1.",
|
||||||
|
"Sort results by file path. Note that this currently \
|
||||||
|
disables all parallelism and runs search in a single thread.");
|
||||||
doc!(h, "threads",
|
doc!(h, "threads",
|
||||||
"The approximate number of threads to use.",
|
"The approximate number of threads to use.",
|
||||||
"The approximate number of threads to use. A value of 0 (which \
|
"The approximate number of threads to use. A value of 0 (which \
|
||||||
|
@ -64,6 +64,7 @@ pub struct Args {
|
|||||||
quiet: bool,
|
quiet: bool,
|
||||||
quiet_matched: QuietMatched,
|
quiet_matched: QuietMatched,
|
||||||
replace: Option<Vec<u8>>,
|
replace: Option<Vec<u8>>,
|
||||||
|
sort_files: bool,
|
||||||
text: bool,
|
text: bool,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
type_list: bool,
|
type_list: bool,
|
||||||
@ -277,6 +278,9 @@ impl Args {
|
|||||||
wd.ignore(!self.no_ignore);
|
wd.ignore(!self.no_ignore);
|
||||||
wd.parents(!self.no_ignore_parent);
|
wd.parents(!self.no_ignore_parent);
|
||||||
wd.threads(self.threads());
|
wd.threads(self.threads());
|
||||||
|
if self.sort_files {
|
||||||
|
wd.sort_by(|a, b| a.cmp(b));
|
||||||
|
}
|
||||||
wd
|
wd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,6 +337,7 @@ impl<'a> ArgMatches<'a> {
|
|||||||
quiet: quiet,
|
quiet: quiet,
|
||||||
quiet_matched: QuietMatched::new(quiet),
|
quiet_matched: QuietMatched::new(quiet),
|
||||||
replace: self.replace(),
|
replace: self.replace(),
|
||||||
|
sort_files: self.is_present("sort-files"),
|
||||||
text: self.text(),
|
text: self.text(),
|
||||||
threads: try!(self.threads()),
|
threads: try!(self.threads()),
|
||||||
type_list: self.is_present("type-list"),
|
type_list: self.is_present("type-list"),
|
||||||
@ -654,6 +659,9 @@ impl<'a> ArgMatches<'a> {
|
|||||||
|
|
||||||
/// Returns the approximate number of threads that ripgrep should use.
|
/// Returns the approximate number of threads that ripgrep should use.
|
||||||
fn threads(&self) -> Result<usize> {
|
fn threads(&self) -> Result<usize> {
|
||||||
|
if self.is_present("sort-files") {
|
||||||
|
return Ok(1);
|
||||||
|
}
|
||||||
let threads = try!(self.usize_of("threads")).unwrap_or(0);
|
let threads = try!(self.usize_of("threads")).unwrap_or(0);
|
||||||
Ok(if threads == 0 {
|
Ok(if threads == 0 {
|
||||||
cmp::min(12, num_cpus::get())
|
cmp::min(12, num_cpus::get())
|
||||||
|
@ -1203,6 +1203,18 @@ clean!(feature_159_zero_max, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
|||||||
wd.assert_err(&mut cmd);
|
wd.assert_err(&mut cmd);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/263
|
||||||
|
clean!(feature_263_sort_files, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("foo", "test");
|
||||||
|
wd.create("abc", "test");
|
||||||
|
wd.create("zoo", "test");
|
||||||
|
wd.create("bar", "test");
|
||||||
|
cmd.arg("--sort-files");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
assert_eq!(lines, "abc:test\nbar:test\nfoo:test\nzoo:test\n");
|
||||||
|
});
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn binary_nosearch() {
|
fn binary_nosearch() {
|
||||||
let wd = WorkDir::new("binary_nosearch");
|
let wd = WorkDir::new("binary_nosearch");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user