/* $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 * to incorporate a patch that was also submitted to the author. The change * shall be incorporated upstream in due course. */ #include "c2man.h" #include #include #include "manpage.h" #include "strconcat.h" #include "strappend.h" #include "semantic.h" #include "output.h" #ifdef I_SYS_FILE #include #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] " */ 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); }