1 | /* |
2 | * |
3 | * Copyright (c) 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 <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 | const 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 | */ |
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 | |
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 | |
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 */ |
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 | |
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 | |
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 | |
233 | static tagResult growFields (tagFile *const file) |
234 | { |
235 | tagResult result = TagFailure; |
236 | unsigned short newCount = 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 | |
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 | |
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 | int fieldsPresent = 0; |
298 | |
299 | entry->fields.list = NULL; |
300 | entry->fields.count = 0; |
301 | entry->kind = NULL; |
302 | entry->fileScope = 0; |
303 | |
304 | entry->name = p; |
305 | if (tab != NULL) |
306 | { |
307 | *tab = '\0'; |
308 | p = tab + 1; |
309 | entry->file = p; |
310 | tab = strchr (p, TAB); |
311 | if (tab != NULL) |
312 | { |
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 | if ( p != NULL ) |
345 | { |
346 | fieldsPresent = (strncmp (p, ";\"" , 2) == 0); |
347 | *p = '\0'; |
348 | if (fieldsPresent) |
349 | parseExtensionFields (file, entry, p + 2); |
350 | } |
351 | } |
352 | } |
353 | if (entry->fields.count > 0) |
354 | entry->fields.list = file->fields.list; |
355 | for (i = entry->fields.count ; i < file->fields.max ; ++i) |
356 | { |
357 | file->fields.list [i].key = NULL; |
358 | file->fields.list [i].value = NULL; |
359 | } |
360 | } |
361 | |
362 | static char *duplicate (const char *str) |
363 | { |
364 | char *result = NULL; |
365 | if (str != NULL) |
366 | { |
367 | result = (char*) malloc (strlen (str) + 1); |
368 | if (result == NULL) |
369 | perror (NULL); |
370 | else |
371 | strcpy (result, str); |
372 | } |
373 | return result; |
374 | } |
375 | |
376 | static void readPseudoTags (tagFile *const file, tagFileInfo *const info) |
377 | { |
378 | fpos_t startOfLine; |
379 | const size_t prefixLength = strlen (PseudoTagPrefix); |
380 | if (info != NULL) |
381 | { |
382 | info->file.format = 1; |
383 | info->file.sort = TAG_UNSORTED; |
384 | info->program.author = NULL; |
385 | info->program.name = NULL; |
386 | info->program.url = NULL; |
387 | info->program.version = NULL; |
388 | } |
389 | while (1) |
390 | { |
391 | fgetpos (file->fp, &startOfLine); |
392 | if (! readTagLine (file)) |
393 | break; |
394 | if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0) |
395 | break; |
396 | else |
397 | { |
398 | tagEntry entry; |
399 | const char *key, *value; |
400 | parseTagLine (file, &entry); |
401 | key = entry.name + prefixLength; |
402 | value = entry.file; |
403 | if (strcmp (key, "TAG_FILE_SORTED" ) == 0) |
404 | file->sortMethod = (sortType) atoi (value); |
405 | else if (strcmp (key, "TAG_FILE_FORMAT" ) == 0) |
406 | file->format = atoi (value); |
407 | else if (strcmp (key, "TAG_PROGRAM_AUTHOR" ) == 0) |
408 | file->program.author = duplicate (value); |
409 | else if (strcmp (key, "TAG_PROGRAM_NAME" ) == 0) |
410 | file->program.name = duplicate (value); |
411 | else if (strcmp (key, "TAG_PROGRAM_URL" ) == 0) |
412 | file->program.url = duplicate (value); |
413 | else if (strcmp (key, "TAG_PROGRAM_VERSION" ) == 0) |
414 | file->program.version = duplicate (value); |
415 | if (info != NULL) |
416 | { |
417 | info->file.format = file->format; |
418 | info->file.sort = file->sortMethod; |
419 | info->program.author = file->program.author; |
420 | info->program.name = file->program.name; |
421 | info->program.url = file->program.url; |
422 | info->program.version = file->program.version; |
423 | } |
424 | } |
425 | } |
426 | fsetpos (file->fp, &startOfLine); |
427 | } |
428 | |
429 | static void gotoFirstLogicalTag (tagFile *const file) |
430 | { |
431 | fpos_t startOfLine; |
432 | const size_t prefixLength = strlen (PseudoTagPrefix); |
433 | rewind (file->fp); |
434 | while (1) |
435 | { |
436 | fgetpos (file->fp, &startOfLine); |
437 | if (! readTagLine (file)) |
438 | break; |
439 | if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0) |
440 | break; |
441 | } |
442 | fsetpos (file->fp, &startOfLine); |
443 | } |
444 | |
445 | static tagFile *initialize (const char *const filePath, tagFileInfo *const info) |
446 | { |
447 | tagFile *result = (tagFile*) malloc (sizeof (tagFile)); |
448 | if (result != NULL) |
449 | { |
450 | memset (result, 0, sizeof (tagFile)); |
451 | growString (&result->line); |
452 | growString (&result->name); |
453 | result->fields.max = 20; |
454 | result->fields.list = (tagExtensionField*) malloc ( |
455 | result->fields.max * sizeof (tagExtensionField)); |
456 | result->fp = fopen (filePath, "r" ); |
457 | if (result->fp == NULL) |
458 | { |
459 | free (result); |
460 | result = NULL; |
461 | info->status.error_number = errno; |
462 | } |
463 | else |
464 | { |
465 | fseek (result->fp, 0, SEEK_END); |
466 | result->size = ftell (result->fp); |
467 | rewind (result->fp); |
468 | readPseudoTags (result, info); |
469 | info->status.opened = 1; |
470 | result->initialized = 1; |
471 | } |
472 | } |
473 | return result; |
474 | } |
475 | |
476 | static void terminate (tagFile *const file) |
477 | { |
478 | fclose (file->fp); |
479 | |
480 | free (file->line.buffer); |
481 | free (file->name.buffer); |
482 | free (file->fields.list); |
483 | |
484 | if (file->program.author != NULL) |
485 | free (file->program.author); |
486 | if (file->program.name != NULL) |
487 | free (file->program.name); |
488 | if (file->program.url != NULL) |
489 | free (file->program.url); |
490 | if (file->program.version != NULL) |
491 | free (file->program.version); |
492 | |
493 | memset (file, 0, sizeof (tagFile)); |
494 | |
495 | free (file); |
496 | } |
497 | |
498 | static tagResult readNext (tagFile *const file, tagEntry *const entry) |
499 | { |
500 | tagResult result = TagFailure; |
501 | if (file == NULL || ! file->initialized) |
502 | result = TagFailure; |
503 | else if (! readTagLine (file)) |
504 | result = TagFailure; |
505 | else |
506 | { |
507 | if (entry != NULL) |
508 | parseTagLine (file, entry); |
509 | result = TagSuccess; |
510 | } |
511 | return result; |
512 | } |
513 | |
514 | static const char *readFieldValue ( |
515 | const tagEntry *const entry, const char *const key) |
516 | { |
517 | const char *result = NULL; |
518 | int i; |
519 | if (strcmp (key, "kind" ) == 0) |
520 | result = entry->kind; |
521 | else if (strcmp (key, "file" ) == 0) |
522 | result = EmptyString; |
523 | else for (i = 0 ; i < entry->fields.count && result == NULL ; ++i) |
524 | if (strcmp (entry->fields.list [i].key, key) == 0) |
525 | result = entry->fields.list [i].value; |
526 | return result; |
527 | } |
528 | |
529 | static int readTagLineSeek (tagFile *const file, const off_t pos) |
530 | { |
531 | int result = 0; |
532 | if (fseek (file->fp, pos, SEEK_SET) == 0) |
533 | { |
534 | result = readTagLine (file); /* read probable partial line */ |
535 | if (pos > 0 && result) |
536 | result = readTagLine (file); /* read complete line */ |
537 | } |
538 | return result; |
539 | } |
540 | |
541 | static int nameComparison (tagFile *const file) |
542 | { |
543 | int result; |
544 | if (file->search.ignorecase) |
545 | { |
546 | if (file->search.partial) |
547 | result = strnuppercmp (file->search.name, file->name.buffer, |
548 | file->search.nameLength); |
549 | else |
550 | result = struppercmp (file->search.name, file->name.buffer); |
551 | } |
552 | else |
553 | { |
554 | if (file->search.partial) |
555 | result = strncmp (file->search.name, file->name.buffer, |
556 | file->search.nameLength); |
557 | else |
558 | result = strcmp (file->search.name, file->name.buffer); |
559 | } |
560 | return result; |
561 | } |
562 | |
563 | static void findFirstNonMatchBefore (tagFile *const file) |
564 | { |
565 | #define JUMP_BACK 512 |
566 | int more_lines; |
567 | int comp; |
568 | off_t start = file->pos; |
569 | off_t pos = start; |
570 | do |
571 | { |
572 | if (pos < (off_t) JUMP_BACK) |
573 | pos = 0; |
574 | else |
575 | pos = pos - JUMP_BACK; |
576 | more_lines = readTagLineSeek (file, pos); |
577 | comp = nameComparison (file); |
578 | } while (more_lines && comp == 0 && pos > 0 && pos < start); |
579 | } |
580 | |
581 | static tagResult findFirstMatchBefore (tagFile *const file) |
582 | { |
583 | tagResult result = TagFailure; |
584 | int more_lines; |
585 | off_t start = file->pos; |
586 | findFirstNonMatchBefore (file); |
587 | do |
588 | { |
589 | more_lines = readTagLine (file); |
590 | if (nameComparison (file) == 0) |
591 | result = TagSuccess; |
592 | } while (more_lines && result != TagSuccess && file->pos < start); |
593 | return result; |
594 | } |
595 | |
596 | static tagResult findBinary (tagFile *const file) |
597 | { |
598 | tagResult result = TagFailure; |
599 | off_t lower_limit = 0; |
600 | off_t upper_limit = file->size; |
601 | off_t last_pos = 0; |
602 | off_t pos = upper_limit / 2; |
603 | while (result != TagSuccess) |
604 | { |
605 | if (! readTagLineSeek (file, pos)) |
606 | { |
607 | /* in case we fell off end of file */ |
608 | result = findFirstMatchBefore (file); |
609 | break; |
610 | } |
611 | else if (pos == last_pos) |
612 | { |
613 | /* prevent infinite loop if we backed up to beginning of file */ |
614 | break; |
615 | } |
616 | else |
617 | { |
618 | const int comp = nameComparison (file); |
619 | last_pos = pos; |
620 | if (comp < 0) |
621 | { |
622 | upper_limit = pos; |
623 | pos = lower_limit + ((upper_limit - lower_limit) / 2); |
624 | } |
625 | else if (comp > 0) |
626 | { |
627 | lower_limit = pos; |
628 | pos = lower_limit + ((upper_limit - lower_limit) / 2); |
629 | } |
630 | else if (pos == 0) |
631 | result = TagSuccess; |
632 | else |
633 | result = findFirstMatchBefore (file); |
634 | } |
635 | } |
636 | return result; |
637 | } |
638 | |
639 | static tagResult findSequential (tagFile *const file) |
640 | { |
641 | tagResult result = TagFailure; |
642 | if (file->initialized) |
643 | { |
644 | while (result == TagFailure && readTagLine (file)) |
645 | { |
646 | if (nameComparison (file) == 0) |
647 | result = TagSuccess; |
648 | } |
649 | } |
650 | return result; |
651 | } |
652 | |
653 | static tagResult find (tagFile *const file, tagEntry *const entry, |
654 | const char *const name, const int options) |
655 | { |
656 | tagResult result = TagFailure; |
657 | file->search.name = name; |
658 | file->search.nameLength = strlen (name); |
659 | file->search.partial = (options & TAG_PARTIALMATCH) != 0; |
660 | file->search.ignorecase = (options & TAG_IGNORECASE) != 0; |
661 | fseek (file->fp, 0, SEEK_END); |
662 | file->size = ftell (file->fp); |
663 | rewind (file->fp); |
664 | if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) || |
665 | (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) |
666 | { |
667 | #ifdef DEBUG |
668 | printf ("<performing binary search>\n" ); |
669 | #endif |
670 | result = findBinary (file); |
671 | } |
672 | else |
673 | { |
674 | #ifdef DEBUG |
675 | printf ("<performing sequential search>\n" ); |
676 | #endif |
677 | result = findSequential (file); |
678 | } |
679 | |
680 | if (result != TagSuccess) |
681 | file->search.pos = file->size; |
682 | else |
683 | { |
684 | file->search.pos = file->pos; |
685 | if (entry != NULL) |
686 | parseTagLine (file, entry); |
687 | } |
688 | return result; |
689 | } |
690 | |
691 | static tagResult findNext (tagFile *const file, tagEntry *const entry) |
692 | { |
693 | tagResult result = TagFailure; |
694 | if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) || |
695 | (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase)) |
696 | { |
697 | result = tagsNext (file, entry); |
698 | if (result == TagSuccess && nameComparison (file) != 0) |
699 | result = TagFailure; |
700 | } |
701 | else |
702 | { |
703 | result = findSequential (file); |
704 | if (result == TagSuccess && entry != NULL) |
705 | parseTagLine (file, entry); |
706 | } |
707 | return result; |
708 | } |
709 | |
710 | /* |
711 | * EXTERNAL INTERFACE |
712 | */ |
713 | |
714 | extern tagFile *tagsOpen (const char *const filePath, tagFileInfo *const info) |
715 | { |
716 | return initialize (filePath, info); |
717 | } |
718 | |
719 | extern tagResult tagsSetSortType (tagFile *const file, const sortType type) |
720 | { |
721 | tagResult result = TagFailure; |
722 | if (file != NULL && file->initialized) |
723 | { |
724 | file->sortMethod = type; |
725 | result = TagSuccess; |
726 | } |
727 | return result; |
728 | } |
729 | |
730 | extern tagResult tagsFirst (tagFile *const file, tagEntry *const entry) |
731 | { |
732 | tagResult result = TagFailure; |
733 | if (file != NULL && file->initialized) |
734 | { |
735 | gotoFirstLogicalTag (file); |
736 | result = readNext (file, entry); |
737 | } |
738 | return result; |
739 | } |
740 | |
741 | extern tagResult tagsNext (tagFile *const file, tagEntry *const entry) |
742 | { |
743 | tagResult result = TagFailure; |
744 | if (file != NULL && file->initialized) |
745 | result = readNext (file, entry); |
746 | return result; |
747 | } |
748 | |
749 | extern const char *tagsField (const tagEntry *const entry, const char *const key) |
750 | { |
751 | const char *result = NULL; |
752 | if (entry != NULL) |
753 | result = readFieldValue (entry, key); |
754 | return result; |
755 | } |
756 | |
757 | extern tagResult tagsFind (tagFile *const file, tagEntry *const entry, |
758 | const char *const name, const int options) |
759 | { |
760 | tagResult result = TagFailure; |
761 | if (file != NULL && file->initialized) |
762 | result = find (file, entry, name, options); |
763 | return result; |
764 | } |
765 | |
766 | extern tagResult tagsFindNext (tagFile *const file, tagEntry *const entry) |
767 | { |
768 | tagResult result = TagFailure; |
769 | if (file != NULL && file->initialized) |
770 | result = findNext (file, entry); |
771 | return result; |
772 | } |
773 | |
774 | extern tagResult tagsClose (tagFile *const file) |
775 | { |
776 | tagResult result = TagFailure; |
777 | if (file != NULL && file->initialized) |
778 | { |
779 | terminate (file); |
780 | result = TagSuccess; |
781 | } |
782 | return result; |
783 | } |
784 | |
785 | /* |
786 | * TEST FRAMEWORK |
787 | */ |
788 | |
789 | #ifdef READTAGS_MAIN |
790 | |
791 | static const char *TagFileName = "tags" ; |
792 | static const char *ProgramName; |
793 | static int extensionFields; |
794 | static int SortOverride; |
795 | static sortType SortMethod; |
796 | |
797 | static void printTag (const tagEntry *entry) |
798 | { |
799 | int i; |
800 | int first = 1; |
801 | const char* separator = ";\"" ; |
802 | const char* const empty = "" ; |
803 | /* "sep" returns a value only the first time it is evaluated */ |
804 | #define sep (first ? (first = 0, separator) : empty) |
805 | printf ("%s\t%s\t%s" , |
806 | entry->name, entry->file, entry->address.pattern); |
807 | if (extensionFields) |
808 | { |
809 | if (entry->kind != NULL && entry->kind [0] != '\0') |
810 | printf ("%s\tkind:%s" , sep, entry->kind); |
811 | if (entry->fileScope) |
812 | printf ("%s\tfile:" , sep); |
813 | #if 0 |
814 | if (entry->address.lineNumber > 0) |
815 | printf ("%s\tline:%lu" , sep, entry->address.lineNumber); |
816 | #endif |
817 | for (i = 0 ; i < entry->fields.count ; ++i) |
818 | printf ("%s\t%s:%s" , sep, entry->fields.list [i].key, |
819 | entry->fields.list [i].value); |
820 | } |
821 | putchar ('\n'); |
822 | #undef sep |
823 | } |
824 | |
825 | static void findTag (const char *const name, const int options) |
826 | { |
827 | tagFileInfo info; |
828 | tagEntry entry; |
829 | tagFile *const file = tagsOpen (TagFileName, &info); |
830 | if (file == NULL) |
831 | { |
832 | fprintf (stderr, "%s: cannot open tag file: %s: %s\n" , |
833 | ProgramName, strerror (info.status.error_number), name); |
834 | exit (1); |
835 | } |
836 | else |
837 | { |
838 | if (SortOverride) |
839 | tagsSetSortType (file, SortMethod); |
840 | if (tagsFind (file, &entry, name, options) == TagSuccess) |
841 | { |
842 | do |
843 | { |
844 | printTag (&entry); |
845 | } while (tagsFindNext (file, &entry) == TagSuccess); |
846 | } |
847 | tagsClose (file); |
848 | } |
849 | } |
850 | |
851 | static void listTags (void) |
852 | { |
853 | tagFileInfo info; |
854 | tagEntry entry; |
855 | tagFile *const file = tagsOpen (TagFileName, &info); |
856 | if (file == NULL) |
857 | { |
858 | fprintf (stderr, "%s: cannot open tag file: %s: %s\n" , |
859 | ProgramName, strerror (info.status.error_number), TagFileName); |
860 | exit (1); |
861 | } |
862 | else |
863 | { |
864 | while (tagsNext (file, &entry) == TagSuccess) |
865 | printTag (&entry); |
866 | tagsClose (file); |
867 | } |
868 | } |
869 | |
870 | const char *const Usage = |
871 | "Find tag file entries matching specified names.\n\n" |
872 | "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n" |
873 | "Options:\n" |
874 | " -e Include extension fields in output.\n" |
875 | " -i Perform case-insensitive matching.\n" |
876 | " -l List all tags.\n" |
877 | " -p Perform partial matching.\n" |
878 | " -s[0|1|2] Override sort detection of tag file.\n" |
879 | " -t file Use specified tag file (default: \"tags\").\n" |
880 | "Note that options are acted upon as encountered, so order is significant.\n" ; |
881 | |
882 | extern int main (int argc, char **argv) |
883 | { |
884 | int options = 0; |
885 | int actionSupplied = 0; |
886 | int i; |
887 | ProgramName = argv [0]; |
888 | if (argc == 1) |
889 | { |
890 | fprintf (stderr, Usage, ProgramName); |
891 | exit (1); |
892 | } |
893 | for (i = 1 ; i < argc ; ++i) |
894 | { |
895 | const char *const arg = argv [i]; |
896 | if (arg [0] != '-') |
897 | { |
898 | findTag (arg, options); |
899 | actionSupplied = 1; |
900 | } |
901 | else |
902 | { |
903 | size_t j; |
904 | for (j = 1 ; arg [j] != '\0' ; ++j) |
905 | { |
906 | switch (arg [j]) |
907 | { |
908 | case 'e': extensionFields = 1; break; |
909 | case 'i': options |= TAG_IGNORECASE; break; |
910 | case 'p': options |= TAG_PARTIALMATCH; break; |
911 | case 'l': listTags (); actionSupplied = 1; break; |
912 | |
913 | case 't': |
914 | if (arg [j+1] != '\0') |
915 | { |
916 | TagFileName = arg + j + 1; |
917 | j += strlen (TagFileName); |
918 | } |
919 | else if (i + 1 < argc) |
920 | TagFileName = argv [++i]; |
921 | else |
922 | { |
923 | fprintf (stderr, Usage, ProgramName); |
924 | exit (1); |
925 | } |
926 | break; |
927 | case 's': |
928 | SortOverride = 1; |
929 | ++j; |
930 | if (arg [j] == '\0') |
931 | SortMethod = TAG_SORTED; |
932 | else if (strchr ("012" , arg[j]) != NULL) |
933 | SortMethod = (sortType) (arg[j] - '0'); |
934 | else |
935 | { |
936 | fprintf (stderr, Usage, ProgramName); |
937 | exit (1); |
938 | } |
939 | break; |
940 | default: |
941 | fprintf (stderr, "%s: unknown option: %c\n" , |
942 | ProgramName, arg[j]); |
943 | exit (1); |
944 | break; |
945 | } |
946 | } |
947 | } |
948 | } |
949 | if (! actionSupplied) |
950 | { |
951 | fprintf (stderr, |
952 | "%s: no action specified: specify tag name(s) or -l option\n" , |
953 | ProgramName); |
954 | exit (1); |
955 | } |
956 | return 0; |
957 | } |
958 | |
959 | #endif |
960 | |
961 | /* vi:set tabstop=8 shiftwidth=4: */ |
962 | |