summaryrefslogtreecommitdiff
path: root/manpage.c
diff options
context:
space:
mode:
authorbehdad <behdad>2004-05-03 05:17:48 +0000
committerbehdad <behdad>2004-05-03 05:17:48 +0000
commit577ed4095383ef5284225d45709e6b5f0598a064 (patch)
tree6c7d0ce55124a688b4d7050e684d9d7a1e3aa71d /manpage.c
Initial revisionHEADoriginmaster
Diffstat (limited to 'manpage.c')
-rw-r--r--manpage.c1347
1 files changed, 1347 insertions, 0 deletions
diff --git a/manpage.c b/manpage.c
new file mode 100644
index 0000000..8f6c047
--- /dev/null
+++ b/manpage.c
@@ -0,0 +1,1347 @@
+/* $Id: manpage.c,v 1.1 2004-05-03 05:17:48 behdad Exp $
+ * stuff to do with manual page outputing
+ */
+
+/*
+ * This file has been modified by Manoj Srivastava <srivasta@debian.org>
+ * to incorporate a patch that was also submitted to the author. The change
+ * shall be incorporated upstream in due course.
+ */
+
+#include "c2man.h"
+
+#include <errno.h>
+#include <ctype.h>
+
+#include "manpage.h"
+#include "strconcat.h"
+#include "strappend.h"
+#include "semantic.h"
+#include "output.h"
+
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+
+/* list of manual pages */
+ManualPage *firstpage = NULL;
+ManualPage **lastpagenext = &firstpage;
+
+void dummy() {}
+
+void
+new_manual_page(comment, decl_spec, declarator)
+ char *comment;
+ DeclSpec *decl_spec;
+ Declarator *declarator;
+{
+ ManualPage *newpage;
+
+ /* check that we really want a man page for this */
+ if ((!comment) ||
+ !inbasefile ||
+ (!variables_out && !is_function_declarator(declarator)) ||
+ (decl_spec->flags & DS_JUNK) ||
+ (!static_out && (decl_spec->flags & DS_STATIC) && !header_file) ||
+
+ /* only document extern stuff if it's in a header file, or includes a
+ * function definition.
+ */
+ ((decl_spec->flags & DS_EXTERN) && !header_file &&
+ declarator->type != DECL_FUNCDEF))
+ {
+ free_decl_spec(decl_spec);
+ free_declarator(declarator);
+ safe_free(comment);
+ return;
+ }
+
+ declarator->comment = comment;
+
+ newpage = (ManualPage *)safe_malloc(sizeof *newpage);
+ newpage->decl_spec = (DeclSpec *)safe_malloc(sizeof *newpage->decl_spec);
+ newpage->declarator = declarator;
+
+ *newpage->decl_spec = *decl_spec;
+ newpage->sourcefile = strduplicate(basefile);
+ newpage->sourcetime = basetime;
+
+ *lastpagenext = newpage;
+ newpage->next = NULL;
+ lastpagenext = &newpage->next;
+}
+
+void free_manual_page(page)
+ ManualPage *page;
+{
+ free_decl_spec(page->decl_spec);
+ free(page->decl_spec);
+ free_declarator(page->declarator);
+ safe_free(page->sourcefile);
+}
+
+/* free the list of manual pages */
+void free_manual_pages(first)
+ ManualPage *first;
+{
+ ManualPage *page, *next;
+
+ /* free any terse description read from the file */
+ if (group_terse && !terse_specified)
+ {
+ free(group_terse);
+ group_terse = NULL;
+ }
+
+ for (page = first;page;page = next)
+ {
+ next = page->next;
+ free_manual_page(page);
+ free(page);
+ }
+}
+
+/* allocate a substring starting at start, ending at end (NOT including *end) */
+char *alloc_string(start, end)
+ const char *start;
+ const char *end;
+{
+ int len = end - start;
+ char *ret;
+ if (len == 0) return NULL;
+
+ ret = (char *)safe_malloc((size_t)len+1);
+
+ strncpy(ret,start,len);
+ ret[len] = '\0';
+
+ return ret;
+}
+
+/* remember the terse description from the first comment in a file */
+void remember_terse(comment)
+ char *comment;
+{
+ char *c, *d;
+
+ enum { STUFF, LEADSPACE, DASH, TRAILSPACE, FOUND } state = STUFF;
+
+ /* if we've found a terse comment in a previous file, or one was
+ * specified on the command line, forget it.
+ */
+ if (group_terse) return;
+
+ /* look for a whitespace surrounded sequence of dashes to skip */
+ for (c = comment;*c && state != FOUND;c++)
+ {
+ switch (state)
+ {
+ case STUFF: if (isspace(*c)) state = LEADSPACE;
+ break;
+ case LEADSPACE: if (*c == '-') state = DASH;
+ else if (!isspace(*c)) state = STUFF;
+ break;
+ case DASH: if (isspace(*c)) state = TRAILSPACE;
+ else if (*c != '-') state = STUFF;
+ break;
+ case TRAILSPACE:if (!isspace(*c)) { c--; state = FOUND; }
+ break;
+ case FOUND: break;
+ }
+ }
+
+ /* if no dashes were found, go back to the start */
+ if (state != FOUND) c = comment;
+
+ d = c + 1;
+
+ while (*d && *d != '\n')
+ d++;
+
+ group_terse = alloc_string(c,d);
+}
+
+/* output a comment in man page form, followed by a newline */
+void
+output_comment(comment)
+const char *comment;
+{
+ if (!comment || !comment[0])
+ output->text("Not Documented.");
+ else if (fixup_comments)
+ output->description(comment);
+ else
+ output->text(comment);
+
+ output->character('\n');
+}
+
+/* output the phrase "a[n] <type name>" */
+void output_conjunction(text)
+char *text;
+{
+ output->character('a');
+ if (strchr("aAeEiIoOuU",text[0])) output->character('n');
+ output->character(' ');
+ output->code(text);
+}
+
+/* output the description for an identifier; be it return value or param */
+static void output_identifier_description(comment, outfunc,
+ decl_spec, declarator)
+ const char *comment; /* comment for this identifier */
+ void (*outfunc) _((const char *)); /* function to output comment */
+ const DeclSpec *decl_spec;
+ const Declarator *declarator;
+{
+ /* one day, this may document the contents of structures too */
+
+ /* output list of possible enum values, if any */
+ if (decl_spec->enum_list)
+ {
+ int maxtaglen = 0;
+ char *longestag = NULL;
+ int descriptions = 0;
+ int entries = 0;
+ Enumerator *e;
+ int is_first = 1;
+ boolean started = FALSE;
+
+ /* don't output the "Not Doc." message for enums */
+ if (comment)
+ {
+ (*outfunc)(comment);
+ output->blank_line();
+ }
+
+ /* see if any have descriptions */
+ for (e = decl_spec->enum_list->first; e; e = e->next)
+ if (e->name[0] != '_')
+ {
+ int taglen = strlen(e->name);
+ if (taglen > maxtaglen)
+ {
+ maxtaglen = taglen;
+ longestag = e->name;
+ }
+ if (e->comment) descriptions = 1;
+ entries++;
+ }
+
+ /* if there are a lot of them, the list may be automatically generated,
+ * and probably isn't wanted in every manual page.
+ */
+ if (entries > 20)
+ {
+ char entries_s[15];
+ sprintf(entries_s, "%d", entries);
+ output->text("Since there are ");
+ output->text(entries_s);
+ output->text(" possible values for ");
+ output_conjunction(decl_spec->text);
+ output->text(", they are not all listed here.\n");
+ }
+ else if (entries > 0) /* skip the pathological case */
+ {
+ /* the number of possibilities is reasonable; list them all */
+ output->text("Possible values for ");
+ output_conjunction(decl_spec->text);
+ output->text(" are as follows:\n");
+
+ for (e = decl_spec->enum_list->first; e; e = e->next)
+ {
+ /* don't print names with a leading underscore! */
+ if (e->name[0] == '_') continue;
+
+ if (e->group_comment)
+ {
+ /* break out of table mode for the group comment */
+ if (started)
+ {
+ if (descriptions)
+ output->table_end();
+ else
+ output->list_end();
+ started = FALSE;
+ }
+ output->indent();
+ output_comment(e->group_comment);
+ }
+
+ if (!started)
+ {
+ if (descriptions)
+ output->table_start(longestag);
+ else
+ output->list_start();
+ started = TRUE;
+ }
+
+ if (descriptions)
+ output->table_entry(e->name, e->comment);
+ else
+ {
+ if (!is_first)
+ output->list_separator();
+ is_first = 0;
+ output->list_entry(e->name);
+ }
+ }
+
+ if (started)
+ {
+ if (descriptions)
+ output->table_end();
+ else
+ output->list_end();
+ }
+ }
+ }
+ else
+ (*outfunc)(comment);
+}
+
+/* is there automatic documentation here? */
+static boolean auto_documented(page)
+const ManualPage *page;
+{
+ /* one day we may handle structs too */
+ return
+ page->decl_spec->enum_list != NULL; /* enums are self-documenting. */
+}
+
+/* decide if a manual page needs a RETURNS section.
+ * If this is true, then output_identifier_description must be able to generate
+ * sensible output for it.
+ */
+static boolean needs_returns_section(page)
+const ManualPage *page;
+{
+ return
+ (page->returns && page->returns[0]) ||
+ (auto_documented(page) && is_function_declarator(page->declarator));
+}
+
+/* does this declarator have documented parameters? */
+boolean has_documented_parameters(d)
+const Declarator *d;
+{
+ if (has_parameters(d))
+ {
+ Parameter *p;
+
+ for (p = d->head->params.first; p != NULL; p = p->next)
+ if (p->declarator->comment || always_document_params)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Output the list of function parameter descriptions.
+ */
+void
+output_parameter_descriptions (params, function)
+ParameterList *params;
+char *function;
+{
+ Parameter *p;
+ boolean tag_list_started = FALSE;
+
+ for (p = params->first; p != NULL; p = p->next)
+ {
+ if (p->suppress ||
+ (!always_document_params && p->declarator->comment == NULL))
+ continue;
+
+ if (!tag_list_started)
+ {
+ output->tag_list_start();
+ tag_list_started = TRUE;
+ }
+
+ if (p->duplicate)
+ output->tag_entry_start_extra();
+ else
+ output->tag_entry_start();
+
+ output_parameter(p);
+
+ /* include function name if it's a duplicate */
+ if (p->duplicate)
+ output->tag_entry_end_extra(function);
+ else
+ output->tag_entry_end();
+
+ output_identifier_description(p->declarator->comment, output_comment,
+ &p->decl_spec, p->declarator);
+ }
+
+ if (tag_list_started)
+ output->tag_list_end();
+}
+
+/* split out the 'Returns:' section of a function comment */
+boolean
+split_returns_comment(comment, description, returns)
+ char *comment;
+ char **description;
+ char **returns;
+{
+ char *retstart;
+
+ for (retstart = comment;
+ retstart;
+ retstart = strchr(retstart,'\n'))
+ {
+ if (*retstart == '\n') retstart++; /* skip the newline */
+
+ if (!strncmpi(retstart, "returns",(size_t)7))
+ {
+ char *descend = retstart - 2; /* back before newline */
+
+ /* go back to the end of the description in case there were
+ * linefeeds before the returns.
+ */
+ while (descend > comment && isspace(*descend))
+ descend--;
+
+ *description =
+ descend > comment ? alloc_string(comment,descend+1) : NULL;
+
+ retstart += 7;
+
+ while (*retstart == ':' || isspace(*retstart))
+ retstart++;
+
+ if (*retstart)
+ *returns = strduplicate(retstart);
+ else
+ *returns = NULL;
+ return TRUE;
+ }
+ }
+
+ *description = comment;
+ *returns = NULL;
+ return FALSE;
+}
+
+/* skip to past the dash on the first line, if there is one
+ * The dash must be surrounded by whitespace, so hyphens are not skipped.
+ */
+const char *skipdash(c)
+const char *c;
+{
+ const char *d;
+
+ /* ignore anything on the first line, up to a dash (if any) */
+ for (d = c + 1; *d && *d != '\n' && *d != '-'; d++)
+ ;
+
+ if (isspace(d[-1]) && d[0] == '-' && isspace(d[1]))
+ {
+ do
+ d++;
+ while (*d && *d != '\n' && isspace(*d));
+
+ if (*d && *d != '\n') c = d;
+ }
+ return c;
+}
+
+/* split the function comment into manual page format.
+ * returns TRUE if the DESCRIPTION field was explicit.
+ */
+boolean
+split_function_comment(comment, identifier_name,
+ terse, description, returns, extra_sections)
+ const char *comment;
+ const char *identifier_name;
+ char **terse;
+ char **description;
+ char **returns;
+ Section **extra_sections;
+{
+ const char *c, *start_text = NULL, *end_text = NULL;
+ char **put_ptr = NULL;
+ Section *first_section, **lastnextsection = &first_section;
+ boolean explicit_description = FALSE;
+ boolean lastblank = TRUE;
+ boolean skip_dash = FALSE;
+
+ *description = *returns = NULL;
+ if (terse) *terse = NULL;
+
+ /* for each line... */
+ for (c = comment; *c;)
+ {
+ const char *start_line = c;
+ boolean section_heading;
+ /* remember if it's a blank line */
+ if (*c == '\n')
+ {
+ lastblank = TRUE;
+ c++;
+ continue;
+ }
+
+ /* if the last one was blank, perhaps this one is a section heading
+ */
+ if (lastblank)
+ {
+ boolean need_colon = FALSE;
+
+ /* see if we've found the start of a SECTION */
+ while (isalnum(*c) || *c == ' ' || *c == '/')
+ {
+ if (isspace(*c)) need_colon = TRUE;
+ c++;
+ }
+
+ section_heading = (!need_colon && *c == '\n') ||
+ (*c == ':' && (!need_colon || *(c+1) == '\n')) ||
+ (!need_colon && *c == '\0' && start_line == comment);
+ }
+ else
+ section_heading = FALSE;
+
+ lastblank = FALSE; /* this one's not blank; for next time */
+
+ if (section_heading)
+ {
+ size_t section_len = c - start_line; /* length of section name */
+
+ /* yes, we've found a SECTION; store the previous one (if any) */
+ if (put_ptr && start_text)
+ {
+ if (skip_dash) start_text = skipdash(start_text);
+ *put_ptr = alloc_string(start_text,end_text);
+ }
+
+ skip_dash = FALSE;
+
+ /* check for comments that start with the name of the identifier */
+ if (start_line == comment &&
+ !strncmp(start_line, identifier_name, section_len))
+ {
+ put_ptr = description;
+ }
+
+ /* only accept NAME if not grouped */
+ else if (terse &&
+ (!strncmpi(start_line,"NAME", section_len) ||
+ !strncmpi(start_line,"FUNCTION", section_len) ||
+ !strncmpi(start_line,"PROCEDURE", section_len) ||
+ !strncmpi(start_line,"ROUTINE", section_len))
+ )
+
+ {
+ put_ptr = terse;
+ skip_dash = TRUE;
+ }
+ else if (!strncmpi(start_line,"DESCRIPTION", section_len))
+ {
+ explicit_description = TRUE;
+ put_ptr = description;
+ }
+ else if (!strncmpi(start_line,"RETURNS", section_len))
+ {
+ put_ptr = returns;
+ }
+ else
+ {
+ /* allocate a new section */
+ Section *new_section =
+ (Section *)safe_malloc(sizeof *new_section);
+
+ *lastnextsection = new_section;
+ lastnextsection = &new_section->next;
+
+ new_section->name = alloc_string(start_line,c);
+ strtoupper(new_section->name);
+ new_section->text = NULL;
+ new_section->been_output = FALSE; /* not been output yet */
+ put_ptr = &new_section->text;
+ }
+
+ /* defer decision about where text starts till we find some */
+ start_text = NULL;
+
+ if (*c == ':') /* skip the terminating : */
+ {
+ c++;
+
+ /* skip forward to the start of the text */
+ while (*c && *c != '\n' && isspace(*c))
+ c++;
+
+ /* if we find the text here, then we've got it */
+ if (*c && *c != '\n')
+ start_text = c;
+ }
+ }
+ else
+ {
+ /* are we looking at the top of the function comment? */
+ if (start_line == comment)
+ {
+ /* only look for terse comment if not grouped together */
+ if (terse)
+ {
+ const char *endterse, *afterdash = skipdash(start_line);
+
+ /* find the end of the terse comment */
+ while (*c && *c != '\n')
+ {
+ c++;
+ /* '.' ends terse description only if it ends sentence */
+ if (*(c-1)=='.' && *c && isspace(*c))
+ break;
+ }
+
+ endterse = c;
+ *terse = alloc_string(
+ afterdash < endterse ? afterdash : start_line,
+ endterse);
+
+ /* skip it if it's a ., and any trailing spaces */
+ if (*c == '.')
+ do c++; while (*c && *c != '\n' && isspace(*c));
+
+ start_text = NULL; /* look for it */
+
+ if (*c && *c != '\n')
+ /* actually, it's a description, starting here */
+ start_text = c;
+ }
+ /* must be a description starting at the beginning of the line.
+ */
+ else
+ start_text = start_line;
+
+ put_ptr = description;
+ }
+ else
+ /* have we just located the first real text in a section? */
+ if (put_ptr && !start_text) start_text = start_line;
+ }
+
+ /* skip the line */
+ if (*c && *c != '\n')
+ while (*c && *c != '\n') c++;
+
+ end_text = c; /* so far, the text ends at the end of this line */
+ if (*c) c++;
+ }
+
+ /* store the last one */
+ if (put_ptr && start_text)
+ {
+ if (skip_dash) start_text = skipdash(start_text);
+ *put_ptr = alloc_string(start_text,end_text);
+ }
+
+ /* terminate (or nuke) section list */
+ *lastnextsection = NULL;
+
+ *extra_sections = first_section;
+
+ return explicit_description;
+}
+
+/* see if two parameters are declared identically */
+boolean params_identical(first, second)
+ Parameter *first;
+ Parameter *second;
+{
+ return
+ first->decl_spec.flags == second->decl_spec.flags &&
+
+ /* there may be no decl_spec.text if it's an ellipsis arg */
+ ((!first->decl_spec.text && !second->decl_spec.text) ||
+ (first->decl_spec.text && second->decl_spec.text &&
+ !strcmp(first->decl_spec.text, second->decl_spec.text))) &&
+
+ ((!first->declarator->text && !second->declarator->text) ||
+ (first->declarator->text && second->declarator->text &&
+ !strcmp(first->declarator->text, second->declarator->text)));
+}
+
+/* search all the parameters in this grouped manual page for redundancies */
+boolean mark_duplicate_parameters(firstpage)
+ ManualPage *firstpage;
+{
+ Parameter *param;
+ boolean any = FALSE;
+ ManualPage *page;
+
+ for (page = firstpage; page; page = page->next)
+ {
+ if (has_parameters(page->declarator))
+ for (param = page->declarator->head->params.first; param;
+ param = param->next)
+ {
+ ManualPage *otherpage;
+ Parameter *otherparam;
+
+ if (always_document_params || param->declarator->comment)
+ any = TRUE;
+
+ for (otherpage = page->next; otherpage;
+ otherpage = otherpage->next)
+ {
+ if (has_parameters(otherpage->declarator))
+ for (otherparam = otherpage->declarator->head->params.first;
+ otherparam;
+ otherparam = otherparam->next)
+ {
+ /* do these two look the same? */
+ if (params_identical(param, otherparam))
+ {
+ /* order is important for bit positions */
+ enum { NEITHER, US, THEM, BOTH } has_comm = NEITHER;
+
+ /* work out who has the comment */
+ if (param->declarator->comment) has_comm |= US;
+ if (otherparam->declarator->comment) has_comm |= THEM;
+
+ switch(has_comm)
+ {
+ case NEITHER:
+ case US:
+ otherparam->suppress = TRUE;
+ break;
+ case THEM:
+ param->suppress = TRUE;
+ break;
+ case BOTH:
+ if (!strcmp(param->declarator->comment,
+ otherparam->declarator->comment))
+ otherparam->suppress = TRUE;
+ else
+ {
+ param->duplicate = TRUE;
+ otherparam->duplicate = TRUE;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return any;
+}
+
+/* output a formatting string so that it works with filling on */
+void output_format_string(fmt)
+const char *fmt;
+{
+ while (*fmt)
+ {
+ output->character(*fmt);
+
+ if (*fmt++ == '\n')
+ output->break_line(); /* break the line */
+ }
+}
+
+/* write the warning for the header */
+void output_warning()
+{
+ output->comment();
+ output->text("WARNING! THIS FILE WAS GENERATED AUTOMATICALLY BY ");
+ output->text(progname);
+ output->text("!\n");
+ output->comment();
+ output->text("DO NOT EDIT! CHANGES MADE TO THIS FILE WILL BE LOST!\n");
+}
+
+void output_includes()
+{
+ IncludeFile *incfile;
+
+ for (incfile = first_include; incfile; incfile=incfile->next)
+ {
+ char *name = incfile->name;
+ boolean surrounded = *name == '"' || *name == '<';
+
+ output->text("#include ");
+ if (!surrounded) output->character('<');
+ output->text(name);
+ if (!surrounded) output->character('>');
+ output->text("\n");
+ output->break_line();
+ }
+}
+
+int exclude_section(section)
+const char *section;
+{
+ ExcludeSection *exclude;
+
+ for (exclude = first_excluded_section ; exclude ; exclude = exclude->next)
+ if (!strcmp(section, exclude->name)) return 1;
+
+ return 0;
+}
+
+
+/* Writes the entire contents of the manual page specified by basepage. */
+void
+output_manpage(firstpage, basepage, input_files, title, section)
+ /* the first page in the list of all manual pages. This is used to build
+ * the SEE ALSO section of related pages when group_together is false.
+ */
+ ManualPage *firstpage;
+
+ /* the base page from which the output manual page will be generated. if
+ * group_together indicates that the user wanted grouped pages, basepage
+ * will always be the same as firstpage, and all the ManualPage's in the
+ * list will be grouped together into the one output page.
+ */
+ ManualPage *basepage;
+
+ int input_files;
+ const char *title;
+ const char *section;
+{
+ ManualPage *page;
+ boolean need_returns;
+ char *terseout, *terse = NULL;
+ boolean exclude_description = exclude_section("DESCRIPTION");
+
+ /* check if there's more than one page in the group */
+ boolean grouped = group_together && firstpage->next;
+
+ /* split up all the function comments for this page */
+ for (page = basepage; page; page = page->next)
+ {
+ boolean explicit_description =
+ split_function_comment(page->declarator->comment,
+ page->declarator->name,
+ group_together ? (char **)NULL : &terse,
+ &page->description,&page->returns,&page->first_section);
+
+ /* we may need to look harder if RETURNS wasn't easy to find in the
+ * function comment.
+ */
+ if (page->returns == NULL)
+ {
+ /* if there was a retcomment supplied by the declarator, use it if
+ * we couldn't split anything from the function comment.
+ */
+ if (page->declarator->retcomment)
+ {
+ page->returns = page->declarator->retcomment;
+
+ /* page->returns now owns the string */
+ page->declarator->retcomment = NULL;
+ }
+ else
+ /* if there wasn't a RETURNS section, and the DESCRIPTION field
+ * was not explicit, see if we can split one out of the
+ * description field.
+ */
+ if (!explicit_description)
+ {
+ char *newdesc;
+ if (split_returns_comment(page->description, &newdesc,
+ &page->returns))
+ {
+ free(page->description);
+ page->description = newdesc;
+ }
+ }
+ }
+
+ if (!group_together) break;
+ }
+
+ /* work out what we'll actually print as a terse description */
+ terseout = group_terse ? group_terse : (terse ? terse : "Not Described");
+
+ output->header(basepage, input_files, grouped,
+ title ? title : basepage->declarator->name, terseout, section);
+
+ output->name(NULL);
+ /* output the names of all the stuff documented on this page */
+ for (page = basepage; page; page = page->next)
+ {
+ output->name(page->declarator->name);
+
+ if (!group_together) break;
+
+ if (page->next) output->text(",\n");
+ }
+
+ output->terse_sep();
+ output->text(terseout);
+ output->character('\n');
+
+ if (!exclude_section("SYNOPSIS"))
+ {
+ output->section("SYNOPSIS");
+
+ output->code_start();
+
+ /* list the include files the user asked us to */
+ output_includes();
+
+ /* if it's a header file, say to #include it */
+ if (header_file)
+ {
+ output->text("#include <");
+ if (header_prefix)
+ {
+ output->text(header_prefix);
+ output->character('/');
+ }
+ output->text(basefile);
+ output->text(">\n");
+ }
+
+ /* can't just use .PP; that may reset our font */
+ if (first_include || header_file) output->blank_line();
+
+ for (page = basepage; page; page = page->next)
+ {
+ output_format_string(decl_spec_prefix);
+
+ /* make sure variables are prefixed extern */
+ if (!(page->decl_spec->flags & DS_STATIC) &&
+ !is_function_declarator(page->declarator) &&
+ !strstr(page->decl_spec->text, "extern"))
+ output->text("extern ");
+
+ output_decl_spec(page->decl_spec);
+ output_format_string(declarator_prefix);
+
+ /* format it nicely if there's more than one parameter */
+ output_declarator(page->declarator,
+ page->declarator->head->params.first !=
+ page->declarator->head->params.last);
+
+ output->text(";\n");
+
+ if (!grouped) break;
+ if (page->next) output->blank_line();
+ }
+
+ output->code_end();
+ }
+
+ /* only output paramaters if there actually are some,
+ * not including merely (void)
+ */
+ if (!exclude_section("PARAMETERS") &&
+ ((grouped && mark_duplicate_parameters(basepage)) ||
+ (!grouped && has_documented_parameters(basepage->declarator))))
+ {
+ output->section("PARAMETERS");
+
+ for (page = basepage; page; page = page->next)
+ {
+ if (has_parameters(page->declarator))
+ output_parameter_descriptions(&page->declarator->head->params,
+ page->declarator->name);
+ if (!grouped) break; /* only do first page */
+ }
+ }
+
+ if (!exclude_description)
+ output->section("DESCRIPTION");
+
+ if (grouped)
+ {
+ need_returns = FALSE;
+ for (page = basepage; page; page = page->next)
+ {
+ if (needs_returns_section(page)) need_returns = TRUE;
+
+ if (!exclude_description)
+ {
+ /* enum variables are documented in DESCRIPTION */
+ if (auto_documented(page) &&
+ !is_function_declarator(page->declarator))
+ {
+ output->sub_section(page->declarator->name);
+ output_identifier_description(page->description,
+ output_comment, page->decl_spec, page->declarator);
+ }
+ else if (page->description)
+ {
+ output->sub_section(page->declarator->name);
+ output_comment(page->description);
+ }
+ }
+
+ safe_free(page->description);
+ }
+ }
+ else
+ {
+ need_returns = needs_returns_section(basepage);
+
+ if (!exclude_description)
+ {
+ const char *descr = basepage->description
+ ? basepage->description : terseout;
+
+ if (auto_documented(page) &&
+ !is_function_declarator(page->declarator))
+ output_identifier_description(descr, output_comment,
+ page->decl_spec, page->declarator);
+ else
+ output_comment(descr);
+ }
+
+ safe_free(basepage->description);
+ }
+
+ /* terse can now never be a static string */
+ safe_free(terse);
+
+ if (need_returns && !exclude_section("RETURNS"))
+ {
+ output->section("RETURNS");
+
+ for (page = basepage; page; page = page->next)
+ {
+ if (needs_returns_section(page))
+ {
+ if (grouped) output->sub_section(page->declarator->name);
+
+ output_identifier_description(page->returns, output->returns,
+ page->decl_spec, page->declarator);
+ safe_free(page->returns);
+ }
+
+ if (!grouped) break;
+ }
+ }
+
+ /* output any other sections */
+ for (page = basepage; page; page = page->next)
+ {
+ Section *section, *next;
+
+ for (section = page->first_section; section; section = next)
+ {
+ next = section->next;
+
+ if (!section->been_output && section->text &&
+ strncmpi(section->text,"none",4) &&
+ !exclude_section(section->name))
+ {
+ output->section(section->name);
+ if (grouped) output->sub_section(page->declarator->name);
+ output_comment(section->text);
+ section->been_output = TRUE;
+
+ if (grouped && page->next)
+ {
+ ManualPage *other_page = page->next;
+
+ /* look through all the other pages for matching sections */
+ for (; other_page; other_page = other_page->next)
+ {
+ Section *other_section = other_page->first_section;
+ for (;other_section; other_section =
+ other_section->next)
+ {
+ if (other_section->been_output ||
+ strcmp(other_section->name, section->name))
+ continue;
+
+ output->sub_section(other_page->declarator->name);
+ output_comment(other_section->text);
+ other_section->been_output = TRUE;
+ }
+ }
+ }
+ }
+
+
+ /* free this section */
+ free(section->name);
+ safe_free(section->text);
+ free(section);
+ }
+
+ if (!grouped) break;
+ }
+
+ /* only output SEE ALSO if not grouped */
+ if (!group_together)
+ {
+ ManualPage *also;
+
+ /* add the SEE ALSO section */
+ /* look for any other functions to refer to */
+ for (also = firstpage; also && also == basepage; also = also->next)
+ ;
+
+ if (also && !exclude_section("SEE ALSO")) /* did we find at least one? */
+ {
+ int isfirst = 1;
+
+ output->section("SEE ALSO");
+
+ for (also = firstpage; also; also = also->next)
+ {
+ if (also == basepage) continue;
+
+ if (!isfirst)
+ output->text(",\n");
+ else
+ isfirst = 0;
+
+ output->reference(also->declarator->name);
+ }
+
+ output->character('\n');
+ }
+ }
+
+ if (!make_embeddable)
+ output->file_end();
+}
+
+
+/* generate output filename based on a string */
+char *page_file_name(based_on, object_type, extension)
+ /* string to base the name on; this will be the name of an identifier or
+ * the base of the input file name.
+ */
+ const char *based_on;
+ enum Output_Object object_type; /* class of object documented */
+ const char *extension; /* file extension to use */
+{
+ char *filename;
+ const char *subdir = output_object[object_type].subdir;
+
+#ifndef FLEXFILENAMES
+ char *basename;
+ int chopoff = 14 - strlen(extension) - 1;
+
+ basename = strduplicate(based_on);
+ if (strlen(basename) > chopoff)
+ basename[chopoff] = '\0';
+#else
+ const char *basename = based_on;
+#endif
+
+ filename = strduplicate(output_dir);
+
+ if (subdir)
+ {
+ if (filename) filename = strappend(filename, "/", NULLCP);
+ filename = strappend(filename, subdir, NULLCP);
+ }
+
+ if (filename) filename = strappend(filename, "/", NULLCP);
+ filename = strappend(filename, basename,".",extension, NULLCP);
+
+#ifndef FLEXFILENAMES
+ free(basename);
+#endif
+ return filename;
+}
+
+/* determine the output page type from a declaration */
+enum Output_Object page_output_type(decl_spec, declarator)
+const DeclSpec *decl_spec;
+const Declarator *declarator;
+{
+ boolean is_static = decl_spec->flags & DS_STATIC;
+ return is_function_declarator(declarator)
+ ? (is_static ? OBJECT_STATIC_FUNCTION : OBJECT_FUNCTION)
+ : (is_static ? OBJECT_STATIC_VARIABLE : OBJECT_VARIABLE);
+}
+
+/* determine the extension/section from an output type */
+const char *page_manual_section(output_type)
+enum Output_Object output_type;
+{
+ return output_object[output_type].extension ?
+ output_object[output_type].extension : manual_section;
+}
+
+/* remove an existing file, if it exists & we have write permission to it */
+int remove_old_file(name)
+const char *name;
+{
+#ifdef HAS_ACCESS
+ /* check that we have write premission before blasting it */
+ if (access(name,W_OK) == -1)
+ {
+ if (errno != ENOENT)
+ {
+ my_perror("can't access output file", name);
+ return FALSE;
+ }
+ }
+ else
+#endif
+ {
+ /* if it exists, blast it */
+ if (unlink(name) == -1 && errno != ENOENT)
+ {
+ my_perror("error unlinking old link file", name);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/* output all the manual pages in a list */
+void output_manual_pages(first, input_files, link_type)
+ ManualPage *first;
+ int input_files; /* number of different input files */
+ enum LinkType link_type; /* how grouped pages will be linked */
+{
+ ManualPage *page;
+ int tostdout = output_dir && !strcmp(output_dir,"-");
+
+ char *filename = NULL;
+
+ /* output each page, in turn */
+ for (page = first; page; page = page->next)
+ {
+ char *input_file_base = NULL;
+ enum Output_Object output_type =
+ page_output_type(page->decl_spec, page->declarator);
+
+ /* the manual name is used as the output file extension, and also in
+ * the nroff output header.
+ */
+ const char *section = page_manual_section(output_type);
+
+ /* work out the base name of the file this was generated from */
+ if (page->sourcefile)
+ {
+ const char *base = strrchr(firstpage->sourcefile, '/');
+ const char *last;
+
+ /* use the file name as the manual page title */
+ if (base == NULL)
+ base = firstpage->sourcefile;
+ else
+ base++;
+ last = strrchr(base, '.');
+ if (last == NULL)
+ last = base + strlen(base);
+
+ input_file_base = alloc_string(base, last);
+ }
+
+ if (!tostdout)
+ {
+ safe_free(filename); /* free previous, if any */
+ filename = page_file_name(
+ use_input_name && input_file_base
+ ? input_file_base : page->declarator->name,
+ output_type, section);
+ fprintf(stderr,"generating: %s\n",filename);
+
+ /* a previous run may have left links, so nuke old file first */
+ if (!remove_old_file(filename)) exit(1);
+
+ if (freopen(filename, "w", stdout) == NULL)
+ {
+ my_perror("error opening output file", filename);
+ free(filename);
+ exit(1);
+ }
+ }
+
+ /* do the page itself */
+ output_manpage(first, page, input_files,
+ group_together && input_file_base ? input_file_base
+ : page->declarator->name,
+ group_together ? manual_section : section);
+
+ safe_free(input_file_base);
+
+ /* don't continue if grouped, because all info went into this page */
+ if (group_together) break;
+
+ if (tostdout && page->next) output->character('\f');
+ }
+
+ /* close the last output file if there was one */
+ if (!tostdout && fclose(stdout) == EOF)
+ {
+ my_perror("error linking closing file", filename);
+ exit(1);
+ }
+
+ /* if pages are grouped, just link the rest to the first */
+ if (group_together && !tostdout && link_type != LINK_NONE)
+ {
+ for (page=use_input_name && first->sourcefile ? first : first->next;
+ page; page = page->next)
+ {
+ enum Output_Object output_type =
+ page_output_type(page->decl_spec, page->declarator);
+ const char *extension = page_manual_section(output_type);
+ char *linkname = page_file_name(page->declarator->name,
+ output_type, extension);
+ int result = 0;
+
+ /* we may have a function with the same name as the sourcefile */
+ if (!strcmp(filename, linkname))
+ {
+ free(linkname);
+ continue;
+ }
+
+ fprintf(stderr,"%s: %s\n",
+ link_type == LINK_REMOVE ? "removing" : "linking", linkname);
+
+ /* always nuke old output file, since it may be linked to the one
+ * we've just generated, so LINK_FILE may trash it.
+ */
+ if (!remove_old_file(linkname)) exit(1);
+
+ switch(link_type)
+ {
+#ifdef HAS_LINK
+ case LINK_HARD:
+ result = link(filename, linkname);
+ break;
+#endif
+#ifdef HAS_SYMLINK
+ case LINK_SOFT:
+ result = symlink(filename, linkname);
+ break;
+#endif
+ case LINK_FILE:
+ if (freopen(linkname, "w", stdout) == NULL)
+ {
+ result = -1;
+ break;
+ }
+ output_warning();
+ output->include(filename);
+ if (fclose(stdout) == EOF)
+ result = -1;
+ break;
+ case LINK_NONE:
+ case LINK_REMOVE:
+ break;
+ }
+
+ /* check it went OK */
+ if (result == -1)
+ {
+ my_perror("error linking output file", linkname);
+ exit(1);
+ }
+ free(linkname);
+ }
+ }
+
+ safe_free(filename);
+}