806 lines
22 KiB
C
806 lines
22 KiB
C
/*
|
|
* $Id: readtags.c 592 2007-07-31 03:30:41Z dhiebert $
|
|
*
|
|
* Copyright (c) 1996-2003, Darren Hiebert
|
|
*
|
|
* This source code is released into the public domain.
|
|
*
|
|
* This module contains functions for reading tag files.
|
|
*/
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/weirdtypes.h"
|
|
#include "libc/conv/conv.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/log/log.h"
|
|
#include "libc/mem/mem.h"
|
|
#include "libc/stdio/stdio.h"
|
|
#include "libc/str/str.h"
|
|
#include "third_party/ctags/readtags.h"
|
|
|
|
/*
|
|
* MACROS
|
|
*/
|
|
#define TAB '\t'
|
|
|
|
/*
|
|
* DATA DECLARATIONS
|
|
*/
|
|
typedef struct {
|
|
size_t size;
|
|
char *buffer;
|
|
} vstring;
|
|
|
|
/* Information about current tag file */
|
|
struct sTagFile {
|
|
/* has the file been opened and this structure initialized? */
|
|
short initialized;
|
|
/* format of tag file */
|
|
short format;
|
|
/* how is the tag file sorted? */
|
|
sortType sortMethod;
|
|
/* pointer to file structure */
|
|
FILE *fp;
|
|
/* file position of first character of `line' */
|
|
off_t pos;
|
|
/* size of tag file in seekable positions */
|
|
off_t size;
|
|
/* last line read */
|
|
vstring line;
|
|
/* name of tag in last line read */
|
|
vstring name;
|
|
/* defines tag search state */
|
|
struct {
|
|
/* file position of last match for tag */
|
|
off_t pos;
|
|
/* name of tag last searched for */
|
|
char *name;
|
|
/* length of name for partial matches */
|
|
size_t nameLength;
|
|
/* peforming partial match */
|
|
short partial;
|
|
/* ignoring case */
|
|
short ignorecase;
|
|
} search;
|
|
/* miscellaneous extension fields */
|
|
struct {
|
|
/* number of entries in `list' */
|
|
unsigned short max;
|
|
/* list of key value pairs */
|
|
tagExtensionField *list;
|
|
} fields;
|
|
/* buffers to be freed at close */
|
|
struct {
|
|
/* name of program author */
|
|
char *author;
|
|
/* name of program */
|
|
char *name;
|
|
/* URL of distribution */
|
|
char *url;
|
|
/* program version */
|
|
char *version;
|
|
} program;
|
|
};
|
|
|
|
/*
|
|
* DATA DEFINITIONS
|
|
*/
|
|
const char *const EmptyString = "";
|
|
const char *const PseudoTagPrefix = "!_";
|
|
|
|
/*
|
|
* FUNCTION DEFINITIONS
|
|
*/
|
|
|
|
/*
|
|
* Compare two strings, ignoring case.
|
|
* Return 0 for match, < 0 for smaller, > 0 for bigger
|
|
* Make sure case is folded to uppercase in comparison (like for 'sort -f')
|
|
* This makes a difference when one of the chars lies between upper and lower
|
|
* ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
|
|
*/
|
|
static int struppercmp(const char *s1, const char *s2) {
|
|
int result;
|
|
do {
|
|
result = toupper((int)*s1) - toupper((int)*s2);
|
|
} while (result == 0 && *s1++ != '\0' && *s2++ != '\0');
|
|
return result;
|
|
}
|
|
|
|
static int strnuppercmp(const char *s1, const char *s2, size_t n) {
|
|
int result;
|
|
do {
|
|
result = toupper((int)*s1) - toupper((int)*s2);
|
|
} while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0');
|
|
return result;
|
|
}
|
|
|
|
static int growString(vstring *s) {
|
|
int result = 0;
|
|
size_t newLength;
|
|
char *newLine;
|
|
if (s->size == 0) {
|
|
newLength = 128;
|
|
newLine = (char *)malloc(newLength);
|
|
*newLine = '\0';
|
|
} else {
|
|
newLength = 2 * s->size;
|
|
newLine = (char *)realloc(s->buffer, newLength);
|
|
}
|
|
if (newLine == NULL)
|
|
perror("string too large");
|
|
else {
|
|
s->buffer = newLine;
|
|
s->size = newLength;
|
|
result = 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Copy name of tag out of tag line */
|
|
static void copyName(tagFile *const file) {
|
|
size_t length;
|
|
const char *end = strchr(file->line.buffer, '\t');
|
|
if (end == NULL) {
|
|
end = strchr(file->line.buffer, '\n');
|
|
if (end == NULL) end = strchr(file->line.buffer, '\r');
|
|
}
|
|
if (end != NULL)
|
|
length = end - file->line.buffer;
|
|
else
|
|
length = strlen(file->line.buffer);
|
|
while (length >= file->name.size) growString(&file->name);
|
|
strncpy(file->name.buffer, file->line.buffer, length);
|
|
file->name.buffer[length] = '\0';
|
|
}
|
|
|
|
static int readTagLineRaw(tagFile *const file) {
|
|
int result = 1;
|
|
int reReadLine;
|
|
|
|
/* If reading the line places any character other than a null or a
|
|
* newline at the last character position in the buffer (one less than
|
|
* the buffer size), then we must resize the buffer and reattempt to read
|
|
* the line.
|
|
*/
|
|
do {
|
|
char *const pLastChar = file->line.buffer + file->line.size - 2;
|
|
char *line;
|
|
|
|
file->pos = ftell(file->fp);
|
|
reReadLine = 0;
|
|
*pLastChar = '\0';
|
|
line = fgets(file->line.buffer, (int)file->line.size, file->fp);
|
|
if (line == NULL) {
|
|
/* read error */
|
|
if (!feof(file->fp)) perror("readTagLine");
|
|
result = 0;
|
|
} else if (*pLastChar != '\0' && *pLastChar != '\n' && *pLastChar != '\r') {
|
|
/* buffer overflow */
|
|
growString(&file->line);
|
|
fseek(file->fp, file->pos, SEEK_SET);
|
|
reReadLine = 1;
|
|
} else {
|
|
size_t i = strlen(file->line.buffer);
|
|
while (i > 0 && (file->line.buffer[i - 1] == '\n' ||
|
|
file->line.buffer[i - 1] == '\r')) {
|
|
file->line.buffer[i - 1] = '\0';
|
|
--i;
|
|
}
|
|
}
|
|
} while (reReadLine && result);
|
|
if (result) copyName(file);
|
|
return result;
|
|
}
|
|
|
|
static int readTagLine(tagFile *const file) {
|
|
int result;
|
|
do {
|
|
result = readTagLineRaw(file);
|
|
} while (result && *file->name.buffer == '\0');
|
|
return result;
|
|
}
|
|
|
|
static tagResult growFields(tagFile *const file) {
|
|
tagResult result = TagFailure;
|
|
unsigned short newCount = (unsigned short)2 * file->fields.max;
|
|
tagExtensionField *newFields = (tagExtensionField *)realloc(
|
|
file->fields.list, newCount * sizeof(tagExtensionField));
|
|
if (newFields == NULL)
|
|
perror("too many extension fields");
|
|
else {
|
|
file->fields.list = newFields;
|
|
file->fields.max = newCount;
|
|
result = TagSuccess;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void parseExtensionFields(tagFile *const file, tagEntry *const entry,
|
|
char *const string) {
|
|
char *p = string;
|
|
while (p != NULL && *p != '\0') {
|
|
while (*p == TAB) *p++ = '\0';
|
|
if (*p != '\0') {
|
|
char *colon;
|
|
char *field = p;
|
|
p = strchr(p, TAB);
|
|
if (p != NULL) *p++ = '\0';
|
|
colon = strchr(field, ':');
|
|
if (colon == NULL)
|
|
entry->kind = field;
|
|
else {
|
|
const char *key = field;
|
|
const char *value = colon + 1;
|
|
*colon = '\0';
|
|
if (strcmp(key, "kind") == 0)
|
|
entry->kind = value;
|
|
else if (strcmp(key, "file") == 0)
|
|
entry->fileScope = 1;
|
|
else if (strcmp(key, "line") == 0)
|
|
entry->address.lineNumber = atol(value);
|
|
else {
|
|
if (entry->fields.count == file->fields.max) growFields(file);
|
|
file->fields.list[entry->fields.count].key = key;
|
|
file->fields.list[entry->fields.count].value = value;
|
|
++entry->fields.count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void parseTagLine(tagFile *file, tagEntry *const entry) {
|
|
int i;
|
|
char *p = file->line.buffer;
|
|
char *tab = strchr(p, TAB);
|
|
|
|
entry->fields.list = NULL;
|
|
entry->fields.count = 0;
|
|
entry->kind = NULL;
|
|
entry->fileScope = 0;
|
|
|
|
entry->name = p;
|
|
if (tab != NULL) {
|
|
*tab = '\0';
|
|
p = tab + 1;
|
|
entry->file = p;
|
|
tab = strchr(p, TAB);
|
|
if (tab != NULL) {
|
|
int fieldsPresent;
|
|
*tab = '\0';
|
|
p = tab + 1;
|
|
if (*p == '/' || *p == '?') {
|
|
/* parse pattern */
|
|
int delimiter = *(unsigned char *)p;
|
|
entry->address.lineNumber = 0;
|
|
entry->address.pattern = p;
|
|
do {
|
|
p = strchr(p + 1, delimiter);
|
|
} while (p != NULL && *(p - 1) == '\\');
|
|
if (p == NULL) {
|
|
/* invalid pattern */
|
|
} else
|
|
++p;
|
|
} else if (isdigit((int)*(unsigned char *)p)) {
|
|
/* parse line number */
|
|
entry->address.pattern = p;
|
|
entry->address.lineNumber = atol(p);
|
|
while (isdigit((int)*(unsigned char *)p)) ++p;
|
|
} else {
|
|
/* invalid pattern */
|
|
}
|
|
fieldsPresent = (strncmp(p, ";\"", 2) == 0);
|
|
*p = '\0';
|
|
if (fieldsPresent) parseExtensionFields(file, entry, p + 2);
|
|
}
|
|
}
|
|
if (entry->fields.count > 0) entry->fields.list = file->fields.list;
|
|
for (i = entry->fields.count; i < file->fields.max; ++i) {
|
|
file->fields.list[i].key = NULL;
|
|
file->fields.list[i].value = NULL;
|
|
}
|
|
}
|
|
|
|
static char *duplicate(const char *str) {
|
|
char *result = NULL;
|
|
if (str != NULL) {
|
|
result = strdup(str);
|
|
if (result == NULL) perror(NULL);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void readPseudoTags(tagFile *const file, tagFileInfo *const info) {
|
|
fpos_t startOfLine;
|
|
const size_t prefixLength = strlen(PseudoTagPrefix);
|
|
if (info != NULL) {
|
|
info->file.format = 1;
|
|
info->file.sort = TAG_UNSORTED;
|
|
info->program.author = NULL;
|
|
info->program.name = NULL;
|
|
info->program.url = NULL;
|
|
info->program.version = NULL;
|
|
}
|
|
while (1) {
|
|
fgetpos(file->fp, &startOfLine);
|
|
if (!readTagLine(file)) break;
|
|
if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
|
|
break;
|
|
else {
|
|
tagEntry entry;
|
|
const char *key, *value;
|
|
parseTagLine(file, &entry);
|
|
key = entry.name + prefixLength;
|
|
value = entry.file;
|
|
if (strcmp(key, "TAG_FILE_SORTED") == 0)
|
|
file->sortMethod = (sortType)atoi(value);
|
|
else if (strcmp(key, "TAG_FILE_FORMAT") == 0)
|
|
file->format = (short)atoi(value);
|
|
else if (strcmp(key, "TAG_PROGRAM_AUTHOR") == 0)
|
|
file->program.author = duplicate(value);
|
|
else if (strcmp(key, "TAG_PROGRAM_NAME") == 0)
|
|
file->program.name = duplicate(value);
|
|
else if (strcmp(key, "TAG_PROGRAM_URL") == 0)
|
|
file->program.url = duplicate(value);
|
|
else if (strcmp(key, "TAG_PROGRAM_VERSION") == 0)
|
|
file->program.version = duplicate(value);
|
|
if (info != NULL) {
|
|
info->file.format = file->format;
|
|
info->file.sort = file->sortMethod;
|
|
info->program.author = file->program.author;
|
|
info->program.name = file->program.name;
|
|
info->program.url = file->program.url;
|
|
info->program.version = file->program.version;
|
|
}
|
|
}
|
|
}
|
|
fsetpos(file->fp, &startOfLine);
|
|
}
|
|
|
|
static void gotoFirstLogicalTag(tagFile *const file) {
|
|
fpos_t startOfLine;
|
|
const size_t prefixLength = strlen(PseudoTagPrefix);
|
|
rewind(file->fp);
|
|
while (1) {
|
|
fgetpos(file->fp, &startOfLine);
|
|
if (!readTagLine(file)) break;
|
|
if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0) break;
|
|
}
|
|
fsetpos(file->fp, &startOfLine);
|
|
}
|
|
|
|
static tagFile *initialize(const char *const filePath,
|
|
tagFileInfo *const info) {
|
|
tagFile *result = (tagFile *)calloc((size_t)1, sizeof(tagFile));
|
|
if (result != NULL) {
|
|
growString(&result->line);
|
|
growString(&result->name);
|
|
result->fields.max = 20;
|
|
result->fields.list = (tagExtensionField *)calloc(
|
|
result->fields.max, sizeof(tagExtensionField));
|
|
result->fp = fopen(filePath, "r");
|
|
if (result->fp == NULL) {
|
|
free(result);
|
|
result = NULL;
|
|
info->status.error_number = errno;
|
|
} else {
|
|
fseek(result->fp, 0, SEEK_END);
|
|
result->size = ftell(result->fp);
|
|
rewind(result->fp);
|
|
readPseudoTags(result, info);
|
|
info->status.opened = 1;
|
|
result->initialized = 1;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void terminate(tagFile *const file) {
|
|
fclose(file->fp);
|
|
|
|
free(file->line.buffer);
|
|
free(file->name.buffer);
|
|
free(file->fields.list);
|
|
|
|
if (file->program.author != NULL) free(file->program.author);
|
|
if (file->program.name != NULL) free(file->program.name);
|
|
if (file->program.url != NULL) free(file->program.url);
|
|
if (file->program.version != NULL) free(file->program.version);
|
|
if (file->search.name != NULL) free(file->search.name);
|
|
|
|
memset(file, 0, sizeof(tagFile));
|
|
|
|
free(file);
|
|
}
|
|
|
|
static tagResult readNext(tagFile *const file, tagEntry *const entry) {
|
|
tagResult result;
|
|
if (file == NULL || !file->initialized)
|
|
result = TagFailure;
|
|
else if (!readTagLine(file))
|
|
result = TagFailure;
|
|
else {
|
|
if (entry != NULL) parseTagLine(file, entry);
|
|
result = TagSuccess;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static const char *readFieldValue(const tagEntry *const entry,
|
|
const char *const key) {
|
|
const char *result = NULL;
|
|
int i;
|
|
if (strcmp(key, "kind") == 0)
|
|
result = entry->kind;
|
|
else if (strcmp(key, "file") == 0)
|
|
result = EmptyString;
|
|
else
|
|
for (i = 0; i < entry->fields.count && result == NULL; ++i)
|
|
if (strcmp(entry->fields.list[i].key, key) == 0)
|
|
result = entry->fields.list[i].value;
|
|
return result;
|
|
}
|
|
|
|
static int readTagLineSeek(tagFile *const file, const off_t pos) {
|
|
int result = 0;
|
|
if (fseek(file->fp, pos, SEEK_SET) == 0) {
|
|
result = readTagLine(file); /* read probable partial line */
|
|
if (pos > 0 && result) result = readTagLine(file); /* read complete line */
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int nameComparison(tagFile *const file) {
|
|
int result;
|
|
if (file->search.ignorecase) {
|
|
if (file->search.partial)
|
|
result = strnuppercmp(file->search.name, file->name.buffer,
|
|
file->search.nameLength);
|
|
else
|
|
result = struppercmp(file->search.name, file->name.buffer);
|
|
} else {
|
|
if (file->search.partial)
|
|
result = strncmp(file->search.name, file->name.buffer,
|
|
file->search.nameLength);
|
|
else
|
|
result = strcmp(file->search.name, file->name.buffer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void findFirstNonMatchBefore(tagFile *const file) {
|
|
#define JUMP_BACK 512
|
|
int more_lines;
|
|
int comp;
|
|
off_t start = file->pos;
|
|
off_t pos = start;
|
|
do {
|
|
if (pos < (off_t)JUMP_BACK)
|
|
pos = 0;
|
|
else
|
|
pos = pos - JUMP_BACK;
|
|
more_lines = readTagLineSeek(file, pos);
|
|
comp = nameComparison(file);
|
|
} while (more_lines && comp == 0 && pos > 0 && pos < start);
|
|
}
|
|
|
|
static tagResult findFirstMatchBefore(tagFile *const file) {
|
|
tagResult result = TagFailure;
|
|
int more_lines;
|
|
off_t start = file->pos;
|
|
findFirstNonMatchBefore(file);
|
|
do {
|
|
more_lines = readTagLine(file);
|
|
if (nameComparison(file) == 0) result = TagSuccess;
|
|
} while (more_lines && result != TagSuccess && file->pos < start);
|
|
return result;
|
|
}
|
|
|
|
static tagResult findBinary(tagFile *const file) {
|
|
tagResult result = TagFailure;
|
|
off_t lower_limit = 0;
|
|
off_t upper_limit = file->size;
|
|
off_t last_pos = 0;
|
|
off_t pos = upper_limit / 2;
|
|
while (result != TagSuccess) {
|
|
if (!readTagLineSeek(file, pos)) {
|
|
/* in case we fell off end of file */
|
|
result = findFirstMatchBefore(file);
|
|
break;
|
|
} else if (pos == last_pos) {
|
|
/* prevent infinite loop if we backed up to beginning of file */
|
|
break;
|
|
} else {
|
|
const int comp = nameComparison(file);
|
|
last_pos = pos;
|
|
if (comp < 0) {
|
|
upper_limit = pos;
|
|
pos = lower_limit + ((upper_limit - lower_limit) / 2);
|
|
} else if (comp > 0) {
|
|
lower_limit = pos;
|
|
pos = lower_limit + ((upper_limit - lower_limit) / 2);
|
|
} else if (pos == 0)
|
|
result = TagSuccess;
|
|
else
|
|
result = findFirstMatchBefore(file);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static tagResult findSequential(tagFile *const file) {
|
|
tagResult result = TagFailure;
|
|
if (file->initialized) {
|
|
while (result == TagFailure && readTagLine(file)) {
|
|
if (nameComparison(file) == 0) result = TagSuccess;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static tagResult find(tagFile *const file, tagEntry *const entry,
|
|
const char *const name, const int options) {
|
|
tagResult result;
|
|
if (file->search.name != NULL) free(file->search.name);
|
|
file->search.name = duplicate(name);
|
|
file->search.nameLength = strlen(name);
|
|
file->search.partial = (options & TAG_PARTIALMATCH) != 0;
|
|
file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
|
|
fseek(file->fp, 0, SEEK_END);
|
|
file->size = ftell(file->fp);
|
|
rewind(file->fp);
|
|
if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) ||
|
|
(file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) {
|
|
#ifdef DEBUG
|
|
printf("<performing binary search>\n");
|
|
#endif
|
|
result = findBinary(file);
|
|
} else {
|
|
#ifdef DEBUG
|
|
printf("<performing sequential search>\n");
|
|
#endif
|
|
result = findSequential(file);
|
|
}
|
|
|
|
if (result != TagSuccess)
|
|
file->search.pos = file->size;
|
|
else {
|
|
file->search.pos = file->pos;
|
|
if (entry != NULL) parseTagLine(file, entry);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static tagResult findNext(tagFile *const file, tagEntry *const entry) {
|
|
tagResult result;
|
|
if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) ||
|
|
(file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) {
|
|
result = tagsNext(file, entry);
|
|
if (result == TagSuccess && nameComparison(file) != 0) result = TagFailure;
|
|
} else {
|
|
result = findSequential(file);
|
|
if (result == TagSuccess && entry != NULL) parseTagLine(file, entry);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* EXTERNAL INTERFACE
|
|
*/
|
|
|
|
extern tagFile *tagsOpen(const char *const filePath, tagFileInfo *const info) {
|
|
return initialize(filePath, info);
|
|
}
|
|
|
|
extern tagResult tagsSetSortType(tagFile *const file, const sortType type) {
|
|
tagResult result = TagFailure;
|
|
if (file != NULL && file->initialized) {
|
|
file->sortMethod = type;
|
|
result = TagSuccess;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
extern tagResult tagsFirst(tagFile *const file, tagEntry *const entry) {
|
|
tagResult result = TagFailure;
|
|
if (file != NULL && file->initialized) {
|
|
gotoFirstLogicalTag(file);
|
|
result = readNext(file, entry);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
extern tagResult tagsNext(tagFile *const file, tagEntry *const entry) {
|
|
tagResult result = TagFailure;
|
|
if (file != NULL && file->initialized) result = readNext(file, entry);
|
|
return result;
|
|
}
|
|
|
|
extern const char *tagsField(const tagEntry *const entry,
|
|
const char *const key) {
|
|
const char *result = NULL;
|
|
if (entry != NULL) result = readFieldValue(entry, key);
|
|
return result;
|
|
}
|
|
|
|
extern tagResult tagsFind(tagFile *const file, tagEntry *const entry,
|
|
const char *const name, const int options) {
|
|
tagResult result = TagFailure;
|
|
if (file != NULL && file->initialized)
|
|
result = find(file, entry, name, options);
|
|
return result;
|
|
}
|
|
|
|
extern tagResult tagsFindNext(tagFile *const file, tagEntry *const entry) {
|
|
tagResult result = TagFailure;
|
|
if (file != NULL && file->initialized) result = findNext(file, entry);
|
|
return result;
|
|
}
|
|
|
|
extern tagResult tagsClose(tagFile *const file) {
|
|
tagResult result = TagFailure;
|
|
if (file != NULL && file->initialized) {
|
|
terminate(file);
|
|
result = TagSuccess;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* TEST FRAMEWORK
|
|
*/
|
|
|
|
#ifdef READTAGS_MAIN
|
|
|
|
static const char *TagFileName = "tags";
|
|
static const char *ProgramName;
|
|
static int extensionFields;
|
|
static int SortOverride;
|
|
static sortType SortMethod;
|
|
|
|
static void printTag(const tagEntry *entry) {
|
|
int i;
|
|
int first = 1;
|
|
const char *separator = ";\"";
|
|
const char *const empty = "";
|
|
/* "sep" returns a value only the first time it is evaluated */
|
|
#define sep (first ? (first = 0, separator) : empty)
|
|
printf("%s\t%s\t%s", entry->name, entry->file, entry->address.pattern);
|
|
if (extensionFields) {
|
|
if (entry->kind != NULL && entry->kind[0] != '\0')
|
|
printf("%s\tkind:%s", sep, entry->kind);
|
|
if (entry->fileScope) printf("%s\tfile:", sep);
|
|
#if 0
|
|
if (entry->address.lineNumber > 0)
|
|
printf ("%s\tline:%lu", sep, entry->address.lineNumber);
|
|
#endif
|
|
for (i = 0; i < entry->fields.count; ++i)
|
|
printf("%s\t%s:%s", sep, entry->fields.list[i].key,
|
|
entry->fields.list[i].value);
|
|
}
|
|
putchar('\n');
|
|
#undef sep
|
|
}
|
|
|
|
static void findTag(const char *const name, const int options) {
|
|
tagFileInfo info;
|
|
tagEntry entry;
|
|
tagFile *const file = tagsOpen(TagFileName, &info);
|
|
if (file == NULL) {
|
|
fprintf(stderr, "%s: cannot open tag file: %s: %s\n", ProgramName,
|
|
strerror(info.status.error_number), name);
|
|
exit(1);
|
|
} else {
|
|
if (SortOverride) tagsSetSortType(file, SortMethod);
|
|
if (tagsFind(file, &entry, name, options) == TagSuccess) {
|
|
do {
|
|
printTag(&entry);
|
|
} while (tagsFindNext(file, &entry) == TagSuccess);
|
|
}
|
|
tagsClose(file);
|
|
}
|
|
}
|
|
|
|
static void listTags(void) {
|
|
tagFileInfo info;
|
|
tagEntry entry;
|
|
tagFile *const file = tagsOpen(TagFileName, &info);
|
|
if (file == NULL) {
|
|
fprintf(stderr, "%s: cannot open tag file: %s: %s\n", ProgramName,
|
|
strerror(info.status.error_number), TagFileName);
|
|
exit(1);
|
|
} else {
|
|
while (tagsNext(file, &entry) == TagSuccess) printTag(&entry);
|
|
tagsClose(file);
|
|
}
|
|
}
|
|
|
|
const char *const Usage =
|
|
"Find tag file entries matching specified names.\n\n"
|
|
"Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
|
|
"Options:\n"
|
|
" -e Include extension fields in output.\n"
|
|
" -i Perform case-insensitive matching.\n"
|
|
" -l List all tags.\n"
|
|
" -p Perform partial matching.\n"
|
|
" -s[0|1|2] Override sort detection of tag file.\n"
|
|
" -t file Use specified tag file (default: \"tags\").\n"
|
|
"Note that options are acted upon as encountered, so order is "
|
|
"significant.\n";
|
|
|
|
extern int main(int argc, char **argv) {
|
|
int options = 0;
|
|
int actionSupplied = 0;
|
|
int i;
|
|
ProgramName = argv[0];
|
|
if (argc == 1) {
|
|
fprintf(stderr, Usage, ProgramName);
|
|
exit(1);
|
|
}
|
|
for (i = 1; i < argc; ++i) {
|
|
const char *const arg = argv[i];
|
|
if (arg[0] != '-') {
|
|
findTag(arg, options);
|
|
actionSupplied = 1;
|
|
} else {
|
|
size_t j;
|
|
for (j = 1; arg[j] != '\0'; ++j) {
|
|
switch (arg[j]) {
|
|
case 'e':
|
|
extensionFields = 1;
|
|
break;
|
|
case 'i':
|
|
options |= TAG_IGNORECASE;
|
|
break;
|
|
case 'p':
|
|
options |= TAG_PARTIALMATCH;
|
|
break;
|
|
case 'l':
|
|
listTags();
|
|
actionSupplied = 1;
|
|
break;
|
|
|
|
case 't':
|
|
if (arg[j + 1] != '\0') {
|
|
TagFileName = arg + j + 1;
|
|
j += strlen(TagFileName);
|
|
} else if (i + 1 < argc)
|
|
TagFileName = argv[++i];
|
|
else {
|
|
fprintf(stderr, Usage, ProgramName);
|
|
exit(1);
|
|
}
|
|
break;
|
|
case 's':
|
|
SortOverride = 1;
|
|
++j;
|
|
if (arg[j] == '\0')
|
|
SortMethod = TAG_SORTED;
|
|
else if (strchr("012", arg[j]) != NULL)
|
|
SortMethod = (sortType)(arg[j] - '0');
|
|
else {
|
|
fprintf(stderr, Usage, ProgramName);
|
|
exit(1);
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(stderr, "%s: unknown option: %c\n", ProgramName, arg[j]);
|
|
exit(1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!actionSupplied) {
|
|
fprintf(stderr,
|
|
"%s: no action specified: specify tag name(s) or -l option\n",
|
|
ProgramName);
|
|
exit(1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* vi:set tabstop=4 shiftwidth=4: */
|