2007-09-25 Johannes Schmid <jhs@gnome.org>
[anjuta-git-plugin.git] / plugins / symbol-db / readtags.c
blob396b2796b151a37b500d46b02d47b08d1be869e8
1 /*
2 * $Id: readtags.c 2606 2006-08-16 21:13:34Z naba $
4 * Copyright (c) 1996-2003, Darren Hiebert
6 * This source code is released into the public domain.
8 * This module contains functions for reading tag files.
9 */
12 * INCLUDE FILES
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 */
21 #include "readtags.h"
24 * MACROS
26 #define TAB '\t'
30 * DATA DECLARATIONS
32 typedef struct {
33 size_t size;
34 char *buffer;
35 } vstring;
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;
89 * DATA DEFINITIONS
91 const char *const EmptyString = "";
92 const char *const PseudoTagPrefix = "!_";
95 * FUNCTION DEFINITIONS
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 !)
105 static int struppercmp (const char *s1, const char *s2)
107 int result;
110 result = toupper ((int) *s1) - toupper ((int) *s2);
111 } while (result == 0 && *s1++ != '\0' && *s2++ != '\0');
112 return result;
115 static int strnuppercmp (const char *s1, const char *s2, size_t n)
117 int result;
120 result = toupper ((int) *s1) - toupper ((int) *s2);
121 } while (result == 0 && --n > 0 && *s1++ != '\0' && *s2++ != '\0');
122 return result;
125 static int growString (vstring *s)
127 int result = 0;
128 size_t newLength;
129 char *newLine;
130 if (s->size == 0)
132 newLength = 128;
133 newLine = (char*) malloc (newLength);
134 *newLine = '\0';
136 else
138 newLength = 2 * s->size;
139 newLine = (char*) realloc (s->buffer, newLength);
141 if (newLine == NULL)
142 perror ("string too large");
143 else
145 s->buffer = newLine;
146 s->size = newLength;
147 result = 1;
149 return result;
152 /* Copy name of tag out of tag line */
153 static void copyName (tagFile *const file)
155 size_t length;
156 const char *end = strchr (file->line.buffer, '\t');
157 if (end == NULL)
159 end = strchr (file->line.buffer, '\n');
160 if (end == NULL)
161 end = strchr (file->line.buffer, '\r');
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';
173 static int readTagLineRaw (tagFile *const file)
175 int result = 1;
176 int reReadLine;
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.
185 char *const pLastChar = file->line.buffer + file->line.size - 2;
186 char *line;
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)
194 /* read error */
195 if (! feof (file->fp))
196 perror ("readTagLine");
197 result = 0;
199 else if (*pLastChar != '\0' &&
200 *pLastChar != '\n' && *pLastChar != '\r')
202 /* buffer overflow */
203 growString (&file->line);
204 fseek (file->fp, file->pos, SEEK_SET);
205 reReadLine = 1;
207 else
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'))
213 file->line.buffer [i - 1] = '\0';
214 --i;
217 } while (reReadLine && result);
218 if (result)
219 copyName (file);
220 return result;
223 static int readTagLine (tagFile *const file)
225 int result;
228 result = readTagLineRaw (file);
229 } while (result && *file->name.buffer == '\0');
230 return result;
233 static tagResult growFields (tagFile *const file)
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
243 file->fields.list = newFields;
244 file->fields.max = newCount;
245 result = TagSuccess;
247 return result;
250 static void parseExtensionFields (tagFile *const file, tagEntry *const entry,
251 char *const string)
253 char *p = string;
254 while (p != NULL && *p != '\0')
256 while (*p == TAB)
257 *p++ = '\0';
258 if (*p != '\0')
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
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
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;
292 static void parseTagLine (tagFile *file, tagEntry *const entry)
294 int i;
295 char *p = file->line.buffer;
296 char *tab = strchr (p, TAB);
297 int fieldsPresent = 0;
299 entry->fields.list = NULL;
300 entry->fields.count = 0;
301 entry->kind = NULL;
302 entry->fileScope = 0;
304 entry->name = p;
305 if (tab != NULL)
307 *tab = '\0';
308 p = tab + 1;
309 entry->file = p;
310 tab = strchr (p, TAB);
311 if (tab != NULL)
313 *tab = '\0';
314 p = tab + 1;
315 if (*p == '/' || *p == '?')
317 /* parse pattern */
318 int delimiter = *(unsigned char*) p;
319 entry->address.lineNumber = 0;
320 entry->address.pattern = p;
323 p = strchr (p + 1, delimiter);
324 } while (p != NULL && *(p - 1) == '\\');
325 if (p == NULL)
327 /* invalid pattern */
329 else
330 ++p;
332 else if (isdigit ((int) *(unsigned char*) p))
334 /* parse line number */
335 entry->address.pattern = p;
336 entry->address.lineNumber = atol (p);
337 while (isdigit ((int) *(unsigned char*) p))
338 ++p;
340 else
342 /* invalid pattern */
344 fieldsPresent = (strncmp (p, ";\"", 2) == 0);
345 *p = '\0';
346 if (fieldsPresent)
347 parseExtensionFields (file, entry, p + 2);
350 if (entry->fields.count > 0)
351 entry->fields.list = file->fields.list;
352 for (i = entry->fields.count ; i < file->fields.max ; ++i)
354 file->fields.list [i].key = NULL;
355 file->fields.list [i].value = NULL;
359 static char *duplicate (const char *str)
361 char *result = NULL;
362 if (str != NULL)
364 result = (char*) malloc (strlen (str) + 1);
365 if (result == NULL)
366 perror (NULL);
367 else
368 strcpy (result, str);
370 return result;
373 static void readPseudoTags (tagFile *const file, tagFileInfo *const info)
375 fpos_t startOfLine;
376 const size_t prefixLength = strlen (PseudoTagPrefix);
377 if (info != NULL)
379 info->file.format = 1;
380 info->file.sort = TAG_UNSORTED;
381 info->program.author = NULL;
382 info->program.name = NULL;
383 info->program.url = NULL;
384 info->program.version = NULL;
386 while (1)
388 fgetpos (file->fp, &startOfLine);
389 if (! readTagLine (file))
390 break;
391 if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
392 break;
393 else
395 tagEntry entry;
396 const char *key, *value;
397 parseTagLine (file, &entry);
398 key = entry.name + prefixLength;
399 value = entry.file;
400 if (strcmp (key, "TAG_FILE_SORTED") == 0)
401 file->sortMethod = (sortType) atoi (value);
402 else if (strcmp (key, "TAG_FILE_FORMAT") == 0)
403 file->format = atoi (value);
404 else if (strcmp (key, "TAG_PROGRAM_AUTHOR") == 0)
405 file->program.author = duplicate (value);
406 else if (strcmp (key, "TAG_PROGRAM_NAME") == 0)
407 file->program.name = duplicate (value);
408 else if (strcmp (key, "TAG_PROGRAM_URL") == 0)
409 file->program.url = duplicate (value);
410 else if (strcmp (key, "TAG_PROGRAM_VERSION") == 0)
411 file->program.version = duplicate (value);
412 if (info != NULL)
414 info->file.format = file->format;
415 info->file.sort = file->sortMethod;
416 info->program.author = file->program.author;
417 info->program.name = file->program.name;
418 info->program.url = file->program.url;
419 info->program.version = file->program.version;
423 fsetpos (file->fp, &startOfLine);
426 static void gotoFirstLogicalTag (tagFile *const file)
428 fpos_t startOfLine;
429 const size_t prefixLength = strlen (PseudoTagPrefix);
430 rewind (file->fp);
431 while (1)
433 fgetpos (file->fp, &startOfLine);
434 if (! readTagLine (file))
435 break;
436 if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
437 break;
439 fsetpos (file->fp, &startOfLine);
442 static tagFile *initialize (const char *const filePath, tagFileInfo *const info)
444 tagFile *result = (tagFile*) malloc (sizeof (tagFile));
445 if (result != NULL)
447 memset (result, 0, sizeof (tagFile));
448 growString (&result->line);
449 growString (&result->name);
450 result->fields.max = 20;
451 result->fields.list = (tagExtensionField*) malloc (
452 result->fields.max * sizeof (tagExtensionField));
453 result->fp = fopen (filePath, "r");
454 if (result->fp == NULL)
456 free (result);
457 result = NULL;
458 info->status.error_number = errno;
460 else
462 fseek (result->fp, 0, SEEK_END);
463 result->size = ftell (result->fp);
464 rewind (result->fp);
465 readPseudoTags (result, info);
466 info->status.opened = 1;
467 result->initialized = 1;
470 return result;
473 static tagFile *initialize_1 (FILE* fd, tagFileInfo *const info)
475 tagFile *result = (tagFile*) malloc (sizeof (tagFile));
476 if (result != NULL)
478 memset (result, 0, sizeof (tagFile));
479 growString (&result->line);
480 growString (&result->name);
481 result->fields.max = 20;
482 result->fields.list = (tagExtensionField*) malloc (
483 result->fields.max * sizeof (tagExtensionField));
484 result->fp = fd;
485 if (result->fp == NULL)
487 free (result);
488 result = NULL;
489 info->status.error_number = errno;
491 else
493 fseek (result->fp, 0, SEEK_END);
494 result->size = ftell (result->fp);
495 rewind (result->fp);
496 readPseudoTags (result, info);
497 info->status.opened = 1;
498 result->initialized = 1;
501 return result;
504 static void terminate (tagFile *const file)
506 fclose (file->fp);
508 free (file->line.buffer);
509 free (file->name.buffer);
510 free (file->fields.list);
512 if (file->program.author != NULL)
513 free (file->program.author);
514 if (file->program.name != NULL)
515 free (file->program.name);
516 if (file->program.url != NULL)
517 free (file->program.url);
518 if (file->program.version != NULL)
519 free (file->program.version);
521 memset (file, 0, sizeof (tagFile));
523 free (file);
526 static tagResult readNext (tagFile *const file, tagEntry *const entry)
528 tagResult result = TagFailure;
529 if (file == NULL || ! file->initialized)
530 result = TagFailure;
531 else if (! readTagLine (file))
532 result = TagFailure;
533 else
535 if (entry != NULL)
536 parseTagLine (file, entry);
537 result = TagSuccess;
539 return result;
542 static const char *readFieldValue (
543 const tagEntry *const entry, const char *const key)
545 const char *result = NULL;
546 int i;
547 if (strcmp (key, "kind") == 0)
548 result = entry->kind;
549 else if (strcmp (key, "file") == 0)
550 result = EmptyString;
551 else for (i = 0 ; i < entry->fields.count && result == NULL ; ++i)
552 if (strcmp (entry->fields.list [i].key, key) == 0)
553 result = entry->fields.list [i].value;
554 return result;
557 static int readTagLineSeek (tagFile *const file, const off_t pos)
559 int result = 0;
560 if (fseek (file->fp, pos, SEEK_SET) == 0)
562 result = readTagLine (file); /* read probable partial line */
563 if (pos > 0 && result)
564 result = readTagLine (file); /* read complete line */
566 return result;
569 static int nameComparison (tagFile *const file)
571 int result;
572 if (file->search.ignorecase)
574 if (file->search.partial)
575 result = strnuppercmp (file->search.name, file->name.buffer,
576 file->search.nameLength);
577 else
578 result = struppercmp (file->search.name, file->name.buffer);
580 else
582 if (file->search.partial)
583 result = strncmp (file->search.name, file->name.buffer,
584 file->search.nameLength);
585 else
586 result = strcmp (file->search.name, file->name.buffer);
588 return result;
591 static void findFirstNonMatchBefore (tagFile *const file)
593 #define JUMP_BACK 512
594 int more_lines;
595 int comp;
596 off_t start = file->pos;
597 off_t pos = start;
600 if (pos < (off_t) JUMP_BACK)
601 pos = 0;
602 else
603 pos = pos - JUMP_BACK;
604 more_lines = readTagLineSeek (file, pos);
605 comp = nameComparison (file);
606 } while (more_lines && comp == 0 && pos > 0 && pos < start);
609 static tagResult findFirstMatchBefore (tagFile *const file)
611 tagResult result = TagFailure;
612 int more_lines;
613 off_t start = file->pos;
614 findFirstNonMatchBefore (file);
617 more_lines = readTagLine (file);
618 if (nameComparison (file) == 0)
619 result = TagSuccess;
620 } while (more_lines && result != TagSuccess && file->pos < start);
621 return result;
624 static tagResult findBinary (tagFile *const file)
626 tagResult result = TagFailure;
627 off_t lower_limit = 0;
628 off_t upper_limit = file->size;
629 off_t last_pos = 0;
630 off_t pos = upper_limit / 2;
631 while (result != TagSuccess)
633 if (! readTagLineSeek (file, pos))
635 /* in case we fell off end of file */
636 result = findFirstMatchBefore (file);
637 break;
639 else if (pos == last_pos)
641 /* prevent infinite loop if we backed up to beginning of file */
642 break;
644 else
646 const int comp = nameComparison (file);
647 last_pos = pos;
648 if (comp < 0)
650 upper_limit = pos;
651 pos = lower_limit + ((upper_limit - lower_limit) / 2);
653 else if (comp > 0)
655 lower_limit = pos;
656 pos = lower_limit + ((upper_limit - lower_limit) / 2);
658 else if (pos == 0)
659 result = TagSuccess;
660 else
661 result = findFirstMatchBefore (file);
664 return result;
667 static tagResult findSequential (tagFile *const file)
669 tagResult result = TagFailure;
670 if (file->initialized)
672 while (result == TagFailure && readTagLine (file))
674 if (nameComparison (file) == 0)
675 result = TagSuccess;
678 return result;
681 static tagResult find (tagFile *const file, tagEntry *const entry,
682 const char *const name, const int options)
684 tagResult result = TagFailure;
685 file->search.name = name;
686 file->search.nameLength = strlen (name);
687 file->search.partial = (options & TAG_PARTIALMATCH) != 0;
688 file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
689 fseek (file->fp, 0, SEEK_END);
690 file->size = ftell (file->fp);
691 rewind (file->fp);
692 if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) ||
693 (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase))
695 #ifdef DEBUG
696 printf ("<performing binary search>\n");
697 #endif
698 result = findBinary (file);
700 else
702 #ifdef DEBUG
703 printf ("<performing sequential search>\n");
704 #endif
705 result = findSequential (file);
708 if (result != TagSuccess)
709 file->search.pos = file->size;
710 else
712 file->search.pos = file->pos;
713 if (entry != NULL)
714 parseTagLine (file, entry);
716 return result;
719 static tagResult findNext (tagFile *const file, tagEntry *const entry)
721 tagResult result = TagFailure;
722 if ((file->sortMethod == TAG_SORTED && !file->search.ignorecase) ||
723 (file->sortMethod == TAG_FOLDSORTED && file->search.ignorecase))
725 result = tagsNext (file, entry);
726 if (result == TagSuccess && nameComparison (file) != 0)
727 result = TagFailure;
729 else
731 result = findSequential (file);
732 if (result == TagSuccess && entry != NULL)
733 parseTagLine (file, entry);
735 return result;
739 * EXTERNAL INTERFACE
742 extern tagFile *tagsOpen (const char *const filePath, tagFileInfo *const info)
744 return initialize (filePath, info);
747 extern tagFile *tagsOpen_1 (const FILE *fd, tagFileInfo *const info)
749 return initialize_1 (fd, info);
753 extern tagResult tagsSetSortType (tagFile *const file, const sortType type)
755 tagResult result = TagFailure;
756 if (file != NULL && file->initialized)
758 file->sortMethod = type;
759 result = TagSuccess;
761 return result;
764 extern tagResult tagsFirst (tagFile *const file, tagEntry *const entry)
766 tagResult result = TagFailure;
767 if (file != NULL && file->initialized)
769 gotoFirstLogicalTag (file);
770 result = readNext (file, entry);
772 return result;
775 extern tagResult tagsNext (tagFile *const file, tagEntry *const entry)
777 tagResult result = TagFailure;
778 if (file != NULL && file->initialized)
779 result = readNext (file, entry);
780 return result;
783 extern const char *tagsField (const tagEntry *const entry, const char *const key)
785 const char *result = NULL;
786 if (entry != NULL)
787 result = readFieldValue (entry, key);
788 return result;
791 extern tagResult tagsFind (tagFile *const file, tagEntry *const entry,
792 const char *const name, const int options)
794 tagResult result = TagFailure;
795 if (file != NULL && file->initialized)
796 result = find (file, entry, name, options);
797 return result;
800 extern tagResult tagsFindNext (tagFile *const file, tagEntry *const entry)
802 tagResult result = TagFailure;
803 if (file != NULL && file->initialized)
804 result = findNext (file, entry);
805 return result;
808 extern tagResult tagsClose (tagFile *const file)
810 tagResult result = TagFailure;
811 if (file != NULL && file->initialized)
813 terminate (file);
814 result = TagSuccess;
816 return result;
820 * TEST FRAMEWORK
823 #ifdef READTAGS_MAIN
825 static const char *TagFileName = "tags";
826 static const char *ProgramName;
827 static int extensionFields;
828 static int SortOverride;
829 static sortType SortMethod;
831 static void printTag (const tagEntry *entry)
833 int i;
834 int first = 1;
835 const char* separator = ";\"";
836 const char* const empty = "";
837 /* "sep" returns a value only the first time it is evaluated */
838 #define sep (first ? (first = 0, separator) : empty)
839 printf ("%s\t%s\t%s",
840 entry->name, entry->file, entry->address.pattern);
841 if (extensionFields)
843 if (entry->kind != NULL && entry->kind [0] != '\0')
844 printf ("%s\tkind:%s", sep, entry->kind);
845 if (entry->fileScope)
846 printf ("%s\tfile:", sep);
847 #if 0
848 if (entry->address.lineNumber > 0)
849 printf ("%s\tline:%lu", sep, entry->address.lineNumber);
850 #endif
851 for (i = 0 ; i < entry->fields.count ; ++i)
852 printf ("%s\t%s:%s", sep, entry->fields.list [i].key,
853 entry->fields.list [i].value);
855 putchar ('\n');
856 #undef sep
859 static void findTag (const char *const name, const int options)
861 tagFileInfo info;
862 tagEntry entry;
863 tagFile *const file = tagsOpen (TagFileName, &info);
864 if (file == NULL)
866 fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
867 ProgramName, strerror (info.status.error_number), name);
868 exit (1);
870 else
872 if (SortOverride)
873 tagsSetSortType (file, SortMethod);
874 if (tagsFind (file, &entry, name, options) == TagSuccess)
878 printTag (&entry);
879 } while (tagsFindNext (file, &entry) == TagSuccess);
881 tagsClose (file);
885 static void listTags (void)
887 tagFileInfo info;
888 tagEntry entry;
889 tagFile *const file = tagsOpen (TagFileName, &info);
890 if (file == NULL)
892 fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
893 ProgramName, strerror (info.status.error_number), TagFileName);
894 exit (1);
896 else
898 while (tagsNext (file, &entry) == TagSuccess)
899 printTag (&entry);
900 tagsClose (file);
904 const char *const Usage =
905 "Find tag file entries matching specified names.\n\n"
906 "Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
907 "Options:\n"
908 " -e Include extension fields in output.\n"
909 " -i Perform case-insensitive matching.\n"
910 " -l List all tags.\n"
911 " -p Perform partial matching.\n"
912 " -s[0|1|2] Override sort detection of tag file.\n"
913 " -t file Use specified tag file (default: \"tags\").\n"
914 "Note that options are acted upon as encountered, so order is significant.\n";
916 extern int main (int argc, char **argv)
918 int options = 0;
919 int actionSupplied = 0;
920 int i;
921 ProgramName = argv [0];
922 if (argc == 1)
924 fprintf (stderr, Usage, ProgramName);
925 exit (1);
927 for (i = 1 ; i < argc ; ++i)
929 const char *const arg = argv [i];
930 if (arg [0] != '-')
932 findTag (arg, options);
933 actionSupplied = 1;
935 else
937 size_t j;
938 for (j = 1 ; arg [j] != '\0' ; ++j)
940 switch (arg [j])
942 case 'e': extensionFields = 1; break;
943 case 'i': options |= TAG_IGNORECASE; break;
944 case 'p': options |= TAG_PARTIALMATCH; break;
945 case 'l': listTags (); actionSupplied = 1; break;
947 case 't':
948 if (arg [j+1] != '\0')
950 TagFileName = arg + j + 1;
951 j += strlen (TagFileName);
953 else if (i + 1 < argc)
954 TagFileName = argv [++i];
955 else
957 fprintf (stderr, Usage, ProgramName);
958 exit (1);
960 break;
961 case 's':
962 SortOverride = 1;
963 ++j;
964 if (arg [j] == '\0')
965 SortMethod = TAG_SORTED;
966 else if (strchr ("012", arg[j]) != NULL)
967 SortMethod = (sortType) (arg[j] - '0');
968 else
970 fprintf (stderr, Usage, ProgramName);
971 exit (1);
973 break;
974 default:
975 fprintf (stderr, "%s: unknown option: %c\n",
976 ProgramName, arg[j]);
977 exit (1);
978 break;
983 if (! actionSupplied)
985 fprintf (stderr,
986 "%s: no action specified: specify tag name(s) or -l option\n",
987 ProgramName);
988 exit (1);
990 return 0;
993 #endif
995 /* vi:set tabstop=4 shiftwidth=4: */