help.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /*
  2. * Copyright (C) 2024 Puter Technologies Inc.
  3. *
  4. * This file is part of Puter's Git client.
  5. *
  6. * Puter's Git client is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. /**
  20. * Throw this from a subcommand's execute() in order to print its usage text to stderr.
  21. * @type {symbol}
  22. */
  23. export const SHOW_USAGE = Symbol('SHOW_USAGE');
  24. /**
  25. * Full manual page for the command.
  26. * @param command
  27. * @returns {string}
  28. */
  29. export const produce_help_string = (command) => {
  30. const { name, usage, description, args } = command;
  31. const options = args?.options;
  32. let s = '';
  33. const indent = ' ';
  34. const heading = (text) => {
  35. s += `\n\x1B[34;1m${text}:\x1B[0m\n`
  36. };
  37. heading('SYNOPSIS');
  38. if (!usage) {
  39. s += `${indent}git ${name}\n`;
  40. } else if (typeof usage === 'string') {
  41. s += `${indent}${usage}\n`;
  42. } else {
  43. let first = true;
  44. for (const usage_line of usage) {
  45. if (first) {
  46. first = false;
  47. s += `${indent}${usage_line}\n`;
  48. } else {
  49. s += `${indent}${usage_line}\n`;
  50. }
  51. }
  52. }
  53. if (description) {
  54. heading('DESCRIPTION');
  55. s += `${indent}${description}\n`;
  56. }
  57. if (typeof options === 'object' && Object.keys(options).length > 0) {
  58. heading('OPTIONS');
  59. // Figure out how long each invocation is, so we can align the descriptions
  60. for (const [name, option] of Object.entries(options)) {
  61. // Invocation
  62. s += indent;
  63. if (option.short)
  64. s += `-${option.short}, `;
  65. s += `--${name}`;
  66. if (option.type !== 'boolean')
  67. s += ` <${option.type}>`;
  68. s += '\n';
  69. // Description
  70. s += `${indent}${indent}${option.description}\n\n`;
  71. }
  72. }
  73. if (!s.endsWith('\n\n'))
  74. s += '\n';
  75. return s;
  76. }
  77. /**
  78. * Usage for the command, which is a short summary.
  79. * @param command
  80. * @returns {string}
  81. */
  82. export const produce_usage_string = (command) => {
  83. const { name, usage, args } = command;
  84. const options = args?.options;
  85. let s = '';
  86. // Usage
  87. if (!usage) {
  88. s += `usage: git ${name}\n`;
  89. } else if (typeof usage === 'string') {
  90. s += `usage: ${usage}\n`;
  91. } else {
  92. let first = true;
  93. for (const usage_line of usage) {
  94. if (first) {
  95. first = false;
  96. s += `usage: ${usage_line}\n`;
  97. } else {
  98. s += ` or: ${usage_line}\n`;
  99. }
  100. }
  101. }
  102. // List of options
  103. if (typeof options === 'object' && Object.keys(options).length > 0) {
  104. // Figure out how long each invocation is, so we can align the descriptions
  105. const option_strings = Object.entries(options).map(([name, option]) => {
  106. let invocation = '';
  107. if (option.short)
  108. invocation += `-${option.short}, `;
  109. invocation += `--${name}`;
  110. if (option.type !== 'boolean')
  111. invocation += ` <${option.type}>`;
  112. return [invocation, option.description];
  113. });
  114. const indent_size = 2 + option_strings.reduce(
  115. (max_length, option) => Math.max(max_length, option[0].length), 0);
  116. s += '\n';
  117. for (const [invocation, description] of option_strings) {
  118. s += ` ${invocation}`;
  119. if (indent_size - invocation.length > 0)
  120. s += ' '.repeat(indent_size - invocation.length);
  121. s += `${description}\n`;
  122. }
  123. }
  124. return s;
  125. }