3 var fs = require('fs');
4 var path = require('path');
5 var _ = require('lodash');
6 var glob = require('glob');
7 var parseImports = require('./parse-imports');
9 // resolve a sass module to a path
10 function resolveSassPath(sassPath, loadPaths, extensions) {
11 // trim sass file extensions
12 var re = new RegExp('(\.('+extensions.join('|')+'))$', 'i');
13 var sassPathName = sassPath.replace(re, '');
14 // check all load paths
15 var i, j, length = loadPaths.length, scssPath, partialPath;
16 for (i = 0; i < length; i++) {
17 for (j = 0; j < extensions.length; j++) {
18 scssPath = path.normalize(loadPaths[i] + '/' + sassPathName + '.' + extensions[j]);
19 if (fs.existsSync(scssPath) && fs.lstatSync(scssPath).isFile()) {
24 // special case for _partials
25 for (j = 0; j < extensions.length; j++) {
26 scssPath = path.normalize(loadPaths[i] + '/' + sassPathName + '.' + extensions[j]);
27 partialPath = path.join(path.dirname(scssPath), '_' + path.basename(scssPath));
28 if (fs.existsSync(partialPath) && fs.lstatSync(partialPath).isFile()) {
34 // File to import not found or unreadable so we assume this is a custom import
38 function Graph(options, dir) {
40 this.loadPaths = options.loadPaths || [];
41 this.extensions = options.extensions || [];
46 _.each(glob.sync(dir+'/**/*.@('+this.extensions.join('|')+')', { dot: true, nodir: true }), function(file) {
47 graph.addFile(path.resolve(file));
52 // add a sass file to the graph
53 Graph.prototype.addFile = function(filepath, parent) {
54 var entry = this.index[filepath] = this.index[filepath] || {
57 modified: fs.statSync(filepath).mtime
61 var imports = parseImports(fs.readFileSync(filepath, 'utf-8'));
62 var cwd = path.dirname(filepath);
64 var i, length = imports.length, loadPaths, resolved;
65 for (i = 0; i < length; i++) {
66 loadPaths = _([cwd, this.dir]).concat(this.loadPaths).filter().uniq().value();
67 resolved = resolveSassPath(imports[i], loadPaths, this.extensions);
68 if (!resolved) continue;
70 // recurse into dependencies if not already enumerated
71 if (!_.includes(entry.imports, resolved)) {
72 entry.imports.push(resolved);
73 this.addFile(fs.realpathSync(resolved), filepath);
77 // add link back to parent
79 resolvedParent = _(parent).intersection(this.loadPaths).value();
82 resolvedParent = parent.substr(parent.indexOf(resolvedParent));
84 resolvedParent = parent;
87 entry.importedBy.push(resolvedParent);
91 // visits all files that are ancestors of the provided file
92 Graph.prototype.visitAncestors = function(filepath, callback) {
93 this.visit(filepath, callback, function(err, node) {
94 if (err || !node) return [];
95 return node.importedBy;
99 // visits all files that are descendents of the provided file
100 Graph.prototype.visitDescendents = function(filepath, callback) {
101 this.visit(filepath, callback, function(err, node) {
102 if (err || !node) return [];
107 // a generic visitor that uses an edgeCallback to find the edges to traverse for a node
108 Graph.prototype.visit = function(filepath, callback, edgeCallback, visited) {
109 filepath = fs.realpathSync(filepath);
110 var visited = visited || [];
111 if (!this.index.hasOwnProperty(filepath)) {
112 edgeCallback('Graph doesn\'t contain ' + filepath, null);
114 var edges = edgeCallback(null, this.index[filepath]);
116 var i, length = edges.length;
117 for (i = 0; i < length; i++) {
118 if (!_.includes(visited, edges[i])) {
119 visited.push(edges[i]);
120 callback(edges[i], this.index[edges[i]]);
121 this.visit(edges[i], callback, edgeCallback, visited);
126 function processOptions(options) {
128 loadPaths: [process.cwd()],
129 extensions: ['scss', 'css'],
133 module.exports.parseFile = function(filepath, options) {
134 if (fs.lstatSync(filepath).isFile()) {
135 filepath = path.resolve(filepath);
136 options = processOptions(options);
137 var graph = new Graph(options);
138 graph.addFile(filepath);
144 module.exports.parseDir = function(dirpath, options) {
145 if (fs.lstatSync(dirpath).isDirectory()) {
146 dirpath = path.resolve(dirpath);
147 options = processOptions(options);
148 var graph = new Graph(options, dirpath);