path.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /**
  2. * Extended version of the standard `path` module.
  3. * @module jsdoc/path
  4. */
  5. const env = require('jsdoc/env');
  6. const fs = require('fs');
  7. const path = require('path');
  8. function prefixReducer(previousPath, current) {
  9. let currentPath = [];
  10. // if previousPath is defined, but has zero length, there's no common prefix; move along
  11. if (previousPath && !previousPath.length) {
  12. return currentPath;
  13. }
  14. currentPath = path.resolve(env.pwd, current).split(path.sep) || [];
  15. if (previousPath && currentPath.length) {
  16. // remove chunks that exceed the previous path's length
  17. currentPath = currentPath.slice(0, previousPath.length);
  18. // if a chunk doesn't match the previous path, remove everything from that chunk on
  19. for (let i = 0, l = currentPath.length; i < l; i++) {
  20. if (currentPath[i] !== previousPath[i]) {
  21. currentPath.splice(i, currentPath.length - i);
  22. break;
  23. }
  24. }
  25. }
  26. return currentPath;
  27. }
  28. /**
  29. * Find the common prefix for an array of paths. If there is a common prefix, a trailing separator
  30. * is appended to the prefix. Relative paths are resolved relative to the current working directory.
  31. *
  32. * For example, assuming that the current working directory is `/Users/jsdoc`:
  33. *
  34. * + For the single path `foo/bar/baz/qux.js`, the common prefix is `foo/bar/baz/`.
  35. * + For paths `foo/bar/baz/qux.js`, `foo/bar/baz/quux.js`, and `foo/bar/baz.js`, the common prefix
  36. * is `/Users/jsdoc/foo/bar/`.
  37. * + For paths `../jsdoc/foo/bar/baz/qux/quux/test.js`, `/Users/jsdoc/foo/bar/bazzy.js`, and
  38. * `../../Users/jsdoc/foo/bar/foobar.js`, the common prefix is `/Users/jsdoc/foo/bar/`.
  39. * + For paths `foo/bar/baz/qux.js` and `../../Library/foo/bar/baz.js`, there is no common prefix,
  40. * and an empty string is returned.
  41. *
  42. * @param {Array.<string>} paths - The paths to search for a common prefix.
  43. * @return {string} The common prefix, or an empty string if there is no common prefix.
  44. */
  45. exports.commonPrefix = (paths = []) => {
  46. let prefix = '';
  47. let segments;
  48. // if there's only one path, its resolved dirname (plus a trailing slash) is the common prefix
  49. if (paths.length === 1) {
  50. prefix = path.resolve(env.pwd, paths[0]);
  51. if ( path.extname(prefix) ) {
  52. prefix = path.dirname(prefix);
  53. }
  54. prefix += path.sep;
  55. }
  56. else {
  57. segments = paths.reduce(prefixReducer, undefined) || [];
  58. // if there's anything left (other than a placeholder for a leading slash), add a
  59. // placeholder for a trailing slash
  60. if ( segments.length && (segments.length > 1 || segments[0] !== '') ) {
  61. segments.push('');
  62. }
  63. prefix = segments.join(path.sep);
  64. }
  65. return prefix;
  66. };
  67. /**
  68. * Retrieve the fully qualified path to the requested resource.
  69. *
  70. * If the resource path is specified as a relative path, JSDoc searches for the resource in the
  71. * following locations, in this order:
  72. *
  73. * 1. The current working directory
  74. * 2. The directory where the JSDoc configuration file is located
  75. * 3. The JSDoc directory
  76. * 4. Anyplace where `require()` can find the resource (for example, in your project's
  77. * `node_modules` directory)
  78. *
  79. * If the resource path is specified as a fully qualified path, JSDoc searches for the resource in
  80. * the following locations, in this order:
  81. *
  82. * 1. The resource path
  83. * 2. Anyplace where `require()` can find the resource (for example, in your project's
  84. * `node_modules` directory)
  85. *
  86. * @param {string} filepath - The path to the requested resource. May be an absolute path; a path
  87. * relative to the JSDoc directory; or a path relative to the current working directory.
  88. * @param {string} [filename] - The filename of the requested resource.
  89. * @return {string} The fully qualified path to the requested resource. Includes the filename if one
  90. * was provided.
  91. */
  92. exports.getResourcePath = (filepath, filename) => {
  93. let result = null;
  94. const searchDirs = [env.pwd, path.dirname(env.opts.configure || ''), env.dirname];
  95. function exists(p) {
  96. try {
  97. fs.statSync(p);
  98. return true;
  99. }
  100. catch (e) {
  101. return false;
  102. }
  103. }
  104. function resolve(p) {
  105. try {
  106. return require.resolve(p);
  107. }
  108. catch (e) {
  109. return null;
  110. }
  111. }
  112. function find(p) {
  113. // does the requested path exist?
  114. if ( exists(p) ) {
  115. result = p;
  116. }
  117. else {
  118. // can `require()` find the requested path?
  119. result = resolve(p);
  120. }
  121. return Boolean(result);
  122. }
  123. filepath = path.join(filepath, filename || '');
  124. // is the filepath absolute? if so, just use it
  125. if ( path.isAbsolute(filepath) ) {
  126. find(filepath);
  127. }
  128. else {
  129. searchDirs.some(searchDir => {
  130. if (searchDir) {
  131. return find( path.resolve(path.join(searchDir, filepath)) );
  132. }
  133. else {
  134. return false;
  135. }
  136. });
  137. }
  138. // if we still haven't found the resource, maybe it's an installed module
  139. if (!result) {
  140. result = resolve(filepath);
  141. }
  142. return result;
  143. };
  144. Object.keys(path).forEach(member => {
  145. exports[member] = path[member];
  146. });