note description: "[ Objects representing a path, i.e. a way to identify a file or a directory for the current underlying platform. A path is made of two components: 1 - an optional root which can either be: a - a drive letter followed by colon on Windows, i.e. "C:" or "C:\" b - "/" for UNIX root directory. c - "\" for Windows root directory. d - "\\server\share" or "\\server\share\" for Microsoft UNC path. 2 - a sequence of zero or more names. A path is absolute if it has a root, and on windows if the root is a drive, then it should be followed by "\". Otherwise a path is relative. Validity ======== The current class will not check the validity of filenames. Check your file system for your operating system manual for the list of invalid characters. Windows consideration ===================== When the root of a Windows path is a drive, be aware of the following behavior: 1 - "C:filename.txt" refers to the file name "filename.txt" in the current directory on drive "C:". 2 - "C:sub\filename.txt" refers to the file name "filename.txt" in a subdirectory "sub" of the current directory on drive "C:". 3 - "C:\sub\filename.txt" refers to the file name "filename.txt" in a subdirectory "sub" located at the root of the drive "C:". Both forward and backslashes are accepted, but forward slashes are internally converted to backward slashes whenever they are used to construct a path. On Windows, there is a limit of 259 characters for a path. If you need to create a larger path, you can do so by using the following conventions which will let you have paths of about 32,767 characters: 1 - Use \\?\ for non-UNC path and let the rest unchanged. 2 - Use \\?\UNC\server\share for UNC path and let the rest unchanged. The above path cannot be used to specify a relative path. To know more about Windows paths, read the "Naming Files, Paths, and Namespaces" document located at: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx Unicode consideration ===================== The PATH class treats strings as sequence of Unicode characters, i.e. an instance of a READABLE_STRING_8 or descendant will be treated as if characters in the range 128 .. 255 were Unicode code points. This contrasts to the FILE/DIRECTORY classes where to preserve backward compatibility, those characters are treated as is. Mixed-encoding consideration ============================ Most operating systems have conventions for paths that are incompatible with Unicode. On UNIX, in a sequence of names, each name is just a null-terminated byte sequence, it does not follow any specific encoding. Usually the locale setting enables you to see the filename the way you expect. On Windows, the sequence of names is made of null-terminated UTF-16 code unit sequence. Windows does not guarantee that the sequence is actually a valid UTF-16 sequence. In other words, when there is an invalid UTF-8 encoding on UNIX, or an invalid UTF-16 encoding on Windows, the filename is not directly representable as a Unicode string. To make it possible to create and store paths in a textually representable form, the query name will create an encoded representation that can be then later used in make_from_string to create a PATH equivalent to the original path. The encoding is described in UTF_CONVERTER's note clause and is a fourth variant of the recommended practice for replacement characters in Unicode (see http://www.unicode.org/review/pr-121.html). Immutability ============ Instances of the current class are immutable. ]" library: "Free implementation of ELKS library" status: "See notice at end of class." date: "$Date: 2017-03-23 19:18:26 +0000 (Thu, 23 Mar 2017) $" revision: "$Revision: 100033 $" class PATH inherit HASHABLE redefine out, is_equal, copy end COMPARABLE redefine out, is_equal, copy end NATIVE_STRING_HANDLER redefine out, is_equal, copy end DEBUG_OUTPUT redefine out, is_equal, copy end create {NATIVE_STRING_HANDLER} make_from_pointer create {PATH} make_from_storage, make_from_normalized_storage create make_empty, make_current, make_from_string, make_from_separate feature {NONE} -- Initialization make_empty -- Initialize current as an empty path. do create storage.make_empty is_normalized := True reset_internal_data ensure is_empty: is_empty is_normalized: is_normalized end make_current -- Initialize current as the symbolic representation of the current working directory do create storage.make (unit_size) storage.append_character ('.') if {PLATFORM}.is_windows then storage.append_character ('%U') end is_normalized := True reset_internal_data ensure not_empty: not is_empty is_normalized: is_normalized is_current: is_current_symbol end make_from_string (a_path: READABLE_STRING_GENERAL) -- Initialize current from a_path treated as a sequence of Unicode characters. -- If a_path is trying to represent a mixed-encoded path, then a_path should use -- the escaped representation as described in UTF_CONVERTER. require a_path_not_void: a_path /= Void do create storage.make (a_path.count * unit_size) if not a_path.is_empty then internal_append_into (storage, a_path, '%U') normalize else is_normalized := True end reset_internal_data ensure not_empty: not a_path.is_empty implies not is_empty is_normalized: is_normalized roundtrip: True roundtrip_with_trailing: True end make_from_separate (a_path: separate PATH) -- Initialize from separate a_path. require a_path_not_void: a_path /= Void do create storage.make_from_separate (a_path.storage) is_normalized := True reset_internal_data ensure not_empty: not a_path.is_empty implies not is_empty is_normalized: is_normalized end feature {NONE} -- Internal initialization make_from_storage (a_path: STRING_8) -- Initialize current from a_path and normalize a_path as it may be coming -- from a user provided string or from a C API. require a_path_not_void: a_path /= Void do storage := a_path normalize reset_internal_data ensure shared: storage = a_path is_normalized: is_normalized end make_from_normalized_storage (a_path: STRING_8) -- Initialize current from a_path which has already been normalized. require a_path_not_void: a_path /= Void do storage := a_path is_normalized := True reset_internal_data ensure shared: storage = a_path is_normalized: is_normalized end make_from_pointer (a_path_pointer: POINTER) -- Initialize current from a_path_pointer, a platform system specific encoding of -- a path that is null-terminated. require a_path_pointer_not_null: a_path_pointer /= default_pointer local l_cstr: C_STRING nb: INTEGER_32 do nb := pointer_length_in_bytes (a_path_pointer) nb := nb - nb \\ unit_size create l_cstr.make_shared_from_pointer_and_count (a_path_pointer, nb) storage := l_cstr.substring (1, nb) if not storage.is_empty then normalize end reset_internal_data end feature -- Status report is_current_symbol: BOOLEAN -- Is Current a representation of "."? do if storage.count = unit_size then Result := is_character (storage, 1, '.') end end is_parent_symbol: BOOLEAN -- Is Current Representation of ".."? do if storage.count = 2 * unit_size then Result := is_character (storage, 1, '.') Result := is_character (storage, 1 + unit_size, '.') end end has_root: BOOLEAN -- Does current have a root? do Result := root_end_position /= 0 ensure defintion: Result implies not is_empty end is_empty: BOOLEAN -- Is current empty, i.e. no root and no sequence of names? do Result := storage.is_empty end is_relative: BOOLEAN -- Is current path relative? do Result := not is_absolute end is_absolute: BOOLEAN -- Is current path absolute? local l_root_end_position: INTEGER_32 do l_root_end_position := root_end_position if l_root_end_position > 0 then if {PLATFORM}.is_windows then Result := is_character (storage, l_root_end_position - unit_size + 1, directory_separator) else Result := True end end end is_simple: BOOLEAN -- Is current path made of only one name and no root? -- I.e. readme.txt, usr or the empty path. local l_root_pos: like root_end_position do if storage.is_empty then Result := True else l_root_pos := root_end_position if l_root_pos = 0 and not is_empty then Result := next_directory_separator (1) = 0 end end end is_same_file_as (a_path: PATH): BOOLEAN -- Does Current and a_path points to the same file on disk? It is different from path equality -- as it will take into account symbolic links. -- If Current or/and a_path do not exists, it will yield false, otherwise it will compare -- the file at the file system level. require a_path_not_void: a_path /= Void local l_p1, l_p2: MANAGED_POINTER do l_p1 := to_pointer l_p2 := a_path.to_pointer Result := c_same_files (l_p1.item, l_p2.item) end has_extension (a_ext: READABLE_STRING_GENERAL): BOOLEAN -- Does Current has an extension a_ext compared in a case insensitive manner? require a_ext_not_void: a_ext /= Void a_ext_not_empty: not a_ext.is_empty a_ext_has_no_dot: not a_ext.has ('.'.to_character_32) do Result := attached extension as l_ext and then l_ext.is_case_insensitive_equal_general (a_ext) end feature -- Access root: detachable PATH -- Root if any of current path. local l_pos: INTEGER_32 do l_pos := root_end_position if l_pos /= 0 then if l_pos = storage.count then create Result.make_from_normalized_storage (storage) else create Result.make_from_normalized_storage (storage.substring (1, l_pos)) end end ensure has_root_implies_not_void: has_root implies Result /= Void end parent: PATH -- Parent directory if any, otherwise current working path. -- The parent of a path consists of root if any, and of each -- simple names in the current sequence except for the last. local l_pos, l_root_end_pos: INTEGER_32 do l_pos := end_position_of_last_directory_separator if l_pos = 0 then if attached root as l_root then check is_windows: {PLATFORM}.is_windows end Result := l_root else create Result.make_current end elseif l_pos = unit_size then create Result.make_from_normalized_storage (storage.substring (1, unit_size)) else l_root_end_pos := root_end_position if l_pos <= l_root_end_pos then if l_root_end_pos = storage.count then Result := Current else create Result.make_from_normalized_storage (storage.substring (1, l_root_end_pos)) end else create Result.make_from_normalized_storage (storage.substring (1, l_pos - unit_size)) end end end entry: detachable PATH -- Name of file or directory denoted by Current if any. -- This is the last name in the current sequence. local l_pos: INTEGER_32 l_end_root: INTEGER_32 do l_pos := end_position_of_last_directory_separator if l_pos = 0 then l_end_root := root_end_position if l_end_root > 0 then if l_end_root < storage.count then create Result.make_from_normalized_storage (storage.substring (l_end_root + 1, storage.count)) end else Result := Current end else l_end_root := root_end_position if l_pos <= l_end_root then if l_end_root < storage.count then create Result.make_from_normalized_storage (storage.substring (l_end_root + 1, storage.count)) end else create Result.make_from_normalized_storage (storage.substring (l_pos + 1, storage.count)) end end if Result /= Void and then (Result.is_empty or Result.is_current_symbol or Result.is_parent_symbol) then Result := Void end ensure not_empty: Result /= Void implies not Result.is_empty end extension: detachable IMMUTABLE_STRING_32 -- Extension if any of current entry. local l_name: like name l_pos, nb: INTEGER_32 do if attached entry as l_entry then l_name := l_entry.name nb := l_name.count l_pos := l_name.last_index_of ('.'.to_character_32, nb) if l_pos /= 0 and l_pos /= nb then Result := l_name.shared_substring (l_pos + 1, nb) end end ensure not_empty: attached Result implies not Result.is_empty no_dot: attached Result implies not Result.has ('.'.to_character_32) end components: ARRAYED_LIST [PATH] -- Sequence of simple paths making up Current, including root if any. local l_storage: STRING_8 l_previous_pos, l_pos: INTEGER_32 do create Result.make (10) l_pos := root_end_position if l_pos > 0 then create l_storage.make (l_pos) l_storage.append_substring (storage, 1, l_pos) Result.extend (create {PATH}.make_from_normalized_storage (l_storage)) l_pos := l_pos + 1 else l_pos := 1 end if l_pos <= storage.count then from l_previous_pos := l_pos l_pos := next_directory_separator (l_previous_pos) until l_pos = 0 loop create l_storage.make (l_pos - l_previous_pos) l_storage.append_substring (storage, l_previous_pos, l_pos - 1) Result.extend (create {PATH}.make_from_normalized_storage (l_storage)) l_previous_pos := l_pos + unit_size l_pos := next_directory_separator (l_previous_pos) end if l_previous_pos <= storage.count then create l_storage.make (storage.count - l_previous_pos) l_storage.append_substring (storage, l_previous_pos, storage.count) Result.extend (create {PATH}.make_from_normalized_storage (l_storage)) end end end absolute_path: PATH -- Absolute path of Current. -- If Current is already absolute, then return Current. -- If Current is empty, then return the current working directory. -- Otherwise resolve the path in a platform specific way: -- * On UNIX, resolve against the current working directory -- * On Windows: -- a) if current has a drive letter which is not followed by "\" -- resolve against the current working directory for that drive letter, -- otherwise resolve against the current working directory. -- b) if current path starts with "\", not a double "\\", then resolve -- against the root of the current working directory (i.e. a drive -- letter "C:\" or a UNC path "\\server\share\".) do Result := absolute_path_in (Env.current_working_path) end absolute_path_in (a_current_directory: PATH): PATH -- Absolute path of Current in the context of a_current_directory. -- If Current is already absolute, then return Current. -- If Current is empty, then return a_current_directory. -- Otherwise resolve the path in a platform specific way: -- * On UNIX, resolve against a_current_directory -- * On Windows: -- a) if current has a drive letter which is not followed by "\" -- resolve against a_current_directory for that drive letter, -- otherwise resolve against a_current_directory. -- b) if current path starts with "\", not a double "\\", then resolve -- against the root of `a_current_directory; (i.e. a drive -- letter "C:\" or a UNC path "\\server\share\".) require a_current_directory_not_void: a_current_directory /= Void a_current_directory_absolute: a_current_directory.is_absolute do if storage.is_empty then Result := a_current_directory else if is_absolute then Result := Current else if {PLATFORM}.is_windows then if attached root as l_root then if (l_root.storage.count = 4 and a_current_directory.storage.count >= 4) and then (l_root.storage.item (1) = a_current_directory.storage.item (1) and l_root.storage.item (3) = a_current_directory.storage.item (3)) then Result := a_current_directory.twin else Result := l_root end internal_path_append_substring_into (Result.storage, storage, l_root.storage.count + 1, storage.count, directory_separator) else if is_character (storage, 1, Windows_separator) then Result := a_current_directory.twin if attached Result.root as l_root then Result := l_root end else Result := a_current_directory.twin end internal_path_append_into (Result.storage, storage, directory_separator) end else Result := a_current_directory.twin internal_path_append_into (Result.storage, storage, directory_separator) end Result.reset_internal_data end end ensure has_root: Result.has_root end canonical_path: PATH -- Canonical path of Current. -- Similar to absolute_path except that sequences containing "." or ".." are -- resolved. local l_components: like components l_absolute_path: like absolute_path l_storage: like storage do l_absolute_path := absolute_path if attached l_absolute_path.root as l_root then create l_storage.make (l_absolute_path.storage.count) l_components := l_absolute_path.components check l_components_has_root: l_components.count >= 1 l_components_first_is_root: l_components.first.same_as (l_root) end from l_components.start internal_path_append_into (l_storage, l_components.item.storage, directory_separator) l_components.remove until l_components.after loop if l_components.item.is_current_symbol then l_components.remove elseif l_components.item.is_parent_symbol then if not l_components.isfirst then l_components.back l_components.remove end l_components.remove else l_components.forth end end across l_components as l_component loop internal_path_append_into (l_storage, l_component.item.storage, directory_separator) end create Result.make_from_normalized_storage (l_storage) else check False end Result := l_absolute_path end end hash_code: INTEGER_32 -- Hash code value do if {OPERATING_ENVIRONMENT}.case_sensitive_path_names then Result := storage.hash_code else Result := name.case_insensitive_hash_code end end native_string: NATIVE_STRING -- Convert current into an instance of NATIVE_STRING do create Result.make_from_raw_string (storage) ensure set: Result.raw_string.same_string (storage) end Unix_separator: CHARACTER_8 = '/' Windows_separator: CHARACTER_8 = '\' -- Platform specific directory separator. directory_separator: CHARACTER_8 -- Default directory separator for the current platform. do if {PLATFORM}.is_windows then Result := Windows_separator else Result := Unix_separator end end feature -- Status setting extended (a_name: READABLE_STRING_GENERAL): PATH -- New path instance of current extended with path a_name. -- If current is not empty, then a_name cannot have a root. -- A directory separator is added between two entries except -- 1 - a_name starts with a directory separator (i.e. it has a root) -- 2 - if current is empty or is just a root. -- Note that a_name can be an encoding of a mixed-encoding simple name and it will -- be decoded accordingly (see note clause for the class for more details.) require a_name_not_void: a_name /= Void a_name_not_empty: not a_name.is_empty a_name_has_no_root: not is_empty implies not (create {PATH}.make_from_string (a_name)).has_root local l_storage: like storage do create l_storage.make (storage.count + a_name.count * unit_size + unit_size) l_storage.append (storage) if l_storage.count > 0 and root_end_position = l_storage.count then internal_append_into (l_storage, a_name, '%U') else internal_append_into (l_storage, a_name, directory_separator) end create Result.make_from_storage (l_storage) ensure associated_path_of_name: attached (create {PATH}.make_from_string (a_name)) as l_path not_empty: not Result.is_empty extended_with_only_empty_or_root: (same_as (root) or is_empty) implies Result.name.same_string (name + l_path.name) extended_with_more_than_root_or_not_empty: (not same_as (root) and not is_empty) implies Result.name.same_string (name + Directory_separator_string + l_path.name) end extended_path alias "+" (a_path: PATH): PATH -- New path instance of current extended with path a_path. -- If current is not empty, then a_path cannot have a root. -- A directory separator is added between two entries except -- 1 - a_path starts with a directory separator (i.e. it has a root) -- 2 - if current is empty or is just a root. require a_path_not_void: a_path /= Void a_path_not_empty: not a_path.is_empty a_path_has_no_root: not is_empty implies not a_path.has_root local l_storage: like storage do create l_storage.make (storage.count + a_path.storage.count + unit_size) l_storage.append (storage) if l_storage.count > 0 and root_end_position = l_storage.count then internal_path_append_into (l_storage, a_path.storage, '%U') else internal_path_append_into (l_storage, a_path.storage, directory_separator) end create Result.make_from_normalized_storage (l_storage) ensure not_empty: not Result.is_empty extended_with_only_empty_or_root: (same_as (root) or is_empty) implies Result.name.same_string (name + a_path.name) extended_with_more_than_root_or_not_empty: (not same_as (root) and not is_empty) implies Result.name.same_string (name + Directory_separator_string + a_path.name) end appended (a_extra: READABLE_STRING_GENERAL): PATH -- New path instance of current where entry is appended with a_extra without -- adding a directory separator. -- For example if Current path is "C:" and a_path is "path\file.txt", it will yield -- "C:path\file.txt". -- Note that a_extra can be an encoding of a mixed-encoding simple name and it will -- be decoded accordingly (see note clause for the class for more details.) require a_extra_not_void: a_extra /= Void a_extra_not_empty: not a_extra.is_empty local l_storage: like storage do create l_storage.make (storage.count + a_extra.count * unit_size) l_storage.append (storage) internal_append_into (l_storage, a_extra, '%U') create Result.make_from_storage (l_storage) ensure not_empty: not Result.is_empty appended: Result.name.same_string (name + (create {PATH}.make_from_string (a_extra)).name) end appended_with_extension (a_ext: READABLE_STRING_GENERAL): PATH -- New path instance of current where entry is extended with a dot followed by a_ext. -- If Current already has a dot, no dot is added. -- Note that a_ext can be an encoding of a mixed-encoding simple name and it will -- be decoded accordingly (see note clause for the class for more details.) require a_ext_not_void: a_ext /= Void a_ext_not_empty: not a_ext.is_empty a_ext_has_no_dot: not a_ext.has ('.'.to_character_32) a_ext_has_no_directory_separator: not a_ext.has (Windows_separator.to_character_32) and not a_ext.has (Unix_separator.to_character_32) has_entry: entry /= Void local l_storage: like storage do create l_storage.make (storage.count + a_ext.count * unit_size + unit_size) l_storage.append (storage) internal_append_into (l_storage, a_ext, '.') create Result.make_from_normalized_storage (l_storage) ensure not_empty: not Result.is_empty extension_set: attached Result.extension as l_ext and then l_ext.same_string_general (a_ext) components_stable: Result.components.count = components.count end feature -- Comparison same_as (other: detachable PATH): BOOLEAN -- Is Current the same path as other? -- Note that no canonicalization is being performed to compare paths, -- paths are compared using the OS-specific convention for letter case. do if other = Void then elseif other = Current then Result := True else if {OPERATING_ENVIRONMENT}.case_sensitive_path_names then Result := is_case_sensitive_equal (other) else Result := is_case_insensitive_equal (other) end end end is_less alias "<" (other: like Current): BOOLEAN -- Is current object less than other? do if {OPERATING_ENVIRONMENT}.case_sensitive_path_names then Result := storage < other.storage else Result := name.as_lower < other.name.as_lower end end is_equal (other: like Current): BOOLEAN -- Is other attached to an object considered -- equal to current object? do Result := same_as (other) end is_case_sensitive_equal (other: PATH): BOOLEAN -- Compare path and paying attention to case. require other_not_void: other /= Void do if other = Current then Result := True else Result := storage.is_equal (other.storage) end end is_case_insensitive_equal (other: PATH): BOOLEAN -- Compare path without paying attention to case. If the path is containing some mixed-encoding -- we might ignore many characters when doing the case comparison. require other_not_void: other /= Void do if other = Current then Result := True else Result := name.is_case_insensitive_equal (other.name) end end feature -- Duplication copy (other: like Current) -- Update current object using fields of object attached -- to other, so as to yield equal objects. do if other /= Current then standard_copy (other) storage := other.storage.twin end end feature -- Output out: STRING_8 -- ASCII representation of the underlying filename if representable, -- otherwise a UTF-8 encoded version. -- Use utf_8_name to have a printable representation whose format is not going -- to be changed in the future. do Result := utf_8_name end utf_8_name: STRING_8 -- UTF-8 representation of the underlying filename. local u: UTF_CONVERTER do Result := u.utf_32_string_to_utf_8_string_8 (name) end name: IMMUTABLE_STRING_32 -- If current is representable in Unicode, the Unicode representation. -- Otherwise all non-valid sequences for the current platform in the path are escaped -- as mentioned in the note clause of the class. -- To ensure roundtrip, you cannot use name directly to create a FILE, you have to -- create a PATH instance using make_from_string before passing it to the creation -- procedure of FILE taking an instance of PATH. local u: UTF_CONVERTER do if attached internal_name as l_name then Result := l_name else if {PLATFORM}.is_windows then create Result.make_from_string (u.utf_16le_string_8_to_escaped_string_32 (storage)) else create Result.make_from_string (u.utf_8_string_8_to_escaped_string_32 (storage)) end internal_name := Result end ensure roundtrip: same_as (create {PATH}.make_from_string (Result)) end feature {NONE} -- Output debug_output: READABLE_STRING_32 -- String that should be displayed in debugger to represent Current. do Result := name end feature {NATIVE_STRING_HANDLER} -- Access to_pointer: MANAGED_POINTER -- Platform specific representation of Current. local l_cstr: C_STRING do create l_cstr.make_empty (storage.count + unit_size - 1) l_cstr.set_string (storage) Result := l_cstr.managed_data end feature {PATH} -- Implementation is_normalized: BOOLEAN -- Has current string be normalized? storage: STRING_8 -- Internal storage for Current. -- On UNIX, it is a binary sequence encoded in UTF-8 by default. -- On Windows, it is a binary sequence encoded in UTF-16LE by default. unit_size: INTEGER_32 -- Size in bytes of a unit for storage. do if {PLATFORM}.is_windows then Result := 2 else Result := 1 end end reset_internal_data -- Reset the private cache data. do internal_name := Void end normalize -- Normalize current with respect to directory separators: -- 1 - On Windows, replace all / by \. -- 2 - Remove trailing directory separator unless this is a root for which -- we will keep just one. This is the case of: -- * C:\ -- * \\server\share\ -- * \\?\C:\ -- * / -- where removing the trailing directory separator would transform -- current path from being absolute to be relative. -- 3 - Remove redundant directory separator in a path except on Windows for -- the starting \\ in a UNC path. (i.e. /foo////bar -> /foo/bar, and -- \\server\share\\\\foo\\\bar -> \\server\share\foo\bar. -- 4 - If a path is just made of directory separator, it become that directory separator. require not_normalized: not is_normalized storage_not_empty: storage.count >= unit_size local l_storage: like storage i, j, nb: INTEGER_32 l_root_pos: INTEGER_32 l_in_separator, l_copy_character, l_has_unc: BOOLEAN do l_storage := storage nb := l_storage.count if {PLATFORM}.is_windows then i := 1 if nb >= Min_unc_path_count then if (is_character (l_storage, 1, Unix_separator) and (is_character (l_storage, 3, Unix_separator) or is_character (l_storage, 3, Windows_separator))) or (is_character (l_storage, 1, Windows_separator) and (is_character (l_storage, 3, Windows_separator) or is_character (l_storage, 3, Unix_separator))) then if not is_character (l_storage, 5, Unix_separator) and not is_character (l_storage, 5, Windows_separator) then i := 5 l_has_unc := True end end end from j := i l_copy_character := True until i > nb loop if l_in_separator then l_copy_character := not is_character (l_storage, i, Windows_separator) and not is_character (l_storage, i, Unix_separator) l_in_separator := not l_copy_character else if is_character (l_storage, i, Unix_separator) then l_storage.put (Windows_separator, i) l_in_separator := True else l_in_separator := is_character (l_storage, i, Windows_separator) end end if l_copy_character then if i /= j then l_storage.put (l_storage.item (i), j) l_storage.put (l_storage.item (i + 1), j + 1) end j := j + 2 end i := i + 2 end else from i := 1 j := i l_copy_character := True until i > nb loop if l_in_separator then l_copy_character := not is_character (l_storage, i, Unix_separator) l_in_separator := not l_copy_character else l_in_separator := is_character (l_storage, i, Unix_separator) end if l_copy_character then if i /= j then l_storage.put (l_storage.item (i), j) end j := j + 1 end i := i + 1 end end if i /= j then l_storage.keep_head (j - 1) end is_normalized := True if l_has_unc then l_root_pos := root_end_position if l_root_pos = 0 then l_storage.remove_head (unit_size) end end if is_character (l_storage, l_storage.count - unit_size + 1, directory_separator) and then root_end_position < l_storage.count then l_storage.remove_tail (unit_size) end ensure is_normalized: is_normalized end feature {NONE} -- Implementation internal_name: detachable IMMUTABLE_STRING_32 -- Cache for name. Platform: PLATFORM -- Access underlying platform info, used to satisfy invariant below. once create Result end Env: EXECUTION_ENVIRONMENT -- Access to underlying execution environment. once create Result end root_end_position: INTEGER_32 -- Position of the last character of root as a full unit if any, 0 otherwise. require is_normalized: is_normalized local l_drive_letter: CHARACTER_8 l_pos: INTEGER_32 do if not storage.is_empty then if {PLATFORM}.is_windows then if storage.count = unit_size and then is_character (storage, 1, Windows_separator) then Result := unit_size elseif storage.count >= 4 and then (storage.item (2) = '%U' and storage.item (4) = '%U') then l_drive_letter := storage.item (1).as_lower if l_drive_letter >= 'a' and l_drive_letter <= 'z' and storage.item (3) = ':' then if storage.count >= 3 * unit_size and then is_character (storage, 5, Windows_separator) then Result := 6 else Result := 4 end elseif l_drive_letter = Windows_separator and storage.item (3) /= Windows_separator then Result := unit_size elseif storage.count >= Min_unc_path_count and l_drive_letter = Windows_separator and storage.item (3) = Windows_separator and storage.item (5) /= Windows_separator then l_pos := next_directory_separator (7) if l_pos > 0 and l_pos + unit_size <= storage.count then l_pos := next_directory_separator (l_pos + unit_size) if l_pos > 0 then Result := l_pos + 1 else Result := storage.count end end end end else if storage.item (1) = Unix_separator then Result := 1 end end end ensure non_negative: Result >= 0 not_too_big: Result <= storage.count valid_for_windows: {PLATFORM}.is_windows implies Result \\ unit_size = 0 end end_position_of_last_directory_separator: INTEGER_32 -- Position of the last directory separator in Current, not including the trailing ones if any, 0 if none. require is_normalized: is_normalized do if not storage.is_empty then Result := storage.count - unit_size + 1 if Result >= 1 then from until Result < 1 or is_character (storage, Result, directory_separator) loop Result := Result - unit_size end end if Result < 0 then Result := 0 else Result := Result + unit_size - 1 end end ensure non_negative: Result >= 0 not_too_big: Result <= storage.count valid_for_windows: {PLATFORM}.is_windows implies Result \\ unit_size = 0 end next_directory_separator (a_starting_pos: INTEGER_32): INTEGER_32 -- Starting at a position a_starting_pos find the position in storage of the -- next directory separator, or 0 otherwise. require a_starting_pos_valid: a_starting_pos >= 1 and a_starting_pos <= storage.count a_starting_pos_is_well_positionned: {PLATFORM}.is_windows implies a_starting_pos \\ unit_size = 1 local nb: INTEGER_32 l_step: like unit_size l_sep: like directory_separator l_storage: like storage do from l_step := unit_size l_sep := directory_separator Result := a_starting_pos l_storage := storage nb := l_storage.count until Result < 1 or Result > nb or is_character (l_storage, Result, l_sep) loop Result := Result + l_step end if Result > nb then Result := 0 end ensure valid_position: Result >= 0 and Result <= storage.count well_positionned: {PLATFORM}.is_windows implies ((Result = 0) or (Result \\ unit_size = 1)) has_separator: Result > 0 implies is_character (storage, Result, directory_separator) end internal_append_into (a_storage: STRING_8; other: READABLE_STRING_GENERAL; a_separator: CHARACTER_8) -- Append a_separator if different from '%U' and not already present as last character -- in a_storage, and then other to Current. require other_not_void: other /= Void other_not_empty: not other.is_empty local u: UTF_CONVERTER do if not other.is_empty then if a_separator /= '%U' and not a_storage.is_empty and then not is_character (a_storage, a_storage.count - unit_size + 1, a_separator) and other.item (1) /= Unix_separator.to_character_32 then if {PLATFORM}.is_windows then if other.item (1) /= Windows_separator.to_character_32 then a_storage.append_character (a_separator) a_storage.append_character ('%U') end else a_storage.append_character (a_separator) end end if {PLATFORM}.is_windows then u.escaped_utf_32_string_into_utf_16le_string_8 (other, a_storage) else u.escaped_utf_32_string_into_utf_8_string_8 (other, a_storage) end end end internal_path_append_into (a_storage, other: STRING_8; a_separator: CHARACTER_8) -- Append a_separator if other than '%U' and not already present as last character -- of a_storage, and then other to a_storage. require other_not_void: other /= Void other_not_empty: not other.is_empty do if a_separator /= '%U' and not a_storage.is_empty and then not is_character (a_storage, a_storage.count - unit_size + 1, a_separator) and not is_character (other, 1, a_separator) then a_storage.append_character (a_separator) if {PLATFORM}.is_windows then a_storage.append_character ('%U') end end a_storage.append (other) end internal_path_append_substring_into (a_storage, other: STRING_8; other_start_index, other_end_index: INTEGER_32; a_separator: CHARACTER_8) -- Append a_separator if other than '%U' and not already present as last character -- of a_storage.substring (other_start_index, other_end_index), and then other to a_storage. require other_not_void: other /= Void other_not_empty: not other.is_empty other_has_not_trailing_directory_separator: not is_character (other, other.count - unit_size + 1, directory_separator) do if a_separator /= '%U' and not a_storage.is_empty and then not is_character (a_storage, a_storage.count - unit_size + 1, a_separator) and not is_character (other, other_start_index, a_separator) then a_storage.append_character (a_separator) if {PLATFORM}.is_windows then a_storage.append_character ('%U') end end a_storage.append_substring (other, other_start_index, other_end_index) end is_character (a_storage: like storage; a_pos: INTEGER_32; a_char: CHARACTER_8): BOOLEAN -- Is a_char appearing at position a_pos (in bytes) in a_storage. require a_pos_valid: a_storage.valid_index (a_pos) a_pos_valid_for_windows: {PLATFORM}.is_windows implies a_storage.valid_index (a_pos + 1) a_pos_odd_for_windows: {PLATFORM}.is_windows implies (a_pos \\ unit_size) = 1 a_storage_count_valid_for_windows: {PLATFORM}.is_windows implies (a_storage.count \\ unit_size) = 0 do if {PLATFORM}.is_windows then Result := a_storage.item (a_pos) = a_char and then a_storage.item (a_pos + 1) = '%U' else Result := a_storage.item (a_pos) = a_char end ensure definition: Result = ((a_storage.item (a_pos) = a_char) and then ({PLATFORM}.is_windows implies a_storage.item (a_pos + 1) = '%U')) end Directory_separator_string: STRING_32 -- Default directory separator for the current platform. once if {PLATFORM}.is_windows then Result := {STRING_32}"\" else Result := {STRING_32}"/" end end Min_unc_path_count: INTEGER_32 = 10 -- Number of characters in storage to make up a valid UNC path: \\a\c whic is 5 Unicode characters, thus 10 bytes. feature {NONE} -- Externals c_same_files (a_path1, a_path2: POINTER): BOOLEAN -- Do C paths a_path1 and a_path2 represent the same file? require a_path1_not_null: a_path1 /= default_pointer a_path2_not_null: a_path2 /= default_pointer external "C inline use %"eif_eiffel.h%"" alias "[ EIF_BOOLEAN Result = EIF_FALSE; #ifdef EIF_WINDOWS /* To check this, we use `CreateFileW' to open both file, and then using the information * returned by `GetFileInformationByHandle' we can check whether or not they are indeed * the same. * Note: it is important to use the W version of CreateFileW because arguments * are Unicode, not ASCII. */ BY_HANDLE_FILE_INFORMATION l_path1_info, l_path2_info; HANDLE l_path2_file = CreateFileW ((LPCWSTR) $a_path2, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE l_path1_file = CreateFileW ((LPCWSTR) $a_path1, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if ((l_path2_file == INVALID_HANDLE_VALUE) || (l_path1_file == INVALID_HANDLE_VALUE)) { /* We do not need the handles anymore, simply close them. Since Microsoft * API accepts INVALID_HANDLE_VALUE we don't check the validity of arguments. */ CloseHandle(l_path2_file); CloseHandle(l_path1_file); } else { BOOL success = GetFileInformationByHandle (l_path2_file, &l_path2_info); success = success && GetFileInformationByHandle (l_path1_file, &l_path1_info); /* We do not need the handles anymore, simply close them. */ CloseHandle(l_path2_file); CloseHandle(l_path1_file); if (success) { /* Check that `path2' and `path1' do not represent the same file. */ if ((l_path2_info.dwVolumeSerialNumber == l_path1_info.dwVolumeSerialNumber) && (l_path2_info.nFileIndexLow == l_path1_info.nFileIndexLow) && (l_path2_info.nFileIndexHigh == l_path1_info.nFileIndexHigh)) { Result = EIF_TRUE; } } } #else struct stat buf1, buf2; int status; #ifdef HAS_LSTAT status = lstat($a_path1, &buf1); if (status == 0) { /* We found a file, now let's check if it is not a symbolic link. If it is, we use `stat' * to ensure the validity of the link. */ if ((buf1.st_mode & S_IFLNK) == S_IFLNK) { status = stat ($a_path1, &buf1); } } if (status == 0) { status = lstat($a_path2, &buf2); if (status == 0) { /* We found a file, now let's check if it is not a symbolic link. If it is, we use `stat' * to ensure the validity of the link. */ if ((buf2.st_mode & S_IFLNK) == S_IFLNK) { status = stat ($a_path2, &buf2); } } } #else status = stat ($a_path1, &buf1); if (status == 0) { status = stat ($a_path2, &buf2); } #endif if (status == 0) { /* Both files are present, check they represent the same one. */ if ((buf1.st_dev == buf2.st_dev) && (buf1.st_ino == buf2.st_ino)) { Result = EIF_TRUE; } } #endif return Result; ]" end invariant little_endian_windows: {PLATFORM}.is_windows implies Platform.Is_little_endian even_count_on_windows: {PLATFORM}.is_windows implies storage.count \\ unit_size = 0 no_forward_slash_on_windows: {PLATFORM}.is_windows implies not storage.has_substring ("/%U") note copyright: "Copyright (c) 1984-2017, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software 5949 Hollister Ave., Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end -- class PATH
Generated by ISE EiffelStudio