PageRenderTime 17ms CodeModel.GetById 11ms app.highlight 2ms RepoModel.GetById 1ms app.codeStats 0ms

/src/lib/io/filesystem/path_name/microsoft_path_name.e

http://github.com/tybor/Liberty
Specman e | 490 lines | 393 code | 43 blank | 54 comment | 35 complexity | 5c777dc5026f1fc975ea8d7c0c5212f2 MD5 | raw file
  1-- This file is part of a Liberty Eiffel library.
  2-- See the full copyright at the end.
  3--
  4class MICROSOFT_PATH_NAME
  5   -- Operating system path name, Microsoft notation (DOS, Win9x, WinNT)
  6
  7inherit
  8   UNIXISH_PATH_NAME
  9      export {MICROSOFT_PATH_NAME}
 10         path
 11      redefine copy, is_equal, is_separator, is_valid_file_name, join_element, end_join
 12      end
 13
 14create {ANY}
 15   make_empty, make_root, make_current, make_from_string
 16
 17feature {ANY} -- Creation
 18   make_empty
 19      do
 20         if path = Void then
 21            create path.make_empty
 22            create to_string_cache.make_empty
 23         else
 24            path.clear_count
 25            to_string_cache.clear_count
 26         end
 27         drive := no_drive
 28         valid_cache := True
 29      ensure then
 30         drive = no_drive
 31      end
 32
 33   make_root
 34      do
 35         make_empty
 36         path.extend(directory_separator)
 37         valid_cache := False
 38      ensure then
 39         to_string.is_equal(once "\")
 40      end
 41
 42   make_current
 43      do
 44         make_empty
 45         path.extend('.')
 46         valid_cache := False
 47      ensure then
 48         to_string.is_equal(once ".")
 49      end
 50
 51   make_from_string (s: STRING)
 52      do
 53         if s = to_string then
 54         elseif s = path then
 55            drive := no_drive
 56            valid_cache := True
 57         else
 58            make_empty
 59            if s.count >= 2 and then s.item(2) = drive_separator then
 60               drive := s.first
 61               path.clear_count
 62               path.append_substring(s, 3, s.count)
 63            else
 64               drive := no_drive
 65               path.copy(s)
 66            end
 67            valid_cache := False
 68         end
 69      end
 70
 71feature {ANY} -- Constants
 72   up_directory: STRING ".."
 73
 74   this_directory: STRING "."
 75
 76   extension_separator: CHARACTER '.'
 77
 78   directory_separator: CHARACTER '\'
 79
 80   drive_separator: CHARACTER ':'
 81
 82feature {ANY} -- Access
 83   to_string: STRING
 84      do
 85         if path = Void then
 86            -- This special case is needed because of the old expression in make_from_string's postcondition.
 87            make_empty
 88         end
 89         if drive = no_drive then
 90            Result := path
 91         elseif valid_cache then
 92            Result := to_string_cache
 93         else
 94            Result := to_string_cache
 95            Result.clear_count
 96            Result.extend(drive)
 97            Result.extend(drive_separator)
 98            Result.append(path)
 99            valid_cache := True
100         end
101      end
102
103   drive_specification: STRING
104      do
105         Result := "X:"
106         Result.clear_count
107         if drive /= no_drive then
108            Result.extend(drive)
109            Result.extend(':')
110         end
111      ensure then
112         Result.count = 0 or Result.count = 2
113         drive = '%U' = Result.is_empty
114         not Result.is_empty implies Result.first = drive
115         not Result.is_empty implies Result.item(2) = ':'
116      end
117
118   count: INTEGER
119      local
120         p: INTEGER; sep: BOOLEAN
121      do
122         from
123            p := 1
124            sep := True
125         until
126            p > path.count
127         loop
128            if not sep and is_separator(path.item(p)) then
129               sep := True
130            elseif sep and not is_separator(path.item(p)) then
131               sep := False
132               Result := Result + 1
133            end
134            p := p + 1
135         end
136         if sep and Result > 0 then
137            -- trailing \
138            Result := Result + 1
139         end
140      end
141
142   last: STRING
143      local
144         p: INTEGER
145      do
146         from
147            p := path.count
148         until
149            p = 0 or else is_separator(path.item(p))
150         loop
151            p := p - 1
152         end
153         Result := path.substring(p + 1, path.count)
154      ensure then
155         to_string.has_suffix(Result)
156      end
157
158   extension: STRING
159      local
160         p: INTEGER
161      do
162         Result := once ""
163         p := path.reverse_index_of(extension_separator, path.count)
164         if p /= 0 then
165            if not path.valid_index(path.reverse_index_of('/', p)) and then not path.valid_index(path.index_of(directory_separator, p)) then
166               Result := path.substring(p, path.count)
167            end
168         end
169      end
170
171   is_absolute: BOOLEAN
172      do
173         Result := not path.is_empty and then is_separator(path.first)
174      end
175
176   is_normalized: BOOLEAN
177      local
178         elem: STRING; scan: STRING
179      do
180         elem := once "path_element"
181         scan := once ""
182         scan.copy(to_string)
183         -- Some basic checks
184         Result := not path.has('/') and then not path.is_empty and then (drive /= no_drive implies not path.has_prefix(once "\\"
185         -- UNIX slash not allowed
186         -- Empty path is not normalized
187         ))
188         -- Double slash allowed only without drive
189         -- Remove initial slashes
190         from
191         until
192            scan.is_empty or else scan.first /= directory_separator
193         loop
194            scan.remove_head(1)
195         end
196         -- Check for trailing slashes, double slashes
197         Result := Result and then (scan.is_empty or else scan.last /= directory_separator) and then not scan.has_substring(once "\\")
198         -- Remove initial sequences of ".."
199         if not is_absolute and Result then
200            from
201            until
202               scan.is_empty or else not scan.has_prefix(up_directory)
203            loop
204               if scan.count >= 3 and then scan.item(3) = directory_separator then
205                  scan.remove_head(3)
206               else
207                  scan.remove_head(2)
208               end
209            end
210         else
211            Result := Result and then not scan.has_prefix(once "..\") and then not scan.is_equal(up_directory)
212         end
213         -- Make sure that there is no '..' remaining
214         Result := Result and then not scan.has_substring(once "\..\") and then not scan.has_suffix(once "\..")
215         -- Make sure that there is no '.' remaining except alones
216         Result := Result and then not scan.has_substring(once "\.\") and then not scan.has_suffix(once "\.") and then not scan.has_prefix(once ".\")
217         Result := Result and then (is_absolute implies not scan.is_equal(this_directory))
218      ensure
219         Result implies not to_string.has_substring(once "\.\")
220         Result implies not to_string.has_suffix(once "\.")
221         Result implies not to_string.is_empty
222      end
223
224   is_separator (ch: CHARACTER): BOOLEAN
225         -- Is `ch' a possible path separator? ( '/'  or '\' )
226      do
227         Result := ch = '/' or ch = directory_separator
228      end
229
230   is_valid_path (a_path: STRING): BOOLEAN
231      do
232         --|*** Not nearly strict enough <FM-24/03/2003>
233         Result := not a_path.is_empty
234      end
235
236   is_valid_file_name (elem: STRING): BOOLEAN
237      do
238         Result := Precursor(elem) and then not elem.has('/')
239      end
240
241   exists: BOOLEAN
242      --      local
243      --         i: FILE_INFORMATION
244      do
245         crash
246         --         i.update (to_string)
247         --         Result := i.exists
248         -- FIXME: No way to do this
249      end
250
251   same_file (other: like Current): BOOLEAN
252      --      local
253      --         i, j: FILE_INFORMATION
254      do
255         crash
256         --         i.update (to_string)
257         --         j.update (other.to_string)
258         --         Result := i.exists and then j.exists and then
259         --            (i.inode = j.inode) and (i.device = j.device)
260         -- FIXME: No way to do this
261      end
262
263feature {ANY} -- Operations
264   to_absolute
265      local
266         bd: BASIC_DIRECTORY
267      do
268         if not is_absolute then
269            tmp.copy(Current)
270            make_from_string(bd.current_working_directory.out.twin)
271            join(tmp)
272            tmp.make_empty
273         end
274         normalize
275      end
276
277   normalize
278      do
279         tmp.copy(Current)
280         make_from_path_name(tmp)
281         if path.is_empty then
282            path.copy(this_directory)
283         end
284      end
285
286   normalize_case
287      do
288         drive := drive.to_lower
289         path.to_lower
290         path.replace_all('/', directory_separator)
291         valid_cache := False
292      end
293
294   remove_last
295      local
296         p: INTEGER
297      do
298         -- Find last separator
299         p := path.reverse_index_of('/', path.count).max(path.reverse_index_of(directory_separator, path.count))
300         -- Remove all trailing slashes, leaving one if it is root
301         from
302         until
303            p <= 1 or else not is_separator(path.item(p))
304         loop
305            p := p - 1
306         end
307         path.keep_head(p)
308         valid_cache := False
309      ensure then
310         (old to_string.twin).has_prefix(to_string)
311      end
312
313   add_last (elem: STRING)
314      do
315         if not path.is_empty and then not is_separator(path.last) then
316            path.extend(directory_separator)
317         end
318         path.append(elem)
319         valid_cache := False
320      end
321
322   expand_user
323      local
324         sys: SYSTEM; home: STRING; p: INTEGER
325      do
326         if drive = no_drive and then not path.is_empty and then path.first = '~' then
327            if path.count = 1 or else is_separator(path.item(2)) then
328               home := sys.get_environment_variable(once "HOME")
329               if home = Void then
330                  home := sys.get_environment_variable(once "HOMEPATH")
331                  if home /= Void then
332                     p := path.first_index_of(directory_separator).min(path.first_index_of('\'))
333                     if p = 0 then
334                        p := path.count + 1
335                     end
336                     path.remove_head(p - 1)
337                     path.prepend(home)
338                     home := sys.get_environment_variable(once "HOMEDRIVE")
339                     if home /= Void and not home.is_empty then
340                        drive := home.first
341                     end
342                  end
343               else
344                  p := path.first_index_of(directory_separator).min(path.first_index_of('\'))
345                  if p = 0 then
346                     p := path.count + 1
347                  end
348                  path.remove_head(p - 1)
349                  path.prepend(home)
350               end
351            else
352               -- MS paths do not support ~user. Return original path
353            end
354         end
355      end
356
357   expand_shellouts
358      do
359         not_yet_implemented
360      end
361
362feature {ANY} -- Copying, comparison
363   copy (other: like Current)
364      do
365         if Current /= other then
366            make_empty
367            drive := other.drive
368            path.copy(other.path)
369            valid_cache := False
370         end
371      end
372
373   is_equal (other: like Current): BOOLEAN
374      do
375         -- Note: case insensitive
376         Result := drive.same_as(other.drive) and then path.same_as(other.path)
377      end
378
379feature {MICROSOFT_PATH_NAME} -- Representation
380   drive: CHARACTER
381         -- Drive letter, or '%U' if none
382
383feature {PATH_JOINER}
384   start_join (a_drive: STRING; absoluteness: INTEGER)
385      local
386         new_drive: like drive
387      do
388         inspect absoluteness
389         when 0 then
390            if a_drive /= Void and then drive = no_drive and then not a_drive.is_empty and then path.is_empty then
391               drive := a_drive.first
392            end
393         when 1 then
394            if a_drive /= Void and then not a_drive.is_empty then
395               new_drive := a_drive.first
396            elseif path.count > 1 or else path.count = 1 and then not is_separator(path.last) then
397               new_drive := no_drive
398            else
399               new_drive := drive
400            end
401            make_root
402            drive := new_drive
403         else
404            path.make_filled(directory_separator, absoluteness)
405            drive := no_drive
406         end
407         valid_cache := False
408      ensure
409         not valid_cache
410      end
411
412   join_element (element: STRING)
413      do
414         Precursor(element)
415         valid_cache := False
416      ensure then
417         not valid_cache
418      end
419
420   end_join
421      local
422         dl: like drive
423      do
424         dl := drive
425         Precursor
426         drive := dl
427      ensure then
428         drive = old drive
429      end
430
431feature {} -- Representation
432   no_drive: CHARACTER '%U'
433
434   to_string_cache: STRING
435
436   valid_cache: BOOLEAN
437
438feature {} -- Internal
439   tmp: MICROSOFT_PATH_NAME
440      once
441         create Result.make_empty
442      end
443
444   start_join_to (other: PATH_JOINER): INTEGER
445      local
446         slash_count: INTEGER
447      do
448         from
449            Result := path.lower
450         until
451            Result > path.upper or else not is_separator(path.item(Result))
452         loop
453            Result := Result + 1
454         end
455         slash_count := Result - path.lower
456         if Result <= path.upper then
457            other.start_join(drive_specification, slash_count)
458         elseif slash_count > 0 or else drive /= no_drive then
459            other.start_join(drive_specification, slash_count)
460            other.end_join
461         end
462      end
463
464invariant
465   to_string_cache /= Void
466   drive = no_drive or drive.is_letter
467   valid_cache and drive = no_drive implies to_string.is_equal(path)
468   valid_cache and drive /= no_drive implies to_string.is_equal(drive.to_string + ":" + path)
469
470end -- class MICROSOFT_PATH_NAME
471--
472-- Copyright (C) 2009-2017: by all the people cited in the AUTHORS file.
473--
474-- Permission is hereby granted, free of charge, to any person obtaining a copy
475-- of this software and associated documentation files (the "Software"), to deal
476-- in the Software without restriction, including without limitation the rights
477-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
478-- copies of the Software, and to permit persons to whom the Software is
479-- furnished to do so, subject to the following conditions:
480--
481-- The above copyright notice and this permission notice shall be included in
482-- all copies or substantial portions of the Software.
483--
484-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
485-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
486-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
487-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
488-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
489-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
490-- THE SOFTWARE.