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.
		
		
		
		
		
			
		
			
				
					
					
						
							161 lines
						
					
					
						
							4.7 KiB
						
					
					
				
			
		
		
	
	
							161 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
 | |
| };
 |