|
|
- 'use strict';
-
- var fs = require('fs'),
- path = require('path'),
- defaults = require('lodash.defaults');
-
- var PACKAGE_NAME = require('../package.json').name;
-
- /**
- * Factory for find-file with the given <code>options</code> hash.
- * @param {{debug: boolean, attempts:number}} [opt] Optional options hash
- */
- function findFile(opt) {
- var options = defaults(opt, {
- debug: false,
- attempts: 0
- });
- return {
- absolute: absolute,
- base : base
- };
-
- /**
- * Search for the relative file reference from the <code>startPath</code> up to the process
- * working directory, avoiding any other directories with a <code>package.json</code> or <code>bower.json</code>.
- * @param {string} startPath The location of the uri declaration and the place to start the search from
- * @param {string} uri The content of the url() statement, expected to be a relative file path
- * @param {string} [limit] Optional directory to limit the search to
- * @returns {string|null} <code>null</code> where not found else the absolute path to the file
- */
- function absolute(startPath, uri, limit) {
- var basePath = base(startPath, uri, limit);
- return !!basePath && path.resolve(basePath, uri) || null;
- }
-
- /**
- * Search for the relative file reference from the <code>startPath</code> up to the process
- * working directory, avoiding any other directories with a <code>package.json</code> or <code>bower.json</code>.
- * @param {string} startPath The location of the uri declaration and the place to start the search from
- * @param {string} uri The content of the url() statement, expected to be a relative file path
- * @param {string} [limit] Optional directory to limit the search to
- * @returns {string|null} <code>null</code> where not found else the base path upon which the uri may be resolved
- */
- function base(startPath, uri, limit) {
- var messages = [];
-
- // ensure we have some limit to the search
- limit = limit && path.resolve(limit) || process.cwd();
-
- // #69 limit searching: make at least one attempt
- var remaining = Math.max(0, options.attempts) || 1E+9;
-
- // ignore explicit uris data|http|https and ensure we are at a valid start path
- var absoluteStart = !(/^(data|https?):/.test(uri)) && path.resolve(startPath);
- if (absoluteStart) {
-
- // find path to the root, stopping at cwd, package.json or bower.json
- var pathToRoot = [];
- var isWorking;
- do {
- pathToRoot.push(absoluteStart);
- isWorking = testWithinLimit(absoluteStart) && testNotPackage(absoluteStart);
- absoluteStart = path.resolve(absoluteStart, '..');
- } while (isWorking);
-
- // start a queue with the path to the root
- var appendLimit = options.includeRoot && pathToRoot.indexOf(limit) === -1 ? limit : [];
- var queue = pathToRoot.concat(appendLimit);
-
- // the queue pattern ensures that we favour paths closest the the start path
- // process the queue until empty or until we exhaust our attempts
- while (queue.length && (remaining-- > 0)) {
-
- // shift the first item off the queue, consider it the base for our relative uri
- var basePath = queue.shift();
- var fullPath = path.resolve(basePath, uri);
- messages.push(basePath);
-
- // file exists so convert to a dataURI and end
- if (fs.existsSync(fullPath)) {
- flushMessages('FOUND');
- return basePath;
- }
- // enqueue subdirectories that are not packages and are not in the root path
- else {
- enqueue(queue, basePath);
- }
- }
-
- // interrupted by options.attempts
- if (queue.length) {
- flushMessages('NOT FOUND (INTERRUPTED)');
- }
- // not found
- else {
- flushMessages('NOT FOUND');
- return null;
- }
- }
- // ignored
- else {
- flushMessages('IGNORED');
- return null;
- }
-
- /**
- * Enqueue subdirectories that are not packages and are not in the root path
- * @param {Array} queue The queue to add to
- * @param {string} basePath The path to consider
- */
- function enqueue(queue, basePath) {
- fs.readdirSync(basePath)
- .filter(function notHidden(filename) {
- return (filename.charAt(0) !== '.');
- })
- .map(function toAbsolute(filename) {
- return path.join(basePath, filename);
- })
- .filter(function directoriesOnly(absolutePath) {
- return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory();
- })
- .filter(function notInRootPath(absolutePath) {
- return (pathToRoot.indexOf(absolutePath) < 0);
- })
- .filter(testNotPackage)
- .forEach(function enqueue(absolutePath) {
- queue.push(absolutePath);
- });
- }
-
- /**
- * Test whether the given directory is above but not equal to any of the project root directories.
- * @param {string} absolutePath An absolute path
- * @returns {boolean} True where a package.json or bower.json exists, else False
- */
- function testWithinLimit(absolutePath) {
- var relative = path.relative(limit, absolutePath);
- return !!relative && (relative.slice(0, 2) !== '..');
- }
-
- /**
- * Print verbose debug info where <code>options.debug</code> is in effect.
- * @param {string} result Final text to append to the message
- */
- function flushMessages(result) {
- if (options.debug) {
- var text = ['\n' + PACKAGE_NAME + ': ' + uri]
- .concat(messages)
- .concat(result)
- .join('\n ');
- console.log(text);
- }
- }
- }
-
- /**
- * Test whether the given directory is the root of its own package.
- * @param {string} absolutePath An absolute path
- * @returns {boolean} True where a package.json or bower.json exists, else False
- */
- function testNotPackage(absolutePath) {
- return ['package.json', 'bower.json'].every(function fileFound(file) {
- return !fs.existsSync(path.resolve(absolutePath, file));
- });
- }
- }
-
- module.exports = findFile;
|