You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							160 lines
						
					
					
						
							4.7 KiB
						
					
					
				
			
		
		
	
	
							160 lines
						
					
					
						
							4.7 KiB
						
					
					
				'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 path | 
						|
function 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 graph | 
						|
Graph.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 file | 
						|
Graph.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 file | 
						|
Graph.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 node | 
						|
Graph.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 | 
						|
};
 | 
						|
 |