/*****************************************************************************
*   $Id: main.c,v 3.1 1997/05/13 05:34:26 darren Exp $
*
*   Copyright (c) 1996-1997, Darren Hiebert
*
*   Author: Darren Hiebert <darren@hiwaay.net>, http://fly.hiwaay.net/~darren
*
*   This source code is released into the public domain. It is provided on an
*   as-is basis and no responsibility is accepted for its failure to perform
*   as expected. It is worth at least as much as you paid for it!
*
*   This is a reimplementation of the ctags(1) program. It is an attempt to
*   provide a fully featured ctags program which is free of the limitations
*   which most (all?) others are subject to.
*
*   It is derived from and inspired by the ctags program by Steve Kirkendall
*   (kirkenda@cs.pdx.edu) that comes with the Elvis vi clone (though almost
*   none of the original code remains). This, too, was freely available.
*
*   This program provides the following features:
*
*   Support for both K&R style and new ANSI style function definitions.
*
*   Generates tags for the following objects:
*	- macro definitions
*	- enumeration values
*	- function definitions (and C++ methods)
*	- function declarations (optional)
*	- enum, struct and union tags and C++ class names
*	- typedefs
*	- variables
*****************************************************************************/

/*============================================================================
=   Include files
============================================================================*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#if defined(REMOVE_MISSING) && defined(HAVE_UNISTD_H)
# include <unistd.h>		/* to declare unlink() */
#endif

/*  To define the maximum path length (hopefully)
 */
#ifdef __BORLANDC__
# include <dir.h>		/* to define MAXPATH */
#else
# ifdef INCLUDE_LIMITS_H
#  include <limits.h>		/* to define PATH_MAX (hopefully) */
# else
#  ifdef INCLUDE_DIRENT_H
#   include <dirent.h>		/* to define MAXNAMLEN (hopefully) */
#  endif
# endif
#endif

#include "ctags.h"

/*============================================================================
=   Defines
============================================================================*/

/*----------------------------------------------------------------------------
 *  Portability defines
 *--------------------------------------------------------------------------*/

#ifndef PATH_MAX
# ifdef MAXNAMLEN
#  define PATH_MAX MAXNAMLEN
# else
#  ifdef MAXPATH		/* found in Borland C <dir.h> */
#   define PATH_MAX MAXPATH
#  else
#   if defined(MSDOS) || defined(WIN32)
#    define PATH_MAX 128
#   else
#    define PATH_MAX 256
#   endif
#  endif
# endif
#endif

#if defined(MSDOS) || defined(WIN32) || defined(OS2)
# define PATH_SEPARATOR	'\\'
#else
# define PATH_SEPARATOR	'/'
#endif

/*----------------------------------------------------------------------------
 *  Miscellaneous defines
 *--------------------------------------------------------------------------*/
#ifndef ETAGS
# define ETAGS	"etags"		/* name which causes default use of to -e */
#endif

/*============================================================================
=   Data definitions
============================================================================*/

tagFile TagFile = { NULL, NULL, 0, { 0, 0, 0 }, { "", NULL } };

/*============================================================================
=   Function prototypes
============================================================================*/
static void beginEtagsFile __ARGS((void));
static void endEtagsFile __ARGS((const char *const name));
static boolean createTagsForFile __ARGS((const char *const name));
static const char *getNextListFile __ARGS((FILE *const fp));
static const char *sourceFilePath __ARGS((const char *const file));
static void createTagsForList __ARGS((const char *const listFile));
static void createTagsForArgs __ARGS((char *const *const argList));
static void makeTags __ARGS((char *const *const argList));
static boolean isSourceFile __ARGS((const char *const filename));
static void openTagFile __ARGS((const boolean toStdout));
static void closeTagFile __ARGS((void));
static void testEtagsInvocation __ARGS((const char *const path));
static void setDefaultTagFileName __ARGS((void));
static void setOptionDefaults __ARGS((void));

extern int main __ARGS((int argc, char **argv));

/*============================================================================
=   Function definitions
============================================================================*/

static void beginEtagsFile()
{
    tmpnam(TagFile.etags.name);
    TagFile.etags.fp = fopen(TagFile.etags.name, "w+");
    if (TagFile.etags.fp == NULL)
	perror(TagFile.etags.name);
    TagFile.etags.byteCount = 0;
}

static void endEtagsFile( name )
    const char *const name;
{
    char line[MaxTagLine];

    fprintf(TagFile.fp, "\f\n%s,%ld\n", name, (long)TagFile.etags.byteCount);
    if (TagFile.etags.fp != NULL)
    {
	rewind(TagFile.etags.fp);
	while (fgets(line, MaxTagLine, TagFile.etags.fp) != NULL)
	    fputs(line, TagFile.fp);
	fclose(TagFile.etags.fp);
	remove(TagFile.etags.name);
    }
    TagFile.etags.name[0] = '\0';
}

static boolean createTagsForFile( name )
    const char *const name;
{
    boolean ok;

    if (name == NULL)
	ok = FALSE;
    else
    {
	ok = TRUE;
	if (Option.etags)
	    beginEtagsFile();
	if (cppOpen(name))
	{
	    ok = createTags(0);
	    cppClose();
	}
	if (Option.etags)
	    endEtagsFile(name);
    }
    return ok;
}

