| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 | 'use strict';var fs = require('fs');var path = require('path');var _ = require('lodash');var glob = require('glob');var parseImports = require('./parse-imports');// resolve a sass module to a pathfunction resolveSassPath(sassPath, loadPaths, extensions) {  // trim sass file extensions  var re = new RegExp('(\.('+extensions.join('|')+'))$', 'i');  var sassPathName = sassPath.replace(re, '');  // check all load paths  var i, j, length = loadPaths.length, scssPath, partialPath;  for (i = 0; i < length; i++) {    for (j = 0; j < extensions.length; j++) {      scssPath = path.normalize(loadPaths[i] + '/' + sassPathName + '.' + extensions[j]);      try {        if (fs.lstatSync(scssPath).isFile()) {          return scssPath;        }      } catch (e) {}    }    // special case for _partials    for (j = 0; j < extensions.length; j++) {      scssPath = path.normalize(loadPaths[i] + '/' + sassPathName + '.' + extensions[j]);      partialPath = path.join(path.dirname(scssPath), '_' + path.basename(scssPath));      try {        if (fs.lstatSync(partialPath).isFile()) {          return partialPath;        }      } catch (e) {}    }  }  // File to import not found or unreadable so we assume this is a custom import  return false;}function Graph(options, dir) {  this.dir = dir;  this.extensions = options.extensions || [];  this.index = {};  this.follow = options.follow || false;  this.loadPaths = _(options.loadPaths).map(function(p) {    return path.resolve(p);  }).value();  if (dir) {    var graph = this;    _.each(glob.sync(dir+'/**/*.@('+this.extensions.join('|')+')', { dot: true, nodir: true, follow: this.follow }), function(file) {      graph.addFile(path.resolve(file));    });  }}// add a sass file to the graphGraph.prototype.addFile = function(filepath, parent) {  var entry = this.index[filepath] = this.index[filepath] || {    imports: [],    importedBy: [],    modified: fs.statSync(filepath).mtime  };  var resolvedParent;  var isIndentedSyntax = path.extname(filepath) === '.sass';  var imports = parseImports(fs.readFileSync(filepath, 'utf-8'), isIndentedSyntax);  var cwd = path.dirname(filepath);  var i, length = imports.length, loadPaths, resolved;  for (i = 0; i < length; i++) {    loadPaths = _([cwd, this.dir]).concat(this.loadPaths).filter().uniq().value();    resolved = resolveSassPath(imports[i], loadPaths, this.extensions);    if (!resolved) continue;    // recurse into dependencies if not already enumerated    if (!_.includes(entry.imports, resolved)) {      entry.imports.push(resolved);      this.addFile(fs.realpathSync(resolved), filepath);    }  }  // add link back to parent  if (parent) {    resolvedParent = _(parent).intersection(this.loadPaths).value();    if (resolvedParent) {      resolvedParent = parent.substr(parent.indexOf(resolvedParent));    } else {      resolvedParent = parent;    }    entry.importedBy.push(resolvedParent);  }};// visits all files that are ancestors of the provided fileGraph.prototype.visitAncestors = function(filepath, callback) {  this.visit(filepath, callback, function(err, node) {    if (err || !node) return [];    return node.importedBy;  });};// visits all files that are descendents of the provided fileGraph.prototype.visitDescendents = function(filepath, callback) {  this.visit(filepath, callback, function(err, node) {    if (err || !node) return [];    return node.imports;  });};// a generic visitor that uses an edgeCallback to find the edges to traverse for a nodeGraph.prototype.visit = function(filepath, callback, edgeCallback, visited) {  filepath = fs.realpathSync(filepath);  var visited = visited || [];  if (!this.index.hasOwnProperty(filepath)) {    edgeCallback('Graph doesn\'t contain ' + filepath, null);  }  var edges = edgeCallback(null, this.index[filepath]);  var i, length = edges.length;  for (i = 0; i < length; i++) {    if (!_.includes(visited, edges[i])) {      visited.push(edges[i]);      callback(edges[i], this.index[edges[i]]);      this.visit(edges[i], callback, edgeCallback, visited);    }  }};function processOptions(options) {  return _.assign({    loadPaths: [process.cwd()],    extensions: ['scss', 'css', 'sass'],  }, options);}module.exports.parseFile = function(filepath, options) {  if (fs.lstatSync(filepath).isFile()) {    filepath = path.resolve(filepath);    options = processOptions(options);    var graph = new Graph(options);    graph.addFile(filepath);    return graph;  }  // throws};module.exports.parseDir = function(dirpath, options) {  if (fs.lstatSync(dirpath).isDirectory()) {    dirpath = path.resolve(dirpath);    options = processOptions(options);    var graph = new Graph(options, dirpath);    return graph;  }  // throws};
 |