diff options
author | behdad <behdad> | 2004-05-03 05:17:48 +0000 |
---|---|---|
committer | behdad <behdad> | 2004-05-03 05:17:48 +0000 |
commit | 577ed4095383ef5284225d45709e6b5f0598a064 (patch) | |
tree | 6c7d0ce55124a688b4d7050e684d9d7a1e3aa71d /manpage.c |
Diffstat (limited to 'manpage.c')
-rw-r--r-- | manpage.c | 1347 |
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); +} |