static const char *getNextListFile( fp )
    FILE *const fp;
{
    static char fileName[PATH_MAX + 1];
    const char *const buf = fgets(fileName, PATH_MAX + 1, fp);

    if (buf != NULL)
    {
	char *const newline = strchr(fileName, '\n');

	if (newline == NULL)
	    fileName[PATH_MAX] = '\0';
	else
	    *newline = '\0';
    }
    return buf;
}

static const char *sourceFilePath( file )
    const char *const file;
{
	static char filePath[PATH_MAX + 1];
	const char *result = NULL;

	if (Option.path == NULL  ||  file[0] == PATH_SEPARATOR)
	    result = file;
	else if (strlen(Option.path) + strlen(file) < (size_t)PATH_MAX)
	{
	    if (Option.path[strlen(Option.path) - 1] == PATH_SEPARATOR)
		sprintf(filePath, "%s%s", Option.path, file);
	    else
		sprintf(filePath, "%s%c%s", Option.path, PATH_SEPARATOR, file);
	    result = filePath;
	}
	return result;
}

/*  Create tags for the source files listed in the file specified by
 *  "listFile".
 */
static void createTagsForList( listFile )
    const char *const listFile;
{
    const char *fileName;
    FILE *const fp = (strcmp(listFile,"-") == 0) ? stdin : fopen(listFile, "r");

    if (fp == NULL)
    {
	perror(listFile);
	exit(1);
    }
    else
    {
	fileName = getNextListFile(fp);
	while (fileName != NULL  &&  fileName[0] != '\0')
	{
	    const long startPos		= ftell(TagFile.fp);
	    const size_t numTags	= TagFile.numTags;
	    const char *const filePath	= sourceFilePath(fileName);

	    if (! createTagsForFile(filePath)  &&  ! Option.braceFormat)
	    {
		fseek(TagFile.fp, startPos, SEEK_SET);
		TagFile.numTags = numTags;	    /* restore previous count */
		Option.braceFormat = TRUE;
#ifdef DEBUG
		if (debug(DEBUG_STATUS))
		    printf("%s: Formatting error; retrying\n", fileName);
#endif
	    }
	    else
	    {
		Option.braceFormat = FALSE;
		fileName = getNextListFile(fp);
	    }
	}
	if (fp != stdin)
	    fclose(fp);
    }
}

static void createTagsForArgs( argList )
    char *const *const argList;
{
    int argNum = 0;

    /*	Generate tags for each source file on the command line.
     */
    while (argList[argNum] != NULL)
    {
	const long startPos	    = ftell(TagFile.fp);
	const size_t numTags	    = TagFile.numTags;
	const char *const filePath  = sourceFilePath(argList[argNum]);

	if (! createTagsForFile(filePath)  &&  ! Option.braceFormat)
	{
	    fseek(TagFile.fp, startPos, SEEK_SET);
	    TagFile.numTags = numTags;		/* restore previous count */
	    Option.braceFormat = TRUE;
#ifdef DEBUG
	    if (debug(DEBUG_STATUS))
		printf("%s: Formatting error; retrying\n", argList[argNum]);
#endif
	}
	else
	{
	    ++argNum;
	    Option.braceFormat = FALSE;
	}
    }
}

static void makeTags( argList )
    char *const *const argList;
{
    boolean toStdout = FALSE;

    if (Option.xref  ||  strcmp(Option.tagFile, "-") == 0)
	toStdout = TRUE;

    openTagFile(toStdout);

    if (Option.fileList != NULL)
	createTagsForList(Option.fileList);
    createTagsForArgs(argList);

    closeTagFile();
    freeIgnoreList();

    if (TagFile.numTags > 0  &&  ! Option.unsorted)
    {
#ifdef EXTERNAL_SORT
	externalSortTags(toStdout);
#else
	internalSortTags(toStdout);
#endif
    }
    if (toStdout  &&  TagFile.name != NULL)
	remove(TagFile.name);
}

/*----------------------------------------------------------------------------
 *  Start up and terminate functions
 *--------------------------------------------------------------------------*/

/*  Checks whether the supplied filename contains something other than valid
 *  tag lines. A tag line MUST fit the following regular expression:
 *
 *  ^\([^\t]\+:\)[a-zA-Z_][a-zA-Z0-9_]*\t[^\t]\t[/?].*[/?]$
 *
 *  or, in more readable terms:
 *
 *  [filename:]<tag>	<filename>	/pattern/
 */
