-
-
Notifications
You must be signed in to change notification settings - Fork 814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for --changed-before and --changed-with for modification … #339
Conversation
…time based search
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very much for your contribution.
This is really great work. I added a few (nit-picky) comments.
Edit: The command-line option naming-thing is a bit unfortunate, yes. I'll need to think about this.
src/internal.rs
Outdated
fn from_str(s: &str) -> Option<SystemTime> { | ||
use humantime; | ||
humantime::parse_duration(s) | ||
.map(|duration| SystemTime::now() - duration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be cleaner if we could pass now: SystemTime
into this function in order to make it side-effect free (or pure). This would make testing deterministic.
By getting SystemTime::now()
just once in the main-function, we would also prevent really unlikely situations where we would end up with two different SystemTime::now()
values in multiple TimeFilter
s.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/internal.rs
Outdated
} | ||
|
||
pub fn before(s: &str) -> Option<TimeFilter> { | ||
Some(TimeFilter::Before(TimeFilter::from_str(s)?)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternative implementation:
TimeFilter::from_str(s).map(TimeFilter::Before)
Choose whichever you prefer 😄. I was just shortly confused because I hadn't seen the ?
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I indeed find it more readable with map
:)
src/internal.rs
Outdated
@@ -162,6 +194,9 @@ pub struct FdOptions { | |||
|
|||
/// The given constraints on the size of returned files | |||
pub size_constraints: Vec<SizeFilter>, | |||
|
|||
/// Constraints on last modification time of files | |||
pub modification_constraints: Vec<TimeFilter>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer modification_time_constaints
or maybe just time_constraints
for now.
src/internal.rs
Outdated
|
||
#[test] | ||
fn is_time_within() { | ||
let now = SystemTime::now(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if this is rather unlikely to fail, it would be great if all tests could be completely deterministic by using specific (random) points in time instead of SystemTime::now()
.
src/internal.rs
Outdated
Some(TimeFilter::After(TimeFilter::from_str(s)?)) | ||
} | ||
|
||
pub fn is_within(&self, t: &SystemTime) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not completely happy with this function name. Maybe applies_to
?
src/walk.rs
Outdated
@@ -289,6 +289,21 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) { | |||
} | |||
} | |||
|
|||
// Filter out unwanted modification times | |||
if !config.modification_constraints.is_empty() { | |||
if_chain!{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This crate/macro looks interesting, but I would prefer if we could just use plain nested if
statements here (for consistency).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to avoid the 4 * else { return Continue }
.
I removed the dependency and added a boolean flag instead.
I could put back the else { return } if you prefer.
src/walk.rs
Outdated
if entry_path.is_file(); | ||
if let Ok(metadata) = entry_path.metadata(); | ||
if let Ok(modified) = metadata.modified(); | ||
if config.modification_constraints.iter().all(|tf| tf.is_within(&modified)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we would invert the logic here by using .iter().any(|tf| !tf.is_within(&modified)
we could profit from short-circuiting and also prevent the empty if
/then
clause.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all
also benefits from the same short-circuiting (as-soon as a predicate fails, all
returns).
in this case, it allows to have all the return WalkState::Continue
as the else branch, and thus put them in common.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, of course 👍
@@ -1110,3 +1113,65 @@ fn test_size() { | |||
// Files with size equal 4 kibibytes. | |||
te.assert_output(&["", "--size", "+4ki", "--size", "-4ki"], "4_kibibytes.foo"); | |||
} | |||
|
|||
#[cfg(test)] | |||
fn create_file_with_modified<P: AsRef<Path>>(path: P, duration_in_secs: u64) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!
tests/tests.rs
Outdated
); | ||
|
||
te.assert_output( | ||
&["min", "--changed-within", "12h"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test really confused me for a while because I didn't see the first "min"
argument. Maybe you could name some of the files foo_1_h
and others bar_10_min
and then additionally search for foo
here?
Thank you very much for the updates! We can think about the command-line option names before the next release, but I'd like to get this merged 👍 (one option would be to add aliases |
|
This PR aims at implementing #165
It adds two options
changed-before
andchanged-with
that can accept either a duration (such as10min
,2d
, …) or a date and time (such as2018-10-09 10:00:00
).Some bikeshedding might be useful on the name on the options, as some of the combination reads quite well with:
fd --changed-within 10d
fd --changed-before "2018-10-09 10:00:00"
But the other are a bit less self-explanatory:
fd --changed-before 10d
fd --changed-within "2018-10-09 10:00:00"
Any thoughts ?
On a slightly different topic, as i was running
clippy
before committing my changes, I noticed some lints triggered. Would you welcome a PR that applies those?