PageRenderTime 27ms CodeModel.GetById 22ms app.highlight 2ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://github.com/tybor/Liberty
Specman e | 355 lines | 281 code | 31 blank | 43 comment | 27 complexity | 1ebb5ecff124ed5fc1a7d8b55860ab96 MD5 | raw file
  1-- This file is part of a Liberty Eiffel library.
  2-- See the full copyright at the end.
  3--
  4class POSIX_PATH_NAME
  5   -- Operating system path name, POSIX notation
  6
  7inherit
  8   UNIXISH_PATH_NAME
  9      rename
 10         path as to_string
 11      export {ANY}
 12         to_string
 13      redefine copy, is_equal
 14      end
 15
 16create {ANY}
 17   make_empty, make_root, make_current, make_from_string
 18
 19feature {ANY} -- Creation
 20   make_empty
 21      do
 22         if to_string = Void then
 23            to_string := ""
 24         else
 25            to_string.clear_count
 26         end
 27      end
 28
 29   make_root
 30      do
 31         make_empty
 32         to_string.extend('/')
 33      ensure then
 34         to_string.is_equal(once "/")
 35      end
 36
 37   make_current
 38      do
 39         make_empty
 40         to_string.extend('.')
 41      ensure then
 42         to_string.is_equal(once ".")
 43      end
 44
 45   make_from_string (s: STRING)
 46      do
 47         if to_string = Void then
 48            to_string := s.twin
 49         elseif to_string /= s then
 50            to_string.copy(s)
 51         end
 52      end
 53
 54feature {ANY} -- Constants
 55   extension_separator: CHARACTER '.'
 56
 57   directory_separator: CHARACTER '/'
 58
 59   up_directory: STRING ".."
 60
 61   this_directory: STRING "."
 62
 63feature {ANY} -- Access
 64   drive_specification: STRING
 65      do
 66      ensure then
 67         Result = Void
 68      end
 69
 70   count: INTEGER
 71      local
 72         p: INTEGER; sep: BOOLEAN
 73      do
 74         from
 75            p := 1
 76            sep := True
 77         until
 78            p > to_string.count
 79         loop
 80            if not sep and to_string.item(p) = '/' then
 81               sep := True
 82            elseif sep and to_string.item(p) /= '/' then
 83               sep := False
 84               Result := Result + 1
 85            end
 86            p := p + 1
 87         end
 88         if sep and Result > 0 then
 89            -- trailing /
 90            Result := Result + 1
 91         end
 92      end
 93
 94   last: STRING
 95      local
 96         p: INTEGER
 97      do
 98        p := to_string.last_index_of(directory_separator) + 1
 99        -- check to_string.is_valid_index(p) end
100        Result := to_string.substring(p, to_string.upper)
101      ensure then
102         to_string.has_suffix(Result)
103      end
104
105   extension: STRING
106      local
107         p: INTEGER -- the position of the eventual (last) separator (usually a point)
108         as_string: like to_string
109      do
110         -- A naive implementation, requiring an hidden allocation could be
111         -- Result := last; Result := Result.right(Result.last_index_of(extension_separator))
112         -- beside requiring "right"
113         Result := once ""
114         as_string := to_string -- let's cache the complete path as a string in the eventuality that an heir of POSIX_PATH_NAME redefine to_string into a computed feature (a function)
115         p := as_string.last_index_of(extension_separator)
116         if p > as_string.lower then
117            if p > as_string.last_index_of(directory_separator) then
118               Result := as_string.substring(p, as_string.upper)
119            else
120               check
121                  not last.has(extension_separator) -- Current is like "xxx.d/we"
122               end
123            end
124         else
125            -- path should be like "./without_extension" or like "../asd/./qwerty"
126            check
127               Result.is_empty
128            end
129         end
130      end
131
132   is_absolute: BOOLEAN
133      do
134         Result := not to_string.is_empty and then to_string.first = '/'
135      ensure
136         definition: Result = (not to_string.is_empty and then to_string.first = '/')
137      end
138
139   is_normalized: BOOLEAN
140      local
141         elem: STRING; scan: STRING
142      do
143         elem := once "path_element"
144         scan := once ""
145         scan.copy(to_string)
146         -- Check for emptiness or too many slashes
147         Result := not scan.is_empty and then not scan.has_prefix(once "///")
148         -- zero, one or two slashes allowed by POSIX
149         -- Remove initial slashes
150         from
151         until
152            scan.is_empty or else scan.first /= '/'
153         loop
154            scan.remove_head(1)
155         end
156         -- Check for trailing slashes, double slashes
157         Result := Result and then (scan.is_empty or else scan.last /= '/') and then not scan.has_substring(double_slash)
158         -- Remove initial sequences of ".."
159         if not is_absolute and Result then
160            from
161            until
162               scan.is_empty or else not scan.has_prefix(up_directory)
163            loop
164               if scan.count >= 3 and then scan.item(3) = '/' then
165                  scan.remove_head(3)
166               else
167                  scan.remove_head(2)
168               end
169            end
170         else
171            Result := Result and then not scan.has_prefix(once "../") and then not scan.is_equal(up_directory)
172         end
173         -- Make sure that there is no '..' remaining
174         Result := Result and then not scan.has_substring(once "/../") and then not scan.has_suffix(once "/..")
175         -- Make sure that there is no '.' remaining except alones
176         Result := Result and then not scan.has_substring(once "/./") and then not scan.has_suffix(once "/.") and then not scan.has_prefix(once "./")
177         Result := Result and then (is_absolute implies not scan.is_equal(this_directory))
178      ensure
179         Result implies not to_string.has_substring(once "/./")
180         Result implies not to_string.has_suffix(once "/.")
181         Result implies not to_string.is_empty
182         Result implies to_string.last /= '/' or else to_string.is_equal(once "/") or else to_string.is_equal(once "//")
183      end
184
185   is_valid_path (path: STRING): BOOLEAN
186      do
187         --|*** Not nearly strict enough <FM-24/03/2003>
188         Result := not path.is_empty
189      end
190
191feature {ANY} -- Operations
192   to_absolute
193      local
194         bd: BASIC_DIRECTORY
195      do
196         if not is_absolute then
197            tmp.copy(Current)
198            to_string := bd.current_working_directory.out.twin
199            join(tmp)
200            tmp.to_string.make_empty
201         end
202         normalize
203      end
204
205   normalize
206      do
207         tmp.copy(Current)
208         make_from_path_name(tmp)
209         if to_string.is_empty then
210            make_current
211         end
212      end
213
214   normalize_case
215      do
216         -- POSIX filesystem is case-sensitive
217         -- There are no secondary separators
218      ensure
219         to_string.is_equal(old to_string.twin)
220      end
221
222   remove_last
223      local
224         p: INTEGER
225      do
226         -- Find last separator
227         p := to_string.reverse_index_of('/', to_string.count)
228         -- Remove all trailing slashes, leaving one if it is root
229         from
230         until
231            p <= 1 or else to_string.item(p) /= '/'
232         loop
233            p := p - 1
234         end
235         to_string.keep_head(p)
236      ensure then
237         (old to_string.twin).has_prefix(to_string)
238      end
239
240   add_last (elem: STRING)
241      do
242         if not is_empty then
243            to_string.extend_unless('/')
244         end
245         to_string.append(elem)
246      end
247
248   expand_user
249      local
250         user_home: STRING; sys: SYSTEM; p: INTEGER
251      do
252         if not is_empty and then to_string.first = '~' then
253            if to_string.count = 1 or else to_string.has_prefix(once "~/") then
254               user_home := sys.get_environment_variable(once "HOME")
255            else
256               -- FIXME: Expansion of some other user's home: not done yet
257               not_yet_implemented
258            end
259            if user_home /= Void then
260               p := to_string.first_index_of('/')
261               if p = 0 then
262                  p := to_string.count + 1
263               end
264               to_string.remove_head(p - 1)
265               to_string.prepend(user_home)
266            end
267         end
268      end
269
270   expand_shellouts
271      do
272         not_yet_implemented
273      end
274
275feature {ANY} -- Copying, comparison
276   copy (other: like Current)
277      do
278         if Current /= other then
279            if to_string = Void then
280               to_string := other.to_string.twin
281            else
282               to_string.copy(other.to_string)
283            end
284         end
285      end
286
287   is_equal (other: like Current): BOOLEAN
288      do
289         Result := to_string.is_equal(other.to_string)
290      end
291
292feature {PATH_JOINER}
293   start_join (drive: STRING; absoluteness: INTEGER)
294      do
295         inspect absoluteness
296         when 0 then
297         when 1 then
298            make_root
299         else
300            make_from_string(double_slash)
301         end
302      end
303
304feature {} -- Auxiliary constants/onces
305   tmp: POSIX_PATH_NAME
306      once
307         create Result.make_empty
308      end
309
310   double_slash: STRING "//"
311
312   start_join_to (other: PATH_JOINER): INTEGER
313      local
314         slash_count: INTEGER
315      do
316         from
317            Result := to_string.lower
318         until
319            Result > to_string.upper or else not is_separator(to_string.item(Result))
320         loop
321            Result := Result + 1
322         end
323         slash_count := Result - to_string.lower
324         if slash_count > 2 then
325            slash_count := 1
326         end
327         if Result <= to_string.upper then
328            other.start_join(drive_specification, slash_count)
329         elseif slash_count > 0 then
330            other.start_join(drive_specification, slash_count)
331            other.end_join
332         end
333      end
334
335end -- class POSIX_PATH_NAME
336--
337-- Copyright (C) 2009-2017: by all the people cited in the AUTHORS file.
338--
339-- Permission is hereby granted, free of charge, to any person obtaining a copy
340-- of this software and associated documentation files (the "Software"), to deal
341-- in the Software without restriction, including without limitation the rights
342-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
343-- copies of the Software, and to permit persons to whom the Software is
344-- furnished to do so, subject to the following conditions:
345--
346-- The above copyright notice and this permission notice shall be included in
347-- all copies or substantial portions of the Software.
348--
349-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
350-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
351-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
352-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
353-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
354-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
355-- THE SOFTWARE.