static boolean isSourceFile( filename )
    const char *const filename;
{
    static char line[MaxTagLine];
    boolean isSource = FALSE;		/* we assume not unless found */
    FILE *const fp = fopen(filename, "r");

    if (fp == NULL  ||  fgets(line, MaxTagLine, fp) == NULL)
	;
    else if (Option.etags)
    {
	if (line[0] != '\f'  ||  line[1] != '\n')
	    isSource = TRUE;
    }
    else
    {
	char *tagPrefix, *tagFname, *pattern;
	char *separator1, *separator2;
	char *const buffer = (char *)malloc((size_t)(5 * MaxTagLine));

	if (buffer == NULL)
	{
	    perror("Insufficient memory");
	    exit(1);
	}
	tagPrefix   = &buffer[0 * MaxTagLine];
	separator1  = &buffer[1 * MaxTagLine];
	tagFname    = &buffer[2 * MaxTagLine];
	separator2  = &buffer[3 * MaxTagLine];
	pattern	    = &buffer[4 * MaxTagLine];
	if (sscanf(line, "%[^\t]%[\t]%[^\t]%[\t]%[^\n]",
		   tagPrefix, separator1, tagFname, separator2, pattern) < 5)
	    isSource = TRUE;
	else if (strlen(separator1) != 1  ||  strlen(separator2) != 1)
	    isSource = TRUE;	    /* exactly one tab permitted as separators*/
	else
	{
	    /*	Check for filename:tag format.
	     */
	    char *tag = strchr(tagPrefix, ':');

	    if (tag == NULL)
	    {
		tag = tagPrefix;
		tagPrefix = NULL;		/* no tag filename prefix */
	    }
	    else
	    {
		*tag = '\0';		/* null terminate the tag prefix */
		++tag;			/* tag follows tag prefix */

		/*  The filename portion of the prefixed tags must match
		 *  the supplied filename.
		 */
		if (strcmp(tagPrefix, tagFname) != 0)
		    isSource = TRUE;
	    }
	    if (! isSource)
	    {
		const char *p;

		/*  The tag must be a valid C identifier.
		 */
		if (! isident1(tag[0]))
		    isSource = TRUE;
		else for (p = tag + 1 ; *p != '\0' && !isSource ; ++p)
		{
		    if (! isident(*p))
			isSource = TRUE;
		}

		/*  If the pattern does not begin with either '/' or '?',
		 *  or the first character and last characters do not match,
		 *  then the pattern must be a line number.
		 */
		if (! isSource  &&
		    ((pattern[0] != '/'  &&  pattern[0] != '?') ||
		      pattern[0] != pattern[strlen(pattern) - 1]))
		{
		    for (p = pattern ; *p != '\0'  && ! isSource ; ++p)
			if (! isdigit(*p))
			    isSource = TRUE;
		}
	    }
	}
	free(buffer);
    }
    if (fp != NULL)
	fclose(fp);
    return isSource;
}

static void openTagFile( toStdout )
    const boolean toStdout;
{
	static char tempName[L_tmpnam];

	/*  Open the tags File.
	 */
	if (toStdout)
	{
	    if (Option.unsorted)
	    {
		TagFile.name = NULL;
		TagFile.fp = stdout;
	    }
	    else
	    {
		TagFile.name = tmpnam(tempName);
		TagFile.fp = fopen(TagFile.name, "w");
	    }
	}
	else
	{
	    TagFile.name = Option.tagFile;
	    if (isSourceFile(TagFile.name))
		printUsage("-o: Attempt to overwrite a non-tag file");
	    TagFile.fp = fopen(TagFile.name, Option.append ? "a" : "w");
	}
	if (TagFile.fp == NULL)
	{
	    perror(TagFile.name);
	    exit(1);
	}
}

static void closeTagFile()
{
	fclose(TagFile.fp);
}

static void testEtagsInvocation( path )
    const char *const path;
{
    const char *name = strrchr(path, PATH_SEPARATOR);	/* get tail component */

    if (name != NULL)
	++name;					/* skip over path separator */
    else
	name = path;

    if (strncmp(name, ETAGS, strlen(ETAGS)) == 0)
    {
	Option.startedAsEtags	= TRUE;
	Option.etags		= TRUE;
	Option.unsorted		= TRUE;
    }
}

static void setDefaultTagFileName()
{
    if (Option.tagFile != NULL)
	;		/* accept given name */
    else if (Option.etags)
	Option.tagFile = ETAGS_FILE;
    else
	Option.tagFile = CTAGS_FILE;
}

static void setOptionDefaults()
{
    /*  Disable warnings, since these are only supported with sorted tags.
     */
    if (Option.unsorted)
	Option.warnings = FALSE;

    setDefaultTagFileName();
}

extern int main( argc, argv )
    int argc;
    char **argv;
{
    char **envArgList;
    char *const *fileList;

#ifdef __EMX__
    _wildcard(&argc, &argv);		/* expand wildcards in argument list */
#endif

    testEtagsInvocation(argv[0]);
    envArgList = parseEnvironmentOptions();
    fileList = parseOptions(&argv[1]);		/* 0th arg is program name */
    setOptionDefaults();

    /*	There must always be at least one source file or a file list.
     */
    if (*fileList == NULL  &&  Option.fileList == NULL)
	printUsage("No files specified");
    else
	makeTags(fileList);

    if (envArgList != NULL)
	free(envArgList);
    exit(0);
    return argc;		/* kludge to stop warning about argc unused */
}

/* vi:set tabstop=8 shiftwidth=4: */
