-
-
Notifications
You must be signed in to change notification settings - Fork 40
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 stopDir on recursive search #18
Conversation
This feels a bit fragile. What if you set the |
I have some thoughts on the code, but let's address the design points first.
As in a race condition? Definitely possible, but as it is, our search across levels of the tree is just as susceptible to that, unfortunately. I am slightly against the
I'm also still curious as to whether adding |
} | ||
|
||
const splittedPath = dir.split('/'); | ||
const lastFolder = splittedPath[splittedPath.length - 1]; |
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.
Very important to use path.basename() here to grab the last path component, instead of splitting like this.
- Hard coding
/
as a separator will not work with Windows-style paths. - Even if you use path.sep, the path may end with a separator, which is quite common in practice. That will cause the final array element to be an empty string
''
. Can't use that as a directory name!
Both of the above issues will be taken care of by path.basename()
.
There is another problem, though. What if dir
is the root directory? You will need to take care of that separately, as even path.basename()
will return an empty string in that case.
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 for your advice 👍
const splittedPath = dir.split('/'); | ||
const lastFolder = splittedPath[splittedPath.length - 1]; | ||
|
||
if (lastFolder === compareDir) { |
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.
Simpler...
return lastDir === compareDir;
const filenames = [].concat(filename); | ||
|
||
return new Promise(resolve => { | ||
(function find(dir) { | ||
locatePath(filenames, {cwd: dir}).then(file => { | ||
if (file) { | ||
resolve(path.join(dir, file)); | ||
} else if (isLastDirEqualTo(dir, opts.stopDir)) { |
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.
Conceptually, this is the same as the next else if
; the point is we are stopping and returning null
because we have reached the end. I would prefer to combine these in the form of else if (foo || bar)
.
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 need to path.resolve(opts.stopDir)
, otherwise comparisons against it will fail if the user passes a relative path like ../../
.
That too, but I was thinking more of developer mistake. It's easy to have same-named directories in various places of a deep directory tree. Seems like matching the absolute path on where to stop would solve his use-case. |
We would only give it the information we already have, the current path. This option would solve many potential use-cases. Users could, for example, use it to find the first package.json file up the tree that does not contain a certain string. I like it because it's flexible, as it can enable the user to solve all the use-cases themselves that we don't care about.
They get the upwards directory traversal, which is not that easy to do manually. I've seen so many examples of it being done incorrectly when I did research for this module. |
@sholladay and @sindresorhus thank a lot for your comments. Anyway, I like a lot the So, What I need to do to help you with this feature? Can I help with something? |
Okay, let's do it!
Let's start off with these semantics: Something like this rough sketch... if (file) {
resolve(path.join(dir, file));
} else if (dir === root || shouldContinue(dir) === false) {
resolve(null);
} else {
find(path.dirname(dir));
} @CVarisco I hope this is clear enough. Happy to help if you need it. Thanks for working on this. 👍 |
There are two use-cases I'd like to solve.
findUp('unicorn.png', {
shouldContinue: dir => dir !== rootDir
});
findUp({
shouldContinue: dir => !(path.existsSync(path.join(dir, 'foo')) && path.existsSync(path.join(dir, 'bar')))
}); Maybe it would make more sense to just have a findUp(dir => {
return !(path.existsSync(path.join(dir, 'foo')) && path.existsSync(path.join(dir, 'bar')));
}); But here we would like to return the findUp(dir => {
return path.existsSync(path.join(dir, 'foo')) && path.existsSync(path.join(dir, 'bar')) && path.join(dir, 'foo');
}); |
Hi @sindresorhus 🙂 Do you think that for the user could be simple to have multiple ways to use the lib? But maybe I don't understand your point of view. |
Well, we could, but then your use-case would require more code on your part, as it would be up to you to check whether the file exists. The type params would be different regardless, as there wouldn't be any filename string input for use-case 2. |
I think we are right 🙂 Anyway, How you can proceed? |
Let's wait for feedback from @sholladay. |
Those look pretty good to me @sindresorhus. 👍 I'll propose a slight variation for sake of discussion. Something like this... opts = Object.assign({
match: locatePath
}, opts);
// ...
(function find(dir) {
Promise.resolve(opts.match(filenames, {cwd: dir})).then(file => {
if (file) {
resolve(path.join(dir, file));
} else if (dir === root || opts.shouldContinue && shouldContinue(dir) === false) {
resolve(null);
} else {
find(path.dirname(dir));
}
});
})(startDir); Usage examples:
Of course, they can also be combined, too. What is different?
|
Guys? What do we need to do? |
I think we're on the same page about what needs to be implemented. There's just some room for creativity / improvement. Are you up for doing something like the designs we showed above, @CVarisco? If so, I think it would be fine to move forward with a new PR based on one of those approaches. If you want to wait for Sindre to reply, that is fine, too. We will probably have another round of feedback either way, which we will be easier when we see the actual code in practice. We'll get it merged quickly once the tests are passing and it feels right. :) |
I'm not super excited about this as there are many potential use-cases for this feature when it's not as simple as just using an array of filenames, so having special support for this, means just yet another way of doing things. They could still be kinda pure, by the user creating a factory function that accepts an array of filenames and returns a matcher.
Yeah, I agree findUp(dir => {
return path.existsSync(path.join(dir, 'foo')) && path.existsSync(path.join(dir, 'bar')) && findUp.currentDir;
}); Where |
Works for me. 👍 We're good to go @CVarisco. To recap: if You can, if you feel like it, implement the extra sugar to support returning |
Hi guys! I'm sorry but in the next days, I'll be a little bit busy. Really sorry about that 🙁 |
Not |
@CVarisco No worries. There's no rush :) |
Between the small merge conflict and the amount of discussion in here, I think a new PR is going to be cleaner and easier for everyone, so going to close this. I opened #21 to reflect what we want to implement. If you are still up for doing that @CVarisco, it would be super appreciated. Either way, thank you for contributing. You definitely helped us figure out a good path forward. ❤️ |
@sholladay thanks a lot for your comment. Sorry again, thanks for your amazing job that improve the community ❤️ |
referred to #17