1670 lines
49 KiB
C
1670 lines
49 KiB
C
/*
|
|
* $Id: options.c 576 2007-06-30 04:16:23Z elliotth $
|
|
*
|
|
* Copyright (c) 1996-2003, Darren Hiebert
|
|
*
|
|
* This source code is released for free distribution under the terms of the
|
|
* GNU General Public License.
|
|
*
|
|
* This module contains functions to process command line options.
|
|
*/
|
|
#include "third_party/ctags/general.h"
|
|
/* must always come first */
|
|
#include "third_party/ctags/ctags.h"
|
|
#include "third_party/ctags/debug.h"
|
|
#include "third_party/ctags/main.h"
|
|
#define OPTION_WRITE
|
|
#include "libc/fmt/fmt.h"
|
|
#include "third_party/ctags/options.h"
|
|
#include "third_party/ctags/parse.h"
|
|
#include "third_party/ctags/routines.h"
|
|
|
|
/*
|
|
* MACROS
|
|
*/
|
|
#define INVOCATION "Usage: %s [options] [file(s)]\n"
|
|
|
|
#define CTAGS_ENVIRONMENT "CTAGS"
|
|
#define ETAGS_ENVIRONMENT "ETAGS"
|
|
|
|
#define CTAGS_FILE "tags"
|
|
#define ETAGS_FILE "TAGS"
|
|
|
|
#ifndef ETAGS
|
|
#define ETAGS "etags" /* name which causes default use of to -e */
|
|
#endif
|
|
|
|
/* The following separators are permitted for list options.
|
|
*/
|
|
#define EXTENSION_SEPARATOR '.'
|
|
#define PATTERN_START '('
|
|
#define PATTERN_STOP ')'
|
|
#define IGNORE_SEPARATORS ", \t\n"
|
|
|
|
#ifndef DEFAULT_FILE_FORMAT
|
|
#define DEFAULT_FILE_FORMAT 2
|
|
#endif
|
|
|
|
#if defined(HAVE_OPENDIR) || defined(HAVE_FINDFIRST) || \
|
|
defined(HAVE__FINDFIRST) || defined(AMIGA)
|
|
#define RECURSE_SUPPORTED
|
|
#endif
|
|
|
|
#define isCompoundOption(c) (boolean)(strchr("fohiILpDb", (c)) != NULL)
|
|
|
|
/*
|
|
* Data declarations
|
|
*/
|
|
|
|
enum eOptionLimits {
|
|
MaxHeaderExtensions = 100, /* maximum number of extensions in -h option */
|
|
MaxSupportedTagFormat = 2
|
|
};
|
|
|
|
typedef struct sOptionDescription {
|
|
int usedByEtags;
|
|
const char *description;
|
|
} optionDescription;
|
|
|
|
typedef void (*parametricOptionHandler)(const char *const option,
|
|
const char *const parameter);
|
|
|
|
typedef const struct {
|
|
const char *name; /* name of option as specified by user */
|
|
parametricOptionHandler handler; /* routine to handle option */
|
|
boolean initOnly; /* option must be specified before any files */
|
|
} parametricOption;
|
|
|
|
typedef const struct {
|
|
const char *name; /* name of option as specified by user */
|
|
boolean *pValue; /* pointer to option value */
|
|
boolean initOnly; /* option must be specified before any files */
|
|
} booleanOption;
|
|
|
|
/*
|
|
* DATA DEFINITIONS
|
|
*/
|
|
|
|
static boolean NonOptionEncountered;
|
|
static stringList *OptionFiles;
|
|
static stringList *Excluded;
|
|
static boolean FilesRequired = TRUE;
|
|
static boolean SkipConfiguration;
|
|
|
|
static const char *const HeaderExtensions[] = {
|
|
"h", "H", "hh", "hpp", "hxx", "h++", "inc", "def", NULL};
|
|
|
|
optionValues Option = {
|
|
{
|
|
FALSE, /* --extra=f */
|
|
FALSE, /* --extra=q */
|
|
TRUE, /* --file-scope */
|
|
},
|
|
{
|
|
FALSE, /* -fields=a */
|
|
TRUE, /* -fields=f */
|
|
FALSE, /* -fields=m */
|
|
FALSE, /* -fields=i */
|
|
TRUE, /* -fields=k */
|
|
FALSE, /* -fields=z */
|
|
FALSE, /* -fields=K */
|
|
FALSE, /* -fields=l */
|
|
FALSE, /* -fields=n */
|
|
TRUE, /* -fields=s */
|
|
FALSE, /* -fields=S */
|
|
TRUE /* -fields=t */
|
|
},
|
|
NULL, /* -I */
|
|
FALSE, /* -a */
|
|
FALSE, /* -B */
|
|
FALSE, /* -e */
|
|
#ifdef MACROS_USE_PATTERNS
|
|
EX_PATTERN, /* -n, --excmd */
|
|
#else
|
|
EX_MIX, /* -n, --excmd */
|
|
#endif
|
|
FALSE, /* -R */
|
|
SO_SORTED, /* -u, --sort */
|
|
FALSE, /* -V */
|
|
FALSE, /* -x */
|
|
NULL, /* -L */
|
|
NULL, /* -o */
|
|
NULL, /* -h */
|
|
NULL, /* --etags-include */
|
|
DEFAULT_FILE_FORMAT, /* --format */
|
|
FALSE, /* --if0 */
|
|
FALSE, /* --kind-long */
|
|
LANG_AUTO, /* --lang */
|
|
TRUE, /* --links */
|
|
FALSE, /* --filter */
|
|
NULL, /* --filter-terminator */
|
|
FALSE, /* --tag-relative */
|
|
FALSE, /* --totals */
|
|
FALSE, /* --line-directives */
|
|
#ifdef DEBUG
|
|
0,
|
|
0 /* -D, -b */
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
- Locally used only
|
|
*/
|
|
|
|
static optionDescription LongOptionDescription[] = {
|
|
{1, " -a Append the tags to an existing tag file."},
|
|
#ifdef DEBUG
|
|
{1, " -b <line>"},
|
|
{1, " Set break line."},
|
|
#endif
|
|
{0, " -B Use backward searching patterns (?...?)."},
|
|
#ifdef DEBUG
|
|
{1, " -D <level>"},
|
|
{1, " Set debug level."},
|
|
#endif
|
|
{0, " -e Output tag file for use with Emacs."},
|
|
{1, " -f <name>"},
|
|
{1, " Write tags to specified file. Value of \"-\" writes tags to "
|
|
"stdout"},
|
|
{1, " [\"tags\"; or \"TAGS\" when -e supplied]."},
|
|
{0, " -F Use forward searching patterns (/.../) (default)."},
|
|
{1, " -h <list>"},
|
|
{1,
|
|
" Specify list of file extensions to be treated as include files."},
|
|
{1, " [\".h.H.hh.hpp.hxx.h++\"]."},
|
|
{1, " -I <list|@file>"},
|
|
{1,
|
|
" A list of tokens to be specially handled is read from either the"},
|
|
{1, " command line or the specified file."},
|
|
{1, " -L <file>"},
|
|
{1, " A list of source file names are read from the specified file."},
|
|
{1, " If specified as \"-\", then standard input is read."},
|
|
{0, " -n Equivalent to --excmd=number."},
|
|
{0, " -N Equivalent to --excmd=pattern."},
|
|
{1, " -o Alternative for -f."},
|
|
#ifdef RECURSE_SUPPORTED
|
|
{1, " -R Equivalent to --recurse."},
|
|
#else
|
|
{1, " -R Not supported on this platform."},
|
|
#endif
|
|
{0, " -u Equivalent to --sort=no."},
|
|
{1, " -V Equivalent to --verbose."},
|
|
{1, " -x Print a tabular cross reference file to standard output."},
|
|
{1, " --append=[yes|no]"},
|
|
{1, " Should tags should be appended to existing tag file [no]?"},
|
|
{1, " --etags-include=file"},
|
|
{1, " Include reference to 'file' in Emacs-style tag file (requires "
|
|
"-e)."},
|
|
{1, " --exclude=pattern"},
|
|
{1, " Exclude files and directories matching 'pattern'."},
|
|
{0, " --excmd=number|pattern|mix"},
|
|
#ifdef MACROS_USE_PATTERNS
|
|
{0,
|
|
" Uses the specified type of EX command to locate tags [pattern]."},
|
|
#else
|
|
{0, " Uses the specified type of EX command to locate tags [mix]."},
|
|
#endif
|
|
{1, " --extra=[+|-]flags"},
|
|
{1, " Include extra tag entries for selected information (flags: "
|
|
"\"fq\")."},
|
|
{1, " --fields=[+|-]flags"},
|
|
{1, " Include selected extension fields (flags: \"afmikKlnsStz\") "
|
|
"[fks]."},
|
|
{1, " --file-scope=[yes|no]"},
|
|
{1,
|
|
" Should tags scoped only for a single file (e.g. \"static\" tags"},
|
|
{1, " be included in the output [yes]?"},
|
|
{1, " --filter=[yes|no]"},
|
|
{1,
|
|
" Behave as a filter, reading file names from standard input and"},
|
|
{1, " writing tags to standard output [no]."},
|
|
{1, " --filter-terminator=string"},
|
|
{1, " Specify string to print to stdout following the tags for each "
|
|
"file"},
|
|
{1, " parsed when --filter is enabled."},
|
|
{0, " --format=level"},
|
|
#if DEFAULT_FILE_FORMAT == 1
|
|
{0, " Force output of specified tag file format [1]."},
|
|
#else
|
|
{0, " Force output of specified tag file format [2]."},
|
|
#endif
|
|
{1, " --help"},
|
|
{1, " Print this option summary."},
|
|
{1, " --if0=[yes|no]"},
|
|
{1,
|
|
" Should C code within #if 0 conditional branches be parsed [no]?"},
|
|
{1, " --<LANG>-kinds=[+|-]kinds"},
|
|
{1, " Enable/disable tag kinds for language <LANG>."},
|
|
{1, " --langdef=name"},
|
|
{1, " Define a new language to be parsed with regular expressions."},
|
|
{1, " --langmap=map(s)"},
|
|
{1,
|
|
" Override default mapping of language to source file extension."},
|
|
{1, " --language-force=language"},
|
|
{1, " Force all files to be interpreted using specified language."},
|
|
{1, " --languages=[+|-]list"},
|
|
{1, " Restrict files scanned for tags to those mapped to langauges"},
|
|
{1, " specified in the comma-separated 'list'. The list can contain "
|
|
"any"},
|
|
{1, " built-in or user-defined language [all]."},
|
|
{1, " --license"},
|
|
{1, " Print details of software license."},
|
|
{0, " --line-directives=[yes|no]"},
|
|
{0, " Should #line directives be processed [no]?"},
|
|
{1, " --links=[yes|no]"},
|
|
{1, " Indicate whether symbolic links should be followed [yes]."},
|
|
{1, " --list-kinds=[language|all]"},
|
|
{1, " Output a list of all tag kinds for specified language or all."},
|
|
{1, " --list-languages"},
|
|
{1, " Output list of supported languages."},
|
|
{1, " --list-maps=[language|all]"},
|
|
{1, " Output list of language mappings."},
|
|
{1, " --options=file"},
|
|
{1, " Specify file from which command line options should be read."},
|
|
{1, " --recurse=[yes|no]"},
|
|
#ifdef RECURSE_SUPPORTED
|
|
{1, " Recurse into directories supplied on command line [no]."},
|
|
#else
|
|
{1, " Not supported on this platform."},
|
|
#endif
|
|
#ifdef HAVE_REGEX
|
|
{1, " --regex-<LANG>=/line_pattern/name_pattern/[flags]"},
|
|
{1, " Define regular expression for locating tags in specific "
|
|
"language."},
|
|
#endif
|
|
{0, " --sort=[yes|no|foldcase]"},
|
|
{0, " Should tags be sorted (optionally ignoring case) [yes]?."},
|
|
{0, " --tag-relative=[yes|no]"},
|
|
{0, " Should paths be relative to location of tag file [no; yes when "
|
|
"-e]?"},
|
|
{1, " --totals=[yes|no]"},
|
|
{1, " Print statistics about source and tag files [no]."},
|
|
{1, " --verbose=[yes|no]"},
|
|
{1,
|
|
" Enable verbose messages describing actions on each source file."},
|
|
{1, " --version"},
|
|
{1, " Print version identifier to standard output."},
|
|
{1, NULL}};
|
|
|
|
static const char *const License1 =
|
|
"This program is free software; you can redistribute it and/or\n"
|
|
"modify it under the terms of the GNU General Public License\n"
|
|
"as published by the Free Software Foundation; either version 2\n"
|
|
"of the License, or (at your option) any later version.\n"
|
|
"\n";
|
|
static const char *const License2 =
|
|
"This program is distributed in the hope that it will be useful,\n"
|
|
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
|
|
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
|
|
"GNU General Public License for more details.\n"
|
|
"\n"
|
|
"You should have received a copy of the GNU General Public License\n"
|
|
"along with this program; if not, write to the Free Software\n"
|
|
"Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, "
|
|
"USA.\n";
|
|
|
|
/* Contains a set of strings describing the set of "features" compiled into
|
|
* the code.
|
|
*/
|
|
static const char *const Features[] = {
|
|
#ifdef WIN32
|
|
"win32",
|
|
#endif
|
|
#ifdef DJGPP
|
|
"msdos_32",
|
|
#else
|
|
#ifdef MSDOS
|
|
"msdos_16",
|
|
#endif
|
|
#endif
|
|
#ifdef OS2
|
|
"os2",
|
|
#endif
|
|
#ifdef AMIGA
|
|
"amiga",
|
|
#endif
|
|
#ifdef VMS
|
|
"vms",
|
|
#endif
|
|
#ifdef HAVE_FNMATCH
|
|
"wildcards",
|
|
#endif
|
|
#ifdef HAVE_REGEX
|
|
"regex",
|
|
#endif
|
|
#ifndef EXTERNAL_SORT
|
|
"internal-sort",
|
|
#endif
|
|
#ifdef CUSTOM_CONFIGURATION_FILE
|
|
"custom-conf",
|
|
#endif
|
|
#if (defined(MSDOS) || defined(WIN32) || defined(OS2)) && \
|
|
defined(UNIX_PATH_SEPARATOR)
|
|
"unix-path-separator",
|
|
#endif
|
|
#ifdef DEBUG
|
|
"debug",
|
|
#endif
|
|
NULL};
|
|
|
|
/*
|
|
* FUNCTION PROTOTYPES
|
|
*/
|
|
static boolean parseFileOptions(const char *const fileName);
|
|
|
|
/*
|
|
* FUNCTION DEFINITIONS
|
|
*/
|
|
|
|
extern void verbose(const char *const format, ...) {
|
|
if (Option.verbose) {
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
vprintf(format, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
|
|
static char *stringCopy(const char *const string) {
|
|
char *result = NULL;
|
|
if (string != NULL) result = eStrdup(string);
|
|
return result;
|
|
}
|
|
|
|
static void freeString(char **const pString) {
|
|
if (*pString != NULL) {
|
|
eFree(*pString);
|
|
*pString = NULL;
|
|
}
|
|
}
|
|
|
|
extern void freeList(stringList **const pList) {
|
|
if (*pList != NULL) {
|
|
stringListDelete(*pList);
|
|
*pList = NULL;
|
|
}
|
|
}
|
|
|
|
extern void setDefaultTagFileName(void) {
|
|
if (Option.tagFileName != NULL)
|
|
; /* accept given name */
|
|
else if (Option.etags)
|
|
Option.tagFileName = stringCopy(ETAGS_FILE);
|
|
else
|
|
Option.tagFileName = stringCopy(CTAGS_FILE);
|
|
}
|
|
|
|
extern boolean filesRequired(void) {
|
|
boolean result = FilesRequired;
|
|
if (Option.recurse) result = FALSE;
|
|
return result;
|
|
}
|
|
|
|
extern void checkOptions(void) {
|
|
const char *notice;
|
|
if (Option.xref) {
|
|
notice = "xref output";
|
|
if (Option.include.fileNames) {
|
|
error(WARNING, "%s disables file name tags", notice);
|
|
Option.include.fileNames = FALSE;
|
|
}
|
|
}
|
|
if (Option.append) {
|
|
notice = "append mode is not compatible with";
|
|
if (isDestinationStdout()) error(FATAL, "%s tags to stdout", notice);
|
|
}
|
|
if (Option.filter) {
|
|
notice = "filter mode";
|
|
if (Option.printTotals) {
|
|
error(WARNING, "%s disables totals", notice);
|
|
Option.printTotals = FALSE;
|
|
}
|
|
if (Option.tagFileName != NULL)
|
|
error(WARNING, "%s ignores output tag file name", notice);
|
|
}
|
|
}
|
|
|
|
static void setEtagsMode(void) {
|
|
Option.etags = TRUE;
|
|
Option.sorted = SO_UNSORTED;
|
|
Option.lineDirectives = FALSE;
|
|
Option.tagRelative = TRUE;
|
|
}
|
|
|
|
extern void testEtagsInvocation(void) {
|
|
char *const execName = eStrdup(getExecutableName());
|
|
char *const etags = eStrdup(ETAGS);
|
|
#ifdef CASE_INSENSITIVE_FILENAMES
|
|
toLowerString(execName);
|
|
toLowerString(etags);
|
|
#endif
|
|
if (strstr(execName, etags) != NULL) {
|
|
verbose("Running in etags mode\n");
|
|
setEtagsMode();
|
|
}
|
|
eFree(execName);
|
|
eFree(etags);
|
|
}
|
|
|
|
/*
|
|
* Cooked argument parsing
|
|
*/
|
|
|
|
static void parseShortOption(cookedArgs *const args) {
|
|
args->simple[0] = *args->shortOptions++;
|
|
args->simple[1] = '\0';
|
|
args->item = args->simple;
|
|
if (!isCompoundOption(*args->simple))
|
|
args->parameter = "";
|
|
else if (*args->shortOptions == '\0') {
|
|
argForth(args->args);
|
|
if (argOff(args->args))
|
|
args->parameter = NULL;
|
|
else
|
|
args->parameter = argItem(args->args);
|
|
args->shortOptions = NULL;
|
|
} else {
|
|
args->parameter = args->shortOptions;
|
|
args->shortOptions = NULL;
|
|
}
|
|
}
|
|
|
|
static void parseLongOption(cookedArgs *const args, const char *item) {
|
|
const char *const equal = strchr(item, '=');
|
|
if (equal == NULL) {
|
|
args->item = eStrdup(item); /* FIXME: memory leak. */
|
|
args->parameter = "";
|
|
} else {
|
|
const size_t length = equal - item;
|
|
args->item = xMalloc(length + 1, char); /* FIXME: memory leak. */
|
|
strncpy(args->item, item, length);
|
|
args->item[length] = '\0';
|
|
args->parameter = equal + 1;
|
|
}
|
|
Assert(args->item != NULL);
|
|
Assert(args->parameter != NULL);
|
|
}
|
|
|
|
static void cArgRead(cookedArgs *const current) {
|
|
char *item;
|
|
|
|
Assert(current != NULL);
|
|
if (!argOff(current->args)) {
|
|
item = argItem(current->args);
|
|
current->shortOptions = NULL;
|
|
Assert(item != NULL);
|
|
if (strncmp(item, "--", (size_t)2) == 0) {
|
|
current->isOption = TRUE;
|
|
current->longOption = TRUE;
|
|
parseLongOption(current, item + 2);
|
|
Assert(current->item != NULL);
|
|
Assert(current->parameter != NULL);
|
|
} else if (*item == '-') {
|
|
current->isOption = TRUE;
|
|
current->longOption = FALSE;
|
|
current->shortOptions = item + 1;
|
|
parseShortOption(current);
|
|
} else {
|
|
current->isOption = FALSE;
|
|
current->longOption = FALSE;
|
|
current->item = item;
|
|
current->parameter = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern cookedArgs *cArgNewFromString(const char *string) {
|
|
cookedArgs *const result = xMalloc(1, cookedArgs);
|
|
memset(result, 0, sizeof(cookedArgs));
|
|
result->args = argNewFromString(string);
|
|
cArgRead(result);
|
|
return result;
|
|
}
|
|
|
|
extern cookedArgs *cArgNewFromArgv(char *const *const argv) {
|
|
cookedArgs *const result = xMalloc(1, cookedArgs);
|
|
memset(result, 0, sizeof(cookedArgs));
|
|
result->args = argNewFromArgv(argv);
|
|
cArgRead(result);
|
|
return result;
|
|
}
|
|
|
|
extern cookedArgs *cArgNewFromFile(FILE *const fp) {
|
|
cookedArgs *const result = xMalloc(1, cookedArgs);
|
|
memset(result, 0, sizeof(cookedArgs));
|
|
result->args = argNewFromFile(fp);
|
|
cArgRead(result);
|
|
return result;
|
|
}
|
|
|
|
extern cookedArgs *cArgNewFromLineFile(FILE *const fp) {
|
|
cookedArgs *const result = xMalloc(1, cookedArgs);
|
|
memset(result, 0, sizeof(cookedArgs));
|
|
result->args = argNewFromLineFile(fp);
|
|
cArgRead(result);
|
|
return result;
|
|
}
|
|
|
|
extern void cArgDelete(cookedArgs *const current) {
|
|
Assert(current != NULL);
|
|
argDelete(current->args);
|
|
memset(current, 0, sizeof(cookedArgs));
|
|
eFree(current);
|
|
}
|
|
|
|
static boolean cArgOptionPending(cookedArgs *const current) {
|
|
boolean result = FALSE;
|
|
if (current->shortOptions != NULL)
|
|
if (*current->shortOptions != '\0') result = TRUE;
|
|
return result;
|
|
}
|
|
|
|
extern boolean cArgOff(cookedArgs *const current) {
|
|
Assert(current != NULL);
|
|
return (boolean)(argOff(current->args) && !cArgOptionPending(current));
|
|
}
|
|
|
|
extern boolean cArgIsOption(cookedArgs *const current) {
|
|
Assert(current != NULL);
|
|
return current->isOption;
|
|
}
|
|
|
|
extern const char *cArgItem(cookedArgs *const current) {
|
|
Assert(current != NULL);
|
|
return current->item;
|
|
}
|
|
|
|
extern void cArgForth(cookedArgs *const current) {
|
|
Assert(current != NULL);
|
|
Assert(!cArgOff(current));
|
|
if (cArgOptionPending(current))
|
|
parseShortOption(current);
|
|
else {
|
|
Assert(!argOff(current->args));
|
|
argForth(current->args);
|
|
if (!argOff(current->args))
|
|
cArgRead(current);
|
|
else {
|
|
current->isOption = FALSE;
|
|
current->longOption = FALSE;
|
|
current->shortOptions = NULL;
|
|
current->item = NULL;
|
|
current->parameter = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* File extension and language mapping
|
|
*/
|
|
|
|
static void addExtensionList(stringList *const slist, const char *const elist,
|
|
const boolean clear) {
|
|
char *const extensionList = eStrdup(elist);
|
|
const char *extension = NULL;
|
|
boolean first = TRUE;
|
|
|
|
if (clear) {
|
|
verbose(" clearing\n");
|
|
stringListClear(slist);
|
|
}
|
|
verbose(" adding: ");
|
|
if (elist != NULL && *elist != '\0') {
|
|
extension = extensionList;
|
|
if (elist[0] == EXTENSION_SEPARATOR) ++extension;
|
|
}
|
|
while (extension != NULL) {
|
|
char *separator = strchr(extension, EXTENSION_SEPARATOR);
|
|
if (separator != NULL) *separator = '\0';
|
|
verbose("%s%s", first ? "" : ", ",
|
|
*extension == '\0' ? "(NONE)" : extension);
|
|
stringListAdd(slist, vStringNewInit(extension));
|
|
first = FALSE;
|
|
if (separator == NULL)
|
|
extension = NULL;
|
|
else
|
|
extension = separator + 1;
|
|
}
|
|
if (Option.verbose) {
|
|
printf("\n now: ");
|
|
stringListPrint(slist);
|
|
putchar('\n');
|
|
}
|
|
eFree(extensionList);
|
|
}
|
|
|
|
static boolean isFalse(const char *parameter) {
|
|
return (boolean)(
|
|
strcasecmp(parameter, "0") == 0 || strcasecmp(parameter, "n") == 0 ||
|
|
strcasecmp(parameter, "no") == 0 || strcasecmp(parameter, "off") == 0);
|
|
}
|
|
|
|
static boolean isTrue(const char *parameter) {
|
|
return (boolean)(
|
|
strcasecmp(parameter, "1") == 0 || strcasecmp(parameter, "y") == 0 ||
|
|
strcasecmp(parameter, "yes") == 0 || strcasecmp(parameter, "on") == 0);
|
|
}
|
|
|
|
/* Determines whether the specified file name is considered to be a header
|
|
* file for the purposes of determining whether enclosed tags are global or
|
|
* static.
|
|
*/
|
|
extern boolean isIncludeFile(const char *const fileName) {
|
|
boolean result = FALSE;
|
|
const char *const extension = fileExtension(fileName);
|
|
if (Option.headerExt != NULL)
|
|
result = stringListExtensionMatched(Option.headerExt, extension);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Specific option processing
|
|
*/
|
|
|
|
static void processEtagsInclude(const char *const option,
|
|
const char *const parameter) {
|
|
if (!Option.etags)
|
|
error(FATAL, "Etags must be enabled to use \"%s\" option", option);
|
|
else {
|
|
vString *const file = vStringNewInit(parameter);
|
|
if (Option.etagsInclude == NULL) Option.etagsInclude = stringListNew();
|
|
stringListAdd(Option.etagsInclude, file);
|
|
FilesRequired = FALSE;
|
|
}
|
|
}
|
|
|
|
static void processExcludeOption(const char *const option __unused__,
|
|
const char *const parameter) {
|
|
const char *const fileName = parameter + 1;
|
|
if (parameter[0] == '\0')
|
|
freeList(&Excluded);
|
|
else if (parameter[0] == '@') {
|
|
stringList *const sl = stringListNewFromFile(fileName);
|
|
if (sl == NULL) error(FATAL | PERROR, "cannot open \"%s\"", fileName);
|
|
if (Excluded == NULL)
|
|
Excluded = sl;
|
|
else
|
|
stringListCombine(Excluded, sl);
|
|
verbose(" adding exclude patterns from %s\n", fileName);
|
|
} else {
|
|
vString *const item = vStringNewInit(parameter);
|
|
if (Excluded == NULL) Excluded = stringListNew();
|
|
stringListAdd(Excluded, item);
|
|
verbose(" adding exclude pattern: %s\n", parameter);
|
|
}
|
|
}
|
|
|
|
extern boolean isExcludedFile(const char *const name) {
|
|
const char *base = baseFilename(name);
|
|
boolean result = FALSE;
|
|
if (Excluded != NULL) {
|
|
result = stringListFileMatched(Excluded, base);
|
|
if (!result && name != base) result = stringListFileMatched(Excluded, name);
|
|
}
|
|
#ifdef AMIGA
|
|
/* not a good solution, but the only one which works often */
|
|
if (!result) result = (boolean)(strcmp(name, TagFile.name) == 0);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
static void processExcmdOption(const char *const option,
|
|
const char *const parameter) {
|
|
switch (*parameter) {
|
|
case 'm':
|
|
Option.locate = EX_MIX;
|
|
break;
|
|
case 'n':
|
|
Option.locate = EX_LINENUM;
|
|
break;
|
|
case 'p':
|
|
Option.locate = EX_PATTERN;
|
|
break;
|
|
default:
|
|
error(FATAL, "Invalid value for \"%s\" option", option);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void processExtraTagsOption(const char *const option,
|
|
const char *const parameter) {
|
|
struct sInclude *const inc = &Option.include;
|
|
const char *p = parameter;
|
|
boolean mode = TRUE;
|
|
int c;
|
|
|
|
if (*p != '+' && *p != '-') {
|
|
inc->fileNames = FALSE;
|
|
inc->qualifiedTags = FALSE;
|
|
#if 0
|
|
inc->fileScope = FALSE;
|
|
#endif
|
|
}
|
|
while ((c = *p++) != '\0') switch (c) {
|
|
case '+':
|
|
mode = TRUE;
|
|
break;
|
|
case '-':
|
|
mode = FALSE;
|
|
break;
|
|
|
|
case 'f':
|
|
inc->fileNames = mode;
|
|
break;
|
|
case 'q':
|
|
inc->qualifiedTags = mode;
|
|
break;
|
|
#if 0
|
|
case 'F': inc->fileScope = mode; break;
|
|
#endif
|
|
|
|
default:
|
|
error(WARNING, "Unsupported parameter '%c' for \"%s\" option", c,
|
|
option);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void processFieldsOption(const char *const option,
|
|
const char *const parameter) {
|
|
struct sExtFields *field = &Option.extensionFields;
|
|
const char *p = parameter;
|
|
boolean mode = TRUE;
|
|
int c;
|
|
|
|
if (*p != '+' && *p != '-') {
|
|
field->access = FALSE;
|
|
field->fileScope = FALSE;
|
|
field->implementation = FALSE;
|
|
field->inheritance = FALSE;
|
|
field->kind = FALSE;
|
|
field->kindKey = FALSE;
|
|
field->kindLong = FALSE;
|
|
field->language = FALSE;
|
|
field->scope = FALSE;
|
|
field->typeRef = FALSE;
|
|
}
|
|
while ((c = *p++) != '\0') switch (c) {
|
|
case '+':
|
|
mode = TRUE;
|
|
break;
|
|
case '-':
|
|
mode = FALSE;
|
|
break;
|
|
|
|
case 'a':
|
|
field->access = mode;
|
|
break;
|
|
case 'f':
|
|
field->fileScope = mode;
|
|
break;
|
|
case 'm':
|
|
field->implementation = mode;
|
|
break;
|
|
case 'i':
|
|
field->inheritance = mode;
|
|
break;
|
|
case 'k':
|
|
field->kind = mode;
|
|
break;
|
|
case 'K':
|
|
field->kindLong = mode;
|
|
break;
|
|
case 'l':
|
|
field->language = mode;
|
|
break;
|
|
case 'n':
|
|
field->lineNumber = mode;
|
|
break;
|
|
case 's':
|
|
field->scope = mode;
|
|
break;
|
|
case 'S':
|
|
field->signature = mode;
|
|
break;
|
|
case 'z':
|
|
field->kindKey = mode;
|
|
break;
|
|
case 't':
|
|
field->typeRef = mode;
|
|
break;
|
|
|
|
default:
|
|
error(WARNING, "Unsupported parameter '%c' for \"%s\" option", c,
|
|
option);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void processFilterTerminatorOption(const char *const option __unused__,
|
|
const char *const parameter) {
|
|
freeString(&Option.filterTerminator);
|
|
Option.filterTerminator = stringCopy(parameter);
|
|
}
|
|
|
|
static void processFormatOption(const char *const option,
|
|
const char *const parameter) {
|
|
unsigned int format;
|
|
|
|
if (sscanf(parameter, "%u", &format) < 1)
|
|
error(FATAL, "Invalid value for \"%s\" option", option);
|
|
else if (format <= (unsigned int)MaxSupportedTagFormat)
|
|
Option.tagFileFormat = format;
|
|
else
|
|
error(FATAL, "Unsupported value for \"%s\" option", option);
|
|
}
|
|
|
|
static void printInvocationDescription(void) {
|
|
printf(INVOCATION, getExecutableName());
|
|
}
|
|
|
|
static void printOptionDescriptions(const optionDescription *const optDesc) {
|
|
int i;
|
|
for (i = 0; optDesc[i].description != NULL; ++i) {
|
|
if (!Option.etags || optDesc[i].usedByEtags) puts(optDesc[i].description);
|
|
}
|
|
}
|
|
|
|
static void printFeatureList(void) {
|
|
int i;
|
|
|
|
for (i = 0; Features[i] != NULL; ++i) {
|
|
if (i == 0) printf(" Optional compiled features: ");
|
|
printf("%s+%s", (i > 0 ? ", " : ""), Features[i]);
|
|
#ifdef CUSTOM_CONFIGURATION_FILE
|
|
if (strcmp(Features[i], "custom-conf") == 0)
|
|
printf("=%s", CUSTOM_CONFIGURATION_FILE);
|
|
#endif
|
|
}
|
|
if (i > 0) putchar('\n');
|
|
}
|
|
|
|
static void printProgramIdentification(void) {
|
|
printf("%s %s, %s %s\n", PROGRAM_NAME, PROGRAM_VERSION, PROGRAM_COPYRIGHT,
|
|
AUTHOR_NAME);
|
|
printf(" Addresses: <%s>, %s\n", AUTHOR_EMAIL, PROGRAM_URL);
|
|
printFeatureList();
|
|
}
|
|
|
|
static void processHelpOption(const char *const option __unused__,
|
|
const char *const parameter __unused__) {
|
|
printProgramIdentification();
|
|
putchar('\n');
|
|
printInvocationDescription();
|
|
putchar('\n');
|
|
printOptionDescriptions(LongOptionDescription);
|
|
exit(0);
|
|
}
|
|
|
|
static void processLanguageForceOption(const char *const option,
|
|
const char *const parameter) {
|
|
langType language;
|
|
if (strcasecmp(parameter, "auto") == 0)
|
|
language = LANG_AUTO;
|
|
else
|
|
language = getNamedLanguage(parameter);
|
|
|
|
if (strcmp(option, "lang") == 0 || strcmp(option, "language") == 0)
|
|
error(WARNING,
|
|
"\"--%s\" option is obsolete; use \"--language-force\" instead",
|
|
option);
|
|
if (language == LANG_IGNORE)
|
|
error(FATAL, "Unknown language \"%s\" in \"%s\" option", parameter, option);
|
|
else
|
|
Option.language = language;
|
|
}
|
|
static char *skipPastMap(char *p) {
|
|
while (*p != EXTENSION_SEPARATOR && *p != PATTERN_START && *p != ',' &&
|
|
*p != '\0')
|
|
++p;
|
|
return p;
|
|
}
|
|
|
|
/* Parses the mapping beginning at `map', adds it to the language map, and
|
|
* returns first character past the map.
|
|
*/
|
|
static char *addLanguageMap(const langType language, char *map) {
|
|
char *p = NULL;
|
|
const char first = *map;
|
|
if (first == EXTENSION_SEPARATOR) /* extension map */
|
|
{
|
|
++map;
|
|
p = skipPastMap(map);
|
|
if (*p == '\0') {
|
|
verbose(" .%s", map);
|
|
addLanguageExtensionMap(language, map);
|
|
p = map + strlen(map);
|
|
} else {
|
|
const char separator = *p;
|
|
*p = '\0';
|
|
verbose(" .%s", map);
|
|
addLanguageExtensionMap(language, map);
|
|
*p = separator;
|
|
}
|
|
} else if (first == PATTERN_START) /* pattern map */
|
|
{
|
|
++map;
|
|
for (p = map; *p != PATTERN_STOP && *p != '\0'; ++p) {
|
|
if (*p == '\\' && *(p + 1) == PATTERN_STOP) ++p;
|
|
}
|
|
if (*p == '\0')
|
|
error(FATAL, "Unterminated file name pattern for %s language",
|
|
getLanguageName(language));
|
|
else {
|
|
*p++ = '\0';
|
|
verbose(" (%s)", map);
|
|
addLanguagePatternMap(language, map);
|
|
}
|
|
} else
|
|
error(FATAL, "Badly formed language map for %s language",
|
|
getLanguageName(language));
|
|
return p;
|
|
}
|
|
|
|
static char *processLanguageMap(char *map) {
|
|
char *const separator = strchr(map, ':');
|
|
char *result = NULL;
|
|
if (separator != NULL) {
|
|
langType language;
|
|
char *list = separator + 1;
|
|
boolean clear = FALSE;
|
|
*separator = '\0';
|
|
language = getNamedLanguage(map);
|
|
if (language != LANG_IGNORE) {
|
|
const char *const deflt = "default";
|
|
char *p;
|
|
if (*list == '+')
|
|
++list;
|
|
else
|
|
clear = TRUE;
|
|
for (p = list; *p != ',' && *p != '\0'; ++p) /*no-op*/
|
|
;
|
|
if ((size_t)(p - list) == strlen(deflt) &&
|
|
strncasecmp(list, deflt, p - list) == 0) {
|
|
verbose(" Restoring default %s language map: ",
|
|
getLanguageName(language));
|
|
installLanguageMapDefault(language);
|
|
list = p;
|
|
} else {
|
|
if (clear) {
|
|
verbose(" Setting %s language map:", getLanguageName(language));
|
|
clearLanguageMap(language);
|
|
} else
|
|
verbose(" Adding to %s language map:", getLanguageName(language));
|
|
while (list != NULL && *list != '\0' && *list != ',')
|
|
list = addLanguageMap(language, list);
|
|
verbose("\n");
|
|
}
|
|
if (list != NULL && *list == ',') ++list;
|
|
result = list;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void processLanguageMapOption(const char *const option,
|
|
const char *const parameter) {
|
|
char *const maps = eStrdup(parameter);
|
|
char *map = maps;
|
|
|
|
if (strcmp(parameter, "default") == 0) {
|
|
verbose(" Restoring default language maps:\n");
|
|
installLanguageMapDefaults();
|
|
} else
|
|
while (map != NULL && *map != '\0') {
|
|
char *const next = processLanguageMap(map);
|
|
if (next == NULL)
|
|
error(WARNING, "Unknown language \"%s\" in \"%s\" option", parameter,
|
|
option);
|
|
map = next;
|
|
}
|
|
eFree(maps);
|
|
}
|
|
|
|
static void processLanguagesOption(const char *const option,
|
|
const char *const parameter) {
|
|
char *const langs = eStrdup(parameter);
|
|
enum { Add, Remove, Replace } mode = Replace;
|
|
boolean first = TRUE;
|
|
char *lang = langs;
|
|
const char *prefix = "";
|
|
verbose(" Enabled languages: ");
|
|
while (lang != NULL) {
|
|
char *const end = strchr(lang, ',');
|
|
if (lang[0] == '+') {
|
|
++lang;
|
|
mode = Add;
|
|
prefix = "+ ";
|
|
} else if (lang[0] == '-') {
|
|
++lang;
|
|
mode = Remove;
|
|
prefix = "- ";
|
|
}
|
|
if (mode == Replace) enableLanguages(FALSE);
|
|
if (end != NULL) *end = '\0';
|
|
if (lang[0] != '\0') {
|
|
if (strcmp(lang, "all") == 0)
|
|
enableLanguages((boolean)(mode != Remove));
|
|
else {
|
|
const langType language = getNamedLanguage(lang);
|
|
if (language == LANG_IGNORE)
|
|
error(WARNING, "Unknown language \"%s\" in \"%s\" option", lang,
|
|
option);
|
|
else
|
|
enableLanguage(language, (boolean)(mode != Remove));
|
|
}
|
|
verbose("%s%s%s", (first ? "" : ", "), prefix, lang);
|
|
prefix = "";
|
|
first = FALSE;
|
|
if (mode == Replace) mode = Add;
|
|
}
|
|
lang = (end != NULL ? end + 1 : NULL);
|
|
}
|
|
verbose("\n");
|
|
eFree(langs);
|
|
}
|
|
|
|
static void processLicenseOption(const char *const option __unused__,
|
|
const char *const parameter __unused__) {
|
|
printProgramIdentification();
|
|
puts("");
|
|
puts(License1);
|
|
puts(License2);
|
|
exit(0);
|
|
}
|
|
|
|
static void processListKindsOption(const char *const option,
|
|
const char *const parameter) {
|
|
if (parameter[0] == '\0' || strcasecmp(parameter, "all") == 0)
|
|
printLanguageKinds(LANG_AUTO);
|
|
else {
|
|
langType language = getNamedLanguage(parameter);
|
|
if (language == LANG_IGNORE)
|
|
error(FATAL, "Unknown language \"%s\" in \"%s\" option", parameter,
|
|
option);
|
|
else
|
|
printLanguageKinds(language);
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
static void processListMapsOption(const char *const __unused__ option,
|
|
const char *const __unused__ parameter) {
|
|
if (parameter[0] == '\0' || strcasecmp(parameter, "all") == 0)
|
|
printLanguageMaps(LANG_AUTO);
|
|
else {
|
|
langType language = getNamedLanguage(parameter);
|
|
if (language == LANG_IGNORE)
|
|
error(FATAL, "Unknown language \"%s\" in \"%s\" option", parameter,
|
|
option);
|
|
else
|
|
printLanguageMaps(language);
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
static void processListLanguagesOption(const char *const option __unused__,
|
|
const char *const parameter __unused__) {
|
|
printLanguageList();
|
|
exit(0);
|
|
}
|
|
|
|
static void processOptionFile(const char *const option,
|
|
const char *const parameter) {
|
|
if (parameter[0] == '\0')
|
|
error(WARNING, "no option file supplied for \"%s\"", option);
|
|
else if (!parseFileOptions(parameter))
|
|
error(FATAL | PERROR, "cannot open option file \"%s\"", parameter);
|
|
}
|
|
|
|
static void processSortOption(const char *const option,
|
|
const char *const parameter) {
|
|
if (isFalse(parameter))
|
|
Option.sorted = SO_UNSORTED;
|
|
else if (isTrue(parameter))
|
|
Option.sorted = SO_SORTED;
|
|
else if (strcasecmp(parameter, "f") == 0 ||
|
|
strcasecmp(parameter, "fold") == 0 ||
|
|
strcasecmp(parameter, "foldcase") == 0)
|
|
Option.sorted = SO_FOLDSORTED;
|
|
else
|
|
error(FATAL, "Invalid value for \"%s\" option", option);
|
|
}
|
|
|
|
static void installHeaderListDefaults(void) {
|
|
Option.headerExt = stringListNewFromArgv(HeaderExtensions);
|
|
if (Option.verbose) {
|
|
printf(" Setting default header extensions: ");
|
|
stringListPrint(Option.headerExt);
|
|
putchar('\n');
|
|
}
|
|
}
|
|
|
|
static void processHeaderListOption(const int option, const char *parameter) {
|
|
/* Check to make sure that the user did not enter "ctags -h *.c"
|
|
* by testing to see if the list is a filename that exists.
|
|
*/
|
|
if (doesFileExist(parameter)) error(FATAL, "-%c: Invalid list", option);
|
|
if (strcmp(parameter, "default") == 0)
|
|
installHeaderListDefaults();
|
|
else {
|
|
boolean clear = TRUE;
|
|
|
|
if (parameter[0] == '+') {
|
|
++parameter;
|
|
clear = FALSE;
|
|
}
|
|
if (Option.headerExt == NULL) Option.headerExt = stringListNew();
|
|
verbose(" Header Extensions:\n");
|
|
addExtensionList(Option.headerExt, parameter, clear);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Token ignore processing
|
|
*/
|
|
|
|
/* Determines whether or not "name" should be ignored, per the ignore list.
|
|
*/
|
|
extern boolean isIgnoreToken(const char *const name,
|
|
boolean *const pIgnoreParens,
|
|
const char **const replacement) {
|
|
boolean result = FALSE;
|
|
|
|
if (Option.ignore != NULL) {
|
|
const size_t nameLen = strlen(name);
|
|
unsigned int i;
|
|
|
|
if (pIgnoreParens != NULL) *pIgnoreParens = FALSE;
|
|
|
|
for (i = 0; i < stringListCount(Option.ignore); ++i) {
|
|
vString *token = stringListItem(Option.ignore, i);
|
|
|
|
if (strncmp(vStringValue(token), name, nameLen) == 0) {
|
|
const size_t tokenLen = vStringLength(token);
|
|
|
|
if (nameLen == tokenLen) {
|
|
result = TRUE;
|
|
break;
|
|
} else if (tokenLen == nameLen + 1 &&
|
|
vStringChar(token, tokenLen - 1) == '+') {
|
|
result = TRUE;
|
|
if (pIgnoreParens != NULL) *pIgnoreParens = TRUE;
|
|
break;
|
|
} else if (vStringChar(token, nameLen) == '=') {
|
|
if (replacement != NULL)
|
|
*replacement = vStringValue(token) + nameLen + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void saveIgnoreToken(vString *const ignoreToken) {
|
|
if (Option.ignore == NULL) Option.ignore = stringListNew();
|
|
stringListAdd(Option.ignore, ignoreToken);
|
|
verbose(" ignore token: %s\n", vStringValue(ignoreToken));
|
|
}
|
|
|
|
static void readIgnoreList(const char *const list) {
|
|
char *newList = stringCopy(list);
|
|
const char *token = strtok(newList, IGNORE_SEPARATORS);
|
|
|
|
while (token != NULL) {
|
|
vString *const entry = vStringNewInit(token);
|
|
|
|
saveIgnoreToken(entry);
|
|
token = strtok(NULL, IGNORE_SEPARATORS);
|
|
}
|
|
eFree(newList);
|
|
}
|
|
|
|
static void addIgnoreListFromFile(const char *const fileName) {
|
|
stringList *tokens = stringListNewFromFile(fileName);
|
|
if (tokens == NULL) error(FATAL | PERROR, "cannot open \"%s\"", fileName);
|
|
if (Option.ignore == NULL)
|
|
Option.ignore = tokens;
|
|
else
|
|
stringListCombine(Option.ignore, tokens);
|
|
}
|
|
|
|
static void processIgnoreOption(const char *const list) {
|
|
if (strchr("@./\\", list[0]) != NULL) {
|
|
const char *fileName = (*list == '@') ? list + 1 : list;
|
|
addIgnoreListFromFile(fileName);
|
|
}
|
|
#if defined(MSDOS) || defined(WIN32) || defined(OS2)
|
|
else if (isalpha(list[0]) && list[1] == ':')
|
|
addIgnoreListFromFile(list);
|
|
#endif
|
|
else if (strcmp(list, "-") == 0) {
|
|
freeList(&Option.ignore);
|
|
verbose(" clearing list\n");
|
|
} else
|
|
readIgnoreList(list);
|
|
}
|
|
|
|
static void processVersionOption(const char *const option __unused__,
|
|
const char *const parameter __unused__) {
|
|
printProgramIdentification();
|
|
exit(0);
|
|
}
|
|
|
|
/*
|
|
* Option tables
|
|
*/
|
|
|
|
static parametricOption ParametricOptions[] = {
|
|
{"etags-include", processEtagsInclude, FALSE},
|
|
{"exclude", processExcludeOption, FALSE},
|
|
{"excmd", processExcmdOption, FALSE},
|
|
{"extra", processExtraTagsOption, FALSE},
|
|
{"fields", processFieldsOption, FALSE},
|
|
{"filter-terminator", processFilterTerminatorOption, TRUE},
|
|
{"format", processFormatOption, TRUE},
|
|
{"help", processHelpOption, TRUE},
|
|
{"lang", processLanguageForceOption, FALSE},
|
|
{"language", processLanguageForceOption, FALSE},
|
|
{"language-force", processLanguageForceOption, FALSE},
|
|
{"languages", processLanguagesOption, FALSE},
|
|
{"langdef", processLanguageDefineOption, FALSE},
|
|
{"langmap", processLanguageMapOption, FALSE},
|
|
{"license", processLicenseOption, TRUE},
|
|
{"list-kinds", processListKindsOption, TRUE},
|
|
{"list-maps", processListMapsOption, TRUE},
|
|
{"list-languages", processListLanguagesOption, TRUE},
|
|
{"options", processOptionFile, FALSE},
|
|
{"sort", processSortOption, TRUE},
|
|
{"version", processVersionOption, TRUE},
|
|
};
|
|
|
|
static booleanOption BooleanOptions[] = {
|
|
{"append", &Option.append, TRUE},
|
|
{"file-scope", &Option.include.fileScope, FALSE},
|
|
{"file-tags", &Option.include.fileNames, FALSE},
|
|
{"filter", &Option.filter, TRUE},
|
|
{"if0", &Option.if0, FALSE},
|
|
{"kind-long", &Option.kindLong, TRUE},
|
|
{"line-directives", &Option.lineDirectives, FALSE},
|
|
{"links", &Option.followLinks, FALSE},
|
|
#ifdef RECURSE_SUPPORTED
|
|
{"recurse", &Option.recurse, FALSE},
|
|
#endif
|
|
{"tag-relative", &Option.tagRelative, TRUE},
|
|
{"totals", &Option.printTotals, TRUE},
|
|
{"verbose", &Option.verbose, FALSE},
|
|
};
|
|
|
|
/*
|
|
* Generic option parsing
|
|
*/
|
|
|
|
static void checkOptionOrder(const char *const option) {
|
|
if (NonOptionEncountered)
|
|
error(FATAL, "-%s option may not follow a file name", option);
|
|
}
|
|
|
|
static boolean processParametricOption(const char *const option,
|
|
const char *const parameter) {
|
|
const int count = sizeof(ParametricOptions) / sizeof(parametricOption);
|
|
boolean found = FALSE;
|
|
int i;
|
|
|
|
for (i = 0; i < count && !found; ++i) {
|
|
parametricOption *const entry = &ParametricOptions[i];
|
|
if (strcmp(option, entry->name) == 0) {
|
|
found = TRUE;
|
|
if (entry->initOnly) checkOptionOrder(option);
|
|
(entry->handler)(option, parameter);
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static boolean getBooleanOption(const char *const option,
|
|
const char *const parameter) {
|
|
boolean selection = TRUE;
|
|
|
|
if (parameter[0] == '\0')
|
|
selection = TRUE;
|
|
else if (isFalse(parameter))
|
|
selection = FALSE;
|
|
else if (isTrue(parameter))
|
|
selection = TRUE;
|
|
else
|
|
error(FATAL, "Invalid value for \"%s\" option", option);
|
|
|
|
return selection;
|
|
}
|
|
|
|
static boolean processBooleanOption(const char *const option,
|
|
const char *const parameter) {
|
|
const int count = sizeof(BooleanOptions) / sizeof(booleanOption);
|
|
boolean found = FALSE;
|
|
int i;
|
|
|
|
for (i = 0; i < count && !found; ++i) {
|
|
booleanOption *const entry = &BooleanOptions[i];
|
|
if (strcmp(option, entry->name) == 0) {
|
|
found = TRUE;
|
|
if (entry->initOnly) checkOptionOrder(option);
|
|
*entry->pValue = getBooleanOption(option, parameter);
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static void processLongOption(const char *const option,
|
|
const char *const parameter) {
|
|
Assert(parameter != NULL);
|
|
if (parameter == NULL && parameter[0] == '\0')
|
|
verbose(" Option: --%s\n", option);
|
|
else
|
|
verbose(" Option: --%s=%s\n", option, parameter);
|
|
|
|
if (processBooleanOption(option, parameter))
|
|
;
|
|
else if (processParametricOption(option, parameter))
|
|
;
|
|
else if (processKindOption(option, parameter))
|
|
;
|
|
else if (processRegexOption(option, parameter))
|
|
;
|
|
#ifndef RECURSE_SUPPORTED
|
|
else if (strcmp(option, "recurse") == 0)
|
|
error(WARNING, "%s option not supported on this host", option);
|
|
#endif
|
|
else
|
|
error(FATAL, "Unknown option: --%s", option);
|
|
}
|
|
|
|
static void processShortOption(const char *const option,
|
|
const char *const parameter) {
|
|
if (parameter == NULL || parameter[0] == '\0')
|
|
verbose(" Option: -%s\n", option);
|
|
else
|
|
verbose(" Option: -%s %s\n", option, parameter);
|
|
|
|
if (isCompoundOption(*option) && (parameter == NULL || parameter[0] == '\0'))
|
|
error(FATAL, "Missing parameter for \"%s\" option", option);
|
|
else
|
|
switch (*option) {
|
|
case '?':
|
|
processHelpOption("?", NULL);
|
|
exit(0);
|
|
break;
|
|
case 'a':
|
|
checkOptionOrder(option);
|
|
Option.append = TRUE;
|
|
break;
|
|
#ifdef DEBUG
|
|
case 'b':
|
|
if (atol(parameter) < 0)
|
|
error(FATAL, "-%s: Invalid line number", option);
|
|
Option.breakLine = atol(parameter);
|
|
break;
|
|
case 'D':
|
|
Option.debugLevel = strtol(parameter, NULL, 0);
|
|
if (debug(DEBUG_STATUS)) Option.verbose = TRUE;
|
|
break;
|
|
#endif
|
|
case 'B':
|
|
Option.backward = TRUE;
|
|
break;
|
|
case 'e':
|
|
checkOptionOrder(option);
|
|
setEtagsMode();
|
|
break;
|
|
case 'f':
|
|
case 'o':
|
|
checkOptionOrder(option);
|
|
if (Option.tagFileName != NULL) {
|
|
error(WARNING, "-%s option specified more than once, last value used",
|
|
option);
|
|
freeString(&Option.tagFileName);
|
|
} else if (parameter[0] == '-' && parameter[1] != '\0')
|
|
error(FATAL, "output file name may not begin with a '-'");
|
|
Option.tagFileName = stringCopy(parameter);
|
|
break;
|
|
case 'F':
|
|
Option.backward = FALSE;
|
|
break;
|
|
case 'h':
|
|
processHeaderListOption(*option, parameter);
|
|
break;
|
|
case 'I':
|
|
processIgnoreOption(parameter);
|
|
break;
|
|
case 'L':
|
|
if (Option.fileList != NULL) {
|
|
error(WARNING, "-%s option specified more than once, last value used",
|
|
option);
|
|
freeString(&Option.fileList);
|
|
}
|
|
Option.fileList = stringCopy(parameter);
|
|
break;
|
|
case 'n':
|
|
Option.locate = EX_LINENUM;
|
|
break;
|
|
case 'N':
|
|
Option.locate = EX_PATTERN;
|
|
break;
|
|
case 'R':
|
|
#ifdef RECURSE_SUPPORTED
|
|
Option.recurse = TRUE;
|
|
#else
|
|
error(WARNING, "-%s option not supported on this host", option);
|
|
#endif
|
|
break;
|
|
case 'u':
|
|
checkOptionOrder(option);
|
|
Option.sorted = SO_UNSORTED;
|
|
break;
|
|
case 'V':
|
|
Option.verbose = TRUE;
|
|
break;
|
|
case 'w':
|
|
/* silently ignored */
|
|
break;
|
|
case 'x':
|
|
checkOptionOrder(option);
|
|
Option.xref = TRUE;
|
|
break;
|
|
default:
|
|
error(FATAL, "Unknown option: -%s", option);
|
|
break;
|
|
}
|
|
}
|
|
|
|
extern void parseOption(cookedArgs *const args) {
|
|
Assert(!cArgOff(args));
|
|
if (args->isOption) {
|
|
if (args->longOption)
|
|
processLongOption(args->item, args->parameter);
|
|
else {
|
|
const char *parameter = args->parameter;
|
|
while (*parameter == ' ') ++parameter;
|
|
processShortOption(args->item, parameter);
|
|
}
|
|
cArgForth(args);
|
|
}
|
|
}
|
|
|
|
extern void parseOptions(cookedArgs *const args) {
|
|
NonOptionEncountered = FALSE;
|
|
while (!cArgOff(args) && cArgIsOption(args)) parseOption(args);
|
|
if (!cArgOff(args) && !cArgIsOption(args)) NonOptionEncountered = TRUE;
|
|
}
|
|
|
|
static const char *CheckFile;
|
|
static boolean checkSameFile(const char *const fileName) {
|
|
return isSameFile(CheckFile, fileName);
|
|
}
|
|
|
|
static boolean parseFileOptions(const char *const fileName) {
|
|
boolean fileFound = FALSE;
|
|
const char *const format = "Considering option file %s: %s\n";
|
|
CheckFile = fileName;
|
|
if (stringListHasTest(OptionFiles, checkSameFile))
|
|
verbose(format, fileName, "already considered");
|
|
else {
|
|
FILE *const fp = fopen(fileName, "r");
|
|
if (fp == NULL)
|
|
verbose(format, fileName, "not found");
|
|
else {
|
|
cookedArgs *const args = cArgNewFromLineFile(fp);
|
|
vString *file = vStringNewInit(fileName);
|
|
stringListAdd(OptionFiles, file);
|
|
verbose(format, fileName, "reading...");
|
|
parseOptions(args);
|
|
if (NonOptionEncountered)
|
|
error(WARNING, "Ignoring non-option in %s\n", fileName);
|
|
cArgDelete(args);
|
|
fclose(fp);
|
|
fileFound = TRUE;
|
|
}
|
|
}
|
|
return fileFound;
|
|
}
|
|
|
|
/* Actions to be taken before reading any other options */
|
|
extern void previewFirstOption(cookedArgs *const args) {
|
|
while (cArgIsOption(args)) {
|
|
if (strcmp(args->item, "V") == 0 || strcmp(args->item, "verbose") == 0)
|
|
parseOption(args);
|
|
else if (strcmp(args->item, "options") == 0 &&
|
|
strcmp(args->parameter, "NONE") == 0) {
|
|
fprintf(stderr, "No options will be read from files or environment\n");
|
|
SkipConfiguration = TRUE;
|
|
cArgForth(args);
|
|
} else
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void parseConfigurationFileOptionsInDirectoryWithLeafname(
|
|
const char *directory, const char *leafname) {
|
|
vString *const pathname = combinePathAndFile(directory, leafname);
|
|
parseFileOptions(vStringValue(pathname));
|
|
vStringDelete(pathname);
|
|
}
|
|
|
|
static void parseConfigurationFileOptionsInDirectory(const char *directory) {
|
|
parseConfigurationFileOptionsInDirectoryWithLeafname(directory, ".ctags");
|
|
#ifdef MSDOS_STYLE_PATH
|
|
parseConfigurationFileOptionsInDirectoryWithLeafname(directory, "ctags.cnf");
|
|
#endif
|
|
}
|
|
|
|
static void parseConfigurationFileOptions(void) {
|
|
/* We parse .ctags on all systems, and additionally ctags.cnf on DOS. */
|
|
const char *const home = getenv("HOME");
|
|
#ifdef CUSTOM_CONFIGURATION_FILE
|
|
parseFileOptions(CUSTOM_CONFIGURATION_FILE);
|
|
#endif
|
|
#ifdef MSDOS_STYLE_PATH
|
|
parseFileOptions("/ctags.cnf");
|
|
#endif
|
|
parseFileOptions("/etc/ctags.conf");
|
|
parseFileOptions("/usr/local/etc/ctags.conf");
|
|
if (home != NULL) {
|
|
parseConfigurationFileOptionsInDirectory(home);
|
|
} else {
|
|
#ifdef MSDOS_STYLE_PATH
|
|
/*
|
|
* Windows users don't usually set HOME.
|
|
* The OS sets HOMEDRIVE and HOMEPATH for them.
|
|
*/
|
|
const char *homeDrive = getenv("HOMEDRIVE");
|
|
const char *homePath = getenv("HOMEPATH");
|
|
if (homeDrive != NULL && homePath != NULL) {
|
|
vString *const windowsHome = vStringNew();
|
|
vStringCatS(windowsHome, homeDrive);
|
|
vStringCatS(windowsHome, homePath);
|
|
parseConfigurationFileOptionsInDirectory(vStringValue(windowsHome));
|
|
vStringDelete(windowsHome);
|
|
}
|
|
#endif
|
|
}
|
|
parseConfigurationFileOptionsInDirectory(".");
|
|
}
|
|
|
|
static void parseEnvironmentOptions(void) {
|
|
const char *envOptions = NULL;
|
|
const char *var = NULL;
|
|
|
|
if (Option.etags) {
|
|
var = ETAGS_ENVIRONMENT;
|
|
envOptions = getenv(var);
|
|
}
|
|
if (envOptions == NULL) {
|
|
var = CTAGS_ENVIRONMENT;
|
|
envOptions = getenv(var);
|
|
}
|
|
if (envOptions != NULL && envOptions[0] != '\0') {
|
|
cookedArgs *const args = cArgNewFromString(envOptions);
|
|
verbose("Reading options from $CTAGS\n");
|
|
parseOptions(args);
|
|
cArgDelete(args);
|
|
if (NonOptionEncountered)
|
|
error(WARNING, "Ignoring non-option in %s variable", var);
|
|
}
|
|
}
|
|
|
|
extern void readOptionConfiguration(void) {
|
|
if (!SkipConfiguration) {
|
|
parseConfigurationFileOptions();
|
|
parseEnvironmentOptions();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Option initialization
|
|
*/
|
|
|
|
extern void initOptions(void) {
|
|
OptionFiles = stringListNew();
|
|
verbose("Setting option defaults\n");
|
|
installHeaderListDefaults();
|
|
verbose(" Installing default language mappings:\n");
|
|
installLanguageMapDefaults();
|
|
|
|
/* always excluded by default */
|
|
verbose(" Installing default exclude patterns:\n");
|
|
processExcludeOption(NULL, "{arch}");
|
|
processExcludeOption(NULL, ".arch-ids");
|
|
processExcludeOption(NULL, ".arch-inventory");
|
|
processExcludeOption(NULL, "autom4te.cache");
|
|
processExcludeOption(NULL, "BitKeeper");
|
|
processExcludeOption(NULL, ".bzr");
|
|
processExcludeOption(NULL, ".bzrignore");
|
|
processExcludeOption(NULL, "CVS");
|
|
processExcludeOption(NULL, ".cvsignore");
|
|
processExcludeOption(NULL, "_darcs");
|
|
processExcludeOption(NULL, ".deps");
|
|
processExcludeOption(NULL, "EIFGEN");
|
|
processExcludeOption(NULL, ".git");
|
|
processExcludeOption(NULL, ".hg");
|
|
processExcludeOption(NULL, "PENDING");
|
|
processExcludeOption(NULL, "RCS");
|
|
processExcludeOption(NULL, "RESYNC");
|
|
processExcludeOption(NULL, "SCCS");
|
|
processExcludeOption(NULL, ".svn");
|
|
}
|
|
|
|
extern void freeOptionResources(void) {
|
|
freeString(&Option.tagFileName);
|
|
freeString(&Option.fileList);
|
|
freeString(&Option.filterTerminator);
|
|
|
|
freeList(&Excluded);
|
|
freeList(&Option.ignore);
|
|
freeList(&Option.headerExt);
|
|
freeList(&Option.etagsInclude);
|
|
freeList(&OptionFiles);
|
|
}
|
|
|
|
/* vi:set tabstop=4 shiftwidth=4: */
|