1 /*
2 * Copyright (C) 2018-2021 Red Hat, Inc. All rights reserved.
3 *
4 * Author: Christine Caulfield <ccaulfie@redhat.com>
5 *
6 * This software licensed under GPL-2.0+
7 */
8
9
10 /*
11 * NOTE: this code is very rough, it does the bare minimum to parse the
12 * XML out from doxygen and is probably very fragile to changes in that XML
13 * schema. It probably leaks memory all over the place too.
14 *
15 * In its favour, it *does* generate nice man pages and should only be run very ocasionally
16 */
17
18 #define _DEFAULT_SOURCE
19 #define _BSD_SOURCE
20 #define _GNU_SOURCE
21 #define _XOPEN_SOURCE
22 #define _XOPEN_SOURCE_EXTENDED
23 #define _XPG4_2
24 #define _XPG7
25 #include <stdlib.h>
26 #include <sys/time.h>
27 #include <sys/stat.h>
28 #include <time.h>
29 #include <stdio.h>
30 #include <limits.h>
31 #include <string.h>
32 #include <getopt.h>
33 #include <errno.h>
34 #include <ctype.h>
35 #include <libxml/tree.h>
36 #include <libxml/parser.h>
37 #include <qb/qblist.h>
38 #include <qb/qbmap.h>
39 #include "cstring.h"
40
41 /*
42 * This isn't a maximum size, it just defines how long a parameter
43 * type can get before we decide it's not worth lining everything up.
44 * It's mainly to stop function pointer types (which can get VERY long because
45 * of all *their* parameters) making everything else 'line-up' over separate lines
46 */
47 #define LINE_LENGTH 80
48
49 /* Similar - but for structure member comments */
50 #define STRUCT_COMMENT_LENGTH 50
51
52 static int print_ascii = 1;
53 static int print_man = 0;
54 static int print_params = 0;
55 static int print_general = 0;
56 static int num_functions = 0;
57 static int quiet = 0;
58 static int use_header_copyright = 0;
59 static const char *man_section="3";
60 static const char *package_name="Package";
61 static const char *header="Programmer's Manual";
62 static const char *company="Red Hat";
63 static const char *output_dir="./";
64 static const char *xml_dir = "./xml/";
65 static const char *xml_file;
66 static const char *manpage_date = NULL;
67 static const char *headerfile = NULL;
68 static const char *header_prefix = "";
69 static const char *header_src_dir = "./";
70 static char header_copyright[256] = "\0";
71 static long manpage_year = LONG_MIN;
72 static long start_year = 2010;
73 static struct qb_list_head params_list;
74 static struct qb_list_head retval_list;
75 static qb_map_t *function_map;
76 static qb_map_t *structures_map;
77 static qb_map_t *used_structures_map;
78
79 struct param_info {
80 char *paramname;
81 char *paramtype;
82 char *paramdesc;
83 struct param_info *next;
84 struct qb_list_head list;
85 };
86
87 struct struct_info {
88 enum {STRUCTINFO_STRUCT, STRUCTINFO_ENUM} kind;
89 char *structname;
90 char *description;
91 char *brief_description;
92 struct qb_list_head params_list; /* our params */
93 struct qb_list_head list;
94 };
95
96 static cstring_t get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext, int add_nl);
97 static void traverse_node(xmlNode *parentnode, const char *leafname, void (do_members(xmlNode*, void*)), void *arg);
98 static cstring_t get_text(xmlNode *cur_node, char **returntext, char **notetext);
99 static void man_print_long_string(FILE *manfile, char *text);
100
101 static void free_paraminfo(struct param_info *pi)
102 {
103 free(pi->paramname);
104 free(pi->paramtype);
105 free(pi->paramdesc);
106 free(pi);
107 }
108
109 static char *get_attr(xmlNode *node, const char *tag)
110 {
111 xmlAttr *this_attr;
112
|
(1) Event cond_true: |
Condition "this_attr", taking true branch. |
113 for (this_attr = node->properties; this_attr; this_attr = this_attr->next) {
|
(2) Event cond_true: |
Condition "this_attr->type == XML_ATTRIBUTE_NODE", taking true branch. |
|
(3) Event cond_true: |
Condition "strcmp((char *)this_attr->name, tag) == 0", taking true branch. |
114 if (this_attr->type == XML_ATTRIBUTE_NODE && strcmp((char *)this_attr->name, tag) == 0) {
|
(4) Event alloc_fn: |
Storage is returned from allocation function "strdup". |
|
(5) Event return_alloc_fn: |
Directly returning storage allocated by "strdup". |
115 return strdup((char *)this_attr->children->content);
116 }
117 }
118 return NULL;
119 }
120
121 static cstring_t get_child(xmlNode *node, const char *tag)
122 {
123 xmlNode *this_node;
124 xmlNode *child;
125 cstring_t buffer = cstring_alloc();
126 char *refid = NULL;
127 char *declname = NULL;
128
|
(1) Event cond_true: |
Condition "this_node", taking true branch. |
|
(24) Event loop_begin: |
Jumped back to beginning of loop. |
|
(25) Event cond_true: |
Condition "this_node", taking true branch. |
|
(41) Event loop_begin: |
Jumped back to beginning of loop. |
|
(42) Event cond_true: |
Condition "this_node", taking true branch. |
|
(60) Event loop_begin: |
Jumped back to beginning of loop. |
|
(61) Event cond_false: |
Condition "this_node", taking false branch. |
129 for (this_node = node->children; this_node; this_node = this_node->next) {
|
(2) Event cond_true: |
Condition "strcmp((char *)this_node->name, "declname") == 0", taking true branch. |
|
(26) Event cond_true: |
Condition "strcmp((char *)this_node->name, "declname") == 0", taking true branch. |
|
(43) Event cond_true: |
Condition "strcmp((char *)this_node->name, "declname") == 0", taking true branch. |
130 if ((strcmp( (char*)this_node->name, "declname") == 0)) {
131 declname = strdup((char*)this_node->children->content);
132 }
133
|
(3) Event cond_true: |
Condition "this_node->type == XML_ELEMENT_NODE", taking true branch. |
|
(4) Event cond_true: |
Condition "this_node->children", taking true branch. |
|
(5) Event cond_true: |
Condition "strcmp((char *)this_node->name, tag) == 0", taking true branch. |
|
(27) Event cond_true: |
Condition "this_node->type == XML_ELEMENT_NODE", taking true branch. |
|
(28) Event cond_true: |
Condition "this_node->children", taking true branch. |
|
(29) Event cond_true: |
Condition "strcmp((char *)this_node->name, tag) == 0", taking true branch. |
|
(44) Event cond_true: |
Condition "this_node->type == XML_ELEMENT_NODE", taking true branch. |
|
(45) Event cond_true: |
Condition "this_node->children", taking true branch. |
|
(46) Event cond_true: |
Condition "strcmp((char *)this_node->name, tag) == 0", taking true branch. |
134 if ((this_node->type == XML_ELEMENT_NODE && this_node->children) && ((strcmp((char *)this_node->name, tag) == 0))) {
135 refid = NULL;
|
(6) Event cond_true: |
Condition "child", taking true branch. |
|
(11) Event loop_begin: |
Jumped back to beginning of loop. |
|
(12) Event cond_true: |
Condition "child", taking true branch. |
|
(17) Event loop_begin: |
Jumped back to beginning of loop. |
|
(18) Event cond_false: |
Condition "child", taking false branch. |
|
(30) Event cond_true: |
Condition "child", taking true branch. |
|
(35) Event loop_begin: |
Jumped back to beginning of loop. |
|
(36) Event cond_false: |
Condition "child", taking false branch. |
|
(47) Event cond_true: |
Condition "child", taking true branch. |
|
(54) Event loop_begin: |
Jumped back to beginning of loop. |
|
(55) Event cond_false: |
Condition "child", taking false branch. |
136 for (child = this_node->children; child; child = child->next) {
|
(7) Event cond_true: |
Condition "child->content", taking true branch. |
|
(13) Event cond_true: |
Condition "child->content", taking true branch. |
|
(31) Event cond_true: |
Condition "child->content", taking true branch. |
|
(48) Event cond_true: |
Condition "child->content", taking true branch. |
137 if (child->content) {
138 buffer = cstring_append_chars(buffer, (char *)child->content);
139 }
140
|
(8) Event cond_true: |
Condition "strcmp((char *)child->name, "ref") == 0", taking true branch. |
|
(14) Event cond_true: |
Condition "strcmp((char *)child->name, "ref") == 0", taking true branch. |
|
(32) Event cond_true: |
Condition "strcmp((char *)child->name, "ref") == 0", taking true branch. |
|
(49) Event cond_true: |
Condition "strcmp((char *)child->name, "ref") == 0", taking true branch. |
141 if ((strcmp( (char*)child->name, "ref") == 0)) {
|
(9) Event cond_true: |
Condition "child->children->content", taking true branch. |
|
(15) Event cond_true: |
Condition "child->children->content", taking true branch. |
|
(33) Event cond_true: |
Condition "child->children->content", taking true branch. |
|
(50) Event cond_true: |
Condition "child->children->content", taking true branch. |
142 if (child->children->content) {
143 buffer = cstring_append_chars(buffer, (char *)child->children->content);
144 }
|
(51) Event alloc_fn: |
Storage is returned from allocation function "get_attr". [details] |
|
(52) Event var_assign: |
Assigning: "refid" = storage returned from "get_attr(child, "refid")". |
| Also see events: |
[leaked_storage] |
145 refid = get_attr(child, "refid");
146 }
|
(10) Event loop: |
Jumping back to the beginning of the loop. |
|
(16) Event loop: |
Jumping back to the beginning of the loop. |
|
(19) Event loop_end: |
Reached end of loop. |
|
(34) Event loop: |
Jumping back to the beginning of the loop. |
|
(37) Event loop_end: |
Reached end of loop. |
|
(53) Event loop: |
Jumping back to the beginning of the loop. |
|
(56) Event loop_end: |
Reached end of loop. |
147 }
148 }
|
(20) Event cond_true: |
Condition "declname", taking true branch. |
|
(21) Event cond_false: |
Condition "refid", taking false branch. |
|
(38) Event cond_true: |
Condition "declname", taking true branch. |
|
(39) Event cond_true: |
Condition "refid", taking true branch. |
|
(57) Event cond_false: |
Condition "declname", taking false branch. |
149 if (declname && refid) {
150 qb_map_put(used_structures_map, refid, declname);
|
(22) Event if_end: |
End of if statement. |
|
(58) Event if_end: |
End of if statement. |
151 }
|
(23) Event loop: |
Jumping back to the beginning of the loop. |
|
(40) Event loop: |
Jumping back to the beginning of the loop. |
|
(59) Event loop: |
Jumping back to the beginning of the loop. |
|
(62) Event loop_end: |
Reached end of loop. |
152 }
|
(63) Event leaked_storage: |
Variable "refid" going out of scope leaks the storage it points to. |
| Also see events: |
[alloc_fn][var_assign] |
153 return buffer;
154 }
155
156 static struct param_info *find_param_by_name(struct qb_list_head *list, const char *name)
157 {
158 struct qb_list_head *iter;
159 struct param_info *pi;
160
161 qb_list_for_each(iter, list) {
162 pi = qb_list_entry(iter, struct param_info, list);
163 if (strcmp(pi->paramname, name) == 0) {
164 return pi;
165 }
166 }
167 return NULL;
168 }
169
170 static int not_all_whitespace(char *string)
171 {
172 unsigned int i;
173
174 for (i=0; i<strlen(string); i++) {
175 if (string[i] != ' ' &&
176 string[i] != '\n' &&
177 string[i] != '\r' &&
178 string[i] != '\t')
179 return 1;
180 }
181 return 0;
182 }
183
184 static void get_param_info(xmlNode *cur_node, struct qb_list_head *list)
185 {
186 xmlNode *this_tag;
187 xmlNode *sub_tag;
188 char *paramname = NULL;
189 char *paramdesc = NULL;
190 struct param_info *pi;
191
192 /* This is not robust, and very inflexible */
193 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
194 for (sub_tag = this_tag->children; sub_tag; sub_tag = sub_tag->next) {
195 if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "parameternamelist") == 0 &&
196 sub_tag->children->next->children) {
197 paramname = (char*)sub_tag->children->next->children->content;
198 }
199 if (sub_tag->type == XML_ELEMENT_NODE &&
200 strcmp((char *)sub_tag->name, "parameterdescription") == 0 &&
201 paramname && sub_tag->children->next->children) {
202 cstring_t paramdesc_c = get_text(sub_tag->children->next, NULL, NULL);
203 paramdesc = cstring_to_chars(paramdesc_c);
204 free(paramdesc_c);
205
206 /* Add text to the param_map */
207 pi = find_param_by_name(list, paramname);
208 if (pi) {
209 pi->paramdesc = paramdesc;
210 }
211 else {
212 pi = malloc(sizeof(struct param_info));
213 if (pi) {
214 pi->paramname = paramname;
215 pi->paramdesc = paramdesc;
216 pi->paramtype = NULL; /* it's a retval */
217 qb_list_add_tail(&pi->list, list);
218 }
219 }
220 }
221 }
222 }
223 }
224
225 static cstring_t get_codeline(xmlNode *this_tag)
226 {
227 cstring_t buffer = cstring_alloc();
228 xmlNode *sub_tag;
229
230 for (sub_tag = this_tag; sub_tag; sub_tag = sub_tag->next) {
231 if (strcmp((char*)sub_tag->name, "sp") == 0) {
232 buffer = cstring_append_chars(buffer, " ");
233 }
234 if (strcmp((char*)sub_tag->name, "text") == 0) {
235 // If the line starts with a dot then escape the first one to
236 // stop nroff thinking it's a macro
237 char *tmp = (char*)sub_tag->content;
238 if (tmp[0] == '.') {
239 buffer = cstring_append_chars(buffer, (char*)"\\[char46]");
240 tmp += 1;
241 }
242 buffer = cstring_append_chars(buffer, tmp);
243 }
244 if (strcmp((char*)sub_tag->name, "ref") == 0) {
245 // Handled by the child recusion below
246 }
247 if (sub_tag->children) {
248 char *tmp = get_codeline(sub_tag->children);
249 buffer = cstring_append_cstring(buffer, tmp);
250 cstring_free(tmp);
251 }
252 }
253 return buffer;
254 }
255
256 static cstring_t get_codetree(xmlNode *cur_node)
257 {
258 xmlNode *this_tag;
259 cstring_t buffer = cstring_alloc();
260 cstring_t tmp;
261
262 if (print_man) {
263 buffer = cstring_append_chars(buffer, "\n.nf\n");
264 }
265
266 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
267 if (strcmp((char*)this_tag->name, "codeline") == 0) {
268
269 tmp = get_codeline(this_tag->children);
270 buffer = cstring_append_cstring(buffer, tmp);
271 cstring_free(tmp);
272 }
273 if (strcmp((char*)this_tag->name, "text") == 0) {
274 buffer = cstring_append_chars(buffer, (char*)this_tag->content);
275 }
276 }
277
278 if (print_man) {
279 buffer = cstring_append_chars(buffer, ".fi\n");
280 }
281
282 return buffer;
283 }
284
285
286 static cstring_t get_text(xmlNode *cur_node, char **returntext, char **notetext)
287 {
288 xmlNode *this_tag;
289 xmlNode *sub_tag;
290 char *kind;
291 cstring_t buffer = cstring_alloc();
292
293 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
294 if (this_tag->type == XML_TEXT_NODE && strcmp((char *)this_tag->name, "text") == 0) {
295 if (not_all_whitespace((char*)this_tag->content)) {
296 buffer = cstring_append_chars(buffer, (char*)this_tag->content);
297 }
298 }
299 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "emphasis") == 0) {
300 if (print_man) {
301 buffer = cstring_append_chars(buffer, "\\fB");
302 }
303 buffer = cstring_append_chars(buffer, (char*)this_tag->children->content);
304 if (print_man) {
305 buffer = cstring_append_chars(buffer, "\\fR");
306 }
307 }
308
309 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "ref") == 0) {
310 if (print_man) {
311 buffer = cstring_append_chars(buffer, "\\fI");
312 }
313 buffer = cstring_append_chars(buffer, (char*)this_tag->children->content);
314 if (print_man) {
315 buffer = cstring_append_chars(buffer, "\\fR");
316 }
317 }
318 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "computeroutput") == 0) {
319 if (print_man) {
320 buffer = cstring_append_chars(buffer, "\\fB");
321 }
322 buffer = cstring_append_chars(buffer, (char*)this_tag->children->content);
323 if (print_man) {
324 buffer = cstring_append_chars(buffer, "\\fP");
325 }
326 }
327
328 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "itemizedlist") == 0) {
329 for (sub_tag = this_tag->children; sub_tag; sub_tag = sub_tag->next) {
330 if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "listitem") == 0
331 && sub_tag->children->children->content) {
332 buffer = cstring_append_chars(buffer, (char*)sub_tag->children->children->content);
333 buffer = cstring_append_chars(buffer, "\n");
334 }
335 }
336 }
337
338 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "programlisting") == 0) {
339 cstring_t tmp = get_codetree(this_tag);
340 buffer = cstring_append_cstring(buffer, tmp);
341 buffer = cstring_append_chars(buffer, "\n");
342 cstring_free(tmp);
343 }
344
345 /* Look for subsections - return value & params */
346 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "simplesect") == 0) {
347 cstring_t tmp;
348
349 kind = get_attr(this_tag, "kind");
350 tmp = get_text(this_tag->children, NULL, NULL);
351
352 if (returntext && strcmp(kind, "return") == 0) {
353 *returntext = cstring_to_chars(tmp);
354 }
355 if (notetext && strcmp(kind, "note") == 0) {
356 *notetext = cstring_to_chars(tmp);
357 }
358 if (notetext && strcmp(kind, "par") == 0) {
359 int type;
360
361 tmp = get_child(this_tag, "title");
362 buffer = cstring_append_cstring(buffer, tmp);
363 buffer = cstring_append_chars(buffer, "\n");
364 cstring_free(tmp);
365
366 tmp = get_texttree(&type,this_tag, NULL, NULL, 1);
367 buffer = cstring_append_cstring(buffer, tmp);
368 buffer = cstring_append_chars(buffer, "\n");
369 }
370 cstring_free(tmp);
371 }
372
373 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "parameterlist") == 0) {
374 kind = get_attr(this_tag, "kind");
375 if (strcmp(kind, "param") == 0) {
376 get_param_info(this_tag, ¶ms_list);
377 }
378 if (strcmp(kind, "retval") == 0) {
379 get_param_info(this_tag, &retval_list);
380 }
381 }
382 }
383 return buffer;
384 }
385
386 static void read_structname(xmlNode *cur_node, void *arg)
387 {
388 struct struct_info *si=arg;
389 xmlNode *this_tag;
390
391 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
392 if (strcmp((char*)this_tag->name, "compoundname") == 0) {
393 si->structname = strdup((char*)this_tag->children->content);
394 }
395 }
396 }
397
398 static void read_structdesc(xmlNode *cur_node, void *arg)
399 {
400 struct struct_info *si=arg;
401 xmlNode *this_tag;
402
403 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
404 if (strcmp((char*)this_tag->name, "detaileddescription") == 0) {
405 cstring_t desc = get_texttree(NULL, this_tag, NULL, NULL, 1);
406 si->description = cstring_to_chars(desc);
407 cstring_free(desc);
408 }
409 if (strcmp((char*)this_tag->name, "briefdescription") == 0) {
410 cstring_t brief = get_texttree(NULL, this_tag, NULL, NULL, 1);
411 si->brief_description = cstring_to_chars(brief);
412 }
413 }
414 }
415
416
417 static void read_headername(xmlNode *cur_node, void *arg)
418 {
419 char **h_file = arg;
420 xmlNode *this_tag;
421
422 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
423 if (strcmp((char*)this_tag->name, "compoundname") == 0) {
424 *h_file = strdup((char*)this_tag->children->content);
425 }
426 }
427 }
428
429
430 /* Called from traverse_node() */
431 static void read_struct(xmlNode *cur_node, void *arg)
432 {
433 xmlNode *this_tag;
434 struct struct_info *si=arg;
435 struct param_info *pi = NULL;
436 char fullname[1024];
437 char *type = NULL;
438 char *name = NULL;
439 char *desc = NULL;
440 const char *args="";
441
442 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
443 if (strcmp((char*)this_tag->name, "type") == 0) {
444 type = (char*)this_tag->children->content;
445 /* If type is NULL then look for a ref - it's probably an external struct or typedef */
446 if (type == NULL) {
447 cstring_t tmp = get_child(this_tag, "ref");
448 type = cstring_to_chars(tmp);
449 cstring_free(tmp);
450 }
451 }
452 if (strcmp((char*)this_tag->name, "name") == 0) {
453 name = (char*)this_tag->children->content;
454 }
455 if (this_tag->children && strcmp((char*)this_tag->name, "argsstring") == 0) {
456 args = (char*)this_tag->children->content;
457 }
458 if (this_tag->children && strcmp((char*)this_tag->name, "detaileddescription") == 0) {
459 cstring_t *desc_cs = get_texttree(NULL, this_tag, NULL, NULL, 0);
460 if (cstring_len(desc_cs) > 1) {
461 desc = cstring_to_chars(desc_cs);
462 }
463 cstring_free(desc_cs);
464 }
465 }
466
467 if (name) {
468 pi = malloc(sizeof(struct param_info));
469 if (pi) {
470 snprintf(fullname, sizeof(fullname), "%s%s", name, args);
471 pi->paramtype = type?strdup(type):strdup("");
472 pi->paramname = strdup(fullname);
473 pi->paramdesc = desc;
474 qb_list_add_tail(&pi->list, &si->params_list);
475 }
476 }
477 /* Tidy */
478 if (!name || !pi) {
479 free(desc);
480 }
481 }
482
483 static int read_structure_from_xml(const char *refid, const char *name)
484 {
485 char fname[PATH_MAX];
486 xmlNode *rootdoc;
487 xmlDocPtr doc;
488 struct struct_info *si;
489 struct stat st;
490 int ret = -1;
491
492 snprintf(fname, sizeof(fname), "%s/%s.xml", xml_dir, refid);
493
494 /* Don't call into libxml if the file does not exist - saves unwanted error messages */
495 if (stat(fname, &st) == -1) {
496 return -1;
497 }
498
499 doc = xmlParseFile(fname);
500 if (doc == NULL) {
501 fprintf(stderr, "Error: unable to open xml file for %s\n", refid);
502 return -1;
503 }
504
505 rootdoc = xmlDocGetRootElement(doc);
506 if (!rootdoc) {
507 fprintf(stderr, "Can't find \"document root\"\n");
508 return -1;
509 }
510
511 si = malloc(sizeof(struct struct_info));
512 if (si) {
513 memset(si, 0, sizeof(*si));
514 si->kind = STRUCTINFO_STRUCT;
515 qb_list_init(&si->params_list);
516 traverse_node(rootdoc, "memberdef", read_struct, si);
517 traverse_node(rootdoc, "compounddef", read_structdesc, si);
518 traverse_node(rootdoc, "compounddef", read_structname, si);
519 ret = 0;
520 qb_map_put(structures_map, refid, si);
521 }
522 xmlFreeDoc(doc);
523
524 return ret;
525 }
526
527 static char *allcaps(const char *name)
528 {
529 static char buffer[4096] = {'\0'};
530 size_t i;
531
532 if (name) {
533 size_t len = strnlen(name, 4096);
534 for (i=0; i< len; i++) {
535 buffer[i] = toupper(name[i]);
536 }
537 buffer[len] = '\0';
538 }
539 return buffer;
540 }
541
542 /*
543 * Print a structure comment that would be too long
544 * to fit after the structure member, in a style ...
545 * well, in a style like this!
546 */
547 static void print_long_structure_comment(FILE *manfile, char *struct_comment)
548 {
549 int column = 7;
550 char *comment = strdup(struct_comment); /* We're using strdup */
551 char *ptr = strtok(comment, " ");
552
553 fprintf(manfile, "\\fP /*");
554 fprintf(manfile, "\n *");
555 while (ptr) {
556 column += strlen(ptr)+1;
557 if (column > 80) {
558 fprintf(manfile, "\n *");
559 column = 7;
560 }
561 fprintf(manfile, " %s", ptr);
562 ptr = strtok(NULL, " ");
563 }
564 fprintf(manfile, "\n */\n");
565 free(comment);
566 }
567
568 static void print_param(FILE *manfile, struct param_info *pi, int type_field_width, int name_field_width, int bold, const char *delimiter)
569 {
570 const char *asterisks = " ";
571 char *type = pi->paramtype;
572 int typelength = strlen(type);
573
574 /* Reformat pointer params so they look nicer */
575 if (typelength > 0 && pi->paramtype[typelength-1] == '*') {
576 asterisks=" *";
577 type = strdup(pi->paramtype);
578 type[typelength-1] = '\0';
579
580 /* Cope with double pointers */
581 if (typelength > 1 && pi->paramtype[typelength-2] == '*') {
582 asterisks="**";
583 type[typelength-2] = '\0';
584 }
585
586 /* Tidy function pointers */
587 if (typelength > 1 && pi->paramtype[typelength-2] == '(') {
588 asterisks="(*";
589 type[typelength-2] = '\0';
590 }
591 }
592
593 /* Print structure description if available */
594 if (pi->paramdesc) {
595 /* Too long to go on the same line? */
596 if (strlen(pi->paramdesc) > STRUCT_COMMENT_LENGTH) {
597 print_long_structure_comment(manfile, pi->paramdesc);
598 fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\n",
599 bold?"\\fB":"", type_field_width, type,
600 asterisks, bold?"\\fP":"",
601 pi->paramname?pi->paramname:"", delimiter);
602 } else {
603 /* Pad out so they all line up */
604 int pad_length = (name_field_width+2) -
605 (pi->paramname?strlen(pi->paramname):0) - strlen(delimiter) + 1;
606 fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\\fR%*s/* %s*/\n",
607 bold?"\\fB":"", type_field_width, type,
608 asterisks, bold?"\\fP":"",
609 pi->paramname?pi->paramname:"", delimiter,
610 pad_length, " ",
611 pi->paramdesc);
612 }
613 } else {
614 fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\n",
615 bold?"\\fB":"", type_field_width, type,
616 asterisks, bold?"\\fP":"",
617 pi->paramname?pi->paramname:"", delimiter);
618 }
619
620 if (type != pi->paramtype) {
621 free(type);
622 }
623 }
624
625 static void print_structure(FILE *manfile, struct struct_info *si)
626 {
627 struct param_info *pi;
628 struct qb_list_head *iter;
629 unsigned int max_param_length=0;
630 unsigned int max_param_name_length=0;
631
632 fprintf(manfile, ".nf\n");
633
634 if (si->brief_description) {
635 fprintf(manfile, "%s\n", si->brief_description);
636 }
637 if (si->description) {
638 fprintf(manfile, "%s\n", si->description);
639 }
640
641 qb_list_for_each(iter, &si->params_list) {
642 pi = qb_list_entry(iter, struct param_info, list);
643 if (strlen(pi->paramtype) > max_param_length) {
644 max_param_length = strlen(pi->paramtype);
645 }
646 if (strlen(pi->paramname) > max_param_name_length) {
647 max_param_name_length = strlen(pi->paramname);
648 }
649 }
650
651 fprintf(manfile, "\\fB\n");
652 if (si->kind == STRUCTINFO_STRUCT) {
653 fprintf(manfile, "struct %s {\n", si->structname);
654 } else if (si->kind == STRUCTINFO_ENUM) {
655 fprintf(manfile, "enum %s {\n", si->structname);
656 } else {
657 fprintf(manfile, "%s {\n", si->structname);
658 }
659 fprintf(manfile, "\\fR\n");
660
661 qb_list_for_each(iter, &si->params_list) {
662 fprintf(manfile, "\\fB\n");
663 pi = qb_list_entry(iter, struct param_info, list);
664 print_param(manfile, pi, max_param_length, max_param_name_length, 1, ";");
665 }
666 fprintf(manfile, "};\n");
667
668 fprintf(manfile, "\\fP\n");
669 fprintf(manfile, ".fi\n");
670 }
671
672 cstring_t get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext, int add_nl)
673 {
674 xmlNode *this_tag;
675 cstring_t tmp;
676 cstring_t buffer = cstring_alloc();
677
678 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
679
680 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "para") == 0) {
681 tmp = get_text(this_tag, returntext, notetext);
682 buffer = cstring_append_cstring(buffer, tmp);
683 if (add_nl) {
684 buffer = cstring_append_chars(buffer, "\n");
685 }
686
687 cstring_free(tmp);
688 }
689 }
690 return buffer;
691 }
692
693 /* The text output is VERY basic and just a check that it's working really */
694 static void print_text(char *name, char *def, char *brief, char *args, char *detailed,
695 struct qb_list_head *param_list, char *returntext, char *notetext)
696 {
697 printf(" ------------------ %s --------------------\n", name);
698 printf("NAME\n");
699 if (brief) {
700 printf(" %s - %s\n", name, brief);
701 } else {
702 printf(" %s\n", name);
703 }
704
705 printf("SYNOPSIS\n");
706 printf(" #include <%s%s>\n", header_prefix, headerfile);
707 if (args) {
708 printf(" %s %s\n\n", name, args);
709 }
710
711 if (detailed) {
712 printf("DESCRIPTION\n");
713 printf(" %s\n", detailed);
714 }
715
716 if (returntext) {
717 printf("RETURN VALUE\n");
718 printf(" %s\n", returntext);
719 }
720 if (notetext) {
721 printf("NOTE\n");
722 printf(" %s\n", notetext);
723 }
724 }
725
726 /* Print a long string with para marks in it. */
727 static void man_print_long_string(FILE *manfile, char *text)
728 {
729 char *next_nl;
730 char *current = text;
731 int in_prog = 0;
732
733 next_nl = strchr(text, '\n');
734 while (next_nl && *next_nl != '\0') {
735 *next_nl = '\0';
736
737 // Don't format @code blocks
738 if (strncmp(current, ".nf", 3) == 0) {
739 in_prog = 1;
740 fprintf(manfile, "\n");
741 }
742
743 if (in_prog) {
744 fprintf(manfile, "%s\n", current);
745 } else {
746 if (strlen(current)) {
747 fprintf(manfile, ".PP\n%s\n", current);
748 }
749 }
750
751 if (strncmp(current, ".fi", 3) == 0) {
752 in_prog = 0;
753 fprintf(manfile, "\n");
754 }
755
756 *next_nl = '\n';
757 current = next_nl+1;
758 next_nl = strchr(current, '\n');
759 }
760
761 /* The bit at the end */
762 if (strlen(current) && !in_prog) {
763 fprintf(manfile, ".PP\n%s\n", current);
764 }
765 }
766
767 static void print_manpage(char *name, char *def, char *brief, char *args, char *detailed,
768 struct qb_list_head *param_map, char *returntext, char *notetext)
769 {
770 char manfilename[PATH_MAX];
771 char gendate[64];
772 const char *dateptr = gendate;
773 FILE *manfile;
774 time_t t;
775 struct tm *tm;
776 qb_map_iter_t *map_iter;
777 struct qb_list_head *iter;
778 struct qb_list_head *tmp;
779 const char *p;
780 void *data;
781 unsigned int max_param_type_len;
782 unsigned int max_param_name_len;
783 unsigned int num_param_descs;
784 int param_count = 0;
785 int param_num = 0;
786 struct param_info *pi;
787
788 t = time(NULL);
789 tm = localtime(&t);
790 if (!tm) {
791 perror("unable to get localtime");
792 exit(1);
793 }
794 strftime(gendate, sizeof(gendate), "%Y-%m-%d", tm);
795
796 if (manpage_date) {
797 dateptr = manpage_date;
798 }
799 if (manpage_year == LONG_MIN) {
800 manpage_year = tm->tm_year+1900;
801 }
802
803 snprintf(manfilename, sizeof(manfilename), "%s/%s.%s", output_dir, name, man_section);
804 manfile = fopen(manfilename, "w+");
805 if (!manfile) {
806 perror("unable to open output file");
807 printf("%s", manfilename);
808 exit(1);
809 }
810
811 /* Work out the length of the parameters, so we can line them up */
812 max_param_type_len = 0;
813 max_param_name_len = 0;
814 num_param_descs = 0;
815
816 qb_list_for_each(iter, param_map) {
817 pi = qb_list_entry(iter, struct param_info, list);
818
819 /* It's mainly macros that break this,
820 * macros need more work
821 */
822 if (!pi->paramtype) {
823 pi->paramtype = strdup("");
824 }
825 if ((strlen(pi->paramtype) < LINE_LENGTH) &&
826 (strlen(pi->paramtype) > max_param_type_len)) {
827 max_param_type_len = strlen(pi->paramtype);
828 }
829 if (strlen(pi->paramname) > max_param_name_len) {
830 max_param_name_len = strlen(pi->paramname);
831 }
832 if (pi->paramdesc && pi->paramtype[0] != '\0') {
833 num_param_descs++;
834 }
835 param_count++;
836 }
837
838 /* Off we go */
839
840 fprintf(manfile, ".\\\" Automatically generated man page, do not edit\n");
841 fprintf(manfile, ".TH %s %s %s \"%s\" \"%s\"\n", allcaps(name), man_section, dateptr, package_name, header);
842
843 fprintf(manfile, ".SH NAME\n");
844 if (brief && not_all_whitespace(brief)) {
845 fprintf(manfile, "%s \\- %s\n", name, brief);
846 } else {
847 fprintf(manfile, "%s\n", name);
848 }
849
850 fprintf(manfile, ".SH SYNOPSIS\n");
851 fprintf(manfile, ".nf\n");
852 fprintf(manfile, ".B #include <%s%s>\n", header_prefix, headerfile);
853 if (def) {
854 fprintf(manfile, ".sp\n");
855 fprintf(manfile, "\\fB%s\\fP(\n", def);
856
857 qb_list_for_each(iter, param_map) {
858 pi = qb_list_entry(iter, struct param_info, list);
859
860 if (pi->paramtype[0] != '\0') {
861 print_param(manfile, pi, max_param_type_len, max_param_name_len, 1, ++param_num < param_count?",":"");
862 }
863 }
864
865 fprintf(manfile, ");\n");
866 fprintf(manfile, ".fi\n");
867 }
868
869 if (print_params && num_param_descs) {
870 fprintf(manfile, ".SH PARAMS\n");
871
872 qb_list_for_each(iter, ¶ms_list) {
873 pi = qb_list_entry(iter, struct param_info, list);
874 fprintf(manfile, "\\fB%-*s \\fP\\fI%s\\fP\n", (int)max_param_name_len, pi->paramname,
875 pi->paramdesc);
876 fprintf(manfile, ".PP\n");
877 }
878 }
879
880 if (detailed) {
881 fprintf(manfile, ".SH DESCRIPTION\n");
882 man_print_long_string(manfile, detailed);
883 }
884
885 if (qb_map_count_get(used_structures_map)) {
886 int first_struct = 1;
887
888 map_iter = qb_map_iter_create(used_structures_map);
889 for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) {
890 struct struct_info *si;
891 const char *refid = p;
892 char *refname = data;
893
894 /* If it's not been read in - go and look for it */
895 si = qb_map_get(structures_map, refid);
896 if (!si) {
897 if (!read_structure_from_xml(refid, refname)) {
898 si = qb_map_get(structures_map, refid);
899 }
900 }
901
902 /* Only print header if the struct files exist - sometimes they don't */
903 if (si && first_struct) {
904 fprintf(manfile, ".SH STRUCTURES\n");
905 first_struct = 0;
906 }
907 if (si) {
908 print_structure(manfile, si);
909 fprintf(manfile, ".PP\n");
910 }
911 }
912 qb_map_iter_free(map_iter);
913
914 fprintf(manfile, ".RE\n");
915 }
916
917 if (returntext || !qb_list_empty(&retval_list)) {
918 fprintf(manfile, ".SH RETURN VALUE\n");
919 if (returntext) {
920 man_print_long_string(manfile, returntext);
921 }
922 fprintf(manfile, ".PP\n");
923 }
924
925 qb_list_for_each(iter, &retval_list) {
926 pi = qb_list_entry(iter, struct param_info, list);
927
928 fprintf(manfile, "\\fB%-*s \\fP%s\n", 10, pi->paramname,
929 pi->paramdesc);
930 fprintf(manfile, ".PP\n");
931 }
932
933 if (notetext) {
934 fprintf(manfile, ".SH NOTE\n");
935 man_print_long_string(manfile, notetext);
936 }
937
938 fprintf(manfile, ".SH SEE ALSO\n");
939 fprintf(manfile, ".PP\n");
940 fprintf(manfile, ".nh\n");
941 fprintf(manfile, ".ad l\n");
942
943 param_num = 0;
944 map_iter = qb_map_iter_create(function_map);
945 for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) {
946
947 /* Exclude us! */
948 if (strcmp(data, name)) {
949 fprintf(manfile, "\\fI%s\\fR(%s)%s", (char *)data, man_section,
950 param_num < (num_functions - 1)?", ":"");
951 }
952 param_num++;
953 }
954 qb_map_iter_free(map_iter);
955
956 fprintf(manfile, "\n");
957 fprintf(manfile, ".ad\n");
958 fprintf(manfile, ".hy\n");
959 fprintf(manfile, ".SH \"COPYRIGHT\"\n");
960 fprintf(manfile, ".PP\n");
961 if (header_copyright[0] == 'C') {
962 fprintf(manfile, "%s", header_copyright); /* String already contains trailing NL */
963 } else {
964 fprintf(manfile, "Copyright (C) %4ld-%4ld %s, Inc. All rights reserved.\n", start_year, manpage_year, company);
965 }
966 fclose(manfile);
967
968 /* Free the params & retval info */
969 qb_list_for_each_safe(iter, tmp, ¶ms_list) {
970 pi = qb_list_entry(iter, struct param_info, list);
971 qb_list_del(&pi->list);
972 free_paraminfo(pi);
973 }
974
975 qb_list_for_each_safe(iter, tmp, &retval_list) {
976 pi = qb_list_entry(iter, struct param_info, list);
977 qb_list_del(&pi->list);
978 free_paraminfo(pi);
979 }
980
981 /* Free used-structures map */
982 map_iter = qb_map_iter_create(used_structures_map);
983 for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) {
984 qb_map_rm(used_structures_map, p);
985 free(data);
986 }
987 }
988
989 /* Same as traverse_members, but to collect function names */
990 static void collect_functions(xmlNode *cur_node, void *arg)
991 {
992 xmlNode *this_tag;
993 char *kind;
994 char *name = NULL;
995
996 if (cur_node->name && strcmp((char *)cur_node->name, "memberdef") == 0) {
997
998 kind = get_attr(cur_node, "kind");
999 if (kind && strcmp(kind, "function") == 0) {
1000
1001 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
1002 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) {
1003 name = strdup((char *)this_tag->children->content);
1004 }
1005 }
1006
1007 if (name) {
1008 qb_map_put(function_map, name, name);
1009 num_functions++;
1010 }
1011 }
1012 }
1013 }
1014
1015 /* Same as traverse_members, but to collect enums. The behave like structures for,
1016 but, for some reason, are in the main XML file rather than their own */
1017 static void collect_enums(xmlNode *cur_node, void *arg)
1018 {
1019 xmlNode *this_tag;
1020 struct struct_info *si;
1021 char *kind;
1022 char *refid = NULL;
1023 char *name = NULL;
1024
1025 if (cur_node->name && strcmp((char *)cur_node->name, "memberdef") == 0) {
1026
1027 kind = get_attr(cur_node, "kind");
1028 if (kind && strcmp(kind, "enum") == 0) {
1029 refid = get_attr(cur_node, "id");
1030
1031 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) {
1032 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) {
1033 name = strdup((char *)this_tag->children->content);
1034 }
1035 }
1036
1037 if (name) {
1038 si = malloc(sizeof(struct struct_info));
1039 if (si) {
1040 memset(si, 0, sizeof(*si));
1041 si->kind = STRUCTINFO_ENUM;
1042 qb_list_init(&si->params_list);
1043 si->structname = strdup(name);
1044 traverse_node(cur_node, "enumvalue", read_struct, si);
1045 qb_map_put(structures_map, refid, si);
1046 }
1047 }
1048 }
1049 }
1050 }
1051
1052 static void traverse_members(xmlNode *cur_node, void *arg)
1053 {
1054 xmlNode *this_tag;
1055
1056 qb_list_init(¶ms_list);
1057
1058 /* if arg == NULL then we're generating a page for the whole header file */
1059 if ((cur_node->name && (strcmp((char *)cur_node->name, "memberdef") == 0)) ||
1060 ((arg == NULL) && cur_node->name && strcmp((char *)cur_node->name, "compounddef")) == 0) {
1061 char *kind = NULL;
1062 char *def = NULL;
1063 char *args = NULL;
1064 char *name = NULL;
1065 char *brief = NULL;
1066 char *detailed = NULL;
1067 char *returntext = NULL;
1068 char *notetext = NULL;
1069 int type;
1070
1071 kind=def=args=name=NULL;
1072
1073 kind = get_attr(cur_node, "kind");
1074
1075 for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next)
1076 {
1077 if (!this_tag->children || !this_tag->children->content)
1078 continue;
1079
1080 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "definition") == 0)
1081 def = strdup((char *)this_tag->children->content);
1082 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "argsstring") == 0)
1083 args = strdup((char *)this_tag->children->content);
1084 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0)
1085 name = strdup((char *)this_tag->children->content);
1086
1087 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "briefdescription") == 0) {
1088 cstring_t tmp = get_texttree(&type, this_tag, &returntext, ¬etext, 1);
1089 if (!brief) {
1090 brief = cstring_to_chars(tmp);
1091 } else {
1092 fprintf(stderr, "ERROR function %s has 2 briefdescription tags\n", name?name:"unknown");
1093 }
1094 cstring_free(tmp);
1095 }
1096 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "detaileddescription") == 0) {
1097 cstring_t tmp = get_texttree(&type, this_tag, &returntext, ¬etext, 1);
1098 if (!detailed) {
1099 detailed = cstring_to_chars(tmp);
1100 } else {
1101 fprintf(stderr, "ERROR function %s has 2 detaileddescription tags\n", name?name:"unknown");
1102 }
1103 cstring_free(tmp);
1104 }
1105 /* Get all the params */
1106 if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "param") == 0) {
1107 cstring_t param_type = get_child(this_tag, "type");
1108 cstring_t param_name = get_child(this_tag, "declname");
1109 struct param_info *pi = malloc(sizeof(struct param_info));
1110 if (pi) {
1111 pi->paramname = cstring_to_chars(param_name);
1112 pi->paramtype = cstring_to_chars(param_type);
1113 pi->paramdesc = NULL;
1114 qb_list_add_tail(&pi->list, ¶ms_list);
1115 }
1116 }
1117 }
1118
1119 if (arg == headerfile) {
1120 /* Print header page */
1121 name = (char*)headerfile;
1122 if (print_man) {
1123 if (!quiet) {
1124 printf("Printing header manpage for %s\n", name);
1125 }
1126 print_manpage(name, def, brief, args, detailed, ¶ms_list, returntext, notetext);
1127 }
1128 else {
1129 print_text(name, def, brief, args, detailed, ¶ms_list, returntext, notetext);
1130 }
1131 }
1132
1133 if (kind && strcmp(kind, "function") == 0) {
1134
1135 /* Make sure function has a doxygen description */
1136 if (!detailed) {
1137 fprintf(stderr, "No detailed description for function '%s' - please fix this\n", name);
1138 }
1139
1140 if (!name) {
1141 fprintf(stderr, "Internal error - no name found for function\n");
1142 } else {
1143 if (print_man) {
1144 if (!quiet) {
1145 printf("Printing manpage for %s\n", name);
1146 }
1147 print_manpage(name, def, brief, args, detailed, ¶ms_list, returntext, notetext);
1148 }
1149 else {
1150 print_text(name, def, brief, args, detailed, ¶ms_list, returntext, notetext);
1151 }
1152 }
1153
1154 }
1155
1156 free(kind);
1157 free(def);
1158 free(args);
1159 free(name);
1160 free(brief);
1161 free(detailed);
1162 }
1163 }
1164
1165
1166 static void traverse_node(xmlNode *parentnode, const char *leafname, void (do_members(xmlNode*, void*)), void *arg)
1167 {
1168 xmlNode *cur_node;
1169
1170 for (cur_node = parentnode->children; cur_node; cur_node = cur_node->next) {
1171 if (cur_node->type == XML_ELEMENT_NODE && cur_node->name
1172 && strcmp((char*)cur_node->name, leafname)==0) {
1173 do_members(cur_node, arg);
1174 continue;
1175 }
1176 if (cur_node->type == XML_ELEMENT_NODE) {
1177 traverse_node(cur_node, leafname, do_members, arg);
1178 }
1179 }
1180 }
1181
1182
1183 static void usage(char *name)
1184 {
1185 printf("Usage:\n");
1186 printf(" %s [OPTIONS] <XML file>\n", name);
1187 printf("\n");
1188 printf(" This is a tool to generate API manpages from a doxygen-annotated header file.\n");
1189 printf(" First run doxygen on the file and then run this program against the main XML file\n");
1190 printf(" it created and the directory containing the ancilliary files. It will then\n");
1191 printf(" output a lot of *.3 man page files which you can then ship with your library.\n");
1192 printf("\n");
1193 printf(" You will need to invoke this program once for each .h file in your library,\n");
1194 printf(" using the name of the generated .xml file. This file will usually be called\n");
1195 printf(" something like <include-file>_8h.xml, eg qbipcs_8h.xml\n");
1196 printf("\n");
1197 printf(" If you want HTML output then simply use nroff on the generated files as you\n");
1198 printf(" would do with any other man page.\n");
1199 printf("\n");
1200 printf(" -a Print ASCII dump of man pages to stdout\n");
1201 printf(" -m Write man page files to <output dir>\n");
1202 printf(" -P Print PARAMS section\n");
1203 printf(" -g Print general man page for the whole header file\n");
1204 printf(" -c Use the Copyright date from the header file (if one can be found)\n");
1205 printf(" -O <dir> Directory for the original header file. Often needed by -c above\n");
1206 printf(" -s <s> Write man pages into section <s> (default: 3)\n");
1207 printf(" -p <package> Use <package> name (default: Package)\n");
1208 printf(" -H <header> Set header (default: \"Programmer's Manual\"\n");
1209 printf(" -I <include> Set include filename (default taken from xml)\n");
1210 printf(" -i <prefix> Prefix for include files. eg qb/ (nothing by default)\n");
1211 printf(" -C <company> Company name in copyright (default: Red Hat)\n");
1212 printf(" -D <date> Date to print at top of man pages (format not checked, default: today)\n");
1213 printf(" -S <year> Start year to print at end of copyright line (default: 2010)\n");
1214 printf(" -Y <year> Year to print at end of copyright line (default: today's year)\n");
1215 printf(" -o <dir> Write all man pages to <dir> (default: .)\n");
1216 printf(" -d <dir> Directory for XML files (default: ./xml/)\n");
1217 printf(" -h Print this usage text\n");
1218 }
1219
1220 static long get_year(char *optionarg, char optionchar)
1221 {
1222 long year = strtol(optionarg, NULL, 10);
1223 /*
1224 * Don't make too many assumptions about the year. I was on call at the
1225 * 2000 rollover. #experience
1226 */
1227 if (year == LONG_MIN || year == LONG_MAX ||
1228 year < 1900) {
1229 fprintf(stderr, "Value passed to -%c is not a valid year number\n", optionchar);
1230 return 0;
1231 }
1232 return year;
1233 }
1234
1235 int main(int argc, char *argv[])
1236 {
1237 xmlNode *rootdoc;
1238 xmlDocPtr doc;
1239 int opt;
1240 char xml_filename[PATH_MAX];
1241
1242 while ( (opt = getopt_long(argc, argv, "H:amqgcPD:Y:s:S:d:o:p:f:I:i:C:O:h?", NULL, NULL)) != EOF)
1243 {
1244 switch(opt)
1245 {
1246 case 'a':
1247 print_ascii = 1;
1248 print_man = 0;
1249 break;
1250 case 'm':
1251 print_man = 1;
1252 print_ascii = 0;
1253 break;
1254 case 'P':
1255 print_params = 1;
1256 break;
1257 case 'g':
1258 print_general = 1;
1259 break;
1260 case 'q':
1261 quiet = 1;
1262 break;
1263 case 'c':
1264 use_header_copyright = 1;
1265 break;
1266 case 'I':
1267 headerfile = optarg;
1268 break;
1269 case 'i':
1270 header_prefix = optarg;
1271 break;
1272 case 'C':
1273 company = optarg;
1274 break;
1275 case 's':
1276 man_section = optarg;
1277 break;
1278 case 'S':
1279 start_year = get_year(optarg, 'S');
1280 if (start_year == 0) {
1281 return 1;
1282 }
1283 break;
1284 case 'd':
1285 xml_dir = optarg;
1286 break;
1287 case 'D':
1288 manpage_date = optarg;
1289 break;
1290 case 'Y':
1291 manpage_year = get_year(optarg, 'Y');
1292 if (manpage_year == 0) {
1293 return 1;
1294 }
1295 break;
1296 case 'p':
1297 package_name = optarg;
1298 break;
1299 case 'H':
1300 header = optarg;
1301 break;
1302 case 'o':
1303 output_dir = optarg;
1304 break;
1305 case 'O':
1306 header_src_dir = optarg;
1307 break;
1308 case '?':
1309 case 'h':
1310 usage(argv[0]);
1311 return 0;
1312 }
1313 }
1314
1315 if (argv[optind]) {
1316 xml_file = argv[optind];
1317 }
1318 if (!xml_file) {
1319 usage(argv[0]);
1320 exit(1);
1321 }
1322
1323 if (!quiet) {
1324 printf("reading %s ... ", xml_file);
1325 }
1326
1327 snprintf(xml_filename, sizeof(xml_filename), "%s/%s", xml_dir, xml_file);
1328 doc = xmlParseFile(xml_filename);
1329 if (doc == NULL) {
1330 fprintf(stderr, "Error: unable to read xml file %s\n", xml_filename);
1331 exit(1);
1332 }
1333
1334 rootdoc = xmlDocGetRootElement(doc);
1335 if (!rootdoc) {
1336 fprintf(stderr, "Can't find \"document root\"\n");
1337 exit(1);
1338 }
1339 if (!quiet)
1340 printf("done.\n");
1341
1342 /* Get our header file name */
1343 if (!headerfile) {
1344 traverse_node(rootdoc, "compounddef", read_headername, &headerfile);
1345
1346 if (use_header_copyright) {
1347 /* And get the copyright line from this file if we can */
1348 char file_path[PATH_MAX];
1349 char file_line[256];
1350 FILE *hfile;
1351 int lineno = 0;
1352
1353 snprintf(file_path, sizeof(file_path), "%s/%s", header_src_dir, headerfile);
1354 hfile = fopen(file_path, "r");
1355 if (hfile) {
1356 /* Don't look too far, this should be at the top */
1357 while (!feof(hfile) && (lineno++ < 10)) {
1358 if (fgets(file_line, sizeof(file_line)-1, hfile)) {
1359 if (strncmp(file_line, " * Copyright", 12) == 0) {
1360 /* Keep the NL at the end of the buffer, it save us printing one */
1361 strncpy(header_copyright, file_line+3, sizeof(header_copyright)-1);
1362 break;
1363 }
1364 }
1365 }
1366 fclose(hfile);
1367 }
1368 }
1369 }
1370
1371 /* Default to *something* if it all goes wrong */
1372 if (!headerfile) {
1373 headerfile = "unknown.h";
1374 }
1375
1376 qb_list_init(¶ms_list);
1377 qb_list_init(&retval_list);
1378 structures_map = qb_hashtable_create(10);
1379 function_map = qb_hashtable_create(10);
1380 used_structures_map = qb_hashtable_create(10);
1381
1382 /* Collect functions */
1383 traverse_node(rootdoc, "memberdef", collect_functions, NULL);
1384
1385 /* Collect enums */
1386 traverse_node(rootdoc, "memberdef", collect_enums, NULL);
1387
1388 /* print pages */
1389 traverse_node(rootdoc, "memberdef", traverse_members, NULL);
1390
1391 if (print_general) {
1392 /* Generate and print a page for the headerfile itself */
1393 traverse_node(rootdoc, "compounddef", traverse_members, (char *)headerfile);
1394 }
1395 return 0;
1396 }
1397