1 /*
2 *
3 *   SPDX-FileCopyrightText: 1996-2003 Darren Hiebert <dhiebert at users dot sourceforge dot net>
4 *
5 *   This source code is released into the public domain.
6 *
7 *   This module contains functions for reading tag files.
8 *
9 */
10 
11 /*
12 *   INCLUDE FILES
13 */
14 #include "readtags.h"
15 
16 #include <stdlib.h>
17 #include <string.h>
18 #include <ctype.h>
19 #include <stdio.h>
20 #include <errno.h>
21 #include <sys/types.h>  /* to declare off_t */
22 
23 /*
24 *   MACROS
25 */
26 #define TAB '\t'
27 
28 /*
29 *   DATA DECLARATIONS
30 */
31 typedef struct {
32     size_t size;
33     char *buffer;
34 } vstring;
35 
36 /* Information about current tag file */
37 struct sTagFile {
38     /* has the file been opened and this structure initialized? */
39     short initialized;
40     /* format of tag file */
41     short format;
42     /* how is the tag file sorted? */
43     sortType sortMethod;
44     /* pointer to file structure */
45     FILE *fp;
46     /* file position of first character of `line' */
47     off_t pos;
48     /* size of tag file in seekable positions */
49     off_t size;
50     /* last line read */
51     vstring line;
52     /* name of tag in last line read */
53     vstring name;
54     /* defines tag search state */
55     struct {
56         /* file position of last match for tag */
57         off_t pos;
58         /* name of tag last searched for */
59         const char *name;
60         /* length of name for partial matches */
61         size_t nameLength;
62         /* peforming partial match */
63         short partial;
64         /* ignoring case */
65         short ignorecase;
66     } search;
67     /* miscellaneous extension fields */
68     struct {
69         /* number of entries in `list' */
70         unsigned short max;
71         /* list of key value pairs */
72         tagExtensionField *list;
73     } fields;
74     /* buffers to be freed at close */
75     struct {
76         /* name of program author */
77         char *author;
78         /* name of program */
79         char *name;
80         /* URL of distribution */
81         char *url;
82         /* program version */
83         char *version;
84     } program;
85 };
86 
87 /*
88 *   DATA DEFINITIONS
89 */
90 const char *const EmptyString = "";
91 const char *const PseudoTagPrefix = "!_";
92 
93 /*
94 *   FUNCTION DEFINITIONS
95 */
96 
97 /*
98  * Compare two strings, ignoring case.
99  * Return 0 for match, < 0 for smaller, > 0 for bigger
100  * Make sure case is folded to uppercase in comparison (like for 'sort -f')
101  * This makes a difference when one of the chars lies between upper and lower
102  * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
103  */
struppercmp(const char * s1,const char * s2)104 static int struppercmp(const char *s1, const char *s2)
105 {
106     int result;
107     do {
108         result = toupper((int) * s1) - toupper((int) * s2);
109     } while (result == 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
110     return result;
111 }
112 
strnuppercmp(const char * s1,const char * s2,size_t n)113 static int strnuppercmp(const char *s1, const char *s2, size_t n)
114 {
115     int result;
116     do {
117         result = toupper((int) * s1) - toupper((int) * s2);
118     } while (result == 0  &&  --n > 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
119     return result;
120 }
121 
growString(vstring * s)122 static int growString(vstring *s)
123 {
124     int result = 0;
125     size_t newLength;
126     char *newLine;
127     if (s->size == 0) {
128         newLength = 128;
129         newLine = (char *) malloc(newLength);
130         *newLine = '\0';
131     } else {
132         newLength = 2 * s->size;
133         newLine = (char *) realloc(s->buffer, newLength);
134     }
135     if (newLine == nullptr) {
136         perror("string too large");
137     } else {
138         s->buffer = newLine;
139         s->size = newLength;
140         result = 1;
141     }
142     return result;
143 }
144 
145 /* Copy name of tag out of tag line */
copyName(tagFile * const file)146 static void copyName(tagFile *const file)
147 {
148     size_t length;
149     const char *end = strchr(file->line.buffer, '\t');
150     if (end == nullptr) {
151         end = strchr(file->line.buffer, '\n');
152         if (end == nullptr) {
153             end = strchr(file->line.buffer, '\r');
154         }
155     }
156     if (end != nullptr) {
157         length = end - file->line.buffer;
158     } else {
159         length = strlen(file->line.buffer);
160     }
161     while (length >= file->name.size) {
162         growString(&file->name);
163     }
164     strncpy(file->name.buffer, file->line.buffer, length);
165     file->name.buffer [length] = '\0';
166 }
167 
readTagLineRaw(tagFile * const file)168 static int readTagLineRaw(tagFile *const file)
169 {
170     int result = 1;
171     int reReadLine;
172 
173     /*  If reading the line places any character other than a null or a
174      *  newline at the last character position in the buffer (one less than
175      *  the buffer size), then we must resize the buffer and reattempt to read
176      *  the line.
177      */
178     do {
179         char *const pLastChar = file->line.buffer + file->line.size - 2;
180         char *line;
181 
182         file->pos = ftell(file->fp);
183         reReadLine = 0;
184         *pLastChar = '\0';
185         line = fgets(file->line.buffer, (int) file->line.size, file->fp);
186         if (line == nullptr) {
187             /* read error */
188             if (! feof(file->fp)) {
189                 perror("readTagLine");
190             }
191             result = 0;
192         } else if (*pLastChar != '\0'  &&
193                    *pLastChar != '\n'  &&  *pLastChar != '\r') {
194             /*  buffer overflow */
195             growString(&file->line);
196             fseek(file->fp, file->pos, SEEK_SET);
197             reReadLine = 1;
198         } else {
199             size_t i = strlen(file->line.buffer);
200             while (i > 0  &&
201                     (file->line.buffer [i - 1] == '\n' || file->line.buffer [i - 1] == '\r')) {
202                 file->line.buffer [i - 1] = '\0';
203                 --i;
204             }
205         }
206     } while (reReadLine  &&  result);
207     if (result) {
208         copyName(file);
209     }
210     return result;
211 }
212 
readTagLine(tagFile * const file)213 static int readTagLine(tagFile *const file)
214 {
215     int result;
216     do {
217         result = readTagLineRaw(file);
218     } while (result && *file->name.buffer == '\0');
219     return result;
220 }
221 
growFields(tagFile * const file)222 static tagResult growFields(tagFile *const file)
223 {
224     tagResult result = TagFailure;
225     unsigned short newCount = 2 * file->fields.max;
226     tagExtensionField *newFields = (tagExtensionField *)
227                                    realloc(file->fields.list, newCount * sizeof(tagExtensionField));
228     if (newFields == nullptr) {
229         perror("too many extension fields");
230     } else {
231         file->fields.list = newFields;
232         file->fields.max = newCount;
233         result = TagSuccess;
234     }
235     return result;
236 }
237 
parseExtensionFields(tagFile * const file,tagEntry * const entry,char * const string)238 static void parseExtensionFields(tagFile *const file, tagEntry *const entry,
239                                  char *const string)
240 {
241     char *p = string;
242     while (p != nullptr  &&  *p != '\0') {
243         while (*p == TAB) {
244             *p++ = '\0';
245         }
246         if (*p != '\0') {
247             char *colon;
248             char *field = p;
249             p = strchr(p, TAB);
250             if (p != nullptr) {
251                 *p++ = '\0';
252             }
253             colon = strchr(field, ':');
254             if (colon == nullptr) {
255                 entry->kind = field;
256             } else {
257                 const char *key = field;
258                 const char *value = colon + 1;
259                 *colon = '\0';
260                 if (strcmp(key, "kind") == 0) {
261                     entry->kind = value;
262                 } else if (strcmp(key, "file") == 0) {
263                     entry->fileScope = 1;
264                 } else if (strcmp(key, "line") == 0) {
265                     entry->address.lineNumber = atol(value);
266                 } else {
267                     if (entry->fields.count == file->fields.max) {
268                         growFields(file);
269                     }
270                     file->fields.list [entry->fields.count].key = key;
271                     file->fields.list [entry->fields.count].value = value;
272                     ++entry->fields.count;
273                 }
274             }
275         }
276     }
277 }
278 
parseTagLine(tagFile * file,tagEntry * const entry)279 static void parseTagLine(tagFile *file, tagEntry *const entry)
280 {
281     int i;
282     char *p = file->line.buffer;
283     char *tab = strchr(p, TAB);
284     int fieldsPresent = 0;
285 
286     entry->fields.list = nullptr;
287     entry->fields.count = 0;
288     entry->kind = nullptr;
289     entry->fileScope = 0;
290 
291     entry->name = p;
292     if (tab != nullptr) {
293         *tab = '\0';
294         p = tab + 1;
295         entry->file = p;
296         tab = strchr(p, TAB);
297         if (tab != nullptr) {
298             *tab = '\0';
299             p = tab + 1;
300             if (*p == '/'  ||  *p == '?') {
301                 /* parse pattern */
302                 int delimiter = *(unsigned char *) p;
303                 entry->address.lineNumber = 0;
304                 entry->address.pattern = p;
305                 do {
306                     p = strchr(p + 1, delimiter);
307                 } while (p != nullptr  &&  *(p - 1) == '\\');
308                 if (p == nullptr) {
309                     /* invalid pattern */
310                 } else {
311                     ++p;
312                 }
313             } else if (isdigit((int) * (unsigned char *) p)) {
314                 /* parse line number */
315                 entry->address.pattern = p;
316                 entry->address.lineNumber = atol(p);
317                 while (isdigit((int) * (unsigned char *) p)) {
318                     ++p;
319                 }
320             } else {
321                 /* invalid pattern */
322             }
323             if (p != nullptr) {
324                 fieldsPresent = (strncmp(p, ";\"", 2) == 0);
325                 *p = '\0';
326                 if (fieldsPresent) {
327                     parseExtensionFields(file, entry, p + 2);
328                 }
329             }
330         }
331     }
332     if (entry->fields.count > 0) {
333         entry->fields.list = file->fields.list;
334     }
335     for (i = entry->fields.count  ;  i < file->fields.max  ;  ++i) {
336         file->fields.list [i].key = nullptr;
337         file->fields.list [i].value = nullptr;
338     }
339 }
340 
duplicate(const char * str)341 static char *duplicate(const char *str)
342 {
343     char *result = nullptr;
344     if (str != nullptr) {
345         result = (char *) malloc(strlen(str) + 1);
346         if (result == nullptr) {
347             perror(nullptr);
348         } else {
349             strcpy(result, str);
350         }
351     }
352     return result;
353 }
354 
readPseudoTags(tagFile * const file,tagFileInfo * const info)355 static void readPseudoTags(tagFile *const file, tagFileInfo *const info)
356 {
357     fpos_t startOfLine;
358     const size_t prefixLength = strlen(PseudoTagPrefix);
359     if (info != nullptr) {
360         info->file.format     = 1;
361         info->file.sort       = TAG_UNSORTED;
362         info->program.author  = nullptr;
363         info->program.name    = nullptr;
364         info->program.url     = nullptr;
365         info->program.version = nullptr;
366     }
367     while (1) {
368         fgetpos(file->fp, &startOfLine);
369         if (! readTagLine(file)) {
370             break;
371         }
372         if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0) {
373             break;
374         } else {
375             tagEntry entry;
376             const char *key, *value;
377             parseTagLine(file, &entry);
378             key = entry.name + prefixLength;
379             value = entry.file;
380             if (strcmp(key, "TAG_FILE_SORTED") == 0) {
381                 file->sortMethod = (sortType) atoi(value);
382             } else if (strcmp(key, "TAG_FILE_FORMAT") == 0) {
383                 file->format = atoi(value);
384             } else if (strcmp(key, "TAG_PROGRAM_AUTHOR") == 0) {
385                 file->program.author = duplicate(value);
386             } else if (strcmp(key, "TAG_PROGRAM_NAME") == 0) {
387                 file->program.name = duplicate(value);
388             } else if (strcmp(key, "TAG_PROGRAM_URL") == 0) {
389                 file->program.url = duplicate(value);
390             } else if (strcmp(key, "TAG_PROGRAM_VERSION") == 0) {
391                 file->program.version = duplicate(value);
392             }
393             if (info != nullptr) {
394                 info->file.format     = file->format;
395                 info->file.sort       = file->sortMethod;
396                 info->program.author  = file->program.author;
397                 info->program.name    = file->program.name;
398                 info->program.url     = file->program.url;
399                 info->program.version = file->program.version;
400             }
401         }
402     }
403     fsetpos(file->fp, &startOfLine);
404 }
405 
gotoFirstLogicalTag(tagFile * const file)406 static void gotoFirstLogicalTag(tagFile *const file)
407 {
408     fpos_t startOfLine;
409     const size_t prefixLength = strlen(PseudoTagPrefix);
410     rewind(file->fp);
411     while (1) {
412         fgetpos(file->fp, &startOfLine);
413         if (! readTagLine(file)) {
414             break;
415         }
416         if (strncmp(file->line.buffer, PseudoTagPrefix, prefixLength) != 0) {
417             break;
418         }
419     }
420     fsetpos(file->fp, &startOfLine);
421 }
422 
initialize(const char * const filePath,tagFileInfo * const info)423 static tagFile *initialize(const char *const filePath, tagFileInfo *const info)
424 {
425     tagFile *result = (tagFile *) malloc(sizeof(tagFile));
426     if (result != nullptr) {
427         memset(result, 0, sizeof(tagFile));
428         growString(&result->line);
429         growString(&result->name);
430         result->fields.max = 20;
431         result->fields.list = (tagExtensionField *) malloc(
432                                   result->fields.max * sizeof(tagExtensionField));
433         result->fp = fopen(filePath, "r");
434         if (result->fp == nullptr) {
435             free(result);
436             result = nullptr;
437             info->status.error_number = errno;
438         } else {
439             fseek(result->fp, 0, SEEK_END);
440             result->size = ftell(result->fp);
441             rewind(result->fp);
442             readPseudoTags(result, info);
443             info->status.opened = 1;
444             result->initialized = 1;
445         }
446     }
447     return result;
448 }
449 
terminate(tagFile * const file)450 static void terminate(tagFile *const file)
451 {
452     fclose(file->fp);
453 
454     free(file->line.buffer);
455     free(file->name.buffer);
456     free(file->fields.list);
457 
458     if (file->program.author != nullptr) {
459         free(file->program.author);
460     }
461     if (file->program.name != nullptr) {
462         free(file->program.name);
463     }
464     if (file->program.url != nullptr) {
465         free(file->program.url);
466     }
467     if (file->program.version != nullptr) {
468         free(file->program.version);
469     }
470 
471     memset(file, 0, sizeof(tagFile));
472 
473     free(file);
474 }
475 
readNext(tagFile * const file,tagEntry * const entry)476 static tagResult readNext(tagFile *const file, tagEntry *const entry)
477 {
478     tagResult result = TagFailure;
479     if (file == nullptr  ||  ! file->initialized) {
480         result = TagFailure;
481     } else if (! readTagLine(file)) {
482         result = TagFailure;
483     } else {
484         if (entry != nullptr) {
485             parseTagLine(file, entry);
486         }
487         result = TagSuccess;
488     }
489     return result;
490 }
491 
readFieldValue(const tagEntry * const entry,const char * const key)492 static const char *readFieldValue(
493     const tagEntry *const entry, const char *const key)
494 {
495     const char *result = nullptr;
496     int i;
497     if (strcmp(key, "kind") == 0) {
498         result = entry->kind;
499     } else if (strcmp(key, "file") == 0) {
500         result = EmptyString;
501     } else for (i = 0  ;  i < entry->fields.count  &&  result == nullptr  ;  ++i)
502             if (strcmp(entry->fields.list [i].key, key) == 0) {
503                 result = entry->fields.list [i].value;
504             }
505     return result;
506 }
507 
readTagLineSeek(tagFile * const file,const off_t pos)508 static int readTagLineSeek(tagFile *const file, const off_t pos)
509 {
510     int result = 0;
511     if (fseek(file->fp, pos, SEEK_SET) == 0) {
512         result = readTagLine(file);         /* read probable partial line */
513         if (pos > 0  &&  result) {
514             result = readTagLine(file);    /* read complete line */
515         }
516     }
517     return result;
518 }
519 
nameComparison(tagFile * const file)520 static int nameComparison(tagFile *const file)
521 {
522     int result;
523     if (file->search.ignorecase) {
524         if (file->search.partial)
525             result = strnuppercmp(file->search.name, file->name.buffer,
526                                   file->search.nameLength);
527         else {
528             result = struppercmp(file->search.name, file->name.buffer);
529         }
530     } else {
531         if (file->search.partial)
532             result = strncmp(file->search.name, file->name.buffer,
533                              file->search.nameLength);
534         else {
535             result = strcmp(file->search.name, file->name.buffer);
536         }
537     }
538     return result;
539 }
540 
findFirstNonMatchBefore(tagFile * const file)541 static void findFirstNonMatchBefore(tagFile *const file)
542 {
543 #define JUMP_BACK 512
544     int more_lines;
545     int comp;
546     off_t start = file->pos;
547     off_t pos = start;
548     do {
549         if (pos < (off_t) JUMP_BACK) {
550             pos = 0;
551         } else {
552             pos = pos - JUMP_BACK;
553         }
554         more_lines = readTagLineSeek(file, pos);
555         comp = nameComparison(file);
556     } while (more_lines  &&  comp == 0  &&  pos > 0  &&  pos < start);
557 }
558 
findFirstMatchBefore(tagFile * const file)559 static tagResult findFirstMatchBefore(tagFile *const file)
560 {
561     tagResult result = TagFailure;
562     int more_lines;
563     off_t start = file->pos;
564     findFirstNonMatchBefore(file);
565     do {
566         more_lines = readTagLine(file);
567         if (nameComparison(file) == 0) {
568             result = TagSuccess;
569         }
570     } while (more_lines  &&  result != TagSuccess  &&  file->pos < start);
571     return result;
572 }
573 
findBinary(tagFile * const file)574 static tagResult findBinary(tagFile *const file)
575 {
576     tagResult result = TagFailure;
577     off_t lower_limit = 0;
578     off_t upper_limit = file->size;
579     off_t last_pos = 0;
580     off_t pos = upper_limit / 2;
581     while (result != TagSuccess) {
582         if (! readTagLineSeek(file, pos)) {
583             /* in case we fell off end of file */
584             result = findFirstMatchBefore(file);
585             break;
586         } else if (pos == last_pos) {
587             /* prevent infinite loop if we backed up to beginning of file */
588             break;
589         } else {
590             const int comp = nameComparison(file);
591             last_pos = pos;
592             if (comp < 0) {
593                 upper_limit = pos;
594                 pos = lower_limit + ((upper_limit - lower_limit) / 2);
595             } else if (comp > 0) {
596                 lower_limit = pos;
597                 pos = lower_limit + ((upper_limit - lower_limit) / 2);
598             } else if (pos == 0) {
599                 result = TagSuccess;
600             } else {
601                 result = findFirstMatchBefore(file);
602             }
603         }
604     }
605     return result;
606 }
607 
findSequential(tagFile * const file)608 static tagResult findSequential(tagFile *const file)
609 {
610     tagResult result = TagFailure;
611     if (file->initialized) {
612         while (result == TagFailure  &&  readTagLine(file)) {
613             if (nameComparison(file) == 0) {
614                 result = TagSuccess;
615             }
616         }
617     }
618     return result;
619 }
620 
find(tagFile * const file,tagEntry * const entry,const char * const name,const int options)621 static tagResult find(tagFile *const file, tagEntry *const entry,
622                       const char *const name, const int options)
623 {
624     tagResult result = TagFailure;
625     file->search.name = name;
626     file->search.nameLength = strlen(name);
627     file->search.partial = (options & TAG_PARTIALMATCH) != 0;
628     file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
629     fseek(file->fp, 0, SEEK_END);
630     file->size = ftell(file->fp);
631     rewind(file->fp);
632     if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
633             (file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase)) {
634 #ifdef DEBUG
635         printf("<performing binary search>\n");
636 #endif
637         result = findBinary(file);
638     } else {
639 #ifdef DEBUG
640         printf("<performing sequential search>\n");
641 #endif
642         result = findSequential(file);
643     }
644 
645     if (result != TagSuccess) {
646         file->search.pos = file->size;
647     } else {
648         file->search.pos = file->pos;
649         if (entry != nullptr) {
650             parseTagLine(file, entry);
651         }
652     }
653     return result;
654 }
655 
findNext(tagFile * const file,tagEntry * const entry)656 static tagResult findNext(tagFile *const file, tagEntry *const entry)
657 {
658     tagResult result = TagFailure;
659     if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
660             (file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase)) {
661         result = tagsNext(file, entry);
662         if (result == TagSuccess  && nameComparison(file) != 0) {
663             result = TagFailure;
664         }
665     } else {
666         result = findSequential(file);
667         if (result == TagSuccess  &&  entry != nullptr) {
668             parseTagLine(file, entry);
669         }
670     }
671     return result;
672 }
673 
674 /*
675 *  EXTERNAL INTERFACE
676 */
677 
tagsOpen(const char * const filePath,tagFileInfo * const info)678 extern tagFile *tagsOpen(const char *const filePath, tagFileInfo *const info)
679 {
680     return initialize(filePath, info);
681 }
682 
tagsSetSortType(tagFile * const file,const sortType type)683 extern tagResult tagsSetSortType(tagFile *const file, const sortType type)
684 {
685     tagResult result = TagFailure;
686     if (file != nullptr  &&  file->initialized) {
687         file->sortMethod = type;
688         result = TagSuccess;
689     }
690     return result;
691 }
692 
tagsFirst(tagFile * const file,tagEntry * const entry)693 extern tagResult tagsFirst(tagFile *const file, tagEntry *const entry)
694 {
695     tagResult result = TagFailure;
696     if (file != nullptr  &&  file->initialized) {
697         gotoFirstLogicalTag(file);
698         result = readNext(file, entry);
699     }
700     return result;
701 }
702 
tagsNext(tagFile * const file,tagEntry * const entry)703 extern tagResult tagsNext(tagFile *const file, tagEntry *const entry)
704 {
705     tagResult result = TagFailure;
706     if (file != nullptr  &&  file->initialized) {
707         result = readNext(file, entry);
708     }
709     return result;
710 }
711 
tagsField(const tagEntry * const entry,const char * const key)712 extern const char *tagsField(const tagEntry *const entry, const char *const key)
713 {
714     const char *result = nullptr;
715     if (entry != nullptr) {
716         result = readFieldValue(entry, key);
717     }
718     return result;
719 }
720 
tagsFind(tagFile * const file,tagEntry * const entry,const char * const name,const int options)721 extern tagResult tagsFind(tagFile *const file, tagEntry *const entry,
722                           const char *const name, const int options)
723 {
724     tagResult result = TagFailure;
725     if (file != nullptr  &&  file->initialized) {
726         result = find(file, entry, name, options);
727     }
728     return result;
729 }
730 
tagsFindNext(tagFile * const file,tagEntry * const entry)731 extern tagResult tagsFindNext(tagFile *const file, tagEntry *const entry)
732 {
733     tagResult result = TagFailure;
734     if (file != nullptr  &&  file->initialized) {
735         result = findNext(file, entry);
736     }
737     return result;
738 }
739 
tagsClose(tagFile * const file)740 extern tagResult tagsClose(tagFile *const file)
741 {
742     tagResult result = TagFailure;
743     if (file != nullptr  &&  file->initialized) {
744         terminate(file);
745         result = TagSuccess;
746     }
747     return result;
748 }
749 
750 /*
751 *  TEST FRAMEWORK
752 */
753 
754 #ifdef READTAGS_MAIN
755 
756 static const char *TagFileName = "tags";
757 static const char *ProgramName;
758 static int extensionFields;
759 static int SortOverride;
760 static sortType SortMethod;
761 
printTag(const tagEntry * entry)762 static void printTag(const tagEntry *entry)
763 {
764     int i;
765     int first = 1;
766     const char *separator = ";\"";
767     const char *const empty = "";
768     /* "sep" returns a value only the first time it is evaluated */
769 #define sep (first ? (first = 0, separator) : empty)
770     printf("%s\t%s\t%s",
771            entry->name, entry->file, entry->address.pattern);
772     if (extensionFields) {
773         if (entry->kind != NULL  &&  entry->kind [0] != '\0') {
774             printf("%s\tkind:%s", sep, entry->kind);
775         }
776         if (entry->fileScope) {
777             printf("%s\tfile:", sep);
778         }
779 #if 0
780         if (entry->address.lineNumber > 0) {
781             printf("%s\tline:%lu", sep, entry->address.lineNumber);
782         }
783 #endif
784         for (i = 0  ;  i < entry->fields.count  ;  ++i)
785             printf("%s\t%s:%s", sep, entry->fields.list [i].key,
786                    entry->fields.list [i].value);
787     }
788     putchar('\n');
789 #undef sep
790 }
791 
findTag(const char * const name,const int options)792 static void findTag(const char *const name, const int options)
793 {
794     tagFileInfo info;
795     tagEntry entry;
796     tagFile *const file = tagsOpen(TagFileName, &info);
797     if (file == NULL) {
798         fprintf(stderr, "%s: cannot open tag file: %s: %s\n",
799                 ProgramName, strerror(info.status.error_number), name);
800         exit(1);
801     } else {
802         if (SortOverride) {
803             tagsSetSortType(file, SortMethod);
804         }
805         if (tagsFind(file, &entry, name, options) == TagSuccess) {
806             do {
807                 printTag(&entry);
808             } while (tagsFindNext(file, &entry) == TagSuccess);
809         }
810         tagsClose(file);
811     }
812 }
813 
listTags(void)814 static void listTags(void)
815 {
816     tagFileInfo info;
817     tagEntry entry;
818     tagFile *const file = tagsOpen(TagFileName, &info);
819     if (file == NULL) {
820         fprintf(stderr, "%s: cannot open tag file: %s: %s\n",
821                 ProgramName, strerror(info.status.error_number), TagFileName);
822         exit(1);
823     } else {
824         while (tagsNext(file, &entry) == TagSuccess) {
825             printTag(&entry);
826         }
827         tagsClose(file);
828     }
829 }
830 
831 const char *const Usage =
832     "Find tag file entries matching specified names.\n\n"
833     "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
834     "Options:\n"
835     "    -e           Include extension fields in output.\n"
836     "    -i           Perform case-insensitive matching.\n"
837     "    -l           List all tags.\n"
838     "    -p           Perform partial matching.\n"
839     "    -s[0|1|2]    Override sort detection of tag file.\n"
840     "    -t file      Use specified tag file (default: \"tags\").\n"
841     "Note that options are acted upon as encountered, so order is significant.\n";
842 
main(int argc,char ** argv)843 extern int main(int argc, char **argv)
844 {
845     int options = 0;
846     int actionSupplied = 0;
847     int i;
848     ProgramName = argv [0];
849     if (argc == 1) {
850         fprintf(stderr, Usage, ProgramName);
851         exit(1);
852     }
853     for (i = 1  ;  i < argc  ;  ++i) {
854         const char *const arg = argv [i];
855         if (arg [0] != '-') {
856             findTag(arg, options);
857             actionSupplied = 1;
858         } else {
859             size_t j;
860             for (j = 1  ;  arg [j] != '\0'  ;  ++j) {
861                 switch (arg [j]) {
862                 case 'e': extensionFields = 1;         break;
863                 case 'i': options |= TAG_IGNORECASE;   break;
864                 case 'p': options |= TAG_PARTIALMATCH; break;
865                 case 'l': listTags(); actionSupplied = 1; break;
866 
867                 case 't':
868                     if (arg [j + 1] != '\0') {
869                         TagFileName = arg + j + 1;
870                         j += strlen(TagFileName);
871                     } else if (i + 1 < argc) {
872                         TagFileName = argv [++i];
873                     } else {
874                         fprintf(stderr, Usage, ProgramName);
875                         exit(1);
876                     }
877                     break;
878                 case 's':
879                     SortOverride = 1;
880                     ++j;
881                     if (arg [j] == '\0') {
882                         SortMethod = TAG_SORTED;
883                     } else if (strchr("012", arg[j]) != NULL) {
884                         SortMethod = (sortType)(arg[j] - '0');
885                     } else {
886                         fprintf(stderr, Usage, ProgramName);
887                         exit(1);
888                     }
889                     break;
890                 default:
891                     fprintf(stderr, "%s: unknown option: %c\n",
892                             ProgramName, arg[j]);
893                     exit(1);
894                     break;
895                 }
896             }
897         }
898     }
899     if (! actionSupplied) {
900         fprintf(stderr,
901                 "%s: no action specified: specify tag name(s) or -l option\n",
902                 ProgramName);
903         exit(1);
904     }
905     return 0;
906 }
907 
908 #endif
909 
910 /* vi:set tabstop=8 shiftwidth=4: */
911