apiUtils.mjs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /**
  2. * Wrap operations for consumption in Node.
  3. *
  4. * @author d98762625 [d98762625@gmail.com]
  5. * @copyright Crown Copyright 2018
  6. * @license Apache-2.0
  7. */
  8. import SyncDish from "./SyncDish";
  9. /**
  10. * Extract default arg value from operation argument
  11. * @param {Object} arg - an arg from an operation
  12. */
  13. function extractArg(arg) {
  14. if (arg.type === "option" || arg.type === "editableOption") {
  15. // pick default option if not already chosen
  16. return typeof arg.value === "string" ? arg.value : arg.value[0];
  17. }
  18. if (arg.type === "toggleString") {
  19. // ensure string and option exist when user hasn't defined
  20. arg.string = arg.string || "";
  21. arg.option = arg.option || arg.toggleValues[0];
  22. return arg;
  23. }
  24. return arg.value;
  25. }
  26. /**
  27. * transformArgs
  28. *
  29. * Take the default args array and update with any user-defined
  30. * operation arguments. Allows user to define arguments in object style,
  31. * with accommodating name matching. Using named args in the API is more
  32. * clear to the user.
  33. *
  34. * Argument name matching is case and space insensitive
  35. * @private
  36. * @param {Object[]} originalArgs
  37. * @param {Object} newArgs
  38. */
  39. function transformArgs(originalArgs, newArgs) {
  40. const allArgs = Object.assign([], originalArgs);
  41. if (newArgs) {
  42. Object.keys(newArgs).map((key) => {
  43. const index = allArgs.findIndex((arg) => {
  44. return arg.name.toLowerCase().replace(/ /g, "") ===
  45. key.toLowerCase().replace(/ /g, "");
  46. });
  47. if (index > -1) {
  48. const argument = allArgs[index];
  49. if (["toggleString"].indexOf(argument.type) > -1) {
  50. argument.string = newArgs[key].string;
  51. argument.option = newArgs[key].option;
  52. } else if (argument.type === "editableOption") {
  53. // takes key: "option", key: {name, val: "string"}, key: {name, val: [...]}
  54. argument.value = typeof newArgs[key] === "string" ? newArgs[key]: newArgs[key].value;
  55. } else {
  56. argument.value = newArgs[key];
  57. }
  58. }
  59. });
  60. }
  61. return allArgs.map(extractArg);
  62. }
  63. /**
  64. * Wrap an operation to be consumed by node API.
  65. * new Operation().run() becomes operation()
  66. * Perform type conversion on input
  67. * @private
  68. * @param {Operation} Operation
  69. * @returns {Function} The operation's run function, wrapped in
  70. * some type conversion logic
  71. */
  72. export function wrap(OpClass) {
  73. /**
  74. * Wrapped operation run function
  75. * @param {*} input
  76. * @param {Object[]} args
  77. * @returns {SyncDish} operation's output, on a Dish.
  78. * @throws {OperationError} if the operation throws one.
  79. */
  80. return (input, args=null) => {
  81. const operation = new OpClass();
  82. let dish;
  83. if (input instanceof SyncDish) {
  84. dish = input;
  85. } else {
  86. dish = new SyncDish();
  87. const type = SyncDish.typeEnum(input.constructor.name);
  88. dish.set(input, type);
  89. }
  90. args = transformArgs(operation.args, args);
  91. const transformedInput = dish.get(operation.inputType);
  92. const result = operation.run(transformedInput, args);
  93. return new SyncDish({
  94. value: result,
  95. type: operation.outputType
  96. });
  97. };
  98. }
  99. /**
  100. * SomeName => someName
  101. * @param {String} name - string to be altered
  102. * @returns {String} decapitalised
  103. */
  104. export function decapitalise(name) {
  105. // Don't decapitalise names that start with 2+ caps
  106. if (/^[A-Z0-9]{2,}/g.test(name)) {
  107. return name;
  108. }
  109. // reserved. Don't change for now.
  110. if (name === "Return") {
  111. return name;
  112. }
  113. return `${name.charAt(0).toLowerCase()}${name.substr(1)}`;
  114. }
  115. /**
  116. * Extract properties from an operation by instantiating it and
  117. * returning some of its properties for reference.
  118. * @param {Operation} Operation - the operation to extract info from
  119. * @returns {Object} operation properties
  120. */
  121. function extractOperationInfo(Operation) {
  122. const operation = new Operation();
  123. return {
  124. name: decapitalise(operation.name).replace(/ /g, ""),
  125. module: operation.module,
  126. description: operation.description,
  127. inputType: operation.inputType,
  128. outputType: operation.outputType,
  129. // Make arg names lowercase, no spaces to encourage non-sentence
  130. // caps in repl
  131. args: Object.assign([], operation.args).map((s) => {
  132. s.name = decapitalise(s.name).replace(/ /g, "");
  133. return s;
  134. })
  135. };
  136. }
  137. /**
  138. * @namespace Api
  139. * @param {Operation[]} operations - an object filled with operations.
  140. * @param {String} searchTerm - the name of the operation to get help for.
  141. * Case and whitespace are ignored in search.
  142. * @returns {Function} taking search term and outputting description.
  143. */
  144. export function help(operations) {
  145. return function(searchTerm) {
  146. if (typeof searchTerm === "string") {
  147. const operation = operations
  148. .find(o => o.name.toLowerCase() === searchTerm.replace(/ /g, "").toLowerCase());
  149. if (operation) {
  150. return extractOperationInfo(operation);
  151. }
  152. return null;
  153. }
  154. return null;
  155. };
  156. }