(WRITTEN_BY): Rename from AUTHORS.
[platform/upstream/coreutils.git] / src / dircolors.c
1 /* dircolors - output commands to set the LS_COLOR environment variable
2    Copyright (C) 1996-2003 Free Software Foundation, Inc.
3    Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000 H. Peter Anvin
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19 #if HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #include <sys/types.h>
24 #include <getopt.h>
25 #include <stdio.h>
26
27 #include "system.h"
28 #include "dircolors.h"
29 #include "dirname.h"
30 #include "error.h"
31 #include "getline.h"
32 #include "obstack.h"
33 #include "quote.h"
34 #include "xstrndup.h"
35
36 /* The official name of this program (e.g., no `g' prefix).  */
37 #define PROGRAM_NAME "dircolors"
38
39 #define WRITTEN_BY _("Written by H. Peter Anvin.")
40
41 #define obstack_chunk_alloc malloc
42 #define obstack_chunk_free free
43
44 enum Shell_syntax
45 {
46   SHELL_SYNTAX_BOURNE,
47   SHELL_SYNTAX_C,
48   SHELL_SYNTAX_UNKNOWN
49 };
50
51 #define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
52 #define APPEND_TWO_CHAR_STRING(S)                                       \
53   do                                                                    \
54     {                                                                   \
55       APPEND_CHAR (S[0]);                                               \
56       APPEND_CHAR (S[1]);                                               \
57     }                                                                   \
58   while (0)
59
60 /* Accumulate in this obstack the value for the LS_COLORS environment
61    variable.  */
62 static struct obstack lsc_obstack;
63
64 /* Nonzero if the input file was the standard input. */
65 static int have_read_stdin;
66
67 /* FIXME: associate with ls_codes? */
68 static const char *const slack_codes[] =
69 {
70   "NORMAL", "NORM", "FILE", "DIR", "LNK", "LINK",
71   "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK",
72   "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE",
73   "END", "ENDCODE", NULL
74 };
75
76 static const char *const ls_codes[] =
77 {
78   "no", "no", "fi", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
79   "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec"
80 };
81
82 static struct option const long_options[] =
83   {
84     {"bourne-shell", no_argument, NULL, 'b'},
85     {"sh", no_argument, NULL, 'b'},
86     {"csh", no_argument, NULL, 'c'},
87     {"c-shell", no_argument, NULL, 'c'},
88     {"print-database", no_argument, NULL, 'p'},
89     {GETOPT_HELP_OPTION_DECL},
90     {GETOPT_VERSION_OPTION_DECL},
91     {NULL, 0, NULL, 0}
92   };
93
94 char *program_name;
95
96 void
97 usage (int status)
98 {
99   if (status != 0)
100     fprintf (stderr, _("Try `%s --help' for more information.\n"),
101              program_name);
102   else
103     {
104       printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
105       fputs (_("\
106 Output commands to set the LS_COLORS environment variable.\n\
107 \n\
108 Determine format of output:\n\
109   -b, --sh, --bourne-shell    output Bourne shell code to set LS_COLORS\n\
110   -c, --csh, --c-shell        output C shell code to set LS_COLORS\n\
111   -p, --print-database        output defaults\n\
112 "), stdout);
113       fputs (HELP_OPTION_DESCRIPTION, stdout);
114       fputs (VERSION_OPTION_DESCRIPTION, stdout);
115       fputs (_("\
116 \n\
117 If FILE is specified, read it to determine which colors to use for which\n\
118 file types and extensions.  Otherwise, a precompiled database is used.\n\
119 For details on the format of these files, run `dircolors --print-database'.\n\
120 "), stdout);
121       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
122     }
123
124   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
125 }
126
127 /* If the SHELL environment variable is set to `csh' or `tcsh,'
128    assume C shell.  Else Bourne shell.  */
129
130 static enum Shell_syntax
131 guess_shell_syntax (void)
132 {
133   char *shell;
134
135   shell = getenv ("SHELL");
136   if (shell == NULL || *shell == '\0')
137     return SHELL_SYNTAX_UNKNOWN;
138
139   shell = base_name (shell);
140
141   if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
142     return SHELL_SYNTAX_C;
143
144   return SHELL_SYNTAX_BOURNE;
145 }
146
147 static void
148 parse_line (unsigned char const *line, char **keyword, char **arg)
149 {
150   unsigned char const *p;
151   unsigned char const *keyword_start;
152   unsigned char const *arg_start;
153
154   *keyword = NULL;
155   *arg = NULL;
156
157   for (p = line; ISSPACE (*p); ++p)
158     ;
159
160   /* Ignore blank lines and shell-style comments.  */
161   if (*p == '\0' || *p == '#')
162     return;
163
164   keyword_start = p;
165
166   while (!ISSPACE (*p) && *p != '\0')
167     {
168       ++p;
169     }
170
171   *keyword = xstrndup ((const char *) keyword_start, p - keyword_start);
172   if (*p  == '\0')
173     return;
174
175   do
176     {
177       ++p;
178     }
179   while (ISSPACE (*p));
180
181   if (*p == '\0' || *p == '#')
182     return;
183
184   arg_start = p;
185
186   while (*p != '\0' && *p != '#')
187     ++p;
188
189   for (--p; ISSPACE (*p); --p)
190     {
191       /* empty */
192     }
193   ++p;
194
195   *arg = xstrndup ((const char *) arg_start, p - arg_start);
196 }
197
198 /* FIXME: Write a string to standard out, while watching for "dangerous"
199    sequences like unescaped : and = characters.  */
200
201 static void
202 append_quoted (const char *str)
203 {
204   int need_backslash = 1;
205
206   while (*str != '\0')
207     {
208       switch (*str)
209         {
210         case '\\':
211         case '^':
212           need_backslash = !need_backslash;
213           break;
214
215         case ':':
216         case '=':
217           if (need_backslash)
218             APPEND_CHAR ('\\');
219           /* Fall through */
220
221         default:
222           need_backslash = 1;
223           break;
224         }
225
226       APPEND_CHAR (*str);
227       ++str;
228     }
229 }
230
231 /* Read the file open on FP (with name FILENAME).  First, look for a
232    `TERM name' directive where name matches the current terminal type.
233    Once found, translate and accumulate the associated directives onto
234    the global obstack LSC_OBSTACK.  Give a diagnostic and return nonzero
235    upon failure (unrecognized keyword is the only way to fail here).
236    Return zero otherwise.  */
237
238 static int
239 dc_parse_stream (FILE *fp, const char *filename)
240 {
241   size_t line_number = 0;
242   char *line = NULL;
243   size_t line_chars_allocated = 0;
244   int state;
245   char *term;
246   int err = 0;
247
248   /* State for the parser.  */
249   enum states { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL };
250
251   state = ST_GLOBAL;
252
253   /* Get terminal type */
254   term = getenv ("TERM");
255   if (term == NULL || *term == '\0')
256     term = "none";
257
258   while (1)
259     {
260       int line_length;
261       char *keywd, *arg;
262       int unrecognized;
263
264       ++line_number;
265
266       if (fp)
267         {
268           line_length = getline (&line, &line_chars_allocated, fp);
269           if (line_length <= 0)
270             {
271               if (line)
272                 free (line);
273               break;
274             }
275         }
276       else
277         {
278           line = (char *) (G_line[line_number - 1]);
279           line_length = G_line_length[line_number - 1];
280           if (line_number > G_N_LINES)
281             break;
282         }
283
284       parse_line ((unsigned char *) line, &keywd, &arg);
285
286       if (keywd == NULL)
287         continue;
288
289       if (arg == NULL)
290         {
291           error (0, 0, _("%s:%lu: invalid line;  missing second token"),
292                  filename, (long unsigned) line_number);
293           err = 1;
294           free (keywd);
295           continue;
296         }
297
298       unrecognized = 0;
299       if (strcasecmp (keywd, "TERM") == 0)
300         {
301           if (STREQ (arg, term))
302             state = ST_TERMSURE;
303           else if (state != ST_TERMSURE)
304             state = ST_TERMNO;
305         }
306       else
307         {
308           if (state == ST_TERMSURE)
309             state = ST_TERMYES; /* Another TERM can cancel */
310
311           if (state != ST_TERMNO)
312             {
313               if (keywd[0] == '.')
314                 {
315                   APPEND_CHAR ('*');
316                   append_quoted (keywd);
317                   APPEND_CHAR ('=');
318                   append_quoted (arg);
319                   APPEND_CHAR (':');
320                 }
321               else if (keywd[0] == '*')
322                 {
323                   append_quoted (keywd);
324                   APPEND_CHAR ('=');
325                   append_quoted (arg);
326                   APPEND_CHAR (':');
327                 }
328               else if (strcasecmp (keywd, "OPTIONS") == 0
329                        || strcasecmp (keywd, "COLOR") == 0
330                        || strcasecmp (keywd, "EIGHTBIT") == 0)
331                 {
332                   /* Ignore.  */
333                 }
334               else
335                 {
336                   int i;
337
338                   for (i = 0; slack_codes[i] != NULL; ++i)
339                     if (strcasecmp (keywd, slack_codes[i]) == 0)
340                       break;
341
342                   if (slack_codes[i] != NULL)
343                     {
344                       APPEND_TWO_CHAR_STRING (ls_codes[i]);
345                       APPEND_CHAR ('=');
346                       append_quoted (arg);
347                       APPEND_CHAR (':');
348                     }
349                   else
350                     {
351                       unrecognized = 1;
352                     }
353                 }
354             }
355           else
356             {
357               unrecognized = 1;
358             }
359         }
360
361       if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
362         {
363           error (0, 0, _("%s:%lu: unrecognized keyword %s"),
364                  (filename ? quote (filename) : _("<internal>")),
365                  (long unsigned) line_number, keywd);
366           err = 1;
367         }
368
369       free (keywd);
370       if (arg)
371         free (arg);
372     }
373
374   return err;
375 }
376
377 static int
378 dc_parse_file (const char *filename)
379 {
380   FILE *fp;
381   int err;
382
383   if (STREQ (filename, "-"))
384     {
385       have_read_stdin = 1;
386       fp = stdin;
387     }
388   else
389     {
390       /* OPENOPTS is a macro.  It varies with the system.
391          Some systems distinguish between internal and
392          external text representations.  */
393
394       fp = fopen (filename, "r");
395       if (fp == NULL)
396         {
397           error (0, errno, "%s", quote (filename));
398           return 1;
399         }
400     }
401
402   err = dc_parse_stream (fp, filename);
403
404   if (fp != stdin && fclose (fp) == EOF)
405     {
406       error (0, errno, "%s", quote (filename));
407       return 1;
408     }
409
410   return err;
411 }
412
413 int
414 main (int argc, char **argv)
415 {
416   int err = 0;
417   int optc;
418   enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
419   int print_database = 0;
420
421   initialize_main (&argc, &argv);
422   program_name = argv[0];
423   setlocale (LC_ALL, "");
424   bindtextdomain (PACKAGE, LOCALEDIR);
425   textdomain (PACKAGE);
426
427   atexit (close_stdout);
428
429   while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
430     switch (optc)
431       {
432       case 'b': /* Bourne shell syntax.  */
433         syntax = SHELL_SYNTAX_BOURNE;
434         break;
435
436       case 'c': /* C shell syntax.  */
437         syntax = SHELL_SYNTAX_C;
438         break;
439
440       case 'p':
441         print_database = 1;
442         break;
443
444       case_GETOPT_HELP_CHAR;
445
446       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
447
448       default:
449         usage (EXIT_FAILURE);
450       }
451
452   argc -= optind;
453   argv += optind;
454
455   /* It doesn't make sense to use --print with either of
456      --bourne or --c-shell.  */
457   if (print_database && syntax != SHELL_SYNTAX_UNKNOWN)
458     {
459       error (0, 0,
460              _("the options to output dircolors' internal database and\n\
461 to select a shell syntax are mutually exclusive"));
462       usage (EXIT_FAILURE);
463     }
464
465   if (print_database && argc > 0)
466     {
467       error (0, 0,
468              _("no FILE arguments may be used with the option to output\n\
469 dircolors' internal database"));
470       usage (EXIT_FAILURE);
471     }
472
473   if (!print_database && argc > 1)
474     {
475       error (0, 0, _("too many arguments"));
476       usage (EXIT_FAILURE);
477     }
478
479   if (print_database)
480     {
481       int i;
482       for (i = 0; i < G_N_LINES; i++)
483         {
484           fwrite (G_line[i], 1, G_line_length[i], stdout);
485           fputc ('\n', stdout);
486         }
487     }
488   else
489     {
490       /* If shell syntax was not explicitly specified, try to guess it. */
491       if (syntax == SHELL_SYNTAX_UNKNOWN)
492         {
493           syntax = guess_shell_syntax ();
494           if (syntax == SHELL_SYNTAX_UNKNOWN)
495             {
496               error (EXIT_FAILURE, 0,
497          _("no SHELL environment variable, and no shell type option given"));
498             }
499         }
500
501       obstack_init (&lsc_obstack);
502       if (argc == 0)
503         err = dc_parse_stream (NULL, NULL);
504       else
505         err = dc_parse_file (argv[0]);
506
507       if (!err)
508         {
509           size_t len = obstack_object_size (&lsc_obstack);
510           char *s = obstack_finish (&lsc_obstack);
511           const char *prefix;
512           const char *suffix;
513
514           if (syntax == SHELL_SYNTAX_BOURNE)
515             {
516               prefix = "LS_COLORS='";
517               suffix = "';\nexport LS_COLORS\n";
518             }
519           else
520             {
521               prefix = "setenv LS_COLORS '";
522               suffix = "'\n";
523             }
524           fputs (prefix, stdout);
525           fwrite (s, 1, len, stdout);
526           fputs (suffix, stdout);
527         }
528     }
529
530
531   if (have_read_stdin && fclose (stdin) == EOF)
532     error (EXIT_FAILURE, errno, _("standard input"));
533
534   exit (err == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
535 }