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