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