提交学习笔记专用
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.

130 lines
3.7 KiB

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