UNPKG

4.01 kBJavaScriptView Raw
1/**
2 * @module match-tasks
3 * @author Toru Nagashima
4 * @copyright 2015 Toru Nagashima. All rights reserved.
5 * See LICENSE file in root directory for full license.
6 */
7"use strict"
8
9//------------------------------------------------------------------------------
10// Requirements
11//------------------------------------------------------------------------------
12
13const Minimatch = require("minimatch").Minimatch
14
15//------------------------------------------------------------------------------
16// Helpers
17//------------------------------------------------------------------------------
18
19const COLON_OR_SLASH = /[:/]/g
20const CONVERT_MAP = { ":": "/", "/": ":" }
21
22/**
23 * Swaps ":" and "/", in order to use ":" as the separator in minimatch.
24 *
25 * @param {string} s - A text to swap.
26 * @returns {string} The text which was swapped.
27 */
28function swapColonAndSlash(s) {
29 return s.replace(COLON_OR_SLASH, (matched) => CONVERT_MAP[matched])
30}
31
32/**
33 * Creates a filter from user-specified pattern text.
34 *
35 * The task name is the part until the first space.
36 * The rest part is the arguments for this task.
37 *
38 * @param {string} pattern - A pattern to create filter.
39 * @returns {{match: function, task: string, args: string}} The filter object of the pattern.
40 */
41function createFilter(pattern) {
42 const trimmed = pattern.trim()
43 const spacePos = trimmed.indexOf(" ")
44 const task = spacePos < 0 ? trimmed : trimmed.slice(0, spacePos)
45 const args = spacePos < 0 ? "" : trimmed.slice(spacePos)
46 const matcher = new Minimatch(swapColonAndSlash(task))
47 const match = matcher.match.bind(matcher)
48
49 return { match, task, args }
50}
51
52/**
53 * The set to remove overlapped task.
54 */
55class TaskSet {
56 /**
57 * Creates a instance.
58 */
59 constructor() {
60 this.result = []
61 this.sourceMap = Object.create(null)
62 }
63
64 /**
65 * Adds a command (a pattern) into this set if it's not overlapped.
66 * "Overlapped" is meaning that the command was added from a different source.
67 *
68 * @param {string} command - A pattern text to add.
69 * @param {string} source - A task name to check.
70 * @returns {void}
71 */
72 add(command, source) {
73 const sourceList = this.sourceMap[command] || (this.sourceMap[command] = [])
74 if (sourceList.length === 0 || sourceList.indexOf(source) !== -1) {
75 this.result.push(command)
76 }
77 sourceList.push(source)
78 }
79}
80
81//------------------------------------------------------------------------------
82// Public Interface
83//------------------------------------------------------------------------------
84
85/**
86 * Enumerates tasks which matches with given patterns.
87 *
88 * @param {string[]} taskList - A list of actual task names.
89 * @param {string[]} patterns - Pattern texts to match.
90 * @returns {string[]} Tasks which matches with the patterns.
91 * @private
92 */
93module.exports = function matchTasks(taskList, patterns) {
94 const filters = patterns.map(createFilter)
95 const candidates = taskList.map(swapColonAndSlash)
96 const taskSet = new TaskSet()
97 const unknownSet = Object.create(null)
98
99 // Take tasks while keep the order of patterns.
100 for (const filter of filters) {
101 let found = false
102
103 for (const candidate of candidates) {
104 if (filter.match(candidate)) {
105 found = true
106 taskSet.add(
107 swapColonAndSlash(candidate) + filter.args,
108 filter.task
109 )
110 }
111 }
112
113 // Built-in tasks should be allowed.
114 if (!found && (filter.task === "restart" || filter.task === "env")) {
115 taskSet.add(filter.task + filter.args, filter.task)
116 found = true
117 }
118 if (!found) {
119 unknownSet[filter.task] = true
120 }
121 }
122
123 const unknownTasks = Object.keys(unknownSet)
124 if (unknownTasks.length > 0) {
125 throw new Error(`Task not found: "${unknownTasks.join("\", ")}"`)
126 }
127 return taskSet.result
128}