normalize paths
phated opened this issue · 18 comments
In testing on Windows, I found that the slashes in the path history were incorrect. It seems these are coming from node-glob but we aren't doing any translation.
Not sure where we should do this, but in the meantime, I am normalizing in vinyl-fs before we construct the Vinyl
object because it requires the least releases.
cc @contra
This should happen in vinyl
Thanks, should it only happen on construction? I was also thinking that glob-stream should maybe do it so there aren't incorrect paths being streamed on Windows (glob stream emits plain objects, not Vinyl objects)
@contra -
How would this work reliably in vinyl
core? To my understanding the only way to find the correct file path on windows is to use fs
module? I'm not entirely sure.
Would normalization of paths apply to all vinyl
files, or just those created from vinyl-fs
?
Is there code kicking around to normalize the paths anywhere? I found https://github.com/gulpjs/vinyl-fs/blob/master/lib/src/wrapWithVinylFile.js#L9, not sure if that is the current solution?
@Marak that's currently where we do the normalization (in glob-stream - https://github.com/gulpjs/glob-stream/blob/master/index.js#L50) but my thinking is that we should use path.normalize
when a path is set in vinyl instead of deferring that to glob-stream/vinyl-fs
Correction: that got moved into glob-stream at https://github.com/gulpjs/glob-stream/blob/master/index.js#L50
I haven't done much windows testing, path.normalize
will work?
If so, seems like it would be a simple fix to vinyl
?
@Marak yeah, node's path.normalize
is platform dependent. Should be a simple fix but no one has gotten around to changing + writing tests.
Cool.
I'm putting vinyl
into the hook.io dependency tree for some new platform features.
Been doing a general review of the ecosystem and core libs. Very impressed with everything. Thank you.
Replying to #92 (comment) here since this is the place for it :)
I'll probably be very nitpick-y about the normalization stuff because we've had a few problems recently - see: https://github.com/gulpjs/vinyl-fs/blob/master/lib/prepare-write.js#L47-L49 which needs to be moved into vinyl and most of the path stuff needs to be normalized (e.g. directory returning properties should always end in a path.sep, if file.isDirectory() the file.path should also end in path.sep and other things like that)
Why should directory paths end in separator?
@darsain since we already use path.*
methods in our getters/setters for the path properties, a user shouldn't be required to use path.join
to rebuild a path.
Some examples:
// This doesn't currently work
var file = new Vinyl({ path: '/test/123.jpg' });
file.dirname + file.basename === file.path
// Instead you must use path.join which adds another function call
path.join(file.dirname, file.basename) === file.path
// This also doesn't work
file.dirname + file.stem + file.extname === file.path
// So you have to use path.join AND string concatenation
path.join(file.dirname, file.stem + file.extname) === file.path
These inconsistencies also resulted in unexpected results when I was refactoring the vinyl-fs tests. We need to make the behavior consistent and easy to work with.
@phated other things creeped in :/ should get back to it sometime in the next few days.
Should we throw in file.path
getter when file.history
is empty, i.e. there is no path to be retrieved?
Otherwise, accessing stuff like file.dirname
with empty history will throw a non-descriptive path.dirname()
error Path must be a string. Received undefined, forcing the user to walk the stack trace to figure out what the actual issue is.
The solutions are:
- Throw
Error('object has no path')
when retrieving nonexistentfile.path
. - Handle
file.path === undefined
case in all affected getters and:- return
undefined
or empty string. - throw
Error('object has no path')
.
- return
Is file.path
returning undefined
something heavily relied on right now?
In tests, I see that file.dirname()
is supposed to throw when there is no path. file.path
is supposed to throw only when attempt to set an undefined|null
path is made, but there is no documentation or test for retrieving a nonexistent path, so this behavior is currently undefined.
@darsain how would you actually set the path to empty? Is there a code path that allows it?
@phated sorry, Going thought the code again made me realize that it already answers these questions, as well as handles the no path case in a solid way :)
But to answer your question, file.path
setter does allow empty strings.
Finishing up a PR, have hopefully 2 last questions:
- Should
file.basename
end with separator whenfile.isDirectory()
istrue
? I guess not, but not 100% sure. - I'm tweaking tons of tests to pass on windows, which they currently don't due to separators. This will result in a lot of noise in tests, which will have to be plastered with
path.normalize()
calls. It will be a lot easier to just forcepath.sep = '/'
where we are not testing normalization, and revert when done. Any objections?
Edit: Actually, the path module has 2 versions, posix and win32, and the default one can't be flipped just by setting path.sep
, so disregard the 2nd question. Tests will have to be normalized after all.
@darsain I think file.basename
should end with a separator if isDirectory()
is true. I don't know of a good example where it would be useful (but also can't think of anywhere it would be bad); however, I think the consistency with the other methods is important. I think this should be true: file.dirname + file.basename === file.path
in all scenarios and path
should be normalized to end with a separator.
Aside: I'm excited to see this PR.