/* $Id: manpage.c,v 2.0.1.57 2000/02/24 23:58:20 greyham Exp greyham $
* stuff to do with manual page outputing
*/
#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);
}
syntax highlighted by Code2HTML, v. 0.9.1