1 | var path = require('path');
|
2 | var fs = require('fs');
|
3 | var _ = require('underscore')._;
|
4 | var EventEmitter = require('events').EventEmitter;
|
5 | var util = require('util');
|
6 |
|
7 | var build = require('./build');
|
8 |
|
9 | function FileTree(data, file) {
|
10 | this.onChange = _.bind(this.onChange, this);
|
11 | this.onChange = _.debounce(this.onChange, 3000, true);
|
12 | this.onChildChange = _.bind(this.onChildChange, this);
|
13 | EventEmitter.call(this);
|
14 | this.data = data;
|
15 | this.file = file;
|
16 | this.children = [];
|
17 | this.watch();
|
18 | this.branch();
|
19 | }
|
20 |
|
21 | util.inherits(FileTree, EventEmitter);
|
22 |
|
23 | FileTree.prototype.watch = function() {
|
24 | var self = this;
|
25 | if (this.file) {
|
26 | this.watcher = fs.watch(this.file, this.onChange);
|
27 | }
|
28 | };
|
29 | FileTree.prototype.onChange = function(event) {
|
30 |
|
31 | console.log('Mason: changed', this.file);
|
32 | this.emit('change', this);
|
33 | this.branch();
|
34 | };
|
35 | FileTree.prototype.onChildChange = function(tree) {
|
36 | this.emit('change', this);
|
37 | },
|
38 | FileTree.prototype.stop = function() {
|
39 | if (this.watcher) this.watcher.close();
|
40 | this.removeAllListeners();
|
41 | };
|
42 | FileTree.prototype.addChild = function(child) {
|
43 | child.on('change', this.onChildChange);
|
44 | this.children.push(child);
|
45 | };
|
46 | FileTree.prototype.removeAllChildren = function() {
|
47 | this.children.forEach(function(child) {
|
48 | child.stop();
|
49 | });
|
50 | this.children = [];
|
51 | };
|
52 | FileTree.prototype.branch = function() {
|
53 | var self = this,
|
54 | newChild;
|
55 | this.removeAllChildren();
|
56 | if (this.file) {
|
57 |
|
58 | this[this.data.type]();
|
59 | }
|
60 | else {
|
61 |
|
62 | if (typeof this.data.source === 'string') {
|
63 |
|
64 | var fileName = path.join(this.data.dir, this.data.source);
|
65 | newChild = new FileTree(this.data, fileName);
|
66 | self.addChild(newChild);
|
67 | }
|
68 | else if (typeof this.data.source === 'object') {
|
69 |
|
70 | this.data.source.src.forEach(function(src) {
|
71 | var fileName = path.join(self.data.dir, self.data.source.base, src);
|
72 | newChild = new FileTree(self.data, fileName);
|
73 | self.addChild(newChild);
|
74 | });
|
75 | }
|
76 | }
|
77 | };
|
78 | FileTree.prototype.javascript = function() {
|
79 |
|
80 | };
|
81 | FileTree.prototype.stylus = function() {
|
82 | var self = this;
|
83 | var dir = path.dirname(this.file);
|
84 | var file = fs.readFileSync(this.file, 'utf-8');
|
85 | var imports = file.match(/@import '(.*)'/g);
|
86 | imports && imports.forEach(function(imported) {
|
87 | var first = imported.indexOf("'") + 1;
|
88 | var last = imported.lastIndexOf("'");
|
89 | var fileName = imported.slice(first, last);
|
90 | fileName += (path.extname(fileName) === '') ? '.styl' : '';
|
91 | fileName = path.join(dir, fileName);
|
92 | self.addChild(new FileTree(self.data, fileName));
|
93 | });
|
94 | };
|
95 | FileTree.prototype.jade = function() {
|
96 | var self = this;
|
97 | var dir = path.dirname(this.file);
|
98 | var file = fs.readFileSync(this.file, 'utf-8');
|
99 | var extended = file.match(/extends (.*)/g);
|
100 | extended && extended.forEach(function(extend) {
|
101 | var fileName = extend.slice(8);
|
102 | fileName += (path.extname(fileName) === '') ? '.jade' : '';
|
103 | fileName = path.join(dir, fileName);
|
104 | self.addChild(new FileTree(self.data, fileName));
|
105 | });
|
106 | var included = file.match(/include (.*)/g);
|
107 | included && included.forEach(function(include) {
|
108 | var fileName = include.slice(8);
|
109 | fileName += (path.extname(fileName) === '') ? '.jade' : '';
|
110 | fileName = path.join(dir, fileName);
|
111 | self.addChild(new FileTree(self.data, fileName));
|
112 | });
|
113 | };
|
114 |
|
115 |
|
116 | function Asset(name, data) {
|
117 | var self = this;
|
118 | this.rebuild = _.debounce(this.rebuild, 2000, true);
|
119 | EventEmitter.call(this);
|
120 | this.name = name;
|
121 | this.data = data;
|
122 | this.fileTree = new FileTree(this.data);
|
123 | this.fileTree.on('change', function() {
|
124 | self.emit('change', self.name);
|
125 | });
|
126 | }
|
127 |
|
128 | util.inherits(Asset, EventEmitter);
|
129 |
|
130 |
|
131 | function Root(file) {
|
132 | this.onChange = _.bind(this.onChange, this);
|
133 | this.onJsonChange = _.bind(this.onJsonChange, this);
|
134 | EventEmitter.call(this);
|
135 | this.file = file;
|
136 | this.children = [];
|
137 | this.watch();
|
138 | this.branch();
|
139 | }
|
140 |
|
141 | util.inherits(Root, EventEmitter);
|
142 |
|
143 | Root.prototype.watch = function() {
|
144 | fs.watch(this.file, this.onJsonChange);
|
145 | };
|
146 |
|
147 | Root.prototype.onChange = function(name) {
|
148 | names = name ? [name] : undefined;
|
149 | this.emit('change', names);
|
150 | };
|
151 |
|
152 | Root.prototype.onJsonChange = function() {
|
153 | this.emit('change');
|
154 | };
|
155 |
|
156 | Root.prototype.addChild = function(child) {
|
157 | child.on('change', this.onChange);
|
158 | this.children.push(child);
|
159 | };
|
160 |
|
161 | Root.prototype.removeAllChildren = function() {
|
162 | this.children.forEach(function(child) {
|
163 | child.removeAllListeners();
|
164 | });
|
165 | this.children = [];
|
166 | };
|
167 |
|
168 | Root.prototype.branch = function() {
|
169 | var self = this;
|
170 | this.removeAllListeners();
|
171 | var dir = path.dirname(this.file);
|
172 | var assets = require(this.file);
|
173 | Object.keys(assets).forEach(function(name) {
|
174 | var asset = assets[name];
|
175 | asset.dir = dir;
|
176 | self.addChild(new Asset(name, asset));
|
177 | });
|
178 | };
|
179 |
|
180 |
|
181 | module.exports = function watch(root, config) {
|
182 | config = config || {};
|
183 | var file = path.join(root, 'mason.json');
|
184 |
|
185 | var rootWatcher = new Root(file);
|
186 | rootWatcher.on('change', rebuild);
|
187 | rebuild();
|
188 |
|
189 | function rebuild(names) {
|
190 | build(root, config, names);
|
191 | }
|
192 | };
|
193 |
|