File indexing completed on 2024-05-05 05:51:38

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