Initial commit
[yaffs-website] / node_modules / sass-graph / sass-graph.js
1 'use strict';
2
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');
8
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()) {
20         return scssPath;
21       }
22     }
23
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()) {
29         return partialPath;
30       }
31     }
32   }
33
34   // File to import not found or unreadable so we assume this is a custom import
35   return false;
36 }
37
38 function Graph(options, dir) {
39   this.dir = dir;
40   this.loadPaths = options.loadPaths || [];
41   this.extensions = options.extensions || [];
42   this.index = {};
43
44   if (dir) {
45     var graph = this;
46     _.each(glob.sync(dir+'/**/*.@('+this.extensions.join('|')+')', { dot: true, nodir: true }), function(file) {
47       graph.addFile(path.resolve(file));
48     });
49   }
50 }
51
52 // add a sass file to the graph
53 Graph.prototype.addFile = function(filepath, parent) {
54   var entry = this.index[filepath] = this.index[filepath] || {
55     imports: [],
56     importedBy: [],
57     modified: fs.statSync(filepath).mtime
58   };
59
60   var resolvedParent;
61   var imports = parseImports(fs.readFileSync(filepath, 'utf-8'));
62   var cwd = path.dirname(filepath);
63
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;
69
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);
74     }
75   }
76
77   // add link back to parent
78   if (parent) {
79     resolvedParent = _(parent).intersection(this.loadPaths).value();
80
81     if (resolvedParent) {
82       resolvedParent = parent.substr(parent.indexOf(resolvedParent));
83     } else {
84       resolvedParent = parent;
85     }
86
87     entry.importedBy.push(resolvedParent);
88   }
89 };
90
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;
96   });
97 };
98
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 [];
103     return node.imports;
104   });
105 };
106
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);
113   }
114   var edges = edgeCallback(null, this.index[filepath]);
115
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);
122     }
123   }
124 };
125
126 function processOptions(options) {
127   return _.assign({
128     loadPaths: [process.cwd()],
129     extensions: ['scss', 'css'],
130   }, options);
131 }
132
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);
139     return graph;
140   }
141   // throws
142 };
143
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);
149     return graph;
150   }
151   // throws
152 };