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

vlib.os: fix join-path #21425

Merged
merged 1 commit into from May 13, 2024
Merged

vlib.os: fix join-path #21425

merged 1 commit into from May 13, 2024

Conversation

hholst80
Copy link
Contributor

@hholst80 hholst80 commented May 4, 2024

Closes #20231

@hholst80 hholst80 force-pushed the fix-join-path branch 2 times, most recently from 10002cc to 91520e4 Compare May 4, 2024 22:53
vlib/os/os.v Outdated Show resolved Hide resolved
@@ -621,9 +621,10 @@ fn test_join() {
assert os.join_path('v', '', 'dir') == 'v\\dir'
} $else {
assert os.join_path('v', 'vlib', 'os') == 'v/vlib/os'
assert os.join_path('/foo/bar', './file.txt') == '/foo/bar/file.txt'
assert os.join_path('/foo/bar', './file.txt') == '/foo/bar/./file.txt'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a regression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should os.join_path do normalization or not? That is a pretty big task. I think real_path can be used to cleanup it. Compare to os.path.join in Python they also do not normalize ./ it is important that the file will be interpreted correctly, not how it looks like. "foo/./bar" is perfectly valid path and is equivalent to "foo/bar"

Any kind of logic we expose here will be maintained forever. Less is more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to have less edge cases in the output for this particular function.
The call sites can then be simpler.

Copy link
Contributor Author

@hholst80 hholst80 May 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither C++ nor Python does this. I think this would be confusing and hard to maintain properly. If the caller needs proper normalization it should call normalize path after the call. Half baked normalization here helps nobody.

# cat t.cpp
#include <iostream>
#include <filesystem>

int main() {
  std::filesystem::path p1 = "/";
  std::filesystem::path p2 = "./test";
  std::filesystem::path p = p1 / p2;
  std::cout << p << std::endl;
}
[I] root@trump ~ 
# g++ -o t t.cpp
[I] root@trump ~ 
# ./t
"/./test"
[I] root@trump ~ 
# 

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does help code that uses it, and it is not proper to make such a change, that breaks an existing test (which then needed changing), in a PR that fixes an unrelated problem (appending to /) .

Copy link
Member

@spytheman spytheman May 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-		assert os.join_path('/foo/bar', './file.txt') == '/foo/bar/file.txt'
+		assert os.join_path('/foo/bar', './file.txt') == '/foo/bar/./file.txt'

That is a test, that passed on master, and you changed it here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not fud my PR with "breaks tests" when obviously no tests are failing.

Obviously, I am commenting under that very change.

However, I do not want to argue anymore, and justify my opinion, and I am not happy with your PR as it is. I also asked you something in the other PR, which you are ignoring.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @spytheman about the regression.

Please also refer to how it's done in go:

package main

import (
	"path/filepath"
)

func main() {
	println(filepath.Join("/foo/bar", "./file.txt")) // -> /foo/bar/file.txt
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is reasonable argument, only mentioned now, that the API should be compatible with Golang. Given how similar the API are to go in other areas like network that V should try and offer few surprises.

See, I like a good discussion, but I am not unreasonable. ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk if they should be compatible. I wanted to put an example of more mature project that also has good engineers. If we find good reasons for it lets break the rules :D.

I think spy did a good work and also extends a lot of test cases. Personally, I like achieving a better product and finding solutions. So when another approaches arise that solve a problem in a more complete way it I usually have no problem giving into it. So we all get a better language :P

Things were just a little unlucky here that the approaches are so different and hardly compatible.

vlib/os/os.v Outdated Show resolved Hide resolved
vlib/os/os.v Outdated Show resolved Hide resolved
vlib/os/os.v Outdated Show resolved Hide resolved
vlib/os/os.v Outdated
Comment on lines 579 to 610
mut i := 0
mut needs_sep := false
if base != '' {
sb.write_string(base)
needs_sep = !base.ends_with(path_separator)
} else {
for i < dirs.len {
d := dirs[i]
i++
if d != '' {
sb.write_string(d)
needs_sep = !base.ends_with(path_separator)
break
}
}
}
sb.write_string(sbase)
for d in dirs {
for i < dirs.len {
d := dirs[i]
if d != '' {
sb.write_string(path_separator)
if needs_sep {
sb.write_string(path_separator)
}
sb.write_string(d)
needs_sep = !d.ends_with(path_separator)
}
i++
}
mut res := sb.str()
if sbase == '' {
res = res.trim_left(path_separator)
}
if res.contains('/./') {
// Fix `join_path("/foo/bar", "./file.txt")` => `/foo/bar/./file.txt`
res = res.replace('/./', '/')
}
res := sb.str()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a simpler variant, could this work too?

Suggested change
mut i := 0
mut needs_sep := false
if base != '' {
sb.write_string(base)
needs_sep = !base.ends_with(path_separator)
} else {
for i < dirs.len {
d := dirs[i]
i++
if d != '' {
sb.write_string(d)
needs_sep = !base.ends_with(path_separator)
break
}
}
}
sb.write_string(sbase)
for d in dirs {
for i < dirs.len {
d := dirs[i]
if d != '' {
sb.write_string(path_separator)
if needs_sep {
sb.write_string(path_separator)
}
sb.write_string(d)
needs_sep = !d.ends_with(path_separator)
}
i++
}
mut res := sb.str()
if sbase == '' {
res = res.trim_left(path_separator)
}
if res.contains('/./') {
// Fix `join_path("/foo/bar", "./file.txt")` => `/foo/bar/./file.txt`
res = res.replace('/./', '/')
}
res := sb.str()
mut needs_sep := false
if base != '' {
sb.write_string(base)
needs_sep = !base.ends_with(path_separator)
}
for d in dirs {
if d != '' {
if needs_sep {
sb.write_string(path_separator)
}
sb.write_string(d)
needs_sep = !d.ends_with(path_separator)
}
}
res := sb.str()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suggestion looks buggy.

I'm referring to the part starting below the defer block:
Current:

	mut i := 0
	mut needs_sep := false
	if base != '' {
		sb.write_string(base)
		needs_sep = !base.ends_with(path_separator)
	} else {
		for i < dirs.len {
			d := dirs[i]
			i++
			if d != '' {
				sb.write_string(d)
				needs_sep = !base.ends_with(path_separator)
				break
			}
		}
	}
	for i < dirs.len {
		d := dirs[i]
		if d != '' {
			if needs_sep {
				sb.write_string(path_separator)
			}
			sb.write_string(d)
			needs_sep = !d.ends_with(path_separator)
		}
		i++
	}
	res := sb.str()

Suggested:

	mut needs_sep := false
	if base != '' {
		sb.write_string(base)
		needs_sep = !base.ends_with(path_separator)
	}
	for d in dirs {
		if d != '' {
			if needs_sep {
				sb.write_string(path_separator)
			}
			sb.write_string(d)
			needs_sep = !d.ends_with(path_separator)
		}
	}
	res := sb.str()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree the default false value makes it possible to merge the loops and get rid of the index i.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the approach in #21429 is cleaner.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@medvednikov what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but it is still incomplete with respect to Golangs "Clean" method:

If everyone thinks that this is the gold standard, ok then. I have tried my best to argue for a smaller scope. :)

@hholst80 hholst80 force-pushed the fix-join-path branch 3 times, most recently from b662ab5 to 514cd22 Compare May 5, 2024 21:09
@ttytm
Copy link
Member

ttytm commented May 5, 2024

Since I'm mentioned as a co-author in the full diff, because the parts I tried to help with during development got squashed, I have to say it's not signed off by me.

I think it's honorable that you leave me in there, I thank you for that, @hholst80, but the way it's done might also distort the situation.

Now, aiming to keep things technical and to choose the solution that best serves the project, why not go with the suggestion in Spy's PR and have this one updated? Or what issues are there when doing it like this?

Closes vlang#20231

On popular demand, re-added primitive support for path normalization.

Co-authored-by: Turiiya <[email protected]>
@hholst80
Copy link
Contributor Author

hholst80 commented May 6, 2024

Since I'm mentioned as a co-author in the full diff, because the parts I tried to help with during development got squashed, I have to say it's not signed off by me.

I think it's honorable that you leave me in there, I thank you for that, @hholst80, but the way it's done might also distort the situation.

Now, aiming to keep things technical and to choose the solution that best serves the project, why not go with the suggestion in Spy's PR and have this one updated? Or what issues are there when doing it like this?

I don't think that matter. I should add Spy as a co-author as well because I stole code from his PR but he already maxed out on street cred.

I think from a newcomers perspective that it is important that my PR takes precedence because I took my time to report an issue and then to understand what was the problem. For me this was an investment of time, not only now, but in the future because I 1) believe in V's future and and 2) see myself as part of that future.

Ultimately it is a team effort to get it done and a PR process is just an exercise an proving ground if the communication works does the developers listen and respect each other.

@spytheman spytheman merged commit 76142b1 into vlang:master May 13, 2024
62 of 63 checks passed
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.

v fails to run and generate binaries from / folder
4 participants