diff --git a/man/Makefile.am b/man/Makefile.am index 4bcafefd..2ff160be 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -1,160 +1,161 @@ # # Copyright (C) 2017-2021 Red Hat, Inc. All rights reserved. # # Authors: Fabio M. Di Nitto # Federico Simoncelli # # This software licensed under GPL-2.0+ # MAINTAINERCLEANFILES = Makefile.in include $(top_srcdir)/build-aux/check.mk EXTRA_DIST = \ api-to-man-page-coverage # Avoid Automake warnings about overriding these user variables. # Programs in this directory are used during the build only. AUTOMAKE_OPTIONS = -Wno-gnu EXEEXT=$(BUILD_EXEEXT) CC=$(CC_FOR_BUILD) CFLAGS=$(CFLAGS_FOR_BUILD) CPPFLAGS=$(CPPFLAGS_FOR_BUILD) LDFLAGS=$(LDFLAGS_FOR_BUILD) if BUILD_MAN if BUILD_DOXYXML noinst_PROGRAMS = doxyxml +noinst_HEADERS = cstring.h -doxyxml_SOURCES = doxyxml.c +doxyxml_SOURCES = doxyxml.c cstring.c doxyxml_CFLAGS = $(AM_CFLAGS) $(libqb_BUILD_CFLAGS) $(libxml_BUILD_CFLAGS) doxyxml_LDADD = $(libqb_BUILD_LIBS) $(libxml_BUILD_LIBS) endif knet_man3_MANS = \ knet_addrtostr.3 \ knet_handle_add_datafd.3 \ knet_handle_clear_stats.3 \ knet_handle_compress.3 \ knet_handle_crypto.3 \ knet_handle_enable_filter.3 \ knet_handle_enable_pmtud_notify.3 \ knet_handle_enable_sock_notify.3 \ knet_handle_free.3 \ knet_handle_get_channel.3 \ knet_get_compress_list.3 \ knet_get_crypto_list.3 \ knet_handle_get_datafd.3 \ knet_handle_get_stats.3 \ knet_get_transport_id_by_name.3 \ knet_get_transport_list.3 \ knet_get_transport_name_by_id.3 \ knet_handle_get_transport_reconnect_interval.3 \ knet_handle_new.3 \ knet_handle_new_ex.3 \ knet_handle_pmtud_get.3 \ knet_handle_pmtud_set.3 \ knet_handle_pmtud_getfreq.3 \ knet_handle_pmtud_setfreq.3 \ knet_handle_remove_datafd.3 \ knet_handle_setfwd.3 \ knet_handle_set_transport_reconnect_interval.3 \ knet_host_add.3 \ knet_host_enable_status_change_notify.3 \ knet_host_get_host_list.3 \ knet_host_get_id_by_host_name.3 \ knet_host_get_name_by_host_id.3 \ knet_host_get_policy.3 \ knet_host_get_status.3 \ knet_host_remove.3 \ knet_host_set_name.3 \ knet_host_set_policy.3 \ knet_link_clear_config.3 \ knet_link_get_config.3 \ knet_link_get_enable.3 \ knet_link_get_link_list.3 \ knet_link_get_ping_timers.3 \ knet_link_get_pong_count.3 \ knet_link_get_priority.3 \ knet_link_get_status.3 \ knet_link_set_config.3 \ knet_link_set_enable.3 \ knet_link_set_ping_timers.3 \ knet_link_set_pong_count.3 \ knet_link_set_priority.3 \ knet_log_get_loglevel.3 \ knet_log_get_loglevel_id.3 \ knet_log_get_loglevel_name.3 \ knet_log_get_subsystem_id.3 \ knet_log_get_subsystem_name.3 \ knet_log_set_loglevel.3 \ knet_recv.3 \ knet_send.3 \ knet_send_sync.3 \ knet_strtoaddr.3 \ knet_handle_enable_access_lists.3 \ knet_link_add_acl.3 \ knet_link_insert_acl.3 \ knet_link_rm_acl.3 \ knet_link_clear_acl.3 \ knet_handle_crypto_set_config.3 \ knet_handle_crypto_use_config.3 \ knet_handle_crypto_rx_clear_traffic.3 if BUILD_LIBNOZZLE nozzle_man3_MANS = \ nozzle_add_ip.3 \ nozzle_close.3 \ nozzle_del_ip.3 \ nozzle_get_fd.3 \ nozzle_get_handle_by_name.3 \ nozzle_get_ips.3 \ nozzle_get_mac.3 \ nozzle_get_mtu.3 \ nozzle_get_name_by_handle.3 \ nozzle_open.3 \ nozzle_reset_mac.3 \ nozzle_reset_mtu.3 \ nozzle_run_updown.3 \ nozzle_set_down.3 \ nozzle_set_mac.3 \ nozzle_set_mtu.3 \ nozzle_set_up.3 endif man3_MANS = $(knet_man3_MANS) $(nozzle_man3_MANS) $(MANS): doxyfile-knet.stamp doxyfile-nozzle.stamp doxyfile-knet.stamp: $(noinst_PROGRAMS) Doxyfile-knet $(top_srcdir)/libknet/libknet.h $(DOXYGEN) Doxyfile-knet 2>&1 | $(EGREP) -v 'warning.*macro definition' $(DOXYGEN2MAN) -m -P -o $(builddir) -s 3 -p @PACKAGE_NAME@ -H "Kronosnet Programmer's Manual" \ $$($(UTC_DATE_AT)$(SOURCE_EPOCH) +"-D %F -Y %Y") -d $(builddir)/xml-knet/ libknet_8h.xml touch doxyfile-knet.stamp doxyfile-nozzle.stamp: $(noinst_PROGRAMS) Doxyfile-nozzle $(top_srcdir)/libnozzle/libnozzle.h if BUILD_LIBNOZZLE $(DOXYGEN) Doxyfile-nozzle 2>&1 | $(EGREP) -v 'warning.*macro definition' $(DOXYGEN2MAN) -m -P -o $(builddir) -s 3 -p @PACKAGE_NAME@ -H "Kronosnet Programmer's Manual" \ $$($(UTC_DATE_AT)$(SOURCE_EPOCH) +"-D %F -Y %Y") -d $(builddir)/xml-nozzle/ libnozzle_8h.xml endif touch doxyfile-nozzle.stamp noinst_SCRIPTS = api-to-man-page-coverage check-local: check-api-to-man-page-coverage-libknet check-api-to-man-page-coverage-libnozzle check-api-to-man-page-coverage-libnozzle: if BUILD_LIBNOZZLE $(srcdir)/api-to-man-page-coverage $(top_srcdir) nozzle endif check-api-to-man-page-coverage-libknet: $(srcdir)/api-to-man-page-coverage $(top_srcdir) knet endif clean-local: rm -rf doxyxml doxyfile*.stamp xml* *.3 diff --git a/man/cstring.c b/man/cstring.c new file mode 100644 index 00000000..23ba31d3 --- /dev/null +++ b/man/cstring.c @@ -0,0 +1,131 @@ +/* A really basic expanding/appendable string type */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cstring.h" + +#define INITIAL_SIZE 1024 +#define INCREMENT 1024 +#define CHECKER_WORD 0xbcd6712a + +struct cstring_header +{ + size_t checker; + size_t allocated; + size_t used; + char the_string[]; +}; + +cstring_t cstring_alloc(void) +{ + char *cstring = malloc(INITIAL_SIZE); + if (cstring) { + struct cstring_header *h = (struct cstring_header *)cstring; + h->checker = CHECKER_WORD; + h->allocated = INITIAL_SIZE; + h->used = 0; + h->the_string[0] = '\0'; + return cstring; + } else { + return NULL; + } +} + +char *cstring_to_chars(cstring_t cstring) +{ + struct cstring_header *h = (struct cstring_header *)cstring; + + if (!h) { + return NULL; + } + + assert(h->checker == CHECKER_WORD); + return strdup(h->the_string); +} + +size_t cstring_len(cstring_t cstring) +{ + struct cstring_header *h = (struct cstring_header *)cstring; + + if (!h) { + return 0; + } + + assert(h->checker == CHECKER_WORD); + return h->used; +} + + +cstring_t cstring_append_chars(cstring_t cstring, const char *newstring) +{ + struct cstring_header *h = (struct cstring_header *)cstring; + size_t newlen; + + if (!h) { + return NULL; + } + + assert(h->checker == CHECKER_WORD); + if (!newstring) { + return NULL; + } + + newlen = h->used + strlen(newstring)+1 + sizeof(struct cstring_header); + if (newlen > h->allocated) { + size_t new_allocsize = (newlen + 2048) & 0xFFFFFC00; + char *tmp = realloc(cstring, new_allocsize); + if (!tmp) { + return cstring; + } + + cstring = tmp; + h = (struct cstring_header *)cstring; + h->allocated = new_allocsize; + } + strncat(h->the_string, newstring, h->allocated - h->used -1); + h->used += strlen(newstring); + return cstring; +} + +cstring_t cstring_append_cstring(cstring_t cstring, cstring_t newstring) +{ + /* Just check the newstring - cstring_append_chars() will check the target */ + struct cstring_header *h = (struct cstring_header *)newstring; + + if (!h) { + return NULL; + } + + assert(h->checker == CHECKER_WORD); + return cstring_append_chars(cstring, h->the_string); +} + +cstring_t cstring_from_chars(const char* chars) +{ + cstring_t new_string = cstring_alloc(); + if (!new_string) { + return NULL; + } + return cstring_append_chars(new_string, chars); +} + +void cstring_free(cstring_t cstring) +{ + struct cstring_header *h = (struct cstring_header *)cstring; + + if (!h) { + return; + } + assert(h->checker == CHECKER_WORD); + free(cstring); +} + + diff --git a/man/cstring.h b/man/cstring.h new file mode 100644 index 00000000..172a4e6b --- /dev/null +++ b/man/cstring.h @@ -0,0 +1,15 @@ +#ifndef __CSTRING_H__ +#define __CSTRING_H__ + +typedef void* cstring_t; + +cstring_t cstring_alloc(void); +cstring_t cstring_from_chars(const char* chars); +cstring_t cstring_dup(cstring_t string); +char *cstring_to_chars(cstring_t cstring); +cstring_t cstring_append_chars(cstring_t cstring, const char *newstring); +cstring_t cstring_append_cstring(cstring_t cstring, cstring_t newstring); +void cstring_free(cstring_t cstring); +size_t cstring_len(cstring_t cstring); + +#endif diff --git a/man/doxyxml.c b/man/doxyxml.c index 72fda764..73f705d3 100644 --- a/man/doxyxml.c +++ b/man/doxyxml.c @@ -1,1155 +1,1389 @@ /* * Copyright (C) 2018-2021 Red Hat, Inc. All rights reserved. * * Author: Christine Caulfield * * This software licensed under GPL-2.0+ */ /* * NOTE: this code is very rough, it does the bare minimum to parse the * XML out from doxygen and is probably very fragile to changes in that XML * schema. It probably leaks memory all over the place too. * * In its favour, it *does* generate nice man pages and should only be run very ocasionally */ #define _DEFAULT_SOURCE #define _BSD_SOURCE +#define _GNU_SOURCE #define _XOPEN_SOURCE #define _XOPEN_SOURCE_EXTENDED #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "cstring.h" /* * This isn't a maximum size, it just defines how long a parameter * type can get before we decide it's not worth lining everything up. * It's mainly to stop function pointer types (which can get VERY long because * of all *their* parameters) making everything else 'line-up' over separate lines */ #define LINE_LENGTH 80 +/* Similar - but for structure member comments */ +#define STRUCT_COMMENT_LENGTH 50 + static int print_ascii = 1; static int print_man = 0; static int print_params = 0; static int print_general = 0; static int num_functions = 0; -static int quiet=0; +static int quiet = 0; +static int use_header_copyright = 0; static const char *man_section="3"; static const char *package_name="Package"; static const char *header="Programmer's Manual"; static const char *company="Red Hat"; static const char *output_dir="./"; static const char *xml_dir = "./xml/"; static const char *xml_file; static const char *manpage_date = NULL; static const char *headerfile = NULL; static const char *header_prefix = ""; +static const char *header_src_dir = "./"; +static char header_copyright[256] = "\0"; static long manpage_year = LONG_MIN; static long start_year = 2010; static struct qb_list_head params_list; static struct qb_list_head retval_list; static qb_map_t *function_map; static qb_map_t *structures_map; static qb_map_t *used_structures_map; struct param_info { char *paramname; char *paramtype; char *paramdesc; struct param_info *next; struct qb_list_head list; }; struct struct_info { enum {STRUCTINFO_STRUCT, STRUCTINFO_ENUM} kind; char *structname; char *description; char *brief_description; struct qb_list_head params_list; /* our params */ struct qb_list_head list; }; -static char *get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext); +static cstring_t get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext, int add_nl); static void traverse_node(xmlNode *parentnode, const char *leafname, void (do_members(xmlNode*, void*)), void *arg); +static cstring_t get_text(xmlNode *cur_node, char **returntext, char **notetext); +static void man_print_long_string(FILE *manfile, char *text); static void free_paraminfo(struct param_info *pi) { free(pi->paramname); free(pi->paramtype); free(pi->paramdesc); free(pi); } static char *get_attr(xmlNode *node, const char *tag) { xmlAttr *this_attr; for (this_attr = node->properties; this_attr; this_attr = this_attr->next) { if (this_attr->type == XML_ATTRIBUTE_NODE && strcmp((char *)this_attr->name, tag) == 0) { return strdup((char *)this_attr->children->content); } } return NULL; } -static char *get_child(xmlNode *node, const char *tag) +static cstring_t get_child(xmlNode *node, const char *tag) { - xmlNode *this_node; - xmlNode *child; - char buffer[1024] = {'\0'}; + xmlNode *this_node; + xmlNode *child; + cstring_t buffer = cstring_alloc(); char *refid = NULL; char *declname = NULL; for (this_node = node->children; this_node; this_node = this_node->next) { if ((strcmp( (char*)this_node->name, "declname") == 0)) { declname = strdup((char*)this_node->children->content); } if ((this_node->type == XML_ELEMENT_NODE && this_node->children) && ((strcmp((char *)this_node->name, tag) == 0))) { refid = NULL; for (child = this_node->children; child; child = child->next) { if (child->content) { - strncat(buffer, (char *)child->content, sizeof(buffer)-1); + buffer = cstring_append_chars(buffer, (char *)child->content); } if ((strcmp( (char*)child->name, "ref") == 0)) { if (child->children->content) { - strncat(buffer,(char *)child->children->content, sizeof(buffer)-1); + buffer = cstring_append_chars(buffer, (char *)child->children->content); } refid = get_attr(child, "refid"); } } } if (declname && refid) { qb_map_put(used_structures_map, refid, declname); } } - return strdup(buffer); + return buffer; } static struct param_info *find_param_by_name(struct qb_list_head *list, const char *name) { struct qb_list_head *iter; struct param_info *pi; qb_list_for_each(iter, list) { pi = qb_list_entry(iter, struct param_info, list); if (strcmp(pi->paramname, name) == 0) { return pi; } } return NULL; } static int not_all_whitespace(char *string) { unsigned int i; for (i=0; ichildren; this_tag; this_tag = this_tag->next) { for (sub_tag = this_tag->children; sub_tag; sub_tag = sub_tag->next) { if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "parameternamelist") == 0 && sub_tag->children->next->children) { paramname = (char*)sub_tag->children->next->children->content; } - if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "parameterdescription") == 0 && + if (sub_tag->type == XML_ELEMENT_NODE && + strcmp((char *)sub_tag->name, "parameterdescription") == 0 && paramname && sub_tag->children->next->children) { paramdesc = (char*)sub_tag->children->next->children->content; /* Add text to the param_map */ pi = find_param_by_name(list, paramname); if (pi) { pi->paramdesc = paramdesc; } else { pi = malloc(sizeof(struct param_info)); if (pi) { pi->paramname = paramname; pi->paramdesc = paramdesc; - pi->paramtype = NULL; /* retval */ + pi->paramtype = NULL; /* it's a retval */ qb_list_add_tail(&pi->list, list); } } } } } } -static char *get_text(xmlNode *cur_node, char **returntext, char **notetext) +static cstring_t get_codeline(xmlNode *this_tag) +{ + cstring_t buffer = cstring_alloc(); + xmlNode *sub_tag; + + for (sub_tag = this_tag; sub_tag; sub_tag = sub_tag->next) { + if (strcmp((char*)sub_tag->name, "sp") == 0) { + buffer = cstring_append_chars(buffer, " "); + } + if (strcmp((char*)sub_tag->name, "text") == 0) { + // If the line starts with a dot then escape the first one to + // stop nroff thinking it's a macro + char *tmp = (char*)sub_tag->content; + if (tmp[0] == '.') { + buffer = cstring_append_chars(buffer, (char*)"\\[char46]"); + tmp += 1; + } + buffer = cstring_append_chars(buffer, tmp); + } + if (strcmp((char*)sub_tag->name, "ref") == 0) { + // Handled by the child recusion below + } + if (sub_tag->children) { + char *tmp = get_codeline(sub_tag->children); + buffer = cstring_append_cstring(buffer, tmp); + cstring_free(tmp); + } + } + return buffer; +} + +static cstring_t get_codetree(xmlNode *cur_node) +{ + xmlNode *this_tag; + cstring_t buffer = cstring_alloc(); + cstring_t tmp; + + if (print_man) { + buffer = cstring_append_chars(buffer, "\n.nf\n"); + } + + for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { + if (strcmp((char*)this_tag->name, "codeline") == 0) { + + tmp = get_codeline(this_tag->children); + buffer = cstring_append_cstring(buffer, tmp); + cstring_free(tmp); + } + if (strcmp((char*)this_tag->name, "text") == 0) { + buffer = cstring_append_chars(buffer, (char*)this_tag->content); + } + } + + if (print_man) { + buffer = cstring_append_chars(buffer, ".fi\n"); + } + + return buffer; +} + + +static cstring_t get_text(xmlNode *cur_node, char **returntext, char **notetext) { xmlNode *this_tag; xmlNode *sub_tag; char *kind; - char buffer[4096] = {'\0'}; + cstring_t buffer = cstring_alloc(); for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (this_tag->type == XML_TEXT_NODE && strcmp((char *)this_tag->name, "text") == 0) { if (not_all_whitespace((char*)this_tag->content)) { - strncat(buffer, (char*)this_tag->content, sizeof(buffer)-1); + buffer = cstring_append_chars(buffer, (char*)this_tag->content); } } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "emphasis") == 0) { if (print_man) { - strncat(buffer, "\\fB", sizeof(buffer)-1); + buffer = cstring_append_chars(buffer, "\\fB"); } - strncat(buffer, (char*)this_tag->children->content, sizeof(buffer)-1); + buffer = cstring_append_chars(buffer, (char*)this_tag->children->content); if (print_man) { - strncat(buffer, "\\fR", sizeof(buffer)-1); + buffer = cstring_append_chars(buffer, "\\fR"); } } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "ref") == 0) { if (print_man) { - strncat(buffer, "\\fI", sizeof(buffer)-1); + buffer = cstring_append_chars(buffer, "\\fI"); + } + buffer = cstring_append_chars(buffer, (char*)this_tag->children->content); + if (print_man) { + buffer = cstring_append_chars(buffer, "\\fR"); + } + } + if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "computeroutput") == 0) { + if (print_man) { + buffer = cstring_append_chars(buffer, "\\fB"); } - strncat(buffer, (char*)this_tag->children->content, sizeof(buffer)-1); + buffer = cstring_append_chars(buffer, (char*)this_tag->children->content); if (print_man) { - strncat(buffer, "\\fR", sizeof(buffer)-1); + buffer = cstring_append_chars(buffer, "\\fP"); } } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "itemizedlist") == 0) { for (sub_tag = this_tag->children; sub_tag; sub_tag = sub_tag->next) { - if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "listitem") == 0) { - strncat(buffer, (char*)sub_tag->children->children->content, sizeof(buffer)-1); - strncat(buffer, "\n", sizeof(buffer)-1); + if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "listitem") == 0 + && sub_tag->children->children->content) { + buffer = cstring_append_chars(buffer, (char*)sub_tag->children->children->content); + buffer = cstring_append_chars(buffer, "\n"); } } } + if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "programlisting") == 0) { + cstring_t tmp = get_codetree(this_tag); + buffer = cstring_append_cstring(buffer, tmp); + buffer = cstring_append_chars(buffer, "\n"); + cstring_free(tmp); + } + /* Look for subsections - return value & params */ if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "simplesect") == 0) { - char *tmp; + cstring_t tmp; kind = get_attr(this_tag, "kind"); tmp = get_text(this_tag->children, NULL, NULL); if (returntext && strcmp(kind, "return") == 0) { - *returntext = tmp; + *returntext = cstring_to_chars(tmp); } if (notetext && strcmp(kind, "note") == 0) { - *notetext = tmp; + *notetext = cstring_to_chars(tmp); + } + if (notetext && strcmp(kind, "par") == 0) { + int type; + + tmp = get_child(this_tag, "title"); + buffer = cstring_append_cstring(buffer, tmp); + buffer = cstring_append_chars(buffer, "\n"); + cstring_free(tmp); + + tmp = get_texttree(&type,this_tag, NULL, NULL, 1); + buffer = cstring_append_cstring(buffer, tmp); + buffer = cstring_append_chars(buffer, "\n"); } + cstring_free(tmp); } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "parameterlist") == 0) { kind = get_attr(this_tag, "kind"); if (strcmp(kind, "param") == 0) { get_param_info(this_tag, ¶ms_list); } if (strcmp(kind, "retval") == 0) { get_param_info(this_tag, &retval_list); } } } - return strdup(buffer); + return buffer; } static void read_structname(xmlNode *cur_node, void *arg) { struct struct_info *si=arg; xmlNode *this_tag; for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (strcmp((char*)this_tag->name, "compoundname") == 0) { si->structname = strdup((char*)this_tag->children->content); } } } static void read_structdesc(xmlNode *cur_node, void *arg) { struct struct_info *si=arg; xmlNode *this_tag; for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (strcmp((char*)this_tag->name, "detaileddescription") == 0) { - char *desc = get_texttree(NULL, this_tag, NULL, NULL); - if (desc) { - si->description = strdup((char*)desc); - } + cstring_t desc = get_texttree(NULL, this_tag, NULL, NULL, 1); + si->description = cstring_to_chars(desc); + cstring_free(desc); } if (strcmp((char*)this_tag->name, "briefdescription") == 0) { - char *brief = get_texttree(NULL, this_tag, NULL, NULL); - if (brief) { - si->brief_description = brief; - } + cstring_t brief = get_texttree(NULL, this_tag, NULL, NULL, 1); + si->brief_description = cstring_to_chars(brief); } } } static void read_headername(xmlNode *cur_node, void *arg) { char **h_file = arg; xmlNode *this_tag; for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (strcmp((char*)this_tag->name, "compoundname") == 0) { *h_file = strdup((char*)this_tag->children->content); } } } /* Called from traverse_node() */ static void read_struct(xmlNode *cur_node, void *arg) { xmlNode *this_tag; struct struct_info *si=arg; - struct param_info *pi; + struct param_info *pi = NULL; char fullname[1024]; char *type = NULL; char *name = NULL; + char *desc = NULL; const char *args=""; for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (strcmp((char*)this_tag->name, "type") == 0) { type = (char*)this_tag->children->content; /* If type is NULL then look for a ref - it's probably an external struct or typedef */ if (type == NULL) { - type = get_child(this_tag, "ref"); + cstring_t tmp = get_child(this_tag, "ref"); + type = cstring_to_chars(tmp); + cstring_free(tmp); } } if (strcmp((char*)this_tag->name, "name") == 0) { name = (char*)this_tag->children->content; } if (this_tag->children && strcmp((char*)this_tag->name, "argsstring") == 0) { args = (char*)this_tag->children->content; } + if (this_tag->children && strcmp((char*)this_tag->name, "detaileddescription") == 0) { + cstring_t *desc_cs = get_texttree(NULL, this_tag, NULL, NULL, 0); + if (cstring_len(desc_cs) > 1) { + desc = cstring_to_chars(desc_cs); + } + cstring_free(desc_cs); + } } if (name) { pi = malloc(sizeof(struct param_info)); if (pi) { snprintf(fullname, sizeof(fullname), "%s%s", name, args); pi->paramtype = type?strdup(type):strdup(""); pi->paramname = strdup(fullname); - pi->paramdesc = NULL; + pi->paramdesc = desc; qb_list_add_tail(&pi->list, &si->params_list); } } + /* Tidy */ + if (!name || !pi) { + free(desc); + } } static int read_structure_from_xml(const char *refid, const char *name) { char fname[PATH_MAX]; xmlNode *rootdoc; xmlDocPtr doc; struct struct_info *si; struct stat st; int ret = -1; snprintf(fname, sizeof(fname), "%s/%s.xml", xml_dir, refid); /* Don't call into libxml if the file does not exist - saves unwanted error messages */ if (stat(fname, &st) == -1) { return -1; } doc = xmlParseFile(fname); if (doc == NULL) { fprintf(stderr, "Error: unable to open xml file for %s\n", refid); return -1; } rootdoc = xmlDocGetRootElement(doc); if (!rootdoc) { fprintf(stderr, "Can't find \"document root\"\n"); return -1; } si = malloc(sizeof(struct struct_info)); if (si) { memset(si, 0, sizeof(*si)); si->kind = STRUCTINFO_STRUCT; qb_list_init(&si->params_list); traverse_node(rootdoc, "memberdef", read_struct, si); traverse_node(rootdoc, "compounddef", read_structdesc, si); traverse_node(rootdoc, "compounddef", read_structname, si); ret = 0; qb_map_put(structures_map, refid, si); } xmlFreeDoc(doc); return ret; } static char *allcaps(const char *name) { - static char buffer[1024] = {'\0'}; + static char buffer[4096] = {'\0'}; size_t i; - for (i=0; i< strlen(name); i++) { - buffer[i] = toupper(name[i]); + if (name) { + size_t len = strnlen(name, 4096); + for (i=0; i< len; i++) { + buffer[i] = toupper(name[i]); + } + buffer[len] = '\0'; } - buffer[strlen(name)] = '\0'; return buffer; } -static void print_param(FILE *manfile, struct param_info *pi, int field_width, int bold, const char *delimiter) +/* + * Print a structure comment that would be too long + * to fit after the structure member, in a style ... + * well, in a style like this! + */ +static void print_long_structure_comment(FILE *manfile, char *comment) +{ + char *ptr = strtok(comment, " "); + int column = 7; + + fprintf(manfile, "\\fP /*"); + fprintf(manfile, "\n *"); + while (ptr) { + column += strlen(ptr)+1; + if (column > 80) { + fprintf(manfile, "\n *"); + column = 7; + } + fprintf(manfile, " %s", ptr); + ptr = strtok(NULL, " "); + } + fprintf(manfile, "\n */\n"); +} + +static void print_param(FILE *manfile, struct param_info *pi, int type_field_width, int name_field_width, int bold, const char *delimiter) { const char *asterisks = " "; char *type = pi->paramtype; int typelength = strlen(type); /* Reformat pointer params so they look nicer */ if (typelength > 0 && pi->paramtype[typelength-1] == '*') { asterisks=" *"; type = strdup(pi->paramtype); type[typelength-1] = '\0'; /* Cope with double pointers */ if (typelength > 1 && pi->paramtype[typelength-2] == '*') { asterisks="**"; type[typelength-2] = '\0'; } /* Tidy function pointers */ if (typelength > 1 && pi->paramtype[typelength-2] == '(') { asterisks="(*"; type[typelength-2] = '\0'; } } - fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\n", - bold?"\\fB":"", field_width, type, - asterisks, bold?"\\fP":"", pi->paramname, delimiter); + /* Print structure description if available */ + if (pi->paramdesc) { + /* Too long to go on the same line? */ + if (strlen(pi->paramdesc) > STRUCT_COMMENT_LENGTH) { + print_long_structure_comment(manfile, pi->paramdesc); + fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\n", + bold?"\\fB":"", type_field_width, type, + asterisks, bold?"\\fP":"", + pi->paramname?pi->paramname:"", delimiter); + } else { + /* Pad out so they all line up */ + int pad_length = name_field_width - + (pi->paramname?strlen(pi->paramname):0) + 1; + fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\\fR%*s/* %s*/\n", + bold?"\\fB":"", type_field_width, type, + asterisks, bold?"\\fP":"", + pi->paramname?pi->paramname:"", delimiter, + pad_length, " ", + pi->paramdesc); + } + } else { + fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\n", + bold?"\\fB":"", type_field_width, type, + asterisks, bold?"\\fP":"", + pi->paramname?pi->paramname:"", delimiter); + } if (type != pi->paramtype) { free(type); } } static void print_structure(FILE *manfile, struct struct_info *si) { struct param_info *pi; struct qb_list_head *iter; unsigned int max_param_length=0; + unsigned int max_param_name_length=0; fprintf(manfile, ".nf\n"); - fprintf(manfile, "\\fB\n"); if (si->brief_description) { fprintf(manfile, "%s\n", si->brief_description); } if (si->description) { fprintf(manfile, "%s\n", si->description); } qb_list_for_each(iter, &si->params_list) { pi = qb_list_entry(iter, struct param_info, list); if (strlen(pi->paramtype) > max_param_length) { max_param_length = strlen(pi->paramtype); } + if (strlen(pi->paramname) > max_param_name_length) { + max_param_name_length = strlen(pi->paramname); + } } + fprintf(manfile, "\\fB\n"); if (si->kind == STRUCTINFO_STRUCT) { fprintf(manfile, "struct %s {\n", si->structname); } else if (si->kind == STRUCTINFO_ENUM) { fprintf(manfile, "enum %s {\n", si->structname); } else { fprintf(manfile, "%s {\n", si->structname); } + fprintf(manfile, "\\fR\n"); qb_list_for_each(iter, &si->params_list) { + fprintf(manfile, "\\fB\n"); pi = qb_list_entry(iter, struct param_info, list); - print_param(manfile, pi, max_param_length, 0,";"); + print_param(manfile, pi, max_param_length, max_param_name_length, 1, ";"); } fprintf(manfile, "};\n"); fprintf(manfile, "\\fP\n"); fprintf(manfile, ".fi\n"); } -char *get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext) +cstring_t get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext, int add_nl) { xmlNode *this_tag; - char *tmp = NULL; - char buffer[4096] = {'\0'}; + cstring_t tmp; + cstring_t buffer = cstring_alloc(); for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "para") == 0) { tmp = get_text(this_tag, returntext, notetext); - strncat(buffer, tmp, sizeof(buffer)-1); - strncat(buffer, "\n", sizeof(buffer)-1); - free(tmp); - } - } + buffer = cstring_append_cstring(buffer, tmp); + if (add_nl) { + buffer = cstring_append_chars(buffer, "\n"); + } - if (buffer[0]) { - return strdup(buffer); - } else { - return NULL; + cstring_free(tmp); + } } + return buffer; } /* The text output is VERY basic and just a check that it's working really */ static void print_text(char *name, char *def, char *brief, char *args, char *detailed, struct qb_list_head *param_list, char *returntext, char *notetext) { printf(" ------------------ %s --------------------\n", name); printf("NAME\n"); if (brief) { printf(" %s - %s\n", name, brief); } else { printf(" %s\n", name); } printf("SYNOPSIS\n"); printf(" #include <%s%s>\n", header_prefix, headerfile); if (args) { printf(" %s %s\n\n", name, args); } if (detailed) { printf("DESCRIPTION\n"); printf(" %s\n", detailed); } if (returntext) { printf("RETURN VALUE\n"); printf(" %s\n", returntext); } if (notetext) { printf("NOTE\n"); printf(" %s\n", notetext); } } /* Print a long string with para marks in it. */ static void man_print_long_string(FILE *manfile, char *text) { char *next_nl; char *current = text; + int in_prog = 0; next_nl = strchr(text, '\n'); while (next_nl && *next_nl != '\0') { *next_nl = '\0'; - if (strlen(current)) { - fprintf(manfile, ".PP\n%s\n", current); + + // Don't format @code blocks + if (strncmp(current, ".nf", 3) == 0) { + in_prog = 1; + fprintf(manfile, "\n"); + } + + if (in_prog) { + fprintf(manfile, "%s\n", current); + } else { + if (strlen(current)) { + fprintf(manfile, ".PP\n%s\n", current); + } + } + + if (strncmp(current, ".fi", 3) == 0) { + in_prog = 0; + fprintf(manfile, "\n"); } *next_nl = '\n'; current = next_nl+1; next_nl = strchr(current, '\n'); } /* The bit at the end */ - if (strlen(current)) { + if (strlen(current) && !in_prog) { fprintf(manfile, ".PP\n%s\n", current); } } static void print_manpage(char *name, char *def, char *brief, char *args, char *detailed, struct qb_list_head *param_map, char *returntext, char *notetext) { char manfilename[PATH_MAX]; char gendate[64]; const char *dateptr = gendate; FILE *manfile; time_t t; struct tm *tm; qb_map_iter_t *map_iter; struct qb_list_head *iter; struct qb_list_head *tmp; const char *p; void *data; unsigned int max_param_type_len; unsigned int max_param_name_len; unsigned int num_param_descs; int param_count = 0; int param_num = 0; struct param_info *pi; t = time(NULL); tm = localtime(&t); if (!tm) { perror("unable to get localtime"); exit(1); } strftime(gendate, sizeof(gendate), "%Y-%m-%d", tm); if (manpage_date) { dateptr = manpage_date; } if (manpage_year == LONG_MIN) { manpage_year = tm->tm_year+1900; } snprintf(manfilename, sizeof(manfilename), "%s/%s.%s", output_dir, name, man_section); manfile = fopen(manfilename, "w+"); if (!manfile) { perror("unable to open output file"); printf("%s", manfilename); exit(1); } /* Work out the length of the parameters, so we can line them up */ max_param_type_len = 0; max_param_name_len = 0; num_param_descs = 0; - qb_list_for_each(iter, ¶ms_list) { + qb_list_for_each(iter, param_map) { pi = qb_list_entry(iter, struct param_info, list); /* It's mainly macros that break this, * macros need more work */ if (!pi->paramtype) { pi->paramtype = strdup(""); } if ((strlen(pi->paramtype) < LINE_LENGTH) && (strlen(pi->paramtype) > max_param_type_len)) { max_param_type_len = strlen(pi->paramtype); } if (strlen(pi->paramname) > max_param_name_len) { max_param_name_len = strlen(pi->paramname); } - if (pi->paramdesc) { + if (pi->paramdesc && pi->paramtype[0] != '\0') { num_param_descs++; } param_count++; } /* Off we go */ fprintf(manfile, ".\\\" Automatically generated man page, do not edit\n"); fprintf(manfile, ".TH %s %s %s \"%s\" \"%s\"\n", allcaps(name), man_section, dateptr, package_name, header); fprintf(manfile, ".SH NAME\n"); - if (brief) { + if (brief && not_all_whitespace(brief)) { fprintf(manfile, "%s \\- %s\n", name, brief); } else { fprintf(manfile, "%s\n", name); } fprintf(manfile, ".SH SYNOPSIS\n"); fprintf(manfile, ".nf\n"); fprintf(manfile, ".B #include <%s%s>\n", header_prefix, headerfile); if (def) { fprintf(manfile, ".sp\n"); fprintf(manfile, "\\fB%s\\fP(\n", def); - qb_list_for_each(iter, ¶ms_list) { + qb_list_for_each(iter, param_map) { pi = qb_list_entry(iter, struct param_info, list); - print_param(manfile, pi, max_param_type_len, 1, ++param_num < param_count?",":""); + if (pi->paramtype[0] != '\0') { + print_param(manfile, pi, max_param_type_len, 0, 1, ++param_num < param_count?",":""); + } } fprintf(manfile, ");\n"); fprintf(manfile, ".fi\n"); } if (print_params && num_param_descs) { fprintf(manfile, ".SH PARAMS\n"); qb_list_for_each(iter, ¶ms_list) { - pi = qb_list_entry(iter, struct param_info, list); + pi = qb_list_entry(iter, struct param_info, list); fprintf(manfile, "\\fB%-*s \\fP\\fI%s\\fP\n", (int)max_param_name_len, pi->paramname, pi->paramdesc); fprintf(manfile, ".PP\n"); } } if (detailed) { fprintf(manfile, ".SH DESCRIPTION\n"); man_print_long_string(manfile, detailed); } if (qb_map_count_get(used_structures_map)) { int first_struct = 1; map_iter = qb_map_iter_create(used_structures_map); for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) { struct struct_info *si; const char *refid = p; char *refname = data; /* If it's not been read in - go and look for it */ si = qb_map_get(structures_map, refid); if (!si) { if (!read_structure_from_xml(refid, refname)) { si = qb_map_get(structures_map, refid); } } /* Only print header if the struct files exist - sometimes they don't */ if (si && first_struct) { fprintf(manfile, ".SH STRUCTURES\n"); first_struct = 0; } if (si) { print_structure(manfile, si); fprintf(manfile, ".PP\n"); } } qb_map_iter_free(map_iter); fprintf(manfile, ".RE\n"); } - if (returntext) { + if (returntext || !qb_list_empty(&retval_list)) { fprintf(manfile, ".SH RETURN VALUE\n"); - man_print_long_string(manfile, returntext); + if (returntext) { + man_print_long_string(manfile, returntext); + } fprintf(manfile, ".PP\n"); } qb_list_for_each(iter, &retval_list) { pi = qb_list_entry(iter, struct param_info, list); fprintf(manfile, "\\fB%-*s \\fP%s\n", 10, pi->paramname, pi->paramdesc); fprintf(manfile, ".PP\n"); } if (notetext) { fprintf(manfile, ".SH NOTE\n"); man_print_long_string(manfile, notetext); } fprintf(manfile, ".SH SEE ALSO\n"); fprintf(manfile, ".PP\n"); fprintf(manfile, ".nh\n"); fprintf(manfile, ".ad l\n"); param_num = 0; map_iter = qb_map_iter_create(function_map); for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) { /* Exclude us! */ if (strcmp(data, name)) { fprintf(manfile, "\\fI%s\\fR(%s)%s", (char *)data, man_section, param_num < (num_functions - 1)?", ":""); } param_num++; } qb_map_iter_free(map_iter); fprintf(manfile, "\n"); fprintf(manfile, ".ad\n"); fprintf(manfile, ".hy\n"); fprintf(manfile, ".SH \"COPYRIGHT\"\n"); fprintf(manfile, ".PP\n"); - fprintf(manfile, "Copyright (C) %4ld-%4ld %s, Inc. All rights reserved.\n", start_year, manpage_year, company); + if (header_copyright[0] == 'C') { + fprintf(manfile, "%s", header_copyright); /* String already contains trailing NL */ + } else { + fprintf(manfile, "Copyright (C) %4ld-%4ld %s, Inc. All rights reserved.\n", start_year, manpage_year, company); + } fclose(manfile); /* Free the params & retval info */ qb_list_for_each_safe(iter, tmp, ¶ms_list) { pi = qb_list_entry(iter, struct param_info, list); qb_list_del(&pi->list); free_paraminfo(pi); } qb_list_for_each_safe(iter, tmp, &retval_list) { pi = qb_list_entry(iter, struct param_info, list); qb_list_del(&pi->list); free_paraminfo(pi); } /* Free used-structures map */ map_iter = qb_map_iter_create(used_structures_map); for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) { qb_map_rm(used_structures_map, p); free(data); } } /* Same as traverse_members, but to collect function names */ static void collect_functions(xmlNode *cur_node, void *arg) { xmlNode *this_tag; char *kind; char *name = NULL; if (cur_node->name && strcmp((char *)cur_node->name, "memberdef") == 0) { kind = get_attr(cur_node, "kind"); if (kind && strcmp(kind, "function") == 0) { for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) { name = strdup((char *)this_tag->children->content); } } if (name) { qb_map_put(function_map, name, name); num_functions++; } } } } /* Same as traverse_members, but to collect enums. The behave like structures for, but, for some reason, are in the main XML file rather than their own */ static void collect_enums(xmlNode *cur_node, void *arg) { xmlNode *this_tag; struct struct_info *si; char *kind; char *refid = NULL; char *name = NULL; if (cur_node->name && strcmp((char *)cur_node->name, "memberdef") == 0) { kind = get_attr(cur_node, "kind"); if (kind && strcmp(kind, "enum") == 0) { refid = get_attr(cur_node, "id"); for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) { name = strdup((char *)this_tag->children->content); } } if (name) { si = malloc(sizeof(struct struct_info)); if (si) { memset(si, 0, sizeof(*si)); si->kind = STRUCTINFO_ENUM; qb_list_init(&si->params_list); si->structname = strdup(name); traverse_node(cur_node, "enumvalue", read_struct, si); qb_map_put(structures_map, refid, si); } } } } } static void traverse_members(xmlNode *cur_node, void *arg) { xmlNode *this_tag; + qb_list_init(¶ms_list); + /* if arg == NULL then we're generating a page for the whole header file */ if ((cur_node->name && (strcmp((char *)cur_node->name, "memberdef") == 0)) || ((arg == NULL) && cur_node->name && strcmp((char *)cur_node->name, "compounddef")) == 0) { char *kind = NULL; char *def = NULL; char *args = NULL; char *name = NULL; char *brief = NULL; char *detailed = NULL; char *returntext = NULL; char *notetext = NULL; int type; kind=def=args=name=NULL; kind = get_attr(cur_node, "kind"); for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (!this_tag->children || !this_tag->children->content) continue; if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "definition") == 0) def = strdup((char *)this_tag->children->content); if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "argsstring") == 0) args = strdup((char *)this_tag->children->content); if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) name = strdup((char *)this_tag->children->content); if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "briefdescription") == 0) { - brief = get_texttree(&type, this_tag, &returntext, ¬etext); - if (brief) { - /* - * apparently brief text contains extra trailing space and a \n. - * remove them. - */ - brief[strlen(brief) - 2] = '\0'; + cstring_t tmp = get_texttree(&type, this_tag, &returntext, ¬etext, 1); + if (!brief) { + brief = cstring_to_chars(tmp); + } else { + fprintf(stderr, "ERROR function %s has 2 briefdescription tags\n", name?name:"unknown"); } + cstring_free(tmp); } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "detaileddescription") == 0) { - detailed = get_texttree(&type, this_tag, &returntext, ¬etext); + cstring_t tmp = get_texttree(&type, this_tag, &returntext, ¬etext, 1); + if (!detailed) { + detailed = cstring_to_chars(tmp); + } else { + fprintf(stderr, "ERROR function %s has 2 detaileddescription tags\n", name?name:"unknown"); + } + cstring_free(tmp); } /* Get all the params */ if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "param") == 0) { - char *param_type = get_child(this_tag, "type"); - char *param_name = get_child(this_tag, "declname"); + cstring_t param_type = get_child(this_tag, "type"); + cstring_t param_name = get_child(this_tag, "declname"); struct param_info *pi = malloc(sizeof(struct param_info)); if (pi) { - pi->paramname = param_name; - pi->paramtype = param_type; + pi->paramname = cstring_to_chars(param_name); + pi->paramtype = cstring_to_chars(param_type); pi->paramdesc = NULL; qb_list_add_tail(&pi->list, ¶ms_list); } } } if (arg == headerfile) { /* Print header page */ name = (char*)headerfile; if (print_man) { if (!quiet) { printf("Printing header manpage for %s\n", name); } print_manpage(name, def, brief, args, detailed, ¶ms_list, returntext, notetext); } else { print_text(name, def, brief, args, detailed, ¶ms_list, returntext, notetext); } } if (kind && strcmp(kind, "function") == 0) { /* Make sure function has a doxygen description */ if (!detailed) { fprintf(stderr, "No detailed description for function '%s' - please fix this\n", name); } if (!name) { fprintf(stderr, "Internal error - no name found for function\n"); } else { if (print_man) { if (!quiet) { printf("Printing manpage for %s\n", name); } print_manpage(name, def, brief, args, detailed, ¶ms_list, returntext, notetext); } else { print_text(name, def, brief, args, detailed, ¶ms_list, returntext, notetext); } } } free(kind); free(def); free(args); free(name); + free(brief); + free(detailed); } } static void traverse_node(xmlNode *parentnode, const char *leafname, void (do_members(xmlNode*, void*)), void *arg) { xmlNode *cur_node; for (cur_node = parentnode->children; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE && cur_node->name && strcmp((char*)cur_node->name, leafname)==0) { do_members(cur_node, arg); continue; } if (cur_node->type == XML_ELEMENT_NODE) { traverse_node(cur_node, leafname, do_members, arg); } } } static void usage(char *name) { printf("Usage:\n"); printf(" %s [OPTIONS] \n", name); printf("\n"); printf(" This is a tool to generate API manpages from a doxygen-annotated header file.\n"); printf(" First run doxygen on the file and then run this program against the main XML file\n"); printf(" it created and the directory containing the ancilliary files. It will then\n"); printf(" output a lot of *.3 man page files which you can then ship with your library.\n"); printf("\n"); printf(" You will need to invoke this program once for each .h file in your library,\n"); printf(" using the name of the generated .xml file. This file will usually be called\n"); printf(" something like _8h.xml, eg qbipcs_8h.xml\n"); printf("\n"); printf(" If you want HTML output then simpy use nroff on the generated files as you\n"); printf(" would do with any other man page.\n"); printf("\n"); printf(" -a Print ASCII dump of man pages to stdout\n"); printf(" -m Write man page files to \n"); printf(" -P Print PARAMS section\n"); printf(" -g Print general man page for the whole header file\n"); + printf(" -c Use the Copyright date from the header file (if one can be found)\n"); + printf(" -O Directory for the orignal header file. Often needed by -c above\n"); printf(" -s Write man pages into section Use name. default \n"); printf(" -H
Set header (default \"Programmer's Manual\"\n"); printf(" -I Set include filename (default taken from xml)\n"); printf(" -i Prefix for include files. eg qb/ (default \"\")\n"); printf(" -C Company name in copyright (defaults to Red Hat)\n"); printf(" -D Date to print at top of man pages (format not checked, default: today)\n"); printf(" -S Start year to print at end of copyright line (default: 2010)\n"); printf(" -Y Year to print at end of copyright line (default: today's year)\n"); printf(" -o Write all man pages to (default .)\n"); printf(" -d Directory for XML files (./xml/)\n"); printf(" -h Print this usage text\n"); } static long get_year(char *optionarg, char optionchar) { long year = strtol(optionarg, NULL, 10); /* * Don't make too many assumptions about the year. I was on call at the * 2000 rollover. #experience */ if (year == LONG_MIN || year == LONG_MAX || year < 1900) { fprintf(stderr, "Value passed to -%c is not a valid year number\n", optionchar); return 0; } return year; } int main(int argc, char *argv[]) { xmlNode *rootdoc; xmlDocPtr doc; int opt; char xml_filename[PATH_MAX]; - while ( (opt = getopt_long(argc, argv, "H:amqgPD:Y:s:S:d:o:p:f:I:i:C:h?", NULL, NULL)) != EOF) + while ( (opt = getopt_long(argc, argv, "H:amqgcPD:Y:s:S:d:o:p:f:I:i:C:O:h?", NULL, NULL)) != EOF) { switch(opt) { case 'a': print_ascii = 1; print_man = 0; break; case 'm': print_man = 1; print_ascii = 0; break; case 'P': print_params = 1; break; case 'g': print_general = 1; break; case 'q': quiet = 1; break; + case 'c': + use_header_copyright = 1; + break; case 'I': headerfile = optarg; break; case 'i': header_prefix = optarg; break; case 'C': company = optarg; break; case 's': man_section = optarg; break; case 'S': start_year = get_year(optarg, 'S'); if (start_year == 0) { return 1; } break; case 'd': xml_dir = optarg; break; case 'D': manpage_date = optarg; break; case 'Y': manpage_year = get_year(optarg, 'Y'); if (manpage_year == 0) { return 1; } break; case 'p': package_name = optarg; break; case 'H': header = optarg; break; case 'o': output_dir = optarg; break; + case 'O': + header_src_dir = optarg; + break; case '?': case 'h': usage(argv[0]); return 0; } } if (argv[optind]) { xml_file = argv[optind]; } if (!xml_file) { usage(argv[0]); exit(1); } if (!quiet) { printf("reading %s ... ", xml_file); } snprintf(xml_filename, sizeof(xml_filename), "%s/%s", xml_dir, xml_file); doc = xmlParseFile(xml_filename); if (doc == NULL) { fprintf(stderr, "Error: unable to read xml file %s\n", xml_filename); exit(1); } rootdoc = xmlDocGetRootElement(doc); if (!rootdoc) { fprintf(stderr, "Can't find \"document root\"\n"); exit(1); } if (!quiet) printf("done.\n"); /* Get our header file name */ if (!headerfile) { traverse_node(rootdoc, "compounddef", read_headername, &headerfile); + + if (use_header_copyright) { + /* And get the copyright line from this file if we can */ + char file_path[PATH_MAX]; + char file_line[256]; + FILE *hfile; + int lineno = 0; + + snprintf(file_path, sizeof(file_path), "%s/%s", header_src_dir, headerfile); + hfile = fopen(file_path, "r"); + if (hfile) { + /* Don't look too far, this should be at the top */ + while (!feof(hfile) && (lineno++ < 10)) { + if (fgets(file_line, sizeof(file_line)-1, hfile)) { + if (strncmp(file_line, " * Copyright", 12) == 0) { + /* Keep the NL at the end of the buffer, it save us printing one */ + strncpy(header_copyright, file_line+3, sizeof(header_copyright)-1); + break; + } + } + } + fclose(hfile); + } + } } + /* Default to *something* if it all goes wrong */ if (!headerfile) { headerfile = "unknown.h"; } qb_list_init(¶ms_list); qb_list_init(&retval_list); structures_map = qb_hashtable_create(10); function_map = qb_hashtable_create(10); used_structures_map = qb_hashtable_create(10); /* Collect functions */ traverse_node(rootdoc, "memberdef", collect_functions, NULL); /* Collect enums */ traverse_node(rootdoc, "memberdef", collect_enums, NULL); /* print pages */ traverse_node(rootdoc, "memberdef", traverse_members, NULL); if (print_general) { /* Generate and print a page for the headerfile itself */ traverse_node(rootdoc, "compounddef", traverse_members, (char *)headerfile); } return 0; }