You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
3.8 KiB
129 lines
3.8 KiB
/** |
|
* @module match-tasks |
|
* @author Toru Nagashima |
|
* @copyright 2015 Toru Nagashima. All rights reserved. |
|
* See LICENSE file in root directory for full license. |
|
*/ |
|
'use strict' |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Requirements |
|
// ------------------------------------------------------------------------------ |
|
|
|
const { minimatch } = require('minimatch') |
|
const Minimatch = minimatch.Minimatch |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Helpers |
|
// ------------------------------------------------------------------------------ |
|
|
|
const COLON_OR_SLASH = /[:/]/g |
|
const CONVERT_MAP = { ':': '/', '/': ':' } |
|
|
|
/** |
|
* Swaps ":" and "/", in order to use ":" as the separator in minimatch. |
|
* |
|
* @param {string} s - A text to swap. |
|
* @returns {string} The text which was swapped. |
|
*/ |
|
function swapColonAndSlash (s) { |
|
return s.replace(COLON_OR_SLASH, (matched) => CONVERT_MAP[matched]) |
|
} |
|
|
|
/** |
|
* Creates a filter from user-specified pattern text. |
|
* |
|
* The task name is the part until the first space. |
|
* The rest part is the arguments for this task. |
|
* |
|
* @param {string} pattern - A pattern to create filter. |
|
* @returns {{match: function, task: string, args: string}} The filter object of the pattern. |
|
*/ |
|
function createFilter (pattern) { |
|
const trimmed = pattern.trim() |
|
const spacePos = trimmed.indexOf(' ') |
|
const task = spacePos < 0 ? trimmed : trimmed.slice(0, spacePos) |
|
const args = spacePos < 0 ? '' : trimmed.slice(spacePos) |
|
const matcher = new Minimatch(swapColonAndSlash(task), { nonegate: true }) |
|
const match = matcher.match.bind(matcher) |
|
|
|
return { match, task, args } |
|
} |
|
|
|
/** |
|
* The set to remove overlapped task. |
|
*/ |
|
class TaskSet { |
|
/** |
|
* Creates a instance. |
|
*/ |
|
constructor () { |
|
this.result = [] |
|
this.sourceMap = Object.create(null) |
|
} |
|
|
|
/** |
|
* Adds a command (a pattern) into this set if it's not overlapped. |
|
* "Overlapped" is meaning that the command was added from a different source. |
|
* |
|
* @param {string} command - A pattern text to add. |
|
* @param {string} source - A task name to check. |
|
* @returns {void} |
|
*/ |
|
add (command, source) { |
|
const sourceList = this.sourceMap[command] || (this.sourceMap[command] = []) |
|
if (sourceList.length === 0 || sourceList.indexOf(source) !== -1) { |
|
this.result.push(command) |
|
} |
|
sourceList.push(source) |
|
} |
|
} |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Public Interface |
|
// ------------------------------------------------------------------------------ |
|
|
|
/** |
|
* Enumerates tasks which matches with given patterns. |
|
* |
|
* @param {string[]} taskList - A list of actual task names. |
|
* @param {string[]} patterns - Pattern texts to match. |
|
* @returns {string[]} Tasks which matches with the patterns. |
|
* @private |
|
*/ |
|
module.exports = function matchTasks (taskList, patterns) { |
|
const filters = patterns.map(createFilter) |
|
const candidates = taskList.map(swapColonAndSlash) |
|
const taskSet = new TaskSet() |
|
const unknownSet = Object.create(null) |
|
|
|
// Take tasks while keep the order of patterns. |
|
for (const filter of filters) { |
|
let found = false |
|
|
|
for (const candidate of candidates) { |
|
if (filter.match(candidate)) { |
|
found = true |
|
taskSet.add( |
|
swapColonAndSlash(candidate) + filter.args, |
|
filter.task |
|
) |
|
} |
|
} |
|
|
|
// Built-in tasks should be allowed. |
|
if (!found && (filter.task === 'restart' || filter.task === 'env')) { |
|
taskSet.add(filter.task + filter.args, filter.task) |
|
found = true |
|
} |
|
if (!found) { |
|
unknownSet[filter.task] = true |
|
} |
|
} |
|
|
|
const unknownTasks = Object.keys(unknownSet) |
|
if (unknownTasks.length > 0) { |
|
throw new Error(`Task not found: "${unknownTasks.join('", ')}"`) |
|
} |
|
return taskSet.result |
|
}
|
|
|