1 | ;
|
2 |
|
3 | var util = require('util');
|
4 | var Emitter = require('component-emitter');
|
5 |
|
6 | var utils = require('./lib/utils');
|
7 | var Task = require('./lib/task');
|
8 | var noop = require('./lib/noop');
|
9 | var map = require('./lib/map-deps');
|
10 | var session = require('./lib/session');
|
11 | var flowFactory = require('./lib/flow');
|
12 |
|
13 | /**
|
14 | * Composer constructor. Create a new Composer
|
15 | *
|
16 | * ```js
|
17 | * var composer = new Composer();
|
18 | * ```
|
19 | *
|
20 | * @api public
|
21 | */
|
22 |
|
23 | function Composer(name) {
|
24 | Emitter.call(this);
|
25 | this.name = name || 'composer';
|
26 | this.tasks = {};
|
27 | }
|
28 |
|
29 | /**
|
30 | * Mix in `Emitter` methods
|
31 | */
|
32 |
|
33 | Emitter(Composer.prototype);
|
34 |
|
35 | /**
|
36 | * Register a new task with it's options and dependencies.
|
37 | *
|
38 | * Options:
|
39 | *
|
40 | * - `deps`: array of dependencies
|
41 | * - `flow`: How this task will be executed with it's dependencies (`series`, `parallel`, `settleSeries`, `settleParallel`)
|
42 | *
|
43 | * ```js
|
44 | * composer.task('site', ['styles'], function () {
|
45 | * return app.src('templates/pages/*.hbs')
|
46 | * .pipe(app.dest('_gh_pages'));
|
47 | * });
|
48 | * ```
|
49 | *
|
50 | * @param {String} `name` Name of the task to register
|
51 | * @param {Object} `options` Options to set dependencies or control flow.
|
52 | * @param {String|Array|Function} `deps` Additional dependencies for this task.
|
53 | * @param {Function} `fn` Final function is the task to register.
|
54 | * @return {Object} Return `this` for chaining
|
55 | * @api public
|
56 | */
|
57 |
|
58 | Composer.prototype.task = function(name/*, options, dependencies and task */) {
|
59 | // return the currently running task
|
60 | // when no name is given
|
61 | if (arguments.length === 0) {
|
62 | return session(this.name).get('task');
|
63 | }
|
64 |
|
65 | var deps = [].concat.apply([], [].slice.call(arguments, 1));
|
66 | var options = {};
|
67 | var fn = noop;
|
68 | if (typeof deps[deps.length-1] === 'function') {
|
69 | fn = deps.pop();
|
70 | }
|
71 |
|
72 | if (deps.length && utils.isobject(deps[0])) {
|
73 | options = deps.shift();
|
74 | }
|
75 |
|
76 | options.deps = deps
|
77 | .concat(options.deps || [])
|
78 | .map(map.bind(this));
|
79 |
|
80 | var task = new Task({
|
81 | name: name,
|
82 | options: options,
|
83 | fn: fn,
|
84 | session: session(this.name),
|
85 | app: this
|
86 | });
|
87 |
|
88 | // bubble up events from tasks
|
89 | task.on('starting', this.emit.bind(this, 'starting'));
|
90 | task.on('finished', this.emit.bind(this, 'finished'));
|
91 | task.on('error', this.emit.bind(this, 'error'));
|
92 |
|
93 | this.tasks[name] = task;
|
94 | return this;
|
95 | };
|
96 |
|
97 | /**
|
98 | * Build a task or list of tasks.
|
99 | *
|
100 | * ```js
|
101 | * composer.build('default', function (err, results) {
|
102 | * if (err) return console.error(err);
|
103 | * console.log(results);
|
104 | * });
|
105 | * ```
|
106 | *
|
107 | * @param {String|Array|Function} `tasks` List of tasks by name, function, or array of names/functions.
|
108 | * @param {Function} `cb` Callback function to be called when all tasks are finished building.
|
109 | * @api public
|
110 | */
|
111 |
|
112 | Composer.prototype.build = function(/* list of tasks/functions to build */) {
|
113 | var args = [].concat.apply([], [].slice.call(arguments));
|
114 | var done = args.pop();
|
115 | if (typeof done !== 'function') {
|
116 | throw new TypeError('Expected the last argument to be a callback function, but got `' + typeof done + '`.');
|
117 | }
|
118 | var fn = this.series.apply(this, args);
|
119 | return fn(done);
|
120 | };
|
121 |
|
122 | /**
|
123 | * Compose task or list of tasks into a single function that runs the tasks in series.
|
124 | *
|
125 | * ```js
|
126 | * composer.task('foo', function (done) {
|
127 | * console.log('this is foo');
|
128 | * done();
|
129 | * });
|
130 | *
|
131 | * var fn = composer.series('foo', function bar(done) {
|
132 | * console.log('this is bar');
|
133 | * done();
|
134 | * });
|
135 | *
|
136 | * fn(function (err) {
|
137 | * if (err) return console.error(err);
|
138 | * console.log('done');
|
139 | * });
|
140 | * //=> this is foo
|
141 | * //=> this is bar
|
142 | * //=> done
|
143 | * ```
|
144 | *
|
145 | * @param {String|Array|Function} `tasks` List of tasks by name, function, or array of names/functions.
|
146 | * @return {Function} Composed function that may take a callback function.
|
147 | * @api public
|
148 | */
|
149 |
|
150 | Composer.prototype.series = flowFactory('series');
|
151 |
|
152 | /**
|
153 | * Compose task or list of tasks into a single function that runs the tasks in parallel.
|
154 | *
|
155 | * ```js
|
156 | * composer.task('foo', function (done) {
|
157 | * setTimeout(function () {
|
158 | * console.log('this is foo');
|
159 | * done();
|
160 | * }, 500);
|
161 | * });
|
162 | *
|
163 | * var fn = composer.parallel('foo', function bar(done) {
|
164 | * console.log('this is bar');
|
165 | * done();
|
166 | * });
|
167 | *
|
168 | * fn(function (err) {
|
169 | * if (err) return console.error(err);
|
170 | * console.log('done');
|
171 | * });
|
172 | * //=> this is bar
|
173 | * //=> this is foo
|
174 | * //=> done
|
175 | * ```
|
176 | *
|
177 | * @param {String|Array|Function} `tasks` List of tasks by name, function, or array of names/functions.
|
178 | * @return {Function} Composed function that may take a callback function.
|
179 | * @api public
|
180 | */
|
181 |
|
182 | Composer.prototype.parallel = flowFactory('parallel');
|
183 |
|
184 |
|
185 | /**
|
186 | * Watch a file, directory, or glob pattern for changes and build a task or list of tasks
|
187 | * when changes are made. Watch is powered by [chokidar][] so the glob pattern may be
|
188 | * anything that [chokidar.watch](https://github.com/paulmillr/chokidar#api) accepts.
|
189 | *
|
190 | * ```js
|
191 | * var watcher = composer.watch('templates/pages/*.hbs', ['site']);
|
192 | * ```
|
193 | *
|
194 | * @param {String|Array} `glob` Filename, Directory name, or glob pattern to watch
|
195 | * @param {Object} `options` Additional options to be passed to [chokidar][]
|
196 | * @param {String|Array|Function} `tasks` Tasks that are passed to `.build` when files in the glob are changed.
|
197 | * @return {Object} Returns an instance of `FSWatcher` from [chokidar][]
|
198 | * @api public
|
199 | */
|
200 |
|
201 | Composer.prototype.watch = function(glob, options/*, fns/tasks */) {
|
202 | var self = this;
|
203 | var len = arguments.length - 1, i = 0;
|
204 | var args = new Array(len + 1);
|
205 | while (len--) args[i] = arguments[++i];
|
206 | args[i] = done;
|
207 |
|
208 | var opts = {};
|
209 | if (typeof options === 'object' && !Array.isArray(options)) {
|
210 | args.shift();
|
211 | opts = utils.extend(opts, options);
|
212 | }
|
213 |
|
214 | var building = true;
|
215 | function done (err) {
|
216 | building = false;
|
217 | if (err) console.error(err);
|
218 | }
|
219 |
|
220 | var watch = utils.chokidar.watch(glob, opts);
|
221 |
|
222 | // only contains our `done` function
|
223 | if (args.length === 1) {
|
224 | return watch;
|
225 | }
|
226 |
|
227 | watch
|
228 | .on('ready', function () {
|
229 | building = false;
|
230 | })
|
231 | .on('all', function () {
|
232 | if (building) return;
|
233 | building = true;
|
234 | self.build.apply(self, args);
|
235 | });
|
236 |
|
237 | return watch;
|
238 | };
|
239 |
|
240 | /**
|
241 | * Expose Composer
|
242 | */
|
243 |
|
244 | module.exports = Composer;
|