版博士V2.0程序
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

217 строки
5.7 KiB

  1. "use strict";
  2. const { parseArgsStringToArgv } = require("string-argv"); // possible alternative: parse-spawn-args
  3. const detectType = require("type-detect");
  4. module.exports = normalizeArgs;
  5. /**
  6. * This function normalizes the arguments of the {@link sync} and {@link async}
  7. * so they can be passed to Node's {@link child_process.spawn} or
  8. * {@link child_process.spawn} functions.
  9. *
  10. * @param {string|string[]} command
  11. * The command to run (e.g. "git"), or the command and its arguments as a string
  12. * (e.g. "git commit -a -m fixed_stuff"), or the command and its arguments as an
  13. * array (e.g. ["git", "commit", "-a", "-m", "fixed stuff"]).
  14. *
  15. * @param {string|string[]} [args]
  16. * The command arguments as a string (e.g. "git commit -a -m fixed_stuff") or as an array
  17. * (e.g. ["git", "commit", "-a", "-m", "fixed stuff"]).
  18. *
  19. * @param {object} [options]
  20. * The same options as {@link child_process.spawn} or {@link child_process.spawnSync}.
  21. *
  22. * @param {function} [callback]
  23. * The callback that will receive the results, if applicable.
  24. *
  25. * @returns {object}
  26. */
  27. function normalizeArgs (params) {
  28. let command, args, options, callback, error;
  29. try {
  30. // Shift the arguments, if necessary
  31. ({ command, args, options, callback } = shiftArgs(params));
  32. let commandArgs = [];
  33. if (typeof command === "string" && args === undefined) {
  34. // The command parameter is actually the command AND arguments,
  35. // so split the string into an array
  36. command = splitArgString(command);
  37. }
  38. if (Array.isArray(command)) {
  39. // Split the command from the arguments
  40. commandArgs = command.slice(1);
  41. command = command[0];
  42. }
  43. if (typeof args === "string") {
  44. // Convert the `args` argument from a string an array
  45. args = splitArgString(args);
  46. }
  47. if (Array.isArray(args)) {
  48. // Add these arguments to any arguments from above
  49. args = commandArgs.concat(args);
  50. }
  51. if (args === undefined || args === null) {
  52. args = commandArgs;
  53. }
  54. if (options === undefined || options === null) {
  55. options = {};
  56. }
  57. // Set default options
  58. options.encoding = options.encoding || "utf8";
  59. // Validate all arguments
  60. validateArgs(command, args, options, callback);
  61. }
  62. catch (err) {
  63. error = err;
  64. // Sanitize args that are used as output
  65. command = String(command || "");
  66. args = (Array.isArray(args) ? args : []).map((arg) => String(arg || ""));
  67. }
  68. return { command, args, options, callback, error };
  69. }
  70. /**
  71. * Detects whether any optional arguments have been omitted,
  72. * and shifts the other arguments as needed.
  73. *
  74. * @param {string|string[]} command
  75. * @param {string|string[]} [args]
  76. * @param {object} [options]
  77. * @param {function} [callback]
  78. * @returns {object}
  79. */
  80. function shiftArgs (params) {
  81. params = Array.prototype.slice.call(params);
  82. let command, args, options, callback;
  83. // Check for a callback as the final parameter
  84. let lastParam = params[params.length - 1];
  85. if (typeof lastParam === "function") {
  86. callback = lastParam;
  87. params.pop();
  88. }
  89. // Check for an options object as the second-to-last parameter
  90. lastParam = params[params.length - 1];
  91. if (lastParam === null || lastParam === undefined ||
  92. (typeof lastParam === "object" && !Array.isArray(lastParam))) {
  93. options = lastParam;
  94. params.pop();
  95. }
  96. // The first parameter is the command
  97. command = params.shift();
  98. // All remaining parameters are the args
  99. if (params.length === 0) {
  100. args = undefined;
  101. }
  102. else if (params.length === 1 && Array.isArray(params[0])) {
  103. args = params[0];
  104. }
  105. else if (params.length === 1 && params[0] === "") {
  106. args = [];
  107. }
  108. else {
  109. args = params;
  110. }
  111. return { command, args, options, callback };
  112. }
  113. /**
  114. * Validates all arguments, and throws an error if any are invalid.
  115. *
  116. * @param {string} command
  117. * @param {string[]} args
  118. * @param {object} options
  119. * @param {function} [callback]
  120. */
  121. function validateArgs (command, args, options, callback) {
  122. if (command === undefined || command === null) {
  123. throw new Error("The command to execute is missing.");
  124. }
  125. if (typeof command !== "string") {
  126. throw new Error("The command to execute should be a string, not " + friendlyType(command));
  127. }
  128. if (!Array.isArray(args)) {
  129. throw new Error(
  130. "The command arguments should be a string or an array, not " +
  131. friendlyType(args)
  132. );
  133. }
  134. for (let i = 0; i < args.length; i++) {
  135. let arg = args[i];
  136. if (typeof arg !== "string") {
  137. throw new Error(
  138. `The command arguments should be strings, but argument #${i + 1} is ` +
  139. friendlyType(arg)
  140. );
  141. }
  142. }
  143. if (typeof options !== "object") {
  144. throw new Error(
  145. "The options should be an object, not " +
  146. friendlyType(options)
  147. );
  148. }
  149. if (callback !== undefined && callback !== null) {
  150. if (typeof callback !== "function") {
  151. throw new Error("The callback should be a function, not " + friendlyType(callback));
  152. }
  153. }
  154. }
  155. /**
  156. * Splits an argument string (e.g. git commit -a -m "fixed stuff")
  157. * into an array (e.g. ["git", "commit", "-a", "-m", "fixed stuff"]).
  158. *
  159. * @param {string} argString
  160. * @returns {string[]}
  161. */
  162. function splitArgString (argString) {
  163. try {
  164. return parseArgsStringToArgv(argString);
  165. }
  166. catch (error) {
  167. throw new Error(`Could not parse the string: ${argString}\n${error.message}`);
  168. }
  169. }
  170. /**
  171. * Returns the friendly type name of the given value, for use in error messages.
  172. *
  173. * @param {*} val
  174. * @returns {string}
  175. */
  176. function friendlyType (val) {
  177. let type = detectType(val);
  178. let firstChar = String(type)[0].toLowerCase();
  179. if (["a", "e", "i", "o", "u"].indexOf(firstChar) === -1) {
  180. return `a ${type}.`;
  181. }
  182. else {
  183. return `an ${type}.`;
  184. }
  185. }