opt: complete coverage, enhance opt_free_table.
[ccan] / ccan / opt / usage.c
1 /* Licensed under GPLv3+ - see LICENSE file for details */
2 #include <ccan/opt/opt.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <stdint.h>
7 #include "private.h"
8
9 /* We only use this for pointer comparisons. */
10 const char opt_hidden[1];
11
12 static unsigned write_short_options(char *str)
13 {
14         unsigned int i, num = 0;
15         const char *p;
16
17         for (p = first_sopt(&i); p; p = next_sopt(p, &i)) {
18                 if (opt_table[i].desc != opt_hidden)
19                         str[num++] = *p;
20         }
21         return num;
22 }
23
24 #define OPT_SPACE_PAD "                    "
25
26 /* FIXME: Get all purdy. */
27 char *opt_usage(const char *argv0, const char *extra)
28 {
29         unsigned int i, num, len;
30         char *ret, *p;
31
32         if (!extra) {
33                 extra = "";
34                 for (i = 0; i < opt_count; i++) {
35                         if (opt_table[i].cb == (void *)opt_usage_and_exit
36                             && opt_table[i].u.carg) {
37                                 extra = opt_table[i].u.carg;
38                                 break;
39                         }
40                 }
41         }
42
43         /* An overestimate of our length. */
44         len = strlen("Usage: %s ") + strlen(argv0)
45                 + strlen("[-%.*s]") + opt_num_short + 1
46                 + strlen(" ") + strlen(extra)
47                 + strlen("\n");
48
49         for (i = 0; i < opt_count; i++) {
50                 if (opt_table[i].type == OPT_SUBTABLE) {
51                         len += strlen("\n") + strlen(opt_table[i].desc)
52                                 + strlen(":\n");
53                 } else if (opt_table[i].desc != opt_hidden) {
54                         len += strlen(opt_table[i].names) + strlen(" <arg>");
55                         len += strlen(OPT_SPACE_PAD)
56                                 + strlen(opt_table[i].desc) + 1;
57                         if (opt_table[i].show) {
58                                 len += strlen("(default: %s)")
59                                         + OPT_SHOW_LEN + sizeof("...");
60                         }
61                         len += strlen("\n");
62                 }
63         }
64
65         p = ret = malloc(len);
66         p += sprintf(p, "Usage: %s", argv0);
67         p += sprintf(p, " [-");
68         num = write_short_options(p);
69         if (num) {
70                 p += num;
71                 p += sprintf(p, "]");
72         } else {
73                 /* Remove start of single-entry options */
74                 p -= 3;
75         }
76         if (extra)
77                 p += sprintf(p, " %s", extra);
78         p += sprintf(p, "\n");
79
80         for (i = 0; i < opt_count; i++) {
81                 if (opt_table[i].desc == opt_hidden)
82                         continue;
83                 if (opt_table[i].type == OPT_SUBTABLE) {
84                         p += sprintf(p, "%s:\n", opt_table[i].desc);
85                         continue;
86                 }
87                 len = sprintf(p, "%s", opt_table[i].names);
88                 if (opt_table[i].type == OPT_HASARG
89                     && !strchr(opt_table[i].names, ' ')
90                     && !strchr(opt_table[i].names, '='))
91                         len += sprintf(p + len, " <arg>");
92                 len += sprintf(p + len, "%.*s",
93                                len < strlen(OPT_SPACE_PAD)
94                                ? (unsigned)strlen(OPT_SPACE_PAD) - len : 1,
95                                OPT_SPACE_PAD);
96
97                 len += sprintf(p + len, "%s", opt_table[i].desc);
98                 if (opt_table[i].show) {
99                         char buf[OPT_SHOW_LEN + sizeof("...")];
100                         strcpy(buf + OPT_SHOW_LEN, "...");
101                         opt_table[i].show(buf, opt_table[i].u.arg);
102                         len += sprintf(p + len, " (default: %s)", buf);
103                 }
104                 p += len;
105                 p += sprintf(p, "\n");
106         }
107         *p = '\0';
108         return ret;
109 }