Skip to content
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

Merged
merged 2 commits into from
Oct 10, 2018

Conversation

kimsnj
Copy link
Contributor

@kimsnj kimsnj commented Oct 9, 2018

This PR aims at implementing #165

It adds two options changed-before and changed-with that can accept either a duration (such as 10min, 2d, …) or a date and time (such as 2018-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?

Copy link
Owner

@sharkdp sharkdp left a 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)
Copy link
Owner

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 TimeFilters.

Copy link
Contributor Author

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)?))
Copy link
Owner

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 ?.

Copy link
Contributor Author

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>,
Copy link
Owner

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();
Copy link
Owner

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 {
Copy link
Owner

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!{
Copy link
Owner

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).

Copy link
Contributor Author

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));
Copy link
Owner

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.

Copy link
Contributor Author

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.

Copy link
Owner

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) {
Copy link
Owner

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"],
Copy link
Owner

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?

@sharkdp
Copy link
Owner

sharkdp commented Oct 10, 2018

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 changed-after = --changed-within and ??? == changed_before)

@sharkdp sharkdp merged commit abe8aa5 into sharkdp:master Oct 10, 2018
@ptzz
Copy link

ptzz commented Oct 16, 2018

git log has --since <date> and --until <date>, might provide inspiration for the naming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants