usage.c 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /* Licensed under GPLv3+ - see LICENSE file for details */
  2. #include <ccan/opt/opt.h>
  3. #if HAVE_SYS_TERMIOS_H
  4. #include <sys/ioctl.h>
  5. #include <sys/termios.h> /* Required on Solaris for struct winsize */
  6. #endif
  7. #include <sys/unistd.h> /* Required on Solaris for ioctl */
  8. #include <string.h>
  9. #include <stdlib.h>
  10. #include <stdio.h>
  11. #include <stdint.h>
  12. #include "private.h"
  13. /* We only use this for pointer comparisons. */
  14. const char opt_hidden[1];
  15. #define MIN_DESC_WIDTH 40
  16. #define MIN_TOTAL_WIDTH 50
  17. static unsigned int get_columns(void)
  18. {
  19. int ws_col = 0;
  20. const char *env = getenv("COLUMNS");
  21. if (env)
  22. ws_col = atoi(env);
  23. #ifdef TIOCGWINSZ
  24. if (!ws_col)
  25. {
  26. struct winsize w;
  27. if (ioctl(0, TIOCGWINSZ, &w) != -1)
  28. ws_col = w.ws_col;
  29. }
  30. #endif
  31. if (!ws_col)
  32. ws_col = 80;
  33. return ws_col;
  34. }
  35. /* Return number of chars of words to put on this line.
  36. * Prefix is set to number to skip at start, maxlen is max width, returns
  37. * length (after prefix) to put on this line. */
  38. static size_t consume_words(const char *words, size_t maxlen, size_t *prefix)
  39. {
  40. size_t oldlen, len;
  41. /* Swallow leading whitespace. */
  42. *prefix = strspn(words, " \n");
  43. words += *prefix;
  44. /* Use at least one word, even if it takes us over maxlen. */
  45. oldlen = len = strcspn(words, " ");
  46. while (len <= maxlen) {
  47. oldlen = len;
  48. len += strspn(words+len, " ");
  49. if (words[len] == '\n')
  50. break;
  51. len += strcspn(words+len, " \n");
  52. if (len == oldlen)
  53. break;
  54. }
  55. return oldlen;
  56. }
  57. static char *add_str_len(char *base, size_t *len, size_t *max,
  58. const char *str, size_t slen)
  59. {
  60. if (slen >= *max - *len)
  61. base = opt_alloc.realloc(base, *max = (*max * 2 + slen + 1));
  62. memcpy(base + *len, str, slen);
  63. *len += slen;
  64. return base;
  65. }
  66. static char *add_str(char *base, size_t *len, size_t *max, const char *str)
  67. {
  68. return add_str_len(base, len, max, str, strlen(str));
  69. }
  70. static char *add_indent(char *base, size_t *len, size_t *max, size_t indent)
  71. {
  72. if (indent >= *max - *len)
  73. base = opt_alloc.realloc(base, *max = (*max * 2 + indent + 1));
  74. memset(base + *len, ' ', indent);
  75. *len += indent;
  76. return base;
  77. }
  78. static char *add_desc(char *base, size_t *len, size_t *max,
  79. unsigned int indent, unsigned int width,
  80. const struct opt_table *opt)
  81. {
  82. size_t off, prefix, l;
  83. const char *p;
  84. bool same_line = false;
  85. base = add_str(base, len, max, opt->names);
  86. off = strlen(opt->names);
  87. if (opt->type == OPT_HASARG
  88. && !strchr(opt->names, ' ')
  89. && !strchr(opt->names, '=')) {
  90. base = add_str(base, len, max, " <arg>");
  91. off += strlen(" <arg>");
  92. }
  93. /* Do we start description on next line? */
  94. if (off + 2 > indent) {
  95. base = add_str(base, len, max, "\n");
  96. off = 0;
  97. } else {
  98. base = add_indent(base, len, max, indent - off);
  99. off = indent;
  100. same_line = true;
  101. }
  102. /* Indent description. */
  103. p = opt->desc;
  104. while ((l = consume_words(p, width - indent, &prefix)) != 0) {
  105. if (!same_line)
  106. base = add_indent(base, len, max, indent);
  107. p += prefix;
  108. base = add_str_len(base, len, max, p, l);
  109. base = add_str(base, len, max, "\n");
  110. off = indent + l;
  111. p += l;
  112. same_line = false;
  113. }
  114. /* Empty description? Make it match normal case. */
  115. if (same_line)
  116. base = add_str(base, len, max, "\n");
  117. if (opt->show) {
  118. char buf[OPT_SHOW_LEN + sizeof("...")];
  119. strcpy(buf + OPT_SHOW_LEN, "...");
  120. opt->show(buf, opt->u.arg);
  121. /* If it doesn't fit on this line, indent. */
  122. if (off + strlen(" (default: ") + strlen(buf) + strlen(")")
  123. > width) {
  124. base = add_indent(base, len, max, indent);
  125. } else {
  126. /* Remove \n. */
  127. (*len)--;
  128. }
  129. base = add_str(base, len, max, " (default: ");
  130. base = add_str(base, len, max, buf);
  131. base = add_str(base, len, max, ")\n");
  132. }
  133. return base;
  134. }
  135. char *opt_usage(const char *argv0, const char *extra)
  136. {
  137. unsigned int i;
  138. size_t max, len, width, indent;
  139. char *ret;
  140. width = get_columns();
  141. if (width < MIN_TOTAL_WIDTH)
  142. width = MIN_TOTAL_WIDTH;
  143. /* Figure out longest option. */
  144. indent = 0;
  145. for (i = 0; i < opt_count; i++) {
  146. size_t l;
  147. if (opt_table[i].desc == opt_hidden)
  148. continue;
  149. if (opt_table[i].type == OPT_SUBTABLE)
  150. continue;
  151. l = strlen(opt_table[i].names);
  152. if (opt_table[i].type == OPT_HASARG
  153. && !strchr(opt_table[i].names, ' ')
  154. && !strchr(opt_table[i].names, '='))
  155. l += strlen(" <arg>");
  156. if (l + 2 > indent)
  157. indent = l + 2;
  158. }
  159. /* Now we know how much to indent */
  160. if (indent + MIN_DESC_WIDTH > width)
  161. indent = width - MIN_DESC_WIDTH;
  162. len = max = 0;
  163. ret = NULL;
  164. ret = add_str(ret, &len, &max, "Usage: ");
  165. ret = add_str(ret, &len, &max, argv0);
  166. /* Find usage message from among registered options if necessary. */
  167. if (!extra) {
  168. extra = "";
  169. for (i = 0; i < opt_count; i++) {
  170. if (opt_table[i].cb == (void *)opt_usage_and_exit
  171. && opt_table[i].u.carg) {
  172. extra = opt_table[i].u.carg;
  173. break;
  174. }
  175. }
  176. }
  177. ret = add_str(ret, &len, &max, " ");
  178. ret = add_str(ret, &len, &max, extra);
  179. ret = add_str(ret, &len, &max, "\n");
  180. for (i = 0; i < opt_count; i++) {
  181. if (opt_table[i].desc == opt_hidden)
  182. continue;
  183. if (opt_table[i].type == OPT_SUBTABLE) {
  184. ret = add_str(ret, &len, &max, opt_table[i].desc);
  185. ret = add_str(ret, &len, &max, ":\n");
  186. continue;
  187. }
  188. ret = add_desc(ret, &len, &max, indent, width, &opt_table[i]);
  189. }
  190. ret[len] = '\0';
  191. return ret;
  192. }