/* $Id: c2man.c,v 2.0.1.41 2000/02/25 02:39:29 greyham Exp greyham $
*
* C Manual page generator.
* Reads C source code and outputs manual pages.
*/
#include <ctype.h>
#include <errno.h>
#include "c2man.h"
#include "enum.h"
#include "strconcat.h"
#include "strappend.h"
#include "manpage.h"
#include "output.h"
#include "patchlevel.h"
#ifdef I_FCNTL
#include <fcntl.h>
#endif
#ifdef I_SYS_FILE
#include <sys/file.h>
#endif
#include <sys/stat.h>
#include <signal.h>
/* getopt declarations */
extern int getopt();
extern char *optarg;
extern int optind;
/* lex declarations */
extern FILE *yyin; /* lex input stream */
/* Name of the program */
const char *progname = "c2man";
/* Program options */
/* TRUE if static declarations are also output. */
boolean static_out = FALSE;
/* TRUE if variable declarations are output. */
boolean variables_out = FALSE;
/* TRUE if formal parameter promotion is enabled. */
boolean promote_param = TRUE;
/* String output before prototype declaration specifiers */
const char *decl_spec_prefix = "";
/* String output before prototype declarator */
const char *declarator_prefix = " ";
/* String output after prototype declarator */
const char *declarator_suffix = "\n";
/* String output before the first parameter in a function prototype */
const char *first_param_prefix = "\n\t";
/* String output before each subsequent parameter in a function prototype */
const char *middle_param_prefix = "\n\t";
/* String output after the last parameter in a function prototype */
const char *last_param_suffix = "\n";
/* Directory to write output files in */
char *output_dir = NULL;
/* Name of the manual */
char *manual_name = NULL;
/* Section for manual page */
const char *manual_section = NULL;
/* prefix for generated #include lines */
char *header_prefix = NULL;
/* list of include file specified by user */
IncludeFile *first_include;
static IncludeFile **last_next_include = &first_include;
/* list of excluded sections specified by user */
ExcludeSection *first_excluded_section;
static ExcludeSection **last_next_excluded_section = &first_excluded_section;
/* TRUE if c2man should attempt to fixup comment sections */
boolean fixup_comments = TRUE;
/* do we group related stuff into one file? */
boolean group_together;
/* was terse description read from file or command line option? */
boolean terse_specified;
/* terse description when grouped together */
char *group_terse = NULL;
/* should we always document parameters, even if it's only "Not Documented" */
boolean always_document_params = TRUE;
/* look for a function def comment at the start of the function body */
boolean look_at_body_start = FALSE;
/* only look for a function def comment at the start of the function body */
boolean body_start_only = FALSE;
/* default output info for each object type */
struct Output_Object_Info output_object[_OBJECT_NUM] =
{
#if 0
{'c', "class"},
{'s', "struct"},
{'e', "enum"},
{'t', "typedef"},
#endif
{'f', "function"},
{'v', "variable"},
{'F', "static function"},
{'V', "static variable"}
};
/* Include file directories */
#ifdef MSDOS
int num_inc_dir = 1;
const char *inc_dir[MAX_INC_DIR] = { ".\\" };
#else
#ifdef AMIGA
int num_inc_dir = 1;
const char *inc_dir[MAX_INC_DIR] = { "include:" };
#else
int num_inc_dir = 2;
const char *inc_dir[MAX_INC_DIR] = { "./", "/usr/include/" };
#endif
#endif
/* total number of errors encountered */
int errors;
/* name of the base file being processed; NULL = stdin */
const char *basefile;
Time_t basetime; /* modification time of base file */
boolean inbasefile; /* are we parsing in that base file? */
/* is the base file a header file? */
boolean header_file;
#ifdef AMIGA
struct Output *output = &autodoc_output;
const char *default_section = "doc";
#else
/* use nroff output by default */
struct Output *output = &nroff_output;
const char *default_section = "3";
#endif
/* should we generate the output file named after the input file? */
boolean use_input_name = FALSE;
/* should we generate embeddable files? */
boolean make_embeddable = FALSE;
#define USE_CPP
#ifdef USE_CPP
const char *cpp_cmd = CPP_FILE_COM;
#if defined(MSDOS)
#include "popen.h"
#define popen(c,m) os_popen(c,m)
#define pclose(f) os_pclose(f)
#else
#if defined (_MSC_VER) || defined(__WATCOMC__)
#include <process.h>
#define popen(c,m) _popen(c,m)
#define pclose(f) _pclose(f)
#endif
#endif
#endif
boolean verbose = FALSE;
/* can cpp read standard input? */
static boolean cppcanstdin
#ifdef CPP_CAN_STDIN
= 1
#endif
;
/* does cpp ignore header files */
static boolean cppignhdrs
#ifdef CPP_IGN_HDRS
= 1
#endif
;
/* nifty little function for handling I/O errors */
void my_perror(action, filename)
const char *action, *filename;
{
int err = errno;
fprintf(stderr,"%s: %s ", progname, action);
errno = err;
perror(filename);
}
/* write the #include lines as specified by the user */
void print_includes(f)
FILE *f;
{
IncludeFile *incfile;
for (incfile = first_include; incfile; incfile=incfile->next)
{
char *name = incfile->name;
boolean surrounded = *name == '"' || *name == '<';
fputs("#include ", f);
if (!surrounded) fputc('<',f);
fputs(name, f);
if (!surrounded) fputc('>',f);
fputc('\n',f);
}
}
void outmem()
{
fprintf(stderr,"%s: Out of memory!\n", progname);
exit(1);
}
#ifndef DBMALLOC
void *safe_malloc(size)
size_t size;
{
void *mem;
if ((mem = (void *)malloc(size)) == NULL)
outmem();
return mem;
}
#endif
/* Replace any character escape sequences in a string with the actual
* characters. Return a pointer to malloc'ed memory containing the result.
* This function knows only a few escape sequences.
*/
static char *
escape_string (src)
char *src;
{
char *result, *get, *put;
result = strduplicate(src);
put = result;
get = src;
while (*get != '\0') {
if (*get == '\\') {
switch (*(++get)) {
case 'n':
*put++ = '\n';
++get;
break;
case 't':
*put++ = '\t';
++get;
break;
default:
if (*get != '\0')
*put++ = *get++;
}
} else {
*put++ = *get++;
}
}
*put = *get;
return result;
}
/* Output usage message and exit.
*/
static void
usage ()
{
int i;
fprintf(stderr, "usage: %s [ option ... ] [ file ... ]\n", progname);
fputs(" -o directory\twrite output files in directory\n",stderr);
fputs(" -p\t\tdisable prototype promotion\n", stderr);
fputs(" -s\t\toutput static declarations\n", stderr);
fputs(" -v\t\toutput variable declarations\n", stderr);
fputs(" -k\t\tdon't attempt to fixup comments\n", stderr);
fputs(" -b\t\tlook for descriptions at top of function bodies\n", stderr);
fputs(" -B\t\tonly look for descriptions by applying -b\n", stderr);
fputc('\n', stderr);
fputs(" -i incfile\n", stderr);
fputs(" -i \"incfile\"\n", stderr);
fputs(" -i <incfile>\tadd #include for incfile to SYNOPSIS\n",
stderr);
fputc('\n', stderr);
fputs(" -H prefix\tspecify prefix for #include in SYNOPSIS\n", stderr);
fputc('\n', stderr);
fputs(" -g\n", stderr);
fputs(" -G terse\tgroup info from each file into a single page\n",
stderr);
fputs(" -e\t\tmake embeddable files\n", stderr);
fputc('\n', stderr);
fputs(" -l ", stderr);
#ifdef HAS_LINK
fputs("h|", stderr);
#endif
#ifdef HAS_SYMLINK
fputs("s|", stderr);
#endif
fputs("f|n|r\t", stderr);
fputs("linking for grouped pages: ", stderr);
#ifdef HAS_LINK
fputs("hard, ", stderr);
#endif
#ifdef HAS_SYMLINK
fputs("soft, ", stderr);
#endif
fputs("file, none or remove\n", stderr);
fputs(" -n\t\tName output file after input source file\n", stderr);
fputs(" -L\t\tLazy: Be silent about undocumented parameters\n",
stderr);
fputs(" -T n|l|h|t|a|r[,options]\tselect typesetting output format: nroff, LaTeX, HTML ,TeXinfo, AutoDoc or raw text\n",
stderr);
nroff_output.print_options();
latex_output.print_options();
html_output.print_options();
texinfo_output.print_options();
autodoc_output.print_options();
ascii_output.print_options();
fputs(" -M name\tset name of the manual in which the page goes\n",
stderr);
fputs(" -x section\texclude section from ouput\n", stderr);
fputc('\n', stderr);
fputs(" -D name[=value]\n", stderr);
fputs(" -U name\n", stderr);
fputs(" -I directory\tC preprocessor options\n", stderr);
fputc('\n', stderr);
fputs(" -F template\tset prototype template in the form ", stderr);
fputs("\"int f (a, b)\"\n",stderr);
fputs(" -P preprocessor\tAlternate C preprocessor ", stderr);
fputs("(e.g., \"gcc -E -C\")\n", stderr);
fputs(" -V\t\tbe verbose and print version information\n", stderr);
fputs(" -S section\tset the section for the manual page (default = 3)\n",
stderr);
fputs(" -O ", stderr);
for (i = 0; i < _OBJECT_NUM; i++)
fputc(output_object[i].flag, stderr);
fputs("[subdir][.ext]", stderr);
fputs("\tOutput control over different object types:\n\t\t", stderr);
for (i = 0; i < _OBJECT_NUM; i++)
{
fputs(output_object[i].name, stderr);
if (i <= _OBJECT_NUM - 2)
fprintf(stderr,i == _OBJECT_NUM-2 ? " or " : ", ");
}
fputs(".\n", stderr);
exit(1);
}
/* name of the temporary file; kept here so we can blast it if hit with ctrl-C
*/
static char temp_name[20];
Signal_t (*old_interrupt_handler)();
/* ctrl-C signal handler for use when we have a temporary file */
static Signal_t interrupt_handler(sig)
int sig;
{
unlink(temp_name);
exit(128 + sig);
}
/* open a unique temporary file.
* To be universally accepted by cpp's, the file's name must end in .c; so we
* can't use mktemp, tmpnam or tmpfile.
* returns an open stream & sets ret_name to the name.
*/
FILE *open_temp_file()
{
int fd;
long n = getpid();
FILE *tempf;
boolean remove_temp_file();
/* keep generating new names until we hit one that does not exist */
do
{
/* ideally we'd like to put the temporary file in /tmp, but it must go
* in the current directory because when cpp processes a #include, it
* looks in the same directory as the file doing the include; so if we
* use /tmp/blah.c to fake reading fred.h via `#include "fred.h"', cpp
* will look for /tmp/fred.h, and fail.
*/
sprintf(temp_name,"c2man%ld.c",n++ % 1000000);
}
while((fd =
#ifdef HAS_OPEN3
open(temp_name,O_WRONLY|O_CREAT|O_EXCL,0666)
#else
creat(temp_name,O_EXCL|0666) /* do it the old way */
#endif
) == -1
&& errno == EEXIST);
/* install interrupt handler to remove the temporary file */
old_interrupt_handler = signal(SIGINT, interrupt_handler);
/* convert it to a stream */
if ((fd == -1 && errno != EEXIST) || (tempf = fdopen(fd, "w")) == NULL)
{
my_perror("error fdopening temp file",temp_name);
remove_temp_file();
return NULL;
}
return tempf;
}
/* remove the temporary file & restore ctrl-C handler.
* returns FALSE in the event of failure.
*/
boolean remove_temp_file()
{
int ok = unlink(temp_name) == 0; /* this should always succeed */
signal(SIGINT, old_interrupt_handler);
return ok;
}
/* process the specified source file through the pre-processor.
* This is a lower level routine called by both process_stdin and process_file
* to actually get the work done once any required temporary files have been
* generated.
*/
int process_file_directly(base_cpp_cmd, name)
const char *base_cpp_cmd;
const char *name;
{
char *full_cpp_cmd;
#ifdef DEBUG
fprintf(stderr,"process_file_directly: %s, %s\n", base_cpp_cmd, name);
#endif
#ifdef USE_CPP
full_cpp_cmd = strconcat(base_cpp_cmd, " ", name, NULLCP);
if (verbose)
fprintf(stderr,"%s: running `%s'\n", progname, full_cpp_cmd);
if ((yyin = popen(full_cpp_cmd, "r")) == NULL) {
my_perror("error running", base_cpp_cmd);
free(full_cpp_cmd);
return 0;
}
#else
if (verbose) fprintf(stderr,"%s: reading %s\n", progname, name);
if (name && freopen(name, "r", yyin) == NULL)
{
my_perror("cannot open", name);
return 0;
}
#endif
parse_file(name);
#ifdef USE_CPP
free(full_cpp_cmd);
if (pclose(yyin) & 0xFF00)
return 0;
#else
if (fclose(yyin))
{
my_perror("error closing", name);
return 0;
}
#endif
return !errors;
}
/* process a specified file */
int process_file(base_cpp_cmd, name)
const char *base_cpp_cmd;
const char *name;
{
char *period;
struct stat statbuf;
#ifdef DEBUG
fprintf(stderr,"process_file: %s, %s\n", base_cpp_cmd, name);
#endif
basefile = name;
header_file = (period = strrchr(name,'.')) &&
(period[1] == 'h' || period[1] == 'H');
/* use the file's date as the date in the manual page */
if (stat(name,&statbuf) != 0)
{
my_perror("can't stat", name);
return 0;
}
basetime = statbuf.st_mtime;
/* should we do this via a temporary file?
* Only if it's a header file and either CPP ignores them, or the user
* has specified files to include.
*
* For HP/Apollo (SR10.3, CC 6.8), we must always use a temporary file,
* because its compiler recognizes the special macro "__attribute(p)",
* which we cannot redefine in the command line because it has parameters.
*/
#ifndef apollo
if (header_file && (cppignhdrs || first_include))
#endif
{
FILE *tempf;
int ret;
if (verbose)
fprintf(stderr, "%s: preprocessing via temporary file\n", progname);
if ((tempf = open_temp_file()) == NULL)
return 0;
print_includes(tempf);
if (verbose) print_includes(stderr);
#ifdef apollo
fprintf(tempf,"#define __attribute(p)\n", basefile);
#endif
fprintf(tempf,"#include \"%s\"\n", basefile);
if (verbose) fprintf(stderr,"#include \"%s\"\n", basefile);
if (fclose(tempf) == EOF)
{
my_perror("error closing temp file", temp_name);
remove_temp_file();
return 0;
}
/* since we're using a temporary file, it's not the base file */
inbasefile = 0;
ret = process_file_directly(base_cpp_cmd, temp_name);
remove_temp_file();
return ret;
}
/* otherwise, process it directly */
inbasefile = 1;
return process_file_directly(base_cpp_cmd,name);
}
/* process the thing on the standard input */
int process_stdin(base_cpp_cmd)
const char *base_cpp_cmd;
{
if (isatty(fileno(stdin)))
fprintf(stderr,"%s: reading standard input\n", progname);
header_file = 0; /* assume it's not since it's from stdin */
basefile = NULL;
/* use the current date in the man page */
basetime = time((Time_t *)NULL);
inbasefile = 1; /* reading stdin, we start in the base file */
/* always use a temp file if the preprocessor can't read stdin, otherwise
* only use one if the user specified files for inclusion.
*/
if (!cppcanstdin || first_include) /* did user specify include files? */
{
FILE *tempf;
int c, ret;
if (verbose)
fprintf(stderr,"%s: reading stdin to a temporary file\n", progname);
if ((tempf = open_temp_file()) == NULL)
return 0;
print_includes(tempf);
if (verbose) print_includes(stderr);
fprintf(tempf,"#line 1 \"stdin\"\n");
while ((c = getchar()) != EOF)
putc(c,tempf);
if (fclose(tempf) == EOF)
{
my_perror("error closing temp file", temp_name);
remove_temp_file();
return 0;
}
ret = process_file_directly(base_cpp_cmd, temp_name);
remove_temp_file();
return ret;
}
else
{
char *full_cpp_cmd = strconcat(base_cpp_cmd," ", CPP_STDIN_FLAGS,
NULLCP);
if (verbose)
fprintf(stderr,"%s: running `%s'\n", progname, full_cpp_cmd);
if ((yyin = popen(full_cpp_cmd, "r")) == NULL) {
my_perror("error running", full_cpp_cmd);
return 0;
}
parse_file(basefile);
free(full_cpp_cmd);
if (pclose(yyin) & 0xFF00)
return 0;
return !errors;
}
}
int
main (argc, argv)
int argc;
char **argv;
{
int i, c, ok = 0;
char *s, cbuf[2];
const char *base_cpp_cmd;
IncludeFile *includefile;
ExcludeSection *excludesection;
char *cpp_opts;
#ifdef HAS_LINK
enum LinkType link_type = LINK_HARD; /* for -g/G */
#else
enum LinkType link_type = LINK_FILE;
#endif
#ifdef YYDEBUG
extern int yydebug;
#endif
/* initialise CPP options with -D__C2MAN__ */
cbuf[0] = VERSION + '0';
cbuf[1] = '\0';
#ifdef VMS
cpp_opts = strconcat("-\"D__C2MAN__=", cbuf, "\"",NULLCP);
#else
cpp_opts = strconcat("-D__C2MAN__=", cbuf, NULLCP);
#ifdef NeXT
cpp_opts = strappend(cpp_opts, " -D_NEXT_SOURCE", NULLCP);
#endif /* !NeXT */
#endif /* !VMS */
/* Scan command line options. */
while ((c = getopt(argc, argv, "P:D:F:I:psU:Vvo:eM:H:G:gi:x:S:l:LT:nO:kbB"))
!= EOF)
{
switch (c) {
case 'I':
case 'D':
case 'U':
cbuf[0] = c; cbuf[1] = '\0';
if (cpp_opts)
cpp_opts = strappend(cpp_opts," -",cbuf,optarg,NULLCP);
else
cpp_opts = strconcat("-",cbuf,optarg,NULLCP);
break;
case 'P':
cpp_cmd = optarg;
/* with no better info to go on, we have to assume that this
* preprocessor is minimally capable.
*/
cppcanstdin = 0;
cppignhdrs = 1;
break;
case 'G':
group_terse = optarg;
terse_specified = TRUE;
/* FALLTHROUGH */
case 'g':
group_together = TRUE;
break;
case 'F':
s = escape_string(optarg);
decl_spec_prefix = s;
while (*s != '\0' && isascii(*s) && !isalnum(*s)) ++s;
if (*s == '\0') usage();
*s++ = '\0';
while (*s != '\0' && isascii(*s) && isalnum(*s)) ++s;
if (*s == '\0') usage();
declarator_prefix = s;
while (*s != '\0' && isascii(*s) && !isalnum(*s)) ++s;
if (*s == '\0') usage();
*s++ = '\0';
while (*s != '\0' && isascii(*s) && isalnum(*s)) ++s;
if (*s == '\0') usage();
declarator_suffix = s;
while (*s != '\0' && *s != '(') ++s;
if (*s == '\0') usage();
*s++ = '\0';
first_param_prefix = s;
while (*s != '\0' && isascii(*s) && !isalnum(*s)) ++s;
if (*s == '\0') usage();
*s++ = '\0';
while (*s != '\0' && *s != ',') ++s;
if (*s == '\0') usage();
middle_param_prefix = ++s;
while (*s != '\0' && isascii(*s) && !isalnum(*s)) ++s;
if (*s == '\0') usage();
*s++ = '\0';
while (*s != '\0' && isascii(*s) && isalnum(*s)) ++s;
if (*s == '\0') usage();
last_param_suffix = s;
while (*s != '\0' && *s != ')') ++s;
*s = '\0';
break;
case 'p':
promote_param = FALSE;
break;
case 's':
static_out = TRUE;
break;
case 'V':
verbose = TRUE;
fprintf(stderr, "%s: Version %d, Patchlevel %d\n",
progname, VERSION, PATCHLEVEL);
break;
case 'v':
variables_out = TRUE;
break;
case 'k':
fixup_comments = FALSE;
break;
case 'o':
output_dir = optarg;
break;
case 'M':
manual_name = optarg;
break;
case 'H':
header_prefix = optarg;
break;
case 'i':
*last_next_include = includefile =
(IncludeFile *)safe_malloc(sizeof *includefile);
includefile->name = optarg;
includefile->next = NULL;
last_next_include = &includefile->next;
break;
case 'x':
*last_next_excluded_section = excludesection =
(ExcludeSection *)safe_malloc(sizeof *excludesection);
excludesection->name = optarg;
excludesection->next = NULL;
last_next_excluded_section = &excludesection->next;
break;
case 'S':
manual_section = optarg;
break;
case 'l':
switch(optarg[0])
{
#ifdef HAS_LINK
case 'h': link_type = LINK_HARD; break;
#endif
#ifdef HAS_SYMLINK
case 's': link_type = LINK_SOFT; break;
#endif
case 'f': link_type = LINK_FILE; break;
case 'n': link_type = LINK_NONE; break;
case 'r': link_type = LINK_REMOVE;break;
default: usage();
}
break;
case 'e':
make_embeddable = TRUE;
break;
case 'n':
use_input_name = TRUE;
break;
case 'L':
always_document_params = FALSE;
break;
case 'T':
switch(optarg[0])
{
case 'n': output = &nroff_output; default_section = "3";
break;
case 'l': output = &latex_output; default_section = "tex";
break;
case 't': output = &texinfo_output; default_section = "texi";
break;
case 'h': output = &html_output; default_section = "html";
break;
case 'a': output = &autodoc_output; default_section = "doc";
break;
case 'r': output = &ascii_output; default_section = "txt";
break;
default: usage();
}
s = strtok(&optarg[1], ",");
if (s && *output->parse_option == NULL) usage();
while(s)
{
if (output->parse_option(s)) usage();
s = strtok(NULL, ",");
}
break;
case 'O':
for (i = 0; i < _OBJECT_NUM; i++)
if (output_object[i].flag == optarg[0])
break;
if (i == _OBJECT_NUM)
{
fprintf(stderr,"%s: -O option must specify one of:\n\t",
progname);
for (i = 0; i < _OBJECT_NUM; i++)
{
fprintf(stderr,"%c (%s)", output_object[i].flag,
output_object[i].name);
if (i <= _OBJECT_NUM - 2)
fprintf(stderr,i == _OBJECT_NUM-2 ? " or " : ", ");
}
fprintf(stderr, ".\n");
exit(1);
}
if ((s = strchr(++optarg,'.'))) /* look for an extension */
{
output_object[i].subdir = alloc_string(optarg, s);
output_object[i].extension = strduplicate(s+1);
}
else
output_object[i].subdir = strduplicate(optarg);
break;
case 'b':
look_at_body_start = TRUE;
break;
case 'B':
body_start_only = TRUE;
look_at_body_start = TRUE;
break;
case '?':
default:
usage();
}
}
/* make sure we have a manual section */
if (manual_section == NULL) manual_section = default_section;
#ifdef MALLOC_DEBUG
getchar(); /* wait so we can start up NeXT MallocDebug tool */
#endif
#ifdef YYDEBUG
yydebug = 1;
#endif
if (cpp_opts)
{
base_cpp_cmd = strconcat(cpp_cmd, " ", cpp_opts, NULLCP);
free(cpp_opts);
}
else
base_cpp_cmd = cpp_cmd;
if (optind == argc) {
if (use_input_name)
{
fprintf(stderr,"%s: %s\n", progname,
"cannot name output after input file if there isn't one!");
usage();
}
ok = process_stdin(base_cpp_cmd);
}
else
for (i = optind; i < argc; ++i)
if (!(ok = process_file(base_cpp_cmd,argv[i]))) break;
if (ok && firstpage)
output_manual_pages(firstpage,argc - optind, link_type);
free_manual_pages(firstpage);
destroy_enum_lists();
if (cpp_opts) free((char *)base_cpp_cmd);
for (includefile = first_include; includefile;)
{
IncludeFile *next = includefile->next;
free(includefile);
includefile = next;
}
for (excludesection = first_excluded_section; excludesection;)
{
ExcludeSection *next = excludesection->next;
free(excludesection);
excludesection = next;
}
for (i = 0; i < _OBJECT_NUM; i++)
{
safe_free(output_object[i].subdir);
safe_free(output_object[i].extension);
}
#ifdef DBMALLOC
malloc_dump(2);
malloc_chain_check(1);
#endif
#ifdef MALLOC_DEBUG
sleep(1000000);
#endif
return !ok;
}
syntax highlighted by Code2HTML, v. 0.9.1