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

Clarify documentation on Path#join and #== #10455

Merged
merged 3 commits into from
Jun 13, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions src/path.cr
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,15 @@ struct Path
#
# If *suffix* is given, it is stripped from the end.
#
# In case the last component is the empty string (i.e. the path has a trailing
# separator), the second to last component is returned.
# For path that only consists of an anchor, or an empty path, the base name
straight-shoota marked this conversation as resolved.
Show resolved Hide resolved
# is equivalent to the full path.
#
# ```
# Path["/foo/bar/file.cr"].basename # => "file.cr"
# Path["/foo/bar/"].basename # => "bar"
# Path["/foo/bar/."].basename # => "bar"
# Path["/"].basename # => "/"
# Path[""].basename # => ""
# ```
Expand Down Expand Up @@ -793,6 +799,16 @@ struct Path
# Path["foo/"].join("/bar") # => Path["foo/bar"]
# Path["/foo/"].join("/bar/") # => Path["/foo/bar/"]
# ```
#
# Joining an empty string (`""`) appends a trailing path separator.
# In case the path already ends with a trailing separator, no additional
# separator is added.
#
# ```
# Path["a/b"].join("") # => Path["a/b/"]
# Path["a/b/"].join("") # => Path["a/b/"]
# Path["a/b/"].join("c") # => Path["a/b/c"]
# ```
def join(part) : Path
# If we are joining a single part we can use `String.new` instead of
# `String.build` which avoids an extra allocation.
Expand Down Expand Up @@ -862,6 +878,8 @@ struct Path
# Path["foo/"].join("/bar/", "/baz") # => Path["foo/bar/baz"]
# Path["/foo/"].join("/bar/", "/baz/") # => Path["/foo/bar/baz/"]
# ```
#
# See `join(part)` for details.
def join(*parts) : Path
join parts
end
Expand All @@ -880,6 +898,8 @@ struct Path
# Path.posix("foo/bar").join(Path.windows("baz\\baq")) # => Path.posix("foo/bar/baz/baq")
# Path.windows("foo\\bar").join(Path.posix("baz/baq")) # => Path.windows("foo\\bar\\baz/baq")
# ```
#
# See `join(part)` for details.
def join(parts : Enumerable) : Path
if parts.is_a?(Indexable)
return self if parts.empty?
Expand Down Expand Up @@ -952,6 +972,8 @@ struct Path
# Path["foo"] / "bar" / "baz" # => Path["foo/bar/baz"]
# Path["foo/"] / Path["/bar/baz"] # => Path["foo/bar/baz"]
# ```
#
# See `join(part)` for details.
def /(part : Path | String) : Path
join(part)
end
Expand Down Expand Up @@ -1063,12 +1085,14 @@ struct Path
# Compares this path to *other*.
#
# The comparison is performed strictly lexically: `foo` and `./foo` are *not*
# treated as equal. To compare paths semantically, they need to be normalized
# and converted to the same kind.
# treated as equal. Nor are paths of different `kind`.
# To compare paths semantically, they need to be normalized and converted to
# the same kind.
#
# ```
# Path["foo"] <=> Path["foo"] # => 0
# Path["foo"] <=> Path["./foo"] # => 1
# Path["foo"] <=> Path["foo/"] # => 1
# Path.posix("foo") <=> Path.windows("foo") # => -1
# ```
#
Expand All @@ -1086,6 +1110,27 @@ struct Path
@kind <=> other.@kind
end

# Returns `true` if this path is considered equivalent to *other*.
#
# The comparison is performed strictly lexically: `foo` and `./foo` are *not*
# treated as equal. Nor are paths of different `kind`.
# To compare paths semantically, they need to be normalized and converted to
# the same kind.
#
# ```
# Path["foo"] == Path["foo"] # => true
# Path["foo"] == Path["./foo"] # => false
# Path["foo"] == Path["foo/"] # => false
# Path.posix("foo") == Path.windows("foo") # => false
Comment on lines +1121 to +1124
Copy link
Member

Choose a reason for hiding this comment

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

Hmm this sample code is a bit misleading because it says == but then only after a big chunk of whitespace it says "actually that's false".

On the other hand, if you change this to !=, then adding # => true at the end is also misleading.

Maybe just have to drop the comments at the end..

Copy link
Member Author

Choose a reason for hiding this comment

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

I see what you mean, but IMO this is the least surprising variant. Besides, we use this pattern everywhere for documentation. I don't think we should change anything. Using != doesn't help, because the visual difference is even less than with == and false.

Copy link
Member

Choose a reason for hiding this comment

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

OK that's fine.
Could still try to minimize the whitespace.

Suggested change
# Path["foo"] == Path["foo"] # => true
# Path["foo"] == Path["./foo"] # => false
# Path["foo"] == Path["foo/"] # => false
# Path.posix("foo") == Path.windows("foo") # => false
# Path["foo"] == Path["foo"] # => true
# Path["foo"] == Path["./foo"] # => false
# Path["foo"] == Path["foo/"] # => false
#
# Path.posix("foo") == Path.windows("foo") # => false

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know. Is the whitespace really such a visual barrier in the rendered API docs? IMO adding different indents might add confusion, as well as the random empty line, just to force the formatted.
All these expressions form a set. The last one is just visually different because it uses explicitly named constructor methods. But semantically, there should be no distance to the previous ones.

We could consider using explicit .new methods instead of the .[] short form. That would reduce the difference in lenght between the lines.

# ```
#
# Comparison is case-sensitive for POSIX paths and case-insensitive for
# Windows paths.
#
# ```
# Path.posix("foo") == Path.posix("FOO") # => false
# Path.windows("foo") == Path.windows("FOO") # => true
# ```
def ==(other : self)
return false if @kind != other.@kind

Expand Down