@@ -15,7 +15,12 @@ class Path
1515 # Initialize a path from its individual components.
1616 # @parameter components [Array(String)] The path components, including empty components that denote leading or trailing separators.
1717 def initialize ( components = [ ] )
18- @components = components
18+ # An absolute root includes both its leading and trailing separators:
19+ if components == [ "" ]
20+ @components = [ "" , "" ]
21+ else
22+ @components = components
23+ end
1924 end
2025
2126 attr_accessor :components
@@ -39,7 +44,7 @@ def empty?
3944 # Construct the root path.
4045 # @returns [Path] The root path.
4146 def self . root
42- self . new ( [ "" ] )
47+ self . new ( [ "" , "" ] )
4348 end
4449
4550 # Compute the number of leading components shared by two sequences.
@@ -63,8 +68,14 @@ def self.shortest_path(path, root)
6368
6469 # The difference between the root path and the required path, taking into account the common prefix:
6570 up = root . components . size - i
71+ down = path . components [ i ..-1 ]
6672
67- return self . create ( [ ".." ] * up + path . components [ i ..-1 ] )
73+ # A parent component already denotes the destination directory, so a trailing directory separator is redundant:
74+ if up > 0 && down == [ "" ]
75+ down = [ ]
76+ end
77+
78+ return self . create ( [ ".." ] * up + down )
6879 end
6980
7081 # Compute the shortest relative path from the containing directory of `root` to this path.
@@ -212,23 +223,14 @@ def to_relative!
212223 # Convert this object to a string.
213224 # @returns [String] The resulting string.
214225 def to_str
215- if @components == [ "" ]
216- SEPARATOR
217- else
218- @components . join ( SEPARATOR )
219- end
226+ @components . join ( SEPARATOR )
220227 end
221228
222229 alias to_s to_str
223230
224231 # Encode this application path as a URL path.
225232 # @returns [Protocol::URL::Path] The encoded URL path.
226233 def to_url_path
227- # Preserve Utopia's compact representation of the absolute root:
228- if @components == [ "" ]
229- return Protocol ::URL ::Path [ SEPARATOR ]
230- end
231-
232234 return Protocol ::URL ::Path . for (
233235 @components ,
234236 encoding : Protocol ::URL ::Encoding ::System ,
@@ -348,20 +350,27 @@ def first
348350 # Return the last path component, excluding the root marker.
349351 # @returns [String | Nil] The last component.
350352 def last
351- if @components != [ "" ]
352- @components . last
353- end
353+ @components . last
354354 end
355355
356356 alias last? file?
357357
358358 # Remove the last path component without converting the root path to a relative path.
359359 # @returns [String | Nil] The removed component.
360360 def pop
361- # We don't want to convert an absolute path to a relative path.
362- if @components != [ "" ]
363- @components . pop
361+ # The absolute root has no path component to remove:
362+ if @components == [ "" , "" ]
363+ return nil
364364 end
365+
366+ component = @components . pop
367+
368+ # Preserve the canonical absolute root after removing its final component:
369+ if @components == [ "" ]
370+ @components << ""
371+ end
372+
373+ return component
365374 end
366375
367376 # @returns [String] The last path component without its file extension.
@@ -482,6 +491,11 @@ def == other
482491 # @parameter other [Path] The possible prefix.
483492 # @returns [Boolean] Whether this path starts with all components of `other`.
484493 def start_with? other
494+ # The root directory contains every absolute path:
495+ if other . components == [ "" , "" ]
496+ return absolute?
497+ end
498+
485499 other . components . each_with_index do |part , index |
486500 return false if @components [ index ] != part
487501 end
0 commit comments