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