File indexing completed on 2024-05-12 05:51:43

0001 /*
0002 *
0003 *   SPDX-FileCopyrightText: 1996-2003 Darren Hiebert <dhiebert at users dot sourceforge dot net>
0004 *
0005 *   This source code is released into the public domain.
0006 *
0007 *   This module contains functions for reading tag files.
0008 *
0009 */
0010 
0011 /*
0012 *   INCLUDE FILES
0013 */
0014 #include "readtags.h"
0015 
0016 #include <stdlib.h>
0017 #include <string.h>
0018 #include <ctype.h>
0019 #include <stdio.h>
0020 #include <errno.h>
0021 #include <sys/types.h>  /* to declare off_t */
0022 
0023 /*
0024 *   MACROS
0025 */
0026 #define TAB '\t'
0027 
0028 /*
0029 *   DATA DECLARATIONS
0030 */
0031 typedef struct {
0032     size_t size;
0033     char *buffer;
0034 } vstring;
0035 
0036 /* Information about current tag file */
0037 struct sTagFile {
0038     /* has the file been opened and this structure initialized? */
0039     short initialized;
0040     /* format of tag file */
0041     short format;
0042     /* how is the tag file sorted? */
0043     sortType sortMethod;
0044     /* pointer to file structure */
0045     FILE *fp;
0046     /* file position of first character of `line' */
0047     off_t pos;
0048     /* size of tag file in seekable positions */
0049     off_t size;
0050     /* last line read */
0051     vstring line;
0052     /* name of tag in last line read */
0053     vstring name;
0054     /* defines tag search state */
0055     struct {
0056         /* file position of last match for tag */
0057         off_t pos;
0058         /* name of tag last searched for */
0059         const char *name;
0060         /* length of name for partial matches */
0061         size_t nameLength;
0062         /* peforming partial match */
0063         short partial;
0064         /* ignoring case */
0065         short ignorecase;
0066     } search;
0067     /* miscellaneous extension fields */
0068     struct {
0069         /* number of entries in `list' */
0070         unsigned short max;
0071         /* list of key value pairs */
0072         tagExtensionField *list;
0073     } fields;
0074     /* buffers to be freed at close */
0075     struct {
0076         /* name of program author */
0077         char *author;
0078         /* name of program */
0079         char *name;
0080         /* URL of distribution */
0081         char *url;
0082         /* program version */
0083         char *version;
0084     } program;
0085 };
0086 
0087 /*
0088 *   DATA DEFINITIONS
0089 */
0090 const char *const EmptyString = "";
0091 const char *const PseudoTagPrefix = "!_";
0092 
0093 /*
0094 *   FUNCTION DEFINITIONS
0095 */
0096 
0097 /*
0098  * Compare two strings, ignoring case.
0099  * Return 0 for match, < 0 for smaller, > 0 for bigger
0100  * Make sure case is folded to uppercase in comparison (like for 'sort -f')
0101  * This makes a difference when one of the chars lies between upper and lower
0102  * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
0103  */
0104 static int struppercmp(const char *s1, const char *s2)
0105 {
0106     int result;
0107     do {
0108         result = toupper((int) * s1) - toupper((int) * s2);
0109     } while (result == 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
0110     return result;
0111 }
0112 
0113 static int strnuppercmp(const char *s1, const char *s2, size_t n)
0114 {
0115     int result;
0116     do {
0117         result = toupper((int) * s1) - toupper((int) * s2);
0118     } while (result == 0  &&  --n > 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
0119     return result;
0120 }
0121 
0122 static int growString(vstring *s)
0123 {
0124     int result = 0;
0125     size_t newLength;
0126     char *newLine;
0127     if (s->size == 0) {
0128         newLength = 128;
0129         newLine = (char *) malloc(newLength);
0130         *newLine = '\0';
0131     } else {
0132         newLength = 2 * s->size;
0133         newLine = (char *) realloc(s->buffer, newLength);
0134     }
0135     if (newLine == nullptr) {
0136         perror("string too large");
0137     } else {
0138         s->buffer = newLine;
0139         s->size = newLength;
0140         result = 1;
0141     }
0142     return result;
0143 }
0144 
0145 /* Copy name of tag out of tag line */
0146 static void copyName(tagFile *const file)
0147 {
0148     size_t length;
0149     const char *end = strchr(file->line.buffer, '\t');
0150     if (end == nullptr) {
0151         end = strchr(file->line.buffer, '\n');
0152         if (end == nullptr) {
0153             end = strchr(file->line.buffer, '\r');
0154         }
0155     }
0156     if (end != nullptr) {
0157         length = end - file->line.buffer;
0158     } else {
0159         length = strlen(file->line.buffer);
0160     }
0161     while (length >= file->name.size) {
0162         growString(&file->name);
0163     }
0164     strncpy(file->name.buffer, file->line.buffer, length);
0165     file->name.buffer [length] = '\0';
0166 }
0167 
0168 static int readTagLineRaw(tagFile *const file)
0169 {
0170     int result = 1;
0171     int reReadLine;
0172 
0173     /*  If reading the line places any character other than a null or a
0174      *  newline at the last character position in the buffer (one less than
0175      *  the buffer size), then we must resize the buffer and reattempt to read
0176      *  the line.
0177      */
0178     do {
0179         char *const pLastChar = file->line.buffer + file->line.size - 2;
0180         char *line;
0181 
0182         file->pos = ftell(file->fp);
0183         reReadLine = 0;
0184         *pLastChar = '\0';
0185         line = fgets(file->line.buffer, (int) file->line.size, file->fp);
0186         if (line == nullptr) {
0187             /* read error */
0188             if (! feof(file->fp)) {
0189                 perror("readTagLine");
0190             }
0191             result = 0;
0192         } else if (*pLastChar != '\0'  &&
0193                    *pLastChar != '\n'  &&  *pLastChar != '\r') {
0194             /*  buffer overflow */
0195             growString(&file->line);
0196             fseek(file->fp, file->pos, SEEK_SET);
0197             reReadLine = 1;
0198         } else {
0199             size_t i = strlen(file->line.buffer);
0200             while (i > 0  &&
0201                     (file->line.buffer [i - 1] == '\n' || file->line.buffer [i - 1] == '\r')) {
0202                 file->line.buffer [i - 1] = '\0';
0203                 --i;
0204             }
0205         }
0206     } while (reReadLine  &&  result);
0207     if (result) {
0208         copyName(file);
0209     }
0210     return result;
0211 }
0212 
0213 static int readTagLine(tagFile *const file)
0214 {
0215     int result;
0216     do {
0217         result = readTagLineRaw(file);
0218     } while (result && *file->name.buffer == '\0');
0219     return result;
0220 }
0221 
0222 static tagResult growFields(tagFile *const file)
0223 {
0224     tagResult result = TagFailure;
0225     unsigned short newCount = 2 * file->fields.max;
0226     tagExtensionField *newFields = (tagExtensionField *)
0227                                    realloc(file->fields.list, newCount * sizeof(tagExtensionField));
0228     if (newFields == nullptr) {
0229         perror("too many extension fields");
0230     } else {
0231         file->fields.list = newFields;
0232         file->fields.max = newCount;
0233         result = TagSuccess;
0234     }
0235     return result;
0236 }
0237 
0238 static void parseExtensionFields(tagFile *const file, tagEntry *const entry,
0239                                  char *const string)
0240 {
0241     char *p = string;
0242     while (p != nullptr  &&  *p != '\0') {
0243         while (*p == TAB) {
0244             *p++ = '\0';
0245         }
0246         if (*p != '\0') {
0247             char *colon;
0248             char *field = p;
0249             p = strchr(p, TAB);
0250             if (p != nullptr) {
0251                 *p++ = '\0';
0252             }
0253             colon = strchr(field, ':');
0254             if (colon == nullptr) {
0255                 entry->kind = field;
0256             } else {
0257                 const char *key = field;
0258                 const char *value = colon + 1;
0259                 *colon = '\0';
0260                 if (strcmp(key, "kind") == 0) {
0261                     entry->kind = value;
0262                 } else if (strcmp(key, "file") == 0) {
0263                     entry->fileScope = 1;
0264                 } else if (strcmp(key, "line") == 0) {
0265                     entry->address.lineNumber = atol(value);
0266                 } else {
0267                     if (entry->fields.count == file->fields.max) {
0268                         growFields(file);
0269                     }
0270                     file->fields.list [entry->fields.count].key = key;
0271                     file->fields.list [entry->fields.count].value = value;
0272                     ++entry->fields.count;
0273                 }
0274             }
0275         }
0276     }
0277 }
0278 
0279 static void parseTagLine(tagFile *file, tagEntry *const entry)
0280 {
0281     int i;
0282     char *p = file->line.buffer;
0283     char *tab = strchr(p, TAB);
0284     int fieldsPresent = 0;
0285 
0286     entry->fields.list = nullptr;
0287     entry->fields.count = 0;
0288     entry->kind = nullptr;
0289     entry->fileScope = 0;
0290 
0291     entry->name = p;
0292     if (tab != nullptr) {
0293         *tab = '\0';
0294         p = tab + 1;
0295         entry->file = p;
0296         tab = strchr(p, TAB);
0297         if (tab != nullptr) {
0298             *tab = '\0';
0299             p = tab + 1;
0300             if (*p == '/'  ||  *p == '?') {
0301                 /* parse pattern */
0302                 int delimiter = *(unsigned char *) p;
0303                 entry->address.lineNumber = 0;
0304                 entry->address.pattern = p;
0305                 do {
0306                     p = strchr(p + 1, delimiter);
0307                 } while (p != nullptr  &&  *(p - 1) == '\\');
0308                 if (p == nullptr) {
0309                     /* invalid pattern */
0310                 } else {
0311                     ++p;
0312                 }
0313             } else if (isdigit((int) * (unsigned char *) p)) {
0314                 /* parse line number */
0315                 entry->address.pattern = p;
0316                 entry->address.lineNumber = atol(p);
0317                 while (isdigit((int) * (unsigned char *) p)) {
0318                     ++p;
0319                 }
0320             } else {
0321                 /* invalid pattern */
0322             }
0323             if (p != nullptr) {
0324                 fieldsPresent = (strncmp(p, ";\"", 2) == 0);
0325                 *p = '\0';
0326                 if (fieldsPresent) {
0327                     parseExtensionFields(file, entry, p + 2);
0328                 }
0329             }
0330         }
0331     }
0332     if (entry->fields.count > 0) {
0333         entry->fields.list = file->fields.list;
0334     }
0335     for (i = entry->fields.count  ;  i < file->fields.max  ;  ++i) {
0336         file->fields.list [i].key = nullptr;
0337         file->fields.list [i].value = nullptr;
0338     }
0339 }
0340 
0341 static char *duplicate(const char *str)
0342 {
0343     char *result = nullptr;
0344     if (str != nullptr) {
0345         result = (char *) malloc(strlen(str) + 1);
0346         if (result == nullptr) {
0347             perror(nullptr);
0348         } else {
0349             strcpy(result, str);
0350         }
0351     }
0352     return result;
0353 }
0354 
0355 static void readPseudoTags(tagFile *const file, tagFileInfo *const info)
0356 {
0357     fpos_t startOfLine;
0358     const size_t prefixLength = strlen(PseudoTagPrefix);
0359     if (info != nullptr) {
0360         info->file.format     = 1;
0361         info->file.sort       = TAG_UNSORTED;
0362         info->program.author  = nullptr;
0363         info->program.name    = nullptr;
0364         info->program.url     = nullptr;
0365         info->program.version = nullptr;
0366     }
0367     while (1) {
0368         fgetpos(file->fp, &startOfLine);
0369         if (! readTagLine(file)) {
0370             break;
0371         }
0372         if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0) {
0373             break;
0374         } else {
0375             tagEntry entry;
0376             const char *key, *value;
0377             parseTagLine(file, &entry);
0378             key = entry.name + prefixLength;
0379             value = entry.file;
0380             if (strcmp(key, "TAG_FILE_SORTED") == 0) {
0381                 file->sortMethod = (sortType) atoi(value);
0382             } else if (strcmp(key, "TAG_FILE_FORMAT") == 0) {
0383                 file->format = atoi(value);
0384             } else if (strcmp(key, "TAG_PROGRAM_AUTHOR") == 0) {
0385                 file->program.author = duplicate(value);
0386             } else if (strcmp(key, "TAG_PROGRAM_NAME") == 0) {
0387                 file->program.name = duplicate(value);
0388             } else if (strcmp(key, "TAG_PROGRAM_URL") == 0) {
0389                 file->program.url = duplicate(value);
0390             } else if (strcmp(key, "TAG_PROGRAM_VERSION") == 0) {
0391                 file->program.version = duplicate(value);
0392             }
0393             if (info != nullptr) {
0394                 info->file.format     = file->format;
0395                 info->file.sort       = file->sortMethod;
0396                 info->program.author  = file->program.author;
0397                 info->program.name    = file->program.name;
0398                 info->program.url     = file->program.url;
0399                 info->program.version = file->program.version;
0400             }
0401         }
0402     }
0403     fsetpos(file->fp, &startOfLine);
0404 }
0405 
0406 static void gotoFirstLogicalTag(tagFile *const file)
0407 {
0408     fpos_t startOfLine;
0409     const size_t prefixLength = strlen(PseudoTagPrefix);
0410     rewind(file->fp);
0411     while (1) {
0412         fgetpos(file->fp, &startOfLine);
0413         if (! readTagLine(file)) {
0414             break;
0415         }
0416         if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0) {
0417             break;
0418         }
0419     }
0420     fsetpos(file->fp, &startOfLine);
0421 }
0422 
0423 static tagFile *initialize(const char *const filePath, tagFileInfo *const info)
0424 {
0425     tagFile *result = (tagFile *) malloc(sizeof(tagFile));
0426     if (result != nullptr) {
0427         memset(result, 0, sizeof(tagFile));
0428         growString(&result->line);
0429         growString(&result->name);
0430         result->fields.max = 20;
0431         result->fields.list = (tagExtensionField *) malloc(
0432                                   result->fields.max * sizeof(tagExtensionField));
0433         result->fp = fopen(filePath, "r");
0434         if (result->fp == nullptr) {
0435             free(result);
0436             result = nullptr;
0437             info->status.error_number = errno;
0438         } else {
0439             fseek(result->fp, 0, SEEK_END);
0440             result->size = ftell(result->fp);
0441             rewind(result->fp);
0442             readPseudoTags(result, info);
0443             info->status.opened = 1;
0444             result->initialized = 1;
0445         }
0446     }
0447     return result;
0448 }
0449 
0450 static void terminate(tagFile *const file)
0451 {
0452     fclose(file->fp);
0453 
0454     free(file->line.buffer);
0455     free(file->name.buffer);
0456     free(file->fields.list);
0457 
0458     if (file->program.author != nullptr) {
0459         free(file->program.author);
0460     }
0461     if (file->program.name != nullptr) {
0462         free(file->program.name);
0463     }
0464     if (file->program.url != nullptr) {
0465         free(file->program.url);
0466     }
0467     if (file->program.version != nullptr) {
0468         free(file->program.version);
0469     }
0470 
0471     memset(file, 0, sizeof(tagFile));
0472 
0473     free(file);
0474 }
0475 
0476 static tagResult readNext(tagFile *const file, tagEntry *const entry)
0477 {
0478     tagResult result = TagFailure;
0479     if (file == nullptr  ||  ! file->initialized) {
0480         result = TagFailure;
0481     } else if (! readTagLine(file)) {
0482         result = TagFailure;
0483     } else {
0484         if (entry != nullptr) {
0485             parseTagLine(file, entry);
0486         }
0487         result = TagSuccess;
0488     }
0489     return result;
0490 }
0491 
0492 static const char *readFieldValue(
0493     const tagEntry *const entry, const char *const key)
0494 {
0495     const char *result = nullptr;
0496     int i;
0497     if (strcmp(key, "kind") == 0) {
0498         result = entry->kind;
0499     } else if (strcmp(key, "file") == 0) {
0500         result = EmptyString;
0501     } else for (i = 0  ;  i < entry->fields.count  &&  result == nullptr  ;  ++i)
0502             if (strcmp(entry->fields.list [i].key, key) == 0) {
0503                 result = entry->fields.list [i].value;
0504             }
0505     return result;
0506 }
0507 
0508 static int readTagLineSeek(tagFile *const file, const off_t pos)
0509 {
0510     int result = 0;
0511     if (fseek(file->fp, pos, SEEK_SET) == 0) {
0512         result = readTagLine(file);         /* read probable partial line */
0513         if (pos > 0  &&  result) {
0514             result = readTagLine(file);    /* read complete line */
0515         }
0516     }
0517     return result;
0518 }
0519 
0520 static int nameComparison(tagFile *const file)
0521 {
0522     int result;
0523     if (file->search.ignorecase) {
0524         if (file->search.partial)
0525             result = strnuppercmp(file->search.name, file->name.buffer,
0526                                   file->search.nameLength);
0527         else {
0528             result = struppercmp(file->search.name, file->name.buffer);
0529         }
0530     } else {
0531         if (file->search.partial)
0532             result = strncmp(file->search.name, file->name.buffer,
0533                              file->search.nameLength);
0534         else {
0535             result = strcmp(file->search.name, file->name.buffer);
0536         }
0537     }
0538     return result;
0539 }
0540 
0541 static void findFirstNonMatchBefore(tagFile *const file)
0542 {
0543 #define JUMP_BACK 512
0544     int more_lines;
0545     int comp;
0546     off_t start = file->pos;
0547     off_t pos = start;
0548     do {
0549         if (pos < (off_t) JUMP_BACK) {
0550             pos = 0;
0551         } else {
0552             pos = pos - JUMP_BACK;
0553         }
0554         more_lines = readTagLineSeek(file, pos);
0555         comp = nameComparison(file);
0556     } while (more_lines  &&  comp == 0  &&  pos > 0  &&  pos < start);
0557 }
0558 
0559 static tagResult findFirstMatchBefore(tagFile *const file)
0560 {
0561     tagResult result = TagFailure;
0562     int more_lines;
0563     off_t start = file->pos;
0564     findFirstNonMatchBefore(file);
0565     do {
0566         more_lines = readTagLine(file);
0567         if (nameComparison(file) == 0) {
0568             result = TagSuccess;
0569         }
0570     } while (more_lines  &&  result != TagSuccess  &&  file->pos < start);
0571     return result;
0572 }
0573 
0574 static tagResult findBinary(tagFile *const file)
0575 {
0576     tagResult result = TagFailure;
0577     off_t lower_limit = 0;
0578     off_t upper_limit = file->size;
0579     off_t last_pos = 0;
0580     off_t pos = upper_limit / 2;
0581     while (result != TagSuccess) {
0582         if (! readTagLineSeek(file, pos)) {
0583             /* in case we fell off end of file */
0584             result = findFirstMatchBefore(file);
0585             break;
0586         } else if (pos == last_pos) {
0587             /* prevent infinite loop if we backed up to beginning of file */
0588             break;
0589         } else {
0590             const int comp = nameComparison(file);
0591             last_pos = pos;
0592             if (comp < 0) {
0593                 upper_limit = pos;
0594                 pos = lower_limit + ((upper_limit - lower_limit) / 2);
0595             } else if (comp > 0) {
0596                 lower_limit = pos;
0597                 pos = lower_limit + ((upper_limit - lower_limit) / 2);
0598             } else if (pos == 0) {
0599                 result = TagSuccess;
0600             } else {
0601                 result = findFirstMatchBefore(file);
0602             }
0603         }
0604     }
0605     return result;
0606 }
0607 
0608 static tagResult findSequential(tagFile *const file)
0609 {
0610     tagResult result = TagFailure;
0611     if (file->initialized) {
0612         while (result == TagFailure  &&  readTagLine(file)) {
0613             if (nameComparison(file) == 0) {
0614                 result = TagSuccess;
0615             }
0616         }
0617     }
0618     return result;
0619 }
0620 
0621 static tagResult find(tagFile *const file, tagEntry *const entry,
0622                       const char *const name, const int options)
0623 {
0624     tagResult result = TagFailure;
0625     file->search.name = name;
0626     file->search.nameLength = strlen(name);
0627     file->search.partial = (options & TAG_PARTIALMATCH) != 0;
0628     file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
0629     fseek(file->fp, 0, SEEK_END);
0630     file->size = ftell(file->fp);
0631     rewind(file->fp);
0632     if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
0633             (file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase)) {
0634 #ifdef DEBUG
0635         printf("<performing binary search>\n");
0636 #endif
0637         result = findBinary(file);
0638     } else {
0639 #ifdef DEBUG
0640         printf("<performing sequential search>\n");
0641 #endif
0642         result = findSequential(file);
0643     }
0644 
0645     if (result != TagSuccess) {
0646         file->search.pos = file->size;
0647     } else {
0648         file->search.pos = file->pos;
0649         if (entry != nullptr) {
0650             parseTagLine(file, entry);
0651         }
0652     }
0653     return result;
0654 }
0655 
0656 static tagResult findNext(tagFile *const file, tagEntry *const entry)
0657 {
0658     tagResult result = TagFailure;
0659     if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
0660             (file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase)) {
0661         result = tagsNext(file, entry);
0662         if (result == TagSuccess  && nameComparison(file) != 0) {
0663             result = TagFailure;
0664         }
0665     } else {
0666         result = findSequential(file);
0667         if (result == TagSuccess  &&  entry != nullptr) {
0668             parseTagLine(file, entry);
0669         }
0670     }
0671     return result;
0672 }
0673 
0674 /*
0675 *  EXTERNAL INTERFACE
0676 */
0677 
0678 extern tagFile *tagsOpen(const char *const filePath, tagFileInfo *const info)
0679 {
0680     return initialize(filePath, info);
0681 }
0682 
0683 extern tagResult tagsSetSortType(tagFile *const file, const sortType type)
0684 {
0685     tagResult result = TagFailure;
0686     if (file != nullptr  &&  file->initialized) {
0687         file->sortMethod = type;
0688         result = TagSuccess;
0689     }
0690     return result;
0691 }
0692 
0693 extern tagResult tagsFirst(tagFile *const file, tagEntry *const entry)
0694 {
0695     tagResult result = TagFailure;
0696     if (file != nullptr  &&  file->initialized) {
0697         gotoFirstLogicalTag(file);
0698         result = readNext(file, entry);
0699     }
0700     return result;
0701 }
0702 
0703 extern tagResult tagsNext(tagFile *const file, tagEntry *const entry)
0704 {
0705     tagResult result = TagFailure;
0706     if (file != nullptr  &&  file->initialized) {
0707         result = readNext(file, entry);
0708     }
0709     return result;
0710 }
0711 
0712 extern const char *tagsField(const tagEntry *const entry, const char *const key)
0713 {
0714     const char *result = nullptr;
0715     if (entry != nullptr) {
0716         result = readFieldValue(entry, key);
0717     }
0718     return result;
0719 }
0720 
0721 extern tagResult tagsFind(tagFile *const file, tagEntry *const entry,
0722                           const char *const name, const int options)
0723 {
0724     tagResult result = TagFailure;
0725     if (file != nullptr  &&  file->initialized) {
0726         result = find(file, entry, name, options);
0727     }
0728     return result;
0729 }
0730 
0731 extern tagResult tagsFindNext(tagFile *const file, tagEntry *const entry)
0732 {
0733     tagResult result = TagFailure;
0734     if (file != nullptr  &&  file->initialized) {
0735         result = findNext(file, entry);
0736     }
0737     return result;
0738 }
0739 
0740 extern tagResult tagsClose(tagFile *const file)
0741 {
0742     tagResult result = TagFailure;
0743     if (file != nullptr  &&  file->initialized) {
0744         terminate(file);
0745         result = TagSuccess;
0746     }
0747     return result;
0748 }
0749 
0750 /*
0751 *  TEST FRAMEWORK
0752 */
0753 
0754 #ifdef READTAGS_MAIN
0755 
0756 static const char *TagFileName = "tags";
0757 static const char *ProgramName;
0758 static int extensionFields;
0759 static int SortOverride;
0760 static sortType SortMethod;
0761 
0762 static void printTag(const tagEntry *entry)
0763 {
0764     int i;
0765     int first = 1;
0766     const char *separator = ";\"";
0767     const char *const empty = "";
0768     /* "sep" returns a value only the first time it is evaluated */
0769 #define sep (first ? (first = 0, separator) : empty)
0770     printf("%s\t%s\t%s",
0771            entry->name, entry->file, entry->address.pattern);
0772     if (extensionFields) {
0773         if (entry->kind != NULL  &&  entry->kind [0] != '\0') {
0774             printf("%s\tkind:%s", sep, entry->kind);
0775         }
0776         if (entry->fileScope) {
0777             printf("%s\tfile:", sep);
0778         }
0779 #if 0
0780         if (entry->address.lineNumber > 0) {
0781             printf("%s\tline:%lu", sep, entry->address.lineNumber);
0782         }
0783 #endif
0784         for (i = 0  ;  i < entry->fields.count  ;  ++i)
0785             printf("%s\t%s:%s", sep, entry->fields.list [i].key,
0786                    entry->fields.list [i].value);
0787     }
0788     putchar('\n');
0789 #undef sep
0790 }
0791 
0792 static void findTag(const char *const name, const int options)
0793 {
0794     tagFileInfo info;
0795     tagEntry entry;
0796     tagFile *const file = tagsOpen(TagFileName, &info);
0797     if (file == NULL) {
0798         fprintf(stderr, "%s: cannot open tag file: %s: %s\n",
0799                 ProgramName, strerror(info.status.error_number), name);
0800         exit(1);
0801     } else {
0802         if (SortOverride) {
0803             tagsSetSortType(file, SortMethod);
0804         }
0805         if (tagsFind(file, &entry, name, options) == TagSuccess) {
0806             do {
0807                 printTag(&entry);
0808             } while (tagsFindNext(file, &entry) == TagSuccess);
0809         }
0810         tagsClose(file);
0811     }
0812 }
0813 
0814 static void listTags(void)
0815 {
0816     tagFileInfo info;
0817     tagEntry entry;
0818     tagFile *const file = tagsOpen(TagFileName, &info);
0819     if (file == NULL) {
0820         fprintf(stderr, "%s: cannot open tag file: %s: %s\n",
0821                 ProgramName, strerror(info.status.error_number), TagFileName);
0822         exit(1);
0823     } else {
0824         while (tagsNext(file, &entry) == TagSuccess) {
0825             printTag(&entry);
0826         }
0827         tagsClose(file);
0828     }
0829 }
0830 
0831 const char *const Usage =
0832     "Find tag file entries matching specified names.\n\n"
0833     "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
0834     "Options:\n"
0835     "    -e           Include extension fields in output.\n"
0836     "    -i           Perform case-insensitive matching.\n"
0837     "    -l           List all tags.\n"
0838     "    -p           Perform partial matching.\n"
0839     "    -s[0|1|2]    Override sort detection of tag file.\n"
0840     "    -t file      Use specified tag file (default: \"tags\").\n"
0841     "Note that options are acted upon as encountered, so order is significant.\n";
0842 
0843 extern int main(int argc, char **argv)
0844 {
0845     int options = 0;
0846     int actionSupplied = 0;
0847     int i;
0848     ProgramName = argv [0];
0849     if (argc == 1) {
0850         fprintf(stderr, Usage, ProgramName);
0851         exit(1);
0852     }
0853     for (i = 1  ;  i < argc  ;  ++i) {
0854         const char *const arg = argv [i];
0855         if (arg [0] != '-') {
0856             findTag(arg, options);
0857             actionSupplied = 1;
0858         } else {
0859             size_t j;
0860             for (j = 1  ;  arg [j] != '\0'  ;  ++j) {
0861                 switch (arg [j]) {
0862                 case 'e': extensionFields = 1;         break;
0863                 case 'i': options |= TAG_IGNORECASE;   break;
0864                 case 'p': options |= TAG_PARTIALMATCH; break;
0865                 case 'l': listTags(); actionSupplied = 1; break;
0866 
0867                 case 't':
0868                     if (arg [j + 1] != '\0') {
0869                         TagFileName = arg + j + 1;
0870                         j += strlen(TagFileName);
0871                     } else if (i + 1 < argc) {
0872                         TagFileName = argv [++i];
0873                     } else {
0874                         fprintf(stderr, Usage, ProgramName);
0875                         exit(1);
0876                     }
0877                     break;
0878                 case 's':
0879                     SortOverride = 1;
0880                     ++j;
0881                     if (arg [j] == '\0') {
0882                         SortMethod = TAG_SORTED;
0883                     } else if (strchr("012", arg[j]) != NULL) {
0884                         SortMethod = (sortType)(arg[j] - '0');
0885                     } else {
0886                         fprintf(stderr, Usage, ProgramName);
0887                         exit(1);
0888                     }
0889                     break;
0890                 default:
0891                     fprintf(stderr, "%s: unknown option: %c\n",
0892                             ProgramName, arg[j]);
0893                     exit(1);
0894                     break;
0895                 }
0896             }
0897         }
0898     }
0899     if (! actionSupplied) {
0900         fprintf(stderr,
0901                 "%s: no action specified: specify tag name(s) or -l option\n",
0902                 ProgramName);
0903         exit(1);
0904     }
0905     return 0;
0906 }
0907 
0908 #endif
0909 
0910 /* vi:set tabstop=8 shiftwidth=4: */