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