Skip to content

Commit

Permalink
Add custom priority once option
Browse files Browse the repository at this point in the history
Allows to create custom priorities that executes an task only once
(based on
the method name).
Useful for inheritance and composability.
  • Loading branch information
mshima committed Jan 30, 2020
1 parent 006e13e commit 0910c63
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 59 deletions.
160 changes: 115 additions & 45 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const Storage = require('./util/storage');
const promptSuggestion = require('./util/prompt-suggestion');

const EMPTY = '@@_YEOMAN_EMPTY_MARKER_@@';
const DEFAULT_QUEUE = 'default';
const REJECT = () => {};
const debug = createDebug('yeoman:generator');

// Ensure a prototype method is a candidate run by default
Expand Down Expand Up @@ -187,7 +189,7 @@ class Generator extends EventEmitter {

// Add original queues.
Generator.queues.forEach(queue => {
this._queues[queue] = queue;
this._queues[queue] = { name: queue, queueName: queue };
});

// Add custom queues
Expand All @@ -213,11 +215,12 @@ class Generator extends EventEmitter {

// Add queue to runLoop
customPriorities.forEach(customQueue => {
const queueName = `${this.options.namespace}#${customQueue.name}`;
debug(`Registering custom queue ${queueName}`);
this._queues[customQueue.name] = queueName;
customQueue.queueName =
customQueue.queueName || `${this.options.namespace}#${customQueue.name}`;
debug(`Registering custom queue ${customQueue.queueName}`);
this._queues[customQueue.name] = customQueue;

if (this.env.runLoop.queueNames.includes(queueName)) {
if (this.env.runLoop.queueNames.includes(customQueue.queueName)) {
return;
}

Expand Down Expand Up @@ -257,7 +260,10 @@ class Generator extends EventEmitter {
};
}

this.env.runLoop.addSubQueue(queueName, this._queues[customQueue.before]);
let beforeQueue = customQueue.before
? this._queues[customQueue.before].queueName
: undefined;
this.env.runLoop.addSubQueue(customQueue.queueName, beforeQueue);
});
}
}
Expand Down Expand Up @@ -501,66 +507,110 @@ class Generator extends EventEmitter {
* Schedule methods on a run queue.
*
* @param {Function|Object} method: Method to be scheduled or object with function properties.
* @param {String} [methodName]: Name of the method to be scheduled.
* @param {String} [queueName]: Name of the queue to be scheduled on.
* @param {String} [reject]: Reject callback.
* @param {String|Object} [methodName]: Name of the method (task) to be scheduled or queueOptions.
* @param {String} [methodName.taskName]: Name of the task to be scheduled.
* @param {String} [methodName.queueName]: Name of the queue to be scheduled on.
* @param {String} [methodName.reject]: Reject callback.
* @param {String} [queueName]: Name of the queue to be scheduled on.
* @param {Function} [reject]: Reject callback.
*/
queueMethod(method, methodName, queueName, reject = () => {}) {
queueMethod(method, methodName, queueName, reject = REJECT) {
if (typeof queueName === 'function') {
reject = queueName;
queueName = 'default';
queueName = DEFAULT_QUEUE;
} else {
queueName = queueName || 'default';
queueName = queueName || DEFAULT_QUEUE;
}

const self = this;
let queueOptions;
if (!_.isFunction(method)) {
if (typeof methodName === 'function') {
reject = methodName;
queueOptions = {
queueName: DEFAULT_QUEUE,
reject: methodName
};
} else if (typeof methodName === 'object') {
queueOptions = methodName;
methodName = undefined;
queueOptions.queueName = queueOptions.queueName || DEFAULT_QUEUE;
queueOptions.reject = queueOptions.reject || REJECT;
} else {
queueOptions = {
queueName: methodName,
reject
};
}

queueName = methodName || queueName;
// Run each queue items
_.each(method, (newMethod, newMethodName) => {
if (!_.isFunction(newMethod) || !methodIsValid(newMethodName)) return;

self.queueMethod(newMethod, newMethodName, queueName, reject);
self.queueMethod(newMethod, {
...queueOptions,
taskName: newMethodName
});
});
return;
}

if (typeof methodName === 'object') {
queueOptions = methodName;
methodName = undefined;
queueOptions.queueName = queueOptions.queueName || DEFAULT_QUEUE;
queueOptions.reject = queueOptions.reject || REJECT;
} else {
queueOptions = {
taskName: methodName,
queueName,
reject
};
}

let namespace = '';
if (self.options && self.options.namespace) {
namespace = self.options.namespace;
}

debug(`Queueing ${namespace}#${methodName} in ${queueName}`);
self.env.runLoop.add(queueName, completed => {
debug(`Running ${namespace}#${methodName}`);
self.emit(`method:${methodName}`);

runAsync(function() {
self.async = () => this.async();
self.runningState = { namespace, queueName, methodName };
return method.apply(self, self.args);
})()
.then(function() {
delete self.runningState;
completed();
})
.catch(err => {
debug(`An error occured while running ${namespace}#${methodName}`, err);
delete self.runningState;

// Ensure we emit the error event outside the promise context so it won't be
// swallowed when there's no listeners.
setImmediate(() => {
self.emit('error', err);
reject(err);
const once = queueOptions.once ? queueOptions.taskName : undefined;

debug(`Queueing ${namespace}#${queueOptions.taskName} with options %o`, queueOptions);
self.env.runLoop.add(
queueOptions.queueName,
completed => {
debug(`Running ${namespace}#${queueOptions.taskName}`);
self.emit(`method:${queueOptions.taskName}`);

runAsync(function() {
self.async = () => this.async();
self.runningState = {
namespace,
queueName: queueOptions.queueName,
methodName: queueOptions.taskName
};
return method.apply(self, self.args);
})()
.then(function() {
delete self.runningState;
completed();
})
.catch(err => {
debug(
`An error occured while running ${namespace}#${queueOptions.taskName}`,
err
);
delete self.runningState;

// Ensure we emit the error event outside the promise context so it won't be
// swallowed when there's no listeners.
setImmediate(() => {
self.emit('error', err);
queueOptions.reject(err);
});
});
});
});
},
{ once, run: queueOptions.run }
);
}

/**
Expand Down Expand Up @@ -606,19 +656,34 @@ class Generator extends EventEmitter {
);
const item = property.value ? property.value : property.get.call(self);

const queueName = self._queues[name];
const customPriority = self._queues[name];
let queueOptions = { reject };
if (!customPriority) {
queueOptions.queueName = DEFAULT_QUEUE;
} else if (typeof customPriority === 'string') {
queueOptions.queueName = customPriority;
} else {
queueOptions = {
reject,
queueName: customPriority.queueName,
run: customPriority.run,
once: customPriority.once
};
}

debug(queueOptions);
// Name points to a function; run it!
if (typeof item === 'function') {
return self.queueMethod(item, name, queueName, reject);
queueOptions.taskName = name;
return self.queueMethod(item, queueOptions);
}

// Not a queue hash; stop
if (!queueName) {
if (!customPriority) {
return;
}

self.queueMethod(item, queueName, reject);
self.queueMethod(item, queueOptions);
}

validMethods.forEach(addInQueue);
Expand Down Expand Up @@ -680,7 +745,12 @@ class Generator extends EventEmitter {
}

const instantiate = (Generator, path) => {
Generator.resolved = require.resolve(path);
if (path === 'unknown') {
Generator.resolved = path;
} else {
Generator.resolved = require.resolve(path);
}

Generator.namespace = this.env.namespace(path);

return this.env.instantiate(Generator, {
Expand Down
Loading

0 comments on commit 0910c63

Please sign in to comment.