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 |
|
8 |
|
9 | //------------------------------------------------------------------------------
|
10 | // Requirements
|
11 | //------------------------------------------------------------------------------
|
12 |
|
13 | const Minimatch = require("minimatch").Minimatch
|
14 |
|
15 | //------------------------------------------------------------------------------
|
16 | // Helpers
|
17 | //------------------------------------------------------------------------------
|
18 |
|
19 | const COLON_OR_SLASH = /[:/]/g
|
20 | const 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 | */
|
28 | function 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 | */
|
41 | function 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 | */
|
55 | class 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 | */
|
93 | module.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 | }
|