2 * $Id: options.c 576 2007-06-30 04:16:23Z elliotth $
4 * Copyright (c) 1996-2003, Darren Hiebert
6 * This source code is released for free distribution under the terms of the
7 * GNU General Public License.
9 * This module contains functions to process command line options.
15 #include "general.h" /* must always come first */
20 #include <ctype.h> /* to declare isspace () */
33 #define INVOCATION "Usage: %s [options] [file(s)]\n"
35 #define CTAGS_ENVIRONMENT "CTAGS"
36 #define ETAGS_ENVIRONMENT "ETAGS"
38 #define CTAGS_FILE "tags"
39 #define ETAGS_FILE "TAGS"
42 # define ETAGS "etags" /* name which causes default use of to -e */
45 /* The following separators are permitted for list options.
47 #define EXTENSION_SEPARATOR '.'
48 #define PATTERN_START '('
49 #define PATTERN_STOP ')'
50 #define IGNORE_SEPARATORS ", \t\n"
52 #ifndef DEFAULT_FILE_FORMAT
53 # define DEFAULT_FILE_FORMAT 2
56 #if defined (HAVE_OPENDIR) || defined (HAVE_FINDFIRST) || defined (HAVE__FINDFIRST) || defined (AMIGA)
57 # define RECURSE_SUPPORTED
60 #define isCompoundOption(c) (boolean) (strchr ("fohiILpDb", (c)) != NULL)
67 MaxHeaderExtensions
= 100, /* maximum number of extensions in -h option */
68 MaxSupportedTagFormat
= 2
71 typedef struct sOptionDescription
{
73 const char *description
;
76 typedef void (*parametricOptionHandler
) (const char *const option
, const char *const parameter
);
78 typedef const struct {
79 const char* name
; /* name of option as specified by user */
80 parametricOptionHandler handler
; /* routine to handle option */
81 boolean initOnly
; /* option must be specified before any files */
84 typedef const struct {
85 const char* name
; /* name of option as specified by user */
86 boolean
* pValue
; /* pointer to option value */
87 boolean initOnly
; /* option must be specified before any files */
94 static boolean NonOptionEncountered
;
95 static stringList
*OptionFiles
;
96 static stringList
* Excluded
;
97 static boolean FilesRequired
= TRUE
;
98 static boolean SkipConfiguration
;
100 static const char *const HeaderExtensions
[] = {
101 "h", "H", "hh", "hpp", "hxx", "h++", "inc", "def", NULL
104 optionValues Option
= {
106 FALSE
, /* --extra=f */
107 FALSE
, /* --extra=q */
108 TRUE
, /* --file-scope */
111 FALSE
, /* -fields=a */
112 TRUE
, /* -fields=f */
113 FALSE
, /* -fields=m */
114 FALSE
, /* -fields=i */
115 TRUE
, /* -fields=k */
116 FALSE
, /* -fields=z */
117 FALSE
, /* -fields=K */
118 FALSE
, /* -fields=l */
119 FALSE
, /* -fields=n */
120 TRUE
, /* -fields=s */
121 FALSE
, /* -fields=S */
122 FALSE
, /* -fields=T */
129 #ifdef MACROS_USE_PATTERNS
130 EX_PATTERN
, /* -n, --excmd */
132 EX_MIX
, /* -n, --excmd */
135 SO_SORTED
, /* -u, --sort */
141 NULL
, /* --etags-include */
142 DEFAULT_FILE_FORMAT
,/* --format */
144 FALSE
, /* --kind-long */
145 LANG_AUTO
, /* --lang */
147 FALSE
, /* --filter */
148 NULL
, /* --filter-terminator */
149 FALSE
, /* --tag-relative */
150 FALSE
, /* --totals */
151 FALSE
, /* --line-directives */
161 static optionDescription LongOptionDescription
[] = {
162 {1," -a Append the tags to an existing tag file."},
165 {1," Set break line."},
167 {0," -B Use backward searching patterns (?...?)."},
170 {1," Set debug level."},
172 {0," -e Output tag file for use with Emacs."},
174 {1," Write tags to specified file. Value of \"-\" writes tags to stdout"},
175 {1," [\"tags\"; or \"TAGS\" when -e supplied]."},
176 {0," -F Use forward searching patterns (/.../) (default)."},
178 {1," Specify list of file extensions to be treated as include files."},
179 {1," [\".h.H.hh.hpp.hxx.h++\"]."},
180 {1," -I <list|@file>"},
181 {1," A list of tokens to be specially handled is read from either the"},
182 {1," command line or the specified file."},
184 {1," A list of source file names are read from the specified file."},
185 {1," If specified as \"-\", then standard input is read."},
186 {0," -n Equivalent to --excmd=number."},
187 {0," -N Equivalent to --excmd=pattern."},
188 {1," -o Alternative for -f."},
189 #ifdef RECURSE_SUPPORTED
190 {1," -R Equivalent to --recurse."},
192 {1," -R Not supported on this platform."},
194 {0," -u Equivalent to --sort=no."},
195 {1," -V Equivalent to --verbose."},
196 {1," -x Print a tabular cross reference file to standard output."},
197 {1," --append=[yes|no]"},
198 {1," Should tags should be appended to existing tag file [no]?"},
199 {1," --etags-include=file"},
200 {1," Include reference to 'file' in Emacs-style tag file (requires -e)."},
201 {1," --exclude=pattern"},
202 {1," Exclude files and directories matching 'pattern'."},
203 {0," --excmd=number|pattern|mix"},
204 #ifdef MACROS_USE_PATTERNS
205 {0," Uses the specified type of EX command to locate tags [pattern]."},
207 {0," Uses the specified type of EX command to locate tags [mix]."},
209 {1," --extra=[+|-]flags"},
210 {1," Include extra tag entries for selected information (flags: \"fq\")."},
211 {1," --fields=[+|-]flags"},
212 {1," Include selected extension fields (flags: \"afmikKlnsStTz\") [fks]."},
213 {1," --file-scope=[yes|no]"},
214 {1," Should tags scoped only for a single file (e.g. \"static\" tags"},
215 {1," be included in the output [yes]?"},
216 {1," --filter=[yes|no]"},
217 {1," Behave as a filter, reading file names from standard input and"},
218 {1," writing tags to standard output [no]."},
219 {1," --filter-terminator=string"},
220 {1," Specify string to print to stdout following the tags for each file"},
221 {1," parsed when --filter is enabled."},
222 {0," --format=level"},
223 #if DEFAULT_FILE_FORMAT == 1
224 {0," Force output of specified tag file format [1]."},
226 {0," Force output of specified tag file format [2]."},
229 {1," Print this option summary."},
230 {1," --if0=[yes|no]"},
231 {1," Should C code within #if 0 conditional branches be parsed [no]?"},
232 {1," --<LANG>-kinds=[+|-]kinds"},
233 {1," Enable/disable tag kinds for language <LANG>."},
234 {1," --langdef=name"},
235 {1," Define a new language to be parsed with regular expressions."},
236 {1," --langmap=map(s)"},
237 {1," Override default mapping of language to source file extension."},
238 {1," --language-force=language"},
239 {1," Force all files to be interpreted using specified language."},
240 {1," --languages=[+|-]list"},
241 {1," Restrict files scanned for tags to those mapped to langauges"},
242 {1," specified in the comma-separated 'list'. The list can contain any"},
243 {1," built-in or user-defined language [all]."},
245 {1," Print details of software license."},
246 {0," --line-directives=[yes|no]"},
247 {0," Should #line directives be processed [no]?"},
248 {1," --links=[yes|no]"},
249 {1," Indicate whether symbolic links should be followed [yes]."},
250 {1," --list-kinds=[language|all]"},
251 {1," Output a list of all tag kinds for specified language or all."},
252 {1," --list-languages"},
253 {1," Output list of supported languages."},
254 {1," --list-maps=[language|all]"},
255 {1," Output list of language mappings."},
256 {1," --options=file"},
257 {1," Specify file from which command line options should be read."},
258 {1," --recurse=[yes|no]"},
259 #ifdef RECURSE_SUPPORTED
260 {1," Recurse into directories supplied on command line [no]."},
262 {1," Not supported on this platform."},
265 {1," --regex-<LANG>=/line_pattern/name_pattern/[flags]"},
266 {1," Define regular expression for locating tags in specific language."},
268 {0," --sort=[yes|no|foldcase]"},
269 {0," Should tags be sorted (optionally ignoring case) [yes]?."},
270 {0," --tag-relative=[yes|no]"},
271 {0," Should paths be relative to location of tag file [no; yes when -e]?"},
272 {1," --totals=[yes|no]"},
273 {1," Print statistics about source and tag files [no]."},
274 {1," --verbose=[yes|no]"},
275 {1," Enable verbose messages describing actions on each source file."},
277 {1," Print version identifier to standard output."},
281 static const char* const License1
=
282 "This program is free software; you can redistribute it and/or\n"
283 "modify it under the terms of the GNU General Public License\n"
284 "as published by the Free Software Foundation; either version 2\n"
285 "of the License, or (at your option) any later version.\n"
287 static const char* const License2
=
288 "This program is distributed in the hope that it will be useful,\n"
289 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
290 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
291 "GNU General Public License for more details.\n"
293 "You should have received a copy of the GNU General Public License\n"
294 "along with this program; if not, write to the Free Software\n"
295 "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n";
297 /* Contains a set of strings describing the set of "features" compiled into
300 static const char *const Features
[] = {
326 #ifndef EXTERNAL_SORT
329 #ifdef CUSTOM_CONFIGURATION_FILE
332 #if (defined (MSDOS) || defined (WIN32) || defined (OS2)) && defined (UNIX_PATH_SEPARATOR)
333 "unix-path-separator",
342 * FUNCTION PROTOTYPES
344 static boolean
parseFileOptions (const char *const fileName
);
347 * FUNCTION DEFINITIONS
350 extern void verbose (const char *const format
, ...)
355 va_start (ap
, format
);
356 vprintf (format
, ap
);
361 static char *stringCopy (const char *const string
)
365 result
= eStrdup (string
);
369 static void freeString (char **const pString
)
371 if (*pString
!= NULL
)
378 extern void freeList (stringList
** const pList
)
382 stringListDelete (*pList
);
387 extern void setDefaultTagFileName (void)
389 if (Option
.tagFileName
!= NULL
)
390 ; /* accept given name */
391 else if (Option
.etags
)
392 Option
.tagFileName
= stringCopy (ETAGS_FILE
);
394 Option
.tagFileName
= stringCopy (CTAGS_FILE
);
397 extern boolean
filesRequired (void)
399 boolean result
= FilesRequired
;
405 extern void checkOptions (void)
410 notice
= "xref output";
411 if (Option
.include
.fileNames
)
413 error (WARNING
, "%s disables file name tags", notice
);
414 Option
.include
.fileNames
= FALSE
;
419 notice
= "append mode is not compatible with";
420 if (isDestinationStdout ())
421 error (FATAL
, "%s tags to stdout", notice
);
425 notice
= "filter mode";
426 if (Option
.printTotals
)
428 error (WARNING
, "%s disables totals", notice
);
429 Option
.printTotals
= FALSE
;
431 if (Option
.tagFileName
!= NULL
)
432 error (WARNING
, "%s ignores output tag file name", notice
);
436 static void setEtagsMode (void)
439 Option
.sorted
= SO_UNSORTED
;
440 Option
.lineDirectives
= FALSE
;
441 Option
.tagRelative
= TRUE
;
444 extern void testEtagsInvocation (void)
446 char* const execName
= eStrdup (getExecutableName ());
447 char* const etags
= eStrdup (ETAGS
);
448 #ifdef CASE_INSENSITIVE_FILENAMES
449 toLowerString (execName
);
450 toLowerString (etags
);
452 if (strstr (execName
, etags
) != NULL
)
454 verbose ("Running in etags mode\n");
462 * Cooked argument parsing
465 static void parseShortOption (cookedArgs
*const args
)
467 args
->simple
[0] = *args
->shortOptions
++;
468 args
->simple
[1] = '\0';
469 args
->item
= args
->simple
;
470 if (! isCompoundOption (*args
->simple
))
471 args
->parameter
= "";
472 else if (*args
->shortOptions
== '\0')
474 argForth (args
->args
);
475 if (argOff (args
->args
))
476 args
->parameter
= NULL
;
478 args
->parameter
= argItem (args
->args
);
479 args
->shortOptions
= NULL
;
483 args
->parameter
= args
->shortOptions
;
484 args
->shortOptions
= NULL
;
488 static void parseLongOption (cookedArgs
*const args
, const char *item
)
490 const char* const equal
= strchr (item
, '=');
493 args
->item
= eStrdup (item
); /* FIXME: memory leak. */
494 args
->parameter
= "";
498 const size_t length
= equal
- item
;
499 args
->item
= xMalloc (length
+ 1, char); /* FIXME: memory leak. */
500 strncpy (args
->item
, item
, length
);
501 args
->item
[length
] = '\0';
502 args
->parameter
= equal
+ 1;
504 Assert (args
->item
!= NULL
);
505 Assert (args
->parameter
!= NULL
);
508 static void cArgRead (cookedArgs
*const current
)
512 Assert (current
!= NULL
);
513 if (! argOff (current
->args
))
515 item
= argItem (current
->args
);
516 current
->shortOptions
= NULL
;
517 Assert (item
!= NULL
);
518 if (strncmp (item
, "--", (size_t) 2) == 0)
520 current
->isOption
= TRUE
;
521 current
->longOption
= TRUE
;
522 parseLongOption (current
, item
+ 2);
523 Assert (current
->item
!= NULL
);
524 Assert (current
->parameter
!= NULL
);
526 else if (*item
== '-')
528 current
->isOption
= TRUE
;
529 current
->longOption
= FALSE
;
530 current
->shortOptions
= item
+ 1;
531 parseShortOption (current
);
535 current
->isOption
= FALSE
;
536 current
->longOption
= FALSE
;
537 current
->item
= item
;
538 current
->parameter
= NULL
;
543 extern cookedArgs
* cArgNewFromString (const char* string
)
545 cookedArgs
* const result
= xMalloc (1, cookedArgs
);
546 memset (result
, 0, sizeof (cookedArgs
));
547 result
->args
= argNewFromString (string
);
552 extern cookedArgs
* cArgNewFromArgv (char* const* const argv
)
554 cookedArgs
* const result
= xMalloc (1, cookedArgs
);
555 memset (result
, 0, sizeof (cookedArgs
));
556 result
->args
= argNewFromArgv (argv
);
561 extern cookedArgs
* cArgNewFromFile (FILE* const fp
)
563 cookedArgs
* const result
= xMalloc (1, cookedArgs
);
564 memset (result
, 0, sizeof (cookedArgs
));
565 result
->args
= argNewFromFile (fp
);
570 extern cookedArgs
* cArgNewFromLineFile (FILE* const fp
)
572 cookedArgs
* const result
= xMalloc (1, cookedArgs
);
573 memset (result
, 0, sizeof (cookedArgs
));
574 result
->args
= argNewFromLineFile (fp
);
579 extern void cArgDelete (cookedArgs
* const current
)
581 Assert (current
!= NULL
);
582 argDelete (current
->args
);
583 memset (current
, 0, sizeof (cookedArgs
));
587 static boolean
cArgOptionPending (cookedArgs
* const current
)
589 boolean result
= FALSE
;
590 if (current
->shortOptions
!= NULL
)
591 if (*current
->shortOptions
!= '\0')
596 extern boolean
cArgOff (cookedArgs
* const current
)
598 Assert (current
!= NULL
);
599 return (boolean
) (argOff (current
->args
) && ! cArgOptionPending (current
));
602 extern boolean
cArgIsOption (cookedArgs
* const current
)
604 Assert (current
!= NULL
);
605 return current
->isOption
;
608 extern const char* cArgItem (cookedArgs
* const current
)
610 Assert (current
!= NULL
);
611 return current
->item
;
614 extern void cArgForth (cookedArgs
* const current
)
616 Assert (current
!= NULL
);
617 Assert (! cArgOff (current
));
618 if (cArgOptionPending (current
))
619 parseShortOption (current
);
622 Assert (! argOff (current
->args
));
623 argForth (current
->args
);
624 if (! argOff (current
->args
))
628 current
->isOption
= FALSE
;
629 current
->longOption
= FALSE
;
630 current
->shortOptions
= NULL
;
631 current
->item
= NULL
;
632 current
->parameter
= NULL
;
638 * File extension and language mapping
641 static void addExtensionList (
642 stringList
*const slist
, const char *const elist
, const boolean clear
)
644 char *const extensionList
= eStrdup (elist
);
645 const char *extension
= NULL
;
646 boolean first
= TRUE
;
650 verbose (" clearing\n");
651 stringListClear (slist
);
653 verbose (" adding: ");
654 if (elist
!= NULL
&& *elist
!= '\0')
656 extension
= extensionList
;
657 if (elist
[0] == EXTENSION_SEPARATOR
)
660 while (extension
!= NULL
)
662 char *separator
= strchr (extension
, EXTENSION_SEPARATOR
);
663 if (separator
!= NULL
)
665 verbose ("%s%s", first
? "" : ", ",
666 *extension
== '\0' ? "(NONE)" : extension
);
667 stringListAdd (slist
, vStringNewInit (extension
));
669 if (separator
== NULL
)
672 extension
= separator
+ 1;
677 stringListPrint (slist
);
680 eFree (extensionList
);
683 static boolean
isFalse (const char *parameter
)
686 strcasecmp (parameter
, "0" ) == 0 ||
687 strcasecmp (parameter
, "n" ) == 0 ||
688 strcasecmp (parameter
, "no" ) == 0 ||
689 strcasecmp (parameter
, "off") == 0);
692 static boolean
isTrue (const char *parameter
)
695 strcasecmp (parameter
, "1" ) == 0 ||
696 strcasecmp (parameter
, "y" ) == 0 ||
697 strcasecmp (parameter
, "yes") == 0 ||
698 strcasecmp (parameter
, "on" ) == 0);
701 /* Determines whether the specified file name is considered to be a header
702 * file for the purposes of determining whether enclosed tags are global or
705 extern boolean
isIncludeFile (const char *const fileName
)
707 boolean result
= FALSE
;
708 const char *const extension
= fileExtension (fileName
);
709 if (Option
.headerExt
!= NULL
)
710 result
= stringListExtensionMatched (Option
.headerExt
, extension
);
715 * Specific option processing
718 static void processEtagsInclude (
719 const char *const option
, const char *const parameter
)
722 error (FATAL
, "Etags must be enabled to use \"%s\" option", option
);
725 vString
*const file
= vStringNewInit (parameter
);
726 if (Option
.etagsInclude
== NULL
)
727 Option
.etagsInclude
= stringListNew ();
728 stringListAdd (Option
.etagsInclude
, file
);
729 FilesRequired
= FALSE
;
733 static void processExcludeOption (
734 const char *const option __unused__
, const char *const parameter
)
736 const char *const fileName
= parameter
+ 1;
737 if (parameter
[0] == '\0')
738 freeList (&Excluded
);
739 else if (parameter
[0] == '@')
741 stringList
* const sl
= stringListNewFromFile (fileName
);
743 error (FATAL
| PERROR
, "cannot open \"%s\"", fileName
);
744 if (Excluded
== NULL
)
747 stringListCombine (Excluded
, sl
);
748 verbose (" adding exclude patterns from %s\n", fileName
);
752 vString
*const item
= vStringNewInit (parameter
);
753 if (Excluded
== NULL
)
754 Excluded
= stringListNew ();
755 stringListAdd (Excluded
, item
);
756 verbose (" adding exclude pattern: %s\n", parameter
);
760 extern boolean
isExcludedFile (const char* const name
)
762 const char* base
= baseFilename (name
);
763 boolean result
= FALSE
;
764 if (Excluded
!= NULL
)
766 result
= stringListFileMatched (Excluded
, base
);
767 if (! result
&& name
!= base
)
768 result
= stringListFileMatched (Excluded
, name
);
771 /* not a good solution, but the only one which works often */
773 result
= (boolean
) (strcmp (name
, TagFile
.name
) == 0);
778 static void processExcmdOption (
779 const char *const option
, const char *const parameter
)
783 case 'm': Option
.locate
= EX_MIX
; break;
784 case 'n': Option
.locate
= EX_LINENUM
; break;
785 case 'p': Option
.locate
= EX_PATTERN
; break;
787 error (FATAL
, "Invalid value for \"%s\" option", option
);
792 static void processExtraTagsOption (
793 const char *const option
, const char *const parameter
)
795 struct sInclude
*const inc
= &Option
.include
;
796 const char *p
= parameter
;
800 if (*p
!= '+' && *p
!= '-')
802 inc
->fileNames
= FALSE
;
803 inc
->qualifiedTags
= FALSE
;
805 inc
->fileScope
= FALSE
;
808 while ((c
= *p
++) != '\0') switch (c
)
810 case '+': mode
= TRUE
; break;
811 case '-': mode
= FALSE
; break;
813 case 'f': inc
->fileNames
= mode
; break;
814 case 'q': inc
->qualifiedTags
= mode
; break;
816 case 'F': inc
->fileScope
= mode
; break;
819 default: error(WARNING
, "Unsupported parameter '%c' for \"%s\" option",
825 static void processFieldsOption (
826 const char *const option
, const char *const parameter
)
828 struct sExtFields
*field
= &Option
.extensionFields
;
829 const char *p
= parameter
;
833 if (*p
!= '+' && *p
!= '-')
835 field
->access
= FALSE
;
836 field
->fileScope
= FALSE
;
837 field
->implementation
= FALSE
;
838 field
->inheritance
= FALSE
;
840 field
->kindKey
= FALSE
;
841 field
->kindLong
= FALSE
;
842 field
->language
= FALSE
;
843 field
->scope
= FALSE
;
844 field
->typeRef
= FALSE
;
846 while ((c
= *p
++) != '\0') switch (c
)
848 case '+': mode
= TRUE
; break;
849 case '-': mode
= FALSE
; break;
851 case 'a': field
->access
= mode
; break;
852 case 'f': field
->fileScope
= mode
; break;
853 case 'm': field
->implementation
= mode
; break;
854 case 'i': field
->inheritance
= mode
; break;
855 case 'k': field
->kind
= mode
; break;
856 case 'K': field
->kindLong
= mode
; break;
857 case 'l': field
->language
= mode
; break;
858 case 'n': field
->lineNumber
= mode
; break;
859 case 's': field
->scope
= mode
; break;
860 case 'S': field
->signature
= mode
; break;
861 case 'z': field
->kindKey
= mode
; break;
862 case 't': field
->typeRef
= mode
; break;
863 case 'T': field
->returnType
= mode
; break;
865 default: error(WARNING
, "Unsupported parameter '%c' for \"%s\" option",
871 static void processFilterTerminatorOption (
872 const char *const option __unused__
, const char *const parameter
)
874 freeString (&Option
.filterTerminator
);
875 Option
.filterTerminator
= stringCopy (parameter
);
878 static void processFormatOption (
879 const char *const option
, const char *const parameter
)
883 if (sscanf (parameter
, "%u", &format
) < 1)
884 error (FATAL
, "Invalid value for \"%s\" option",option
);
885 else if (format
<= (unsigned int) MaxSupportedTagFormat
)
886 Option
.tagFileFormat
= format
;
888 error (FATAL
, "Unsupported value for \"%s\" option", option
);
891 static void printInvocationDescription (void)
893 printf (INVOCATION
, getExecutableName ());
896 static void printOptionDescriptions (const optionDescription
*const optDesc
)
899 for (i
= 0 ; optDesc
[i
].description
!= NULL
; ++i
)
901 if (! Option
.etags
|| optDesc
[i
].usedByEtags
)
902 puts (optDesc
[i
].description
);
906 static void printFeatureList (void)
910 for (i
= 0 ; Features
[i
] != NULL
; ++i
)
913 printf (" Optional compiled features: ");
914 printf ("%s+%s", (i
>0 ? ", " : ""), Features
[i
]);
915 #ifdef CUSTOM_CONFIGURATION_FILE
916 if (strcmp (Features
[i
], "custom-conf") == 0)
917 printf ("=%s", CUSTOM_CONFIGURATION_FILE
);
924 static void printProgramIdentification (void)
926 printf ("%s %s, %s %s\n",
927 PROGRAM_NAME
, PROGRAM_VERSION
,
928 PROGRAM_COPYRIGHT
, AUTHOR_NAME
);
929 printf (" Compiled: %s, %s\n", __DATE__
, __TIME__
);
930 printf (" Addresses: <%s>, %s\n", AUTHOR_EMAIL
, PROGRAM_URL
);
934 static void processHelpOption (
935 const char *const option __unused__
,
936 const char *const parameter __unused__
)
938 printProgramIdentification ();
940 printInvocationDescription ();
942 printOptionDescriptions (LongOptionDescription
);
946 static void processLanguageForceOption (
947 const char *const option
, const char *const parameter
)
950 if (strcasecmp (parameter
, "auto") == 0)
951 language
= LANG_AUTO
;
953 language
= getNamedLanguage (parameter
);
955 if (strcmp (option
, "lang") == 0 || strcmp (option
, "language") == 0)
957 "\"--%s\" option is obsolete; use \"--language-force\" instead",
959 if (language
== LANG_IGNORE
)
960 error (FATAL
, "Unknown language \"%s\" in \"%s\" option", parameter
, option
);
962 Option
.language
= language
;
964 static char* skipPastMap (char* p
)
966 while (*p
!= EXTENSION_SEPARATOR
&&
967 *p
!= PATTERN_START
&& *p
!= ',' && *p
!= '\0')
972 /* Parses the mapping beginning at `map', adds it to the language map, and
973 * returns first character past the map.
975 static char* addLanguageMap (const langType language
, char* map
)
978 const char first
= *map
;
979 if (first
== EXTENSION_SEPARATOR
) /* extension map */
982 p
= skipPastMap (map
);
985 verbose (" .%s", map
);
986 addLanguageExtensionMap (language
, map
);
987 p
= map
+ strlen (map
);
991 const char separator
= *p
;
993 verbose (" .%s", map
);
994 addLanguageExtensionMap (language
, map
);
998 else if (first
== PATTERN_START
) /* pattern map */
1001 for (p
= map
; *p
!= PATTERN_STOP
&& *p
!= '\0' ; ++p
)
1003 if (*p
== '\\' && *(p
+ 1) == PATTERN_STOP
)
1007 error (FATAL
, "Unterminated file name pattern for %s language",
1008 getLanguageName (language
));
1012 verbose (" (%s)", map
);
1013 addLanguagePatternMap (language
, map
);
1017 error (FATAL
, "Badly formed language map for %s language",
1018 getLanguageName (language
));
1022 static char* processLanguageMap (char* map
)
1024 char* const separator
= strchr (map
, ':');
1025 char* result
= NULL
;
1026 if (separator
!= NULL
)
1029 char *list
= separator
+ 1;
1030 boolean clear
= FALSE
;
1032 language
= getNamedLanguage (map
);
1033 if (language
!= LANG_IGNORE
)
1035 const char *const deflt
= "default";
1041 for (p
= list
; *p
!= ',' && *p
!= '\0' ; ++p
) /*no-op*/ ;
1042 if ((size_t) (p
- list
) == strlen (deflt
) &&
1043 strncasecmp (list
, deflt
, p
- list
) == 0)
1045 verbose (" Restoring default %s language map: ", getLanguageName (language
));
1046 installLanguageMapDefault (language
);
1053 verbose (" Setting %s language map:", getLanguageName (language
));
1054 clearLanguageMap (language
);
1057 verbose (" Adding to %s language map:", getLanguageName (language
));
1058 while (list
!= NULL
&& *list
!= '\0' && *list
!= ',')
1059 list
= addLanguageMap (language
, list
);
1062 if (list
!= NULL
&& *list
== ',')
1070 static void processLanguageMapOption (
1071 const char *const option
, const char *const parameter
)
1073 char *const maps
= eStrdup (parameter
);
1076 if (strcmp (parameter
, "default") == 0)
1078 verbose (" Restoring default language maps:\n");
1079 installLanguageMapDefaults ();
1081 else while (map
!= NULL
&& *map
!= '\0')
1083 char* const next
= processLanguageMap (map
);
1085 error (WARNING
, "Unknown language \"%s\" in \"%s\" option", parameter
, option
);
1091 static void processLanguagesOption (
1092 const char *const option
, const char *const parameter
)
1094 char *const langs
= eStrdup (parameter
);
1095 enum { Add
, Remove
, Replace
} mode
= Replace
;
1096 boolean first
= TRUE
;
1098 const char* prefix
= "";
1099 verbose (" Enabled languages: ");
1100 while (lang
!= NULL
)
1102 char *const end
= strchr (lang
, ',');
1103 if (lang
[0] == '+')
1109 else if (lang
[0] == '-')
1115 if (mode
== Replace
)
1116 enableLanguages (FALSE
);
1119 if (lang
[0] != '\0')
1121 if (strcmp (lang
, "all") == 0)
1122 enableLanguages ((boolean
) (mode
!= Remove
));
1125 const langType language
= getNamedLanguage (lang
);
1126 if (language
== LANG_IGNORE
)
1127 error (WARNING
, "Unknown language \"%s\" in \"%s\" option", lang
, option
);
1129 enableLanguage (language
, (boolean
) (mode
!= Remove
));
1131 verbose ("%s%s%s", (first
? "" : ", "), prefix
, lang
);
1134 if (mode
== Replace
)
1137 lang
= (end
!= NULL
? end
+ 1 : NULL
);
1143 static void processLicenseOption (
1144 const char *const option __unused__
,
1145 const char *const parameter __unused__
)
1147 printProgramIdentification ();
1154 static void processListKindsOption (
1155 const char *const option
, const char *const parameter
)
1157 if (parameter
[0] == '\0' || strcasecmp (parameter
, "all") == 0)
1158 printLanguageKinds (LANG_AUTO
);
1161 langType language
= getNamedLanguage (parameter
);
1162 if (language
== LANG_IGNORE
)
1163 error (FATAL
, "Unknown language \"%s\" in \"%s\" option", parameter
, option
);
1165 printLanguageKinds (language
);
1170 static void processListMapsOption (
1171 const char *const __unused__ option
,
1172 const char *const __unused__ parameter
)
1174 if (parameter
[0] == '\0' || strcasecmp (parameter
, "all") == 0)
1175 printLanguageMaps (LANG_AUTO
);
1178 langType language
= getNamedLanguage (parameter
);
1179 if (language
== LANG_IGNORE
)
1180 error (FATAL
, "Unknown language \"%s\" in \"%s\" option", parameter
, option
);
1182 printLanguageMaps (language
);
1187 static void processListLanguagesOption (
1188 const char *const option __unused__
,
1189 const char *const parameter __unused__
)
1191 printLanguageList ();
1195 static void processOptionFile (
1196 const char *const option
, const char *const parameter
)
1198 if (parameter
[0] == '\0')
1199 error (WARNING
, "no option file supplied for \"%s\"", option
);
1200 else if (! parseFileOptions (parameter
))
1201 error (FATAL
| PERROR
, "cannot open option file \"%s\"", parameter
);
1204 static void processSortOption (
1205 const char *const option
, const char *const parameter
)
1207 if (isFalse (parameter
))
1208 Option
.sorted
= SO_UNSORTED
;
1209 else if (isTrue (parameter
))
1210 Option
.sorted
= SO_SORTED
;
1211 else if (strcasecmp (parameter
, "f") == 0 ||
1212 strcasecmp (parameter
, "fold") == 0 ||
1213 strcasecmp (parameter
, "foldcase") == 0)
1214 Option
.sorted
= SO_FOLDSORTED
;
1216 error (FATAL
, "Invalid value for \"%s\" option", option
);
1219 static void installHeaderListDefaults (void)
1221 Option
.headerExt
= stringListNewFromArgv (HeaderExtensions
);
1224 printf (" Setting default header extensions: ");
1225 stringListPrint (Option
.headerExt
);
1230 static void processHeaderListOption (const int option
, const char *parameter
)
1232 /* Check to make sure that the user did not enter "ctags -h *.c"
1233 * by testing to see if the list is a filename that exists.
1235 if (doesFileExist (parameter
))
1236 error (FATAL
, "-%c: Invalid list", option
);
1237 if (strcmp (parameter
, "default") == 0)
1238 installHeaderListDefaults ();
1241 boolean clear
= TRUE
;
1243 if (parameter
[0] == '+')
1248 if (Option
.headerExt
== NULL
)
1249 Option
.headerExt
= stringListNew ();
1250 verbose (" Header Extensions:\n");
1251 addExtensionList (Option
.headerExt
, parameter
, clear
);
1256 * Token ignore processing
1259 /* Determines whether or not "name" should be ignored, per the ignore list.
1261 extern boolean
isIgnoreToken (
1262 const char *const name
, boolean
*const pIgnoreParens
,
1263 const char **const replacement
)
1265 boolean result
= FALSE
;
1267 if (Option
.ignore
!= NULL
)
1269 const size_t nameLen
= strlen (name
);
1272 if (pIgnoreParens
!= NULL
)
1273 *pIgnoreParens
= FALSE
;
1275 for (i
= 0 ; i
< stringListCount (Option
.ignore
) ; ++i
)
1277 vString
*token
= stringListItem (Option
.ignore
, i
);
1279 if (strncmp (vStringValue (token
), name
, nameLen
) == 0)
1281 const size_t tokenLen
= vStringLength (token
);
1283 if (nameLen
== tokenLen
)
1288 else if (tokenLen
== nameLen
+ 1 &&
1289 vStringChar (token
, tokenLen
- 1) == '+')
1292 if (pIgnoreParens
!= NULL
)
1293 *pIgnoreParens
= TRUE
;
1296 else if (vStringChar (token
, nameLen
) == '=')
1298 if (replacement
!= NULL
)
1299 *replacement
= vStringValue (token
) + nameLen
+ 1;
1308 static void saveIgnoreToken (vString
*const ignoreToken
)
1310 if (Option
.ignore
== NULL
)
1311 Option
.ignore
= stringListNew ();
1312 stringListAdd (Option
.ignore
, ignoreToken
);
1313 verbose (" ignore token: %s\n", vStringValue (ignoreToken
));
1316 static void readIgnoreList (const char *const list
)
1318 char* newList
= stringCopy (list
);
1319 const char *token
= strtok (newList
, IGNORE_SEPARATORS
);
1321 while (token
!= NULL
)
1323 vString
*const entry
= vStringNewInit (token
);
1325 saveIgnoreToken (entry
);
1326 token
= strtok (NULL
, IGNORE_SEPARATORS
);
1331 static void addIgnoreListFromFile (const char *const fileName
)
1333 stringList
* tokens
= stringListNewFromFile (fileName
);
1335 error (FATAL
| PERROR
, "cannot open \"%s\"", fileName
);
1336 if (Option
.ignore
== NULL
)
1337 Option
.ignore
= tokens
;
1339 stringListCombine (Option
.ignore
, tokens
);
1342 static void processIgnoreOption (const char *const list
)
1344 if (strchr ("@./\\", list
[0]) != NULL
)
1346 const char* fileName
= (*list
== '@') ? list
+ 1 : list
;
1347 addIgnoreListFromFile (fileName
);
1349 #if defined (MSDOS) || defined (WIN32) || defined (OS2)
1350 else if (isalpha (list
[0]) && list
[1] == ':')
1351 addIgnoreListFromFile (list
);
1353 else if (strcmp (list
, "-") == 0)
1355 freeList (&Option
.ignore
);
1356 verbose (" clearing list\n");
1359 readIgnoreList (list
);
1362 static void processVersionOption (
1363 const char *const option __unused__
,
1364 const char *const parameter __unused__
)
1366 printProgramIdentification ();
1374 static parametricOption ParametricOptions
[] = {
1375 { "etags-include", processEtagsInclude
, FALSE
},
1376 { "exclude", processExcludeOption
, FALSE
},
1377 { "excmd", processExcmdOption
, FALSE
},
1378 { "extra", processExtraTagsOption
, FALSE
},
1379 { "fields", processFieldsOption
, FALSE
},
1380 { "filter-terminator", processFilterTerminatorOption
, TRUE
},
1381 { "format", processFormatOption
, TRUE
},
1382 { "help", processHelpOption
, TRUE
},
1383 { "lang", processLanguageForceOption
, FALSE
},
1384 { "language", processLanguageForceOption
, FALSE
},
1385 { "language-force", processLanguageForceOption
, FALSE
},
1386 { "languages", processLanguagesOption
, FALSE
},
1387 { "langdef", processLanguageDefineOption
, FALSE
},
1388 { "langmap", processLanguageMapOption
, FALSE
},
1389 { "license", processLicenseOption
, TRUE
},
1390 { "list-kinds", processListKindsOption
, TRUE
},
1391 { "list-maps", processListMapsOption
, TRUE
},
1392 { "list-languages", processListLanguagesOption
, TRUE
},
1393 { "options", processOptionFile
, FALSE
},
1394 { "sort", processSortOption
, TRUE
},
1395 { "version", processVersionOption
, TRUE
},
1398 static booleanOption BooleanOptions
[] = {
1399 { "append", &Option
.append
, TRUE
},
1400 { "file-scope", &Option
.include
.fileScope
, FALSE
},
1401 { "file-tags", &Option
.include
.fileNames
, FALSE
},
1402 { "filter", &Option
.filter
, TRUE
},
1403 { "if0", &Option
.if0
, FALSE
},
1404 { "kind-long", &Option
.kindLong
, TRUE
},
1405 { "line-directives",&Option
.lineDirectives
, FALSE
},
1406 { "links", &Option
.followLinks
, FALSE
},
1407 #ifdef RECURSE_SUPPORTED
1408 { "recurse", &Option
.recurse
, FALSE
},
1410 { "tag-relative", &Option
.tagRelative
, TRUE
},
1411 { "totals", &Option
.printTotals
, TRUE
},
1412 { "verbose", &Option
.verbose
, FALSE
},
1416 * Generic option parsing
1419 static void checkOptionOrder (const char* const option
)
1421 if (NonOptionEncountered
)
1422 error (FATAL
, "-%s option may not follow a file name", option
);
1425 static boolean
processParametricOption (
1426 const char *const option
, const char *const parameter
)
1428 const int count
= sizeof (ParametricOptions
) / sizeof (parametricOption
);
1429 boolean found
= FALSE
;
1432 for (i
= 0 ; i
< count
&& ! found
; ++i
)
1434 parametricOption
* const entry
= &ParametricOptions
[i
];
1435 if (strcmp (option
, entry
->name
) == 0)
1438 if (entry
->initOnly
)
1439 checkOptionOrder (option
);
1440 (entry
->handler
) (option
, parameter
);
1446 static boolean
getBooleanOption (
1447 const char *const option
, const char *const parameter
)
1449 boolean selection
= TRUE
;
1451 if (parameter
[0] == '\0')
1453 else if (isFalse (parameter
))
1455 else if (isTrue (parameter
))
1458 error (FATAL
, "Invalid value for \"%s\" option", option
);
1463 static boolean
processBooleanOption (
1464 const char *const option
, const char *const parameter
)
1466 const int count
= sizeof (BooleanOptions
) / sizeof (booleanOption
);
1467 boolean found
= FALSE
;
1470 for (i
= 0 ; i
< count
&& ! found
; ++i
)
1472 booleanOption
* const entry
= &BooleanOptions
[i
];
1473 if (strcmp (option
, entry
->name
) == 0)
1476 if (entry
->initOnly
)
1477 checkOptionOrder (option
);
1478 *entry
->pValue
= getBooleanOption (option
, parameter
);
1484 static void processLongOption (
1485 const char *const option
, const char *const parameter
)
1487 Assert (parameter
!= NULL
);
1488 if (parameter
== NULL
&& parameter
[0] == '\0')
1489 verbose (" Option: --%s\n", option
);
1491 verbose (" Option: --%s=%s\n", option
, parameter
);
1493 if (processBooleanOption (option
, parameter
))
1495 else if (processParametricOption (option
, parameter
))
1497 else if (processKindOption (option
, parameter
))
1499 else if (processRegexOption (option
, parameter
))
1501 #ifndef RECURSE_SUPPORTED
1502 else if (strcmp (option
, "recurse") == 0)
1503 error (WARNING
, "%s option not supported on this host", option
);
1506 error (FATAL
, "Unknown option: --%s", option
);
1509 static void processShortOption (
1510 const char *const option
, const char *const parameter
)
1512 if (parameter
== NULL
|| parameter
[0] == '\0')
1513 verbose (" Option: -%s\n", option
);
1515 verbose (" Option: -%s %s\n", option
, parameter
);
1517 if (isCompoundOption (*option
) && (parameter
== NULL
|| parameter
[0] == '\0'))
1518 error (FATAL
, "Missing parameter for \"%s\" option", option
);
1519 else switch (*option
)
1522 processHelpOption ("?", NULL
);
1526 checkOptionOrder (option
);
1527 Option
.append
= TRUE
;
1531 if (atol (parameter
) < 0)
1532 error (FATAL
, "-%s: Invalid line number", option
);
1533 Option
.breakLine
= atol (parameter
);
1536 Option
.debugLevel
= strtol (parameter
, NULL
, 0);
1537 if (debug (DEBUG_STATUS
))
1538 Option
.verbose
= TRUE
;
1542 Option
.backward
= TRUE
;
1545 checkOptionOrder (option
);
1550 checkOptionOrder (option
);
1551 if (Option
.tagFileName
!= NULL
)
1554 "-%s option specified more than once, last value used",
1556 freeString (&Option
.tagFileName
);
1558 else if (parameter
[0] == '-' && parameter
[1] != '\0')
1559 error (FATAL
, "output file name may not begin with a '-'");
1560 Option
.tagFileName
= stringCopy (parameter
);
1563 Option
.backward
= FALSE
;
1566 processHeaderListOption (*option
, parameter
);
1569 processIgnoreOption (parameter
);
1572 if (Option
.fileList
!= NULL
)
1575 "-%s option specified more than once, last value used",
1577 freeString (&Option
.fileList
);
1579 Option
.fileList
= stringCopy (parameter
);
1582 Option
.locate
= EX_LINENUM
;
1585 Option
.locate
= EX_PATTERN
;
1588 #ifdef RECURSE_SUPPORTED
1589 Option
.recurse
= TRUE
;
1591 error (WARNING
, "-%s option not supported on this host", option
);
1595 checkOptionOrder (option
);
1596 Option
.sorted
= SO_UNSORTED
;
1599 Option
.verbose
= TRUE
;
1602 /* silently ignored */
1605 checkOptionOrder (option
);
1609 error (FATAL
, "Unknown option: -%s", option
);
1614 extern void parseOption (cookedArgs
* const args
)
1616 Assert (! cArgOff (args
));
1619 if (args
->longOption
)
1620 processLongOption (args
->item
, args
->parameter
);
1623 const char *parameter
= args
->parameter
;
1624 while (*parameter
== ' ')
1626 processShortOption (args
->item
, parameter
);
1632 extern void parseOptions (cookedArgs
* const args
)
1634 NonOptionEncountered
= FALSE
;
1635 while (! cArgOff (args
) && cArgIsOption (args
))
1637 if (! cArgOff (args
) && ! cArgIsOption (args
))
1638 NonOptionEncountered
= TRUE
;
1641 static const char *CheckFile
;
1642 static boolean
checkSameFile (const char *const fileName
)
1644 return isSameFile (CheckFile
, fileName
);
1647 static boolean
parseFileOptions (const char* const fileName
)
1649 boolean fileFound
= FALSE
;
1650 const char* const format
= "Considering option file %s: %s\n";
1651 CheckFile
= fileName
;
1652 if (stringListHasTest (OptionFiles
, checkSameFile
))
1653 verbose (format
, fileName
, "already considered");
1656 FILE* const fp
= fopen (fileName
, "r");
1658 verbose (format
, fileName
, "not found");
1661 cookedArgs
* const args
= cArgNewFromLineFile (fp
);
1662 vString
* file
= vStringNewInit (fileName
);
1663 stringListAdd (OptionFiles
, file
);
1664 verbose (format
, fileName
, "reading...");
1665 parseOptions (args
);
1666 if (NonOptionEncountered
)
1667 error (WARNING
, "Ignoring non-option in %s\n", fileName
);
1676 /* Actions to be taken before reading any other options */
1677 extern void previewFirstOption (cookedArgs
* const args
)
1679 while (cArgIsOption (args
))
1681 if (strcmp (args
->item
, "V") == 0 || strcmp (args
->item
, "verbose") == 0)
1683 else if (strcmp (args
->item
, "options") == 0 &&
1684 strcmp (args
->parameter
, "NONE") == 0)
1686 fprintf (stderr
, "No options will be read from files or environment\n");
1687 SkipConfiguration
= TRUE
;
1695 static void parseConfigurationFileOptionsInDirectoryWithLeafname (const char* directory
, const char* leafname
)
1697 vString
* const pathname
= combinePathAndFile (directory
, leafname
);
1698 parseFileOptions (vStringValue (pathname
));
1699 vStringDelete (pathname
);
1702 static void parseConfigurationFileOptionsInDirectory (const char* directory
)
1704 parseConfigurationFileOptionsInDirectoryWithLeafname (directory
, ".ctags");
1705 #ifdef MSDOS_STYLE_PATH
1706 parseConfigurationFileOptionsInDirectoryWithLeafname (directory
, "ctags.cnf");
1710 static void parseConfigurationFileOptions (void)
1712 /* We parse .ctags on all systems, and additionally ctags.cnf on DOS. */
1713 const char* const home
= getenv ("HOME");
1714 #ifdef CUSTOM_CONFIGURATION_FILE
1715 parseFileOptions (CUSTOM_CONFIGURATION_FILE
);
1717 #ifdef MSDOS_STYLE_PATH
1718 parseFileOptions ("/ctags.cnf");
1720 parseFileOptions ("/etc/ctags.conf");
1721 parseFileOptions ("/usr/local/etc/ctags.conf");
1724 parseConfigurationFileOptionsInDirectory (home
);
1728 #ifdef MSDOS_STYLE_PATH
1730 * Windows users don't usually set HOME.
1731 * The OS sets HOMEDRIVE and HOMEPATH for them.
1733 const char* homeDrive
= getenv ("HOMEDRIVE");
1734 const char* homePath
= getenv ("HOMEPATH");
1735 if (homeDrive
!= NULL
&& homePath
!= NULL
)
1737 vString
* const windowsHome
= vStringNew ();
1738 vStringCatS (windowsHome
, homeDrive
);
1739 vStringCatS (windowsHome
, homePath
);
1740 parseConfigurationFileOptionsInDirectory (vStringValue (windowsHome
));
1741 vStringDelete (windowsHome
);
1745 parseConfigurationFileOptionsInDirectory (".");
1748 static void parseEnvironmentOptions (void)
1750 const char *envOptions
= NULL
;
1751 const char* var
= NULL
;
1755 var
= ETAGS_ENVIRONMENT
;
1756 envOptions
= getenv (var
);
1758 if (envOptions
== NULL
)
1760 var
= CTAGS_ENVIRONMENT
;
1761 envOptions
= getenv (var
);
1763 if (envOptions
!= NULL
&& envOptions
[0] != '\0')
1765 cookedArgs
* const args
= cArgNewFromString (envOptions
);
1766 verbose ("Reading options from $CTAGS\n");
1767 parseOptions (args
);
1769 if (NonOptionEncountered
)
1770 error (WARNING
, "Ignoring non-option in %s variable", var
);
1774 extern void readOptionConfiguration (void)
1776 if (! SkipConfiguration
)
1778 parseConfigurationFileOptions ();
1779 parseEnvironmentOptions ();
1784 * Option initialization
1787 extern void initOptions (void)
1789 OptionFiles
= stringListNew ();
1790 verbose ("Setting option defaults\n");
1791 installHeaderListDefaults ();
1792 verbose (" Installing default language mappings:\n");
1793 installLanguageMapDefaults ();
1795 /* always excluded by default */
1796 verbose (" Installing default exclude patterns:\n");
1797 processExcludeOption (NULL
, "{arch}");
1798 processExcludeOption (NULL
, ".arch-ids");
1799 processExcludeOption (NULL
, ".arch-inventory");
1800 processExcludeOption (NULL
, "autom4te.cache");
1801 processExcludeOption (NULL
, "BitKeeper");
1802 processExcludeOption (NULL
, ".bzr");
1803 processExcludeOption (NULL
, ".bzrignore");
1804 processExcludeOption (NULL
, "CVS");
1805 processExcludeOption (NULL
, ".cvsignore");
1806 processExcludeOption (NULL
, "_darcs");
1807 processExcludeOption (NULL
, ".deps");
1808 processExcludeOption (NULL
, "EIFGEN");
1809 processExcludeOption (NULL
, ".git");
1810 processExcludeOption (NULL
, ".hg");
1811 processExcludeOption (NULL
, "PENDING");
1812 processExcludeOption (NULL
, "RCS");
1813 processExcludeOption (NULL
, "RESYNC");
1814 processExcludeOption (NULL
, "SCCS");
1815 processExcludeOption (NULL
, ".svn");
1818 extern void freeOptionResources (void)
1820 freeString (&Option
.tagFileName
);
1821 freeString (&Option
.fileList
);
1822 freeString (&Option
.filterTerminator
);
1824 freeList (&Excluded
);
1825 freeList (&Option
.ignore
);
1826 freeList (&Option
.headerExt
);
1827 freeList (&Option
.etagsInclude
);
1828 freeList (&OptionFiles
);
1831 /* vi:set tabstop=4 shiftwidth=4: */