PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/doc/Modules/NaturalDocs/File.pm

https://code.google.com/p/lifos/
Perl | 541 lines | 486 code | 13 blank | 42 comment | 2 complexity | 3d438710a0e3dc91fc6dcd36db2903e1 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. ###############################################################################
  2. #
  3. # Package: NaturalDocs::File
  4. #
  5. ###############################################################################
  6. #
  7. # A package to manage file access across platforms. Incorporates functions from various standard File:: packages, but more
  8. # importantly, works around the glorious suckage present in File::Spec, at least in version 0.82 and earlier. Read the "Why oh
  9. # why?" sections for why this package was necessary.
  10. #
  11. # Usage and Dependencies:
  12. #
  13. # - The package doesn't depend on any other Natural Docs packages and is ready to use immediately.
  14. #
  15. # - All functions except <CanonizePath()> assume that all parameters are canonized.
  16. #
  17. ###############################################################################
  18. # This file is part of Natural Docs, which is Copyright Š 2003-2010 Greg Valure
  19. # Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
  20. # Refer to License.txt for the complete details
  21. use File::Spec ();
  22. use File::Path ();
  23. use File::Copy ();
  24. use strict;
  25. use integer;
  26. package NaturalDocs::File;
  27. #
  28. # Function: CheckCompatibility
  29. #
  30. # Checks if the standard packages required by this one are up to snuff and dies if they aren't. This is done because I can't
  31. # tell which versions of File::Spec have splitpath just by the version numbers.
  32. #
  33. sub CheckCompatibility
  34. {
  35. my ($self) = @_;
  36. eval {
  37. File::Spec->splitpath('');
  38. };
  39. if ($@)
  40. {
  41. NaturalDocs::Error->SoftDeath("Natural Docs requires a newer version of File::Spec than you have. "
  42. . "You must either upgrade it or upgrade Perl.");
  43. };
  44. };
  45. ###############################################################################
  46. # Group: Path String Functions
  47. #
  48. # Function: CanonizePath
  49. #
  50. # Takes a path and returns a logically simplified version of it.
  51. #
  52. # Why oh why?:
  53. #
  54. # Because File::Spec->canonpath doesn't strip quotes on Windows. So if you pass in "a b\c" or "a b"\c, they still end up as
  55. # different strings even though they're logically the same.
  56. #
  57. # It also doesn't remove things like "..", so "a/b/../c" doesn't simplify to "a/c" like it should.
  58. #
  59. sub CanonizePath #(path)
  60. {
  61. my ($self, $path) = @_;
  62. if ($::OSNAME eq 'MSWin32')
  63. {
  64. # We don't have to use a smarter algorithm for dropping quotes because they're invalid characters for actual file and
  65. # directory names.
  66. $path =~ s/\"//g;
  67. };
  68. $path = File::Spec->canonpath($path);
  69. # Condense a/b/../c into a/c.
  70. my $upDir = File::Spec->updir();
  71. if (index($path, $upDir) != -1)
  72. {
  73. my ($volume, $directoryString, $file) = $self->SplitPath($path);
  74. my @directories = $self->SplitDirectories($directoryString);
  75. my $i = 1;
  76. while ($i < scalar @directories)
  77. {
  78. if ($i > 0 && $directories[$i] eq $upDir && $directories[$i - 1] ne $upDir)
  79. {
  80. splice(@directories, $i - 1, 2);
  81. $i--;
  82. }
  83. else
  84. { $i++; };
  85. };
  86. $directoryString = $self->JoinDirectories(@directories);
  87. $path = $self->JoinPath($volume, $directoryString, $file);
  88. };
  89. return $path;
  90. };
  91. #
  92. # Function: PathIsAbsolute
  93. #
  94. # Returns whether the passed path is absolute.
  95. #
  96. sub PathIsAbsolute #(path)
  97. {
  98. my ($self, $path) = @_;
  99. return File::Spec->file_name_is_absolute($path);
  100. };
  101. #
  102. # Function: JoinPath
  103. #
  104. # Creates a path from its elements.
  105. #
  106. # Parameters:
  107. #
  108. # volume - The volume, such as the drive letter on Windows. Undef if none.
  109. # dirString - The directory string. Create with <JoinDirectories()> if necessary.
  110. # file - The file name, or undef if none.
  111. #
  112. # Returns:
  113. #
  114. # The joined path.
  115. #
  116. sub JoinPath #(volume, dirString, $file)
  117. {
  118. my ($self, $volume, $dirString, $file) = @_;
  119. return File::Spec->catpath($volume, $dirString, $file);
  120. };
  121. #
  122. # Function: JoinPaths
  123. #
  124. # Joins two paths.
  125. #
  126. # Parameters:
  127. #
  128. # basePath - May be a relative path, an absolute path, or undef.
  129. # extraPath - May be a relative path, a file, a relative path and file together, or undef.
  130. # noFileInExtra - Set this to true if extraPath is a relative path only, and doesn't have a file.
  131. #
  132. # Returns:
  133. #
  134. # The joined path.
  135. #
  136. # Why oh why?:
  137. #
  138. # Because nothing in File::Spec will simply slap two paths together. They have to be split up for catpath/file, and rel2abs
  139. # requires the base to be absolute.
  140. #
  141. sub JoinPaths #(basePath, extraPath, noFileInExtra)
  142. {
  143. my ($self, $basePath, $extraPath, $noFileInExtra) = @_;
  144. # If both are undef, it will return undef, which is what we want.
  145. if (!defined $basePath)
  146. { return $extraPath; }
  147. elsif (!defined $extraPath)
  148. { return $basePath; };
  149. my ($baseVolume, $baseDirString, $baseFile) = File::Spec->splitpath($basePath, 1);
  150. my ($extraVolume, $extraDirString, $extraFile) = File::Spec->splitpath($extraPath, $noFileInExtra);
  151. my @baseDirectories = $self->SplitDirectories($baseDirString);
  152. my @extraDirectories = $self->SplitDirectories($extraDirString);
  153. my $fullDirString = $self->JoinDirectories(@baseDirectories, @extraDirectories);
  154. my $fullPath = File::Spec->catpath($baseVolume, $fullDirString, $extraFile);
  155. return $self->CanonizePath($fullPath);
  156. };
  157. #
  158. # Function: SplitPath
  159. #
  160. # Takes a path and returns its elements.
  161. #
  162. # Parameters:
  163. #
  164. # path - The path to split.
  165. # noFile - Set to true if the path doesn't have a file at the end.
  166. #
  167. # Returns:
  168. #
  169. # The array ( volume, directoryString, file ). If any don't apply, they will be undef. Use <SplitDirectories()> to split the
  170. # directory string if desired.
  171. #
  172. # Why oh Why?:
  173. #
  174. # Because File::Spec->splitpath may leave a trailing slash/backslash/whatever on the directory string, which makes
  175. # it a bit hard to match it with results from File::Spec->catdir.
  176. #
  177. sub SplitPath #(path, noFile)
  178. {
  179. my ($self, $path, $noFile) = @_;
  180. my @segments = File::Spec->splitpath($path, $noFile);
  181. if (!length $segments[0])
  182. { $segments[0] = undef; };
  183. if (!length $segments[2])
  184. { $segments[2] = undef; };
  185. $segments[1] = File::Spec->catdir( File::Spec->splitdir($segments[1]) );
  186. return @segments;
  187. };
  188. #
  189. # Function: JoinDirectories
  190. #
  191. # Creates a directory string from an array of directory names.
  192. #
  193. # Parameters:
  194. #
  195. # directory - A directory name. There may be as many of these as desired.
  196. #
  197. sub JoinDirectories #(directory, directory, ...)
  198. {
  199. my ($self, @directories) = @_;
  200. return File::Spec->catdir(@directories);
  201. };
  202. #
  203. # Function: SplitDirectories
  204. #
  205. # Takes a string of directories and returns an array of its elements.
  206. #
  207. # Why oh why?:
  208. #
  209. # Because File::Spec->splitdir might leave an empty element at the end of the array, which screws up both joining in
  210. # <ConvertToURL> and navigation in <MakeRelativePath>.
  211. #
  212. sub SplitDirectories #(directoryString)
  213. {
  214. my ($self, $directoryString) = @_;
  215. my @directories = File::Spec->splitdir($directoryString);
  216. if (!length $directories[-1])
  217. { pop @directories; };
  218. return @directories;
  219. };
  220. #
  221. # Function: MakeRelativePath
  222. #
  223. # Takes two paths and returns a relative path between them.
  224. #
  225. # Parameters:
  226. #
  227. # basePath - The starting path. May be relative or absolute, so long as the target path is as well.
  228. # targetPath - The target path. May be relative or absolute, so long as the base path is as well.
  229. #
  230. # If both paths are relative, they are assumed to be relative to the same base.
  231. #
  232. # Returns:
  233. #
  234. # The target path relative to base.
  235. #
  236. # Why oh why?:
  237. #
  238. # First, there's nothing that gives a relative path between two relative paths.
  239. #
  240. # Second, if target and base are absolute but on different volumes, File::Spec->abs2rel creates a totally non-functional
  241. # relative path. It should return the target as is, since there is no relative path.
  242. #
  243. # Third, File::Spec->abs2rel between absolute paths on the same volume, at least on Windows, leaves the drive letter
  244. # on. So abs2rel('a:\b\c\d', 'a:\b') returns 'a:c\d' instead of the expected 'c\d'. That makes no sense whatsoever. It's
  245. # not like it was designed to handle only directory names, either; the documentation says 'path' and the code seems to
  246. # explicitly handle it. There's just an 'unless' in there that tacks on the volume, defeating the purpose of a *relative* path
  247. # and making the function worthless.
  248. #
  249. sub MakeRelativePath #(basePath, targetPath)
  250. {
  251. my ($self, $basePath, $targetPath) = @_;
  252. my ($baseVolume, $baseDirString, $baseFile) = $self->SplitPath($basePath, 1);
  253. my ($targetVolume, $targetDirString, $targetFile) = $self->SplitPath($targetPath);
  254. # If the volumes are different, there is no possible relative path.
  255. if ($targetVolume ne $baseVolume)
  256. { return $targetPath; };
  257. my @baseDirectories = $self->SplitDirectories($baseDirString);
  258. my @targetDirectories = $self->SplitDirectories($targetDirString);
  259. # Skip the parts of the path that are the same.
  260. while (scalar @baseDirectories && @targetDirectories && $baseDirectories[0] eq $targetDirectories[0])
  261. {
  262. shift @baseDirectories;
  263. shift @targetDirectories;
  264. };
  265. # Back out of the base path until it reaches where they were similar.
  266. for (my $i = 0; $i < scalar @baseDirectories; $i++)
  267. {
  268. unshift @targetDirectories, File::Spec->updir();
  269. };
  270. $targetDirString = $self->JoinDirectories(@targetDirectories);
  271. return File::Spec->catpath(undef, $targetDirString, $targetFile);
  272. };
  273. #
  274. # Function: IsSubPathOf
  275. #
  276. # Returns whether the path is a descendant of another path.
  277. #
  278. # Parameters:
  279. #
  280. # base - The base path to test against.
  281. # path - The possible subpath to test.
  282. #
  283. # Returns:
  284. #
  285. # Whether path is a descendant of base.
  286. #
  287. sub IsSubPathOf #(base, path)
  288. {
  289. my ($self, $base, $path) = @_;
  290. # This is a quick test that should find a false quickly.
  291. if ($base eq substr($path, 0, length($base)))
  292. {
  293. # This doesn't guarantee true, because it could be "C:\A B" and "C:\A B C\File". So we test for it by seeing if the last
  294. # directory in base is the same as the equivalent directory in path.
  295. my ($baseVolume, $baseDirString, $baseFile) = NaturalDocs::File->SplitPath($base, 1);
  296. my @baseDirectories = NaturalDocs::File->SplitDirectories($baseDirString);
  297. my ($pathVolume, $pathDirString, $pathFile) = NaturalDocs::File->SplitPath($path);
  298. my @pathDirectories = NaturalDocs::File->SplitDirectories($pathDirString);
  299. return ( $baseDirectories[-1] eq $pathDirectories[ scalar @baseDirectories - 1 ] );
  300. }
  301. else
  302. { return undef; };
  303. };
  304. #
  305. # Function: ConvertToURL
  306. #
  307. # Takes a relative path and converts it from the native format to a relative URL. Note that it _doesn't_ convert special characters
  308. # to amp chars.
  309. #
  310. sub ConvertToURL #(path)
  311. {
  312. my ($self, $path) = @_;
  313. my ($pathVolume, $pathDirString, $pathFile) = $self->SplitPath($path);
  314. my @pathDirectories = $self->SplitDirectories($pathDirString);
  315. my $i = 0;
  316. while ($i < scalar @pathDirectories && $pathDirectories[$i] eq File::Spec->updir())
  317. {
  318. $pathDirectories[$i] = '..';
  319. $i++;
  320. };
  321. return join('/', @pathDirectories, $pathFile);
  322. };
  323. #
  324. # Function: NoUpwards
  325. #
  326. # Takes an array of directory entries and returns one without all the entries that refer to the parent directory, such as '.' and '..'.
  327. #
  328. sub NoUpwards #(array)
  329. {
  330. my ($self, @array) = @_;
  331. return File::Spec->no_upwards(@array);
  332. };
  333. #
  334. # Function: NoFileName
  335. #
  336. # Takes a path and returns a version without the file name. Useful for sending paths to <CreatePath()>.
  337. #
  338. sub NoFileName #(path)
  339. {
  340. my ($self, $path) = @_;
  341. my ($pathVolume, $pathDirString, $pathFile) = File::Spec->splitpath($path);
  342. return File::Spec->catpath($pathVolume, $pathDirString, undef);
  343. };
  344. #
  345. # Function: NoExtension
  346. #
  347. # Returns the path without an extension.
  348. #
  349. sub NoExtension #(path)
  350. {
  351. my ($self, $path) = @_;
  352. my $extension = $self->ExtensionOf($path);
  353. if ($extension)
  354. { $path = substr($path, 0, length($path) - length($extension) - 1); };
  355. return $path;
  356. };
  357. #
  358. # Function: ExtensionOf
  359. #
  360. # Returns the extension of the passed path, or undef if none.
  361. #
  362. sub ExtensionOf #(path)
  363. {
  364. my ($self, $path) = @_;
  365. my ($pathVolume, $pathDirString, $pathFile) = File::Spec->splitpath($path);
  366. # We need the leading dot in the regex so files that start with a dot but don't have an extension count as extensionless files.
  367. if ($pathFile =~ /.\.([^\.]+)$/)
  368. { return $1; }
  369. else
  370. { return undef; };
  371. };
  372. #
  373. # Function: IsCaseSensitive
  374. #
  375. # Returns whether the current platform has case-sensitive paths.
  376. #
  377. sub IsCaseSensitive
  378. {
  379. return !(File::Spec->case_tolerant());
  380. };
  381. ###############################################################################
  382. # Group: Disk Functions
  383. #
  384. # Function: CreatePath
  385. #
  386. # Creates a directory tree corresponding to the passed path, regardless of how many directories do or do not already exist.
  387. # Do _not_ include a file name in the path. Use <NoFileName()> first if you need to.
  388. #
  389. sub CreatePath #(path)
  390. {
  391. my ($self, $path) = @_;
  392. File::Path::mkpath($path);
  393. };
  394. #
  395. # Function: RemoveEmptyTree
  396. #
  397. # Removes an empty directory tree. The passed directory will be removed if it's empty, and it will keep removing its parents
  398. # until it reaches one that's not empty or a set limit.
  399. #
  400. # Parameters:
  401. #
  402. # path - The path to start from. It will try to remove this directory and work it's way down.
  403. # limit - The path to stop at if it doesn't find any non-empty directories first. This path will *not* be removed.
  404. #
  405. sub RemoveEmptyTree #(path, limit)
  406. {
  407. my ($self, $path, $limit) = @_;
  408. my ($volume, $directoryString) = $self->SplitPath($path, 1);
  409. my @directories = $self->SplitDirectories($directoryString);
  410. my $directory = $path;
  411. while (-d $directory && $directory ne $limit)
  412. {
  413. opendir FH_ND_FILE, $directory;
  414. my @entries = readdir FH_ND_FILE;
  415. closedir FH_ND_FILE;
  416. @entries = $self->NoUpwards(@entries);
  417. if (scalar @entries || !rmdir($directory))
  418. { last; };
  419. pop @directories;
  420. $directoryString = $self->JoinDirectories(@directories);
  421. $directory = $self->JoinPath($volume, $directoryString);
  422. };
  423. };
  424. #
  425. # Function: Copy
  426. #
  427. # Copies a file from one path to another. If the destination file exists, it is overwritten.
  428. #
  429. # Parameters:
  430. #
  431. # source - The file to copy.
  432. # destination - The destination to copy to.
  433. #
  434. # Returns:
  435. #
  436. # Whether it succeeded
  437. #
  438. sub Copy #(source, destination) => bool
  439. {
  440. my ($self, $source, $destination) = @_;
  441. return File::Copy::copy($source, $destination);
  442. };
  443. 1;