Fix handling of one-liner fields.
[ccan] / tools / doc_extract.c
1 /* This merely extracts, doesn't do XML or anything. */
2 #include <err.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6 #include <string.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <stdbool.h>
11 #include "talloc/talloc.h"
12 #include "string/string.h"
13
14 static char **grab_doc(const char *fname)
15 {
16         char *file;
17         char **lines, **ret;
18         unsigned int i, num;
19         bool printing = false, printed = false;
20
21         file = grab_file(NULL, fname, NULL);
22         if (!file)
23                 err(1, "Reading file %s", fname);
24         lines = strsplit(file, file, "\n", &num);
25         ret = talloc_array(NULL, char *, num+1);
26
27         num = 0;
28         for (i = 0; lines[i]; i++) {
29                 if (streq(lines[i], "/**")) {
30                         printing = true;
31                         if (printed++)
32                                 talloc_append_string(ret[num], "\n");
33                 } else if (streq(lines[i], " */")) 
34                         printing = false;
35                 else if (printing) {
36                         if (strstarts(lines[i], " * "))
37                                 ret[num++] = talloc_strdup(ret, lines[i]+3);
38                         else if (strstarts(lines[i], " *"))
39                                 ret[num++] = talloc_strdup(ret, lines[i]+2);
40                         else
41                                 errx(1, "Malformed line %s:%u", fname, i);
42                 }
43         }
44         ret[num] = NULL;
45         talloc_free(file);
46         return ret;
47 }
48
49 static bool is_blank(const char *line)
50 {
51         return line && line[strspn(line, " \t\n")] == '\0';
52 }
53
54 static bool is_section(const char *line, bool maybe_one_liner)
55 {
56         unsigned int len;
57
58         len = strcspn(line, " \t\n:");
59         if (len == 0)
60                 return false;
61
62         if (line[len] != ':')
63                 return false;
64
65         /* If it can be a one-liner, a space is sufficient.*/
66         if (maybe_one_liner && (line[len+1] == ' ' || line[len+1] == '\t'))
67                 return true;
68
69         return line[len] == ':' && is_blank(line+len+1);
70 }
71
72
73 static bool end_section(const char *line)
74 {
75         return !line || is_section(line, true);
76 }
77
78 static unsigned int find_section(char **lines, const char *name,
79                                  bool maybe_one_liner)
80 {
81         unsigned int i;
82
83         for (i = 0; lines[i]; i++) {
84                 if (!is_section(lines[i], maybe_one_liner))
85                         continue;
86                 if (strncasecmp(lines[i], name, strlen(name)) != 0)
87                         continue;
88                 if (lines[i][strlen(name)] == ':')
89                         break;
90         }
91         return i;
92 }
93
94 int main(int argc, char *argv[])
95 {
96         unsigned int i;
97         const char *type;
98
99         if (argc < 3)
100                 errx(1, "Usage: doc_extract TYPE <file>...\n"
101                      "Where TYPE is author|licence|maintainer|summary|description|example|all");
102
103         type = argv[1];
104         for (i = 2; i < argc; i++) {
105                 unsigned int line;
106                 char **lines = grab_doc(argv[i]);
107
108                 if (!lines[0])
109                         errx(1, "No documentation in file");
110
111                 /* Simple one-line fields. */
112                 if (streq(type, "author")
113                     || streq(type, "maintainer")
114                     || streq(type, "licence")) {
115                         line = find_section(lines, type, true);
116                         if (lines[line]) {
117                                 const char *p = strchr(lines[line], ':') + 1;
118                                 p += strspn(p, " \t\n");
119                                 if (p[0] == '\0') {
120                                         /* Must be on next line. */
121                                         if (end_section(lines[line+1]))
122                                                 errx(1, "Malformed %s", type);
123                                         puts(lines[line+1]);
124                                 } else
125                                         puts(p);
126                         }
127                 } else if (streq(type, "summary")) {
128                         /* Summary comes after - on first line. */
129                         char *dash;
130
131                         dash = strchr(lines[0], '-');
132                         if (!dash)
133                                 errx(1, "Malformed first line: no -");
134                         dash += strspn(dash, "- ");
135                         puts(dash);
136                 } else if (streq(type, "description")) {
137                         line = 1;
138                         while (is_blank(lines[line]))
139                                 line++;
140
141                         while (!end_section(lines[line]))
142                                 puts(lines[line++]);
143                 } else if (streq(type, "example")) {
144                         line = find_section(lines, type, false);
145                         if (lines[line]) {
146                                 unsigned int strip;
147                                 line++;
148
149                                 while (is_blank(lines[line]))
150                                         line++;
151
152                                 /* Examples can be indented.  Take cue
153                                  * from first non-blank line. */
154                                 if (lines[line])
155                                         strip = strspn(lines[line], " \t");
156
157                                 while (!end_section(lines[line])) {
158                                         if (strspn(lines[line], " \t") >= strip)
159                                                 puts(lines[line] + strip);
160                                         else
161                                                 puts(lines[line]);
162                                         line++;
163                                 }
164                         }
165                 } else if (streq(type, "all")) {
166                         for (line = 0; lines[line]; line++)
167                                 puts(lines[line]);
168                 } else
169                         errx(1, "Unknown type '%s'", type);
170                         
171                 talloc_free(lines);
172         }
173         return 0;
174 }
175
176                 
177