define/use AUTHORS
[platform/upstream/coreutils.git] / src / dircolors.c
1 /* dircolors - output commands to set the LS_COLOR environment variable
2    Copyright (C) 1994, 1995, 1997, 1998, 1999 H. Peter Anvin
3    Copyright (C) 1996-1999 Free Software Foundation, Inc.
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 <ctype.h>
25 #include <getopt.h>
26 #include <stdio.h>
27
28 #include "system.h"
29 #include "dircolors.h"
30 #include "error.h"
31 #include "getline.h"
32 #include "obstack.h"
33
34 /* The official name of this program (e.g., no `g' prefix).  */
35 #define PROGRAM_NAME "dircolors"
36
37 #define AUTHORS "H. Peter Anvin"
38
39 #define obstack_chunk_alloc malloc
40 #define obstack_chunk_free free
41
42 char *strndup();
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       printf (_("\
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       --help                  display this help and exit\n\
113       --version               output version information and exit\n\
114 \n\
115 If FILE is specified, read it to determine which colors to use for which\n\
116 file types and extensions.  Otherwise, a precompiled database is used.\n\
117 For details on the format of these files, run `dircolors --print-database'.\n\
118 "));
119       puts (_("\nReport bugs to <bug-fileutils@gnu.org>."));
120       close_stdout ();
121     }
122
123   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
124 }
125
126 static void *
127 xstrndup (const char *s, size_t n)
128 {
129   char *new = strndup (s, n);
130   if (new == NULL)
131     error (EXIT_FAILURE, 0, _("virtual memory exhausted"));
132   return new;
133 }
134
135 /* If the SHELL environment variable is set to `csh' or `tcsh,'
136    assume C shell.  Else Bourne shell.  */
137
138 static enum Shell_syntax
139 guess_shell_syntax (void)
140 {
141   char *shell;
142
143   shell = getenv ("SHELL");
144   if (shell == NULL || *shell == '\0')
145     return SHELL_SYNTAX_UNKNOWN;
146
147   shell = base_name (shell);
148
149   if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
150     return SHELL_SYNTAX_C;
151
152   return SHELL_SYNTAX_BOURNE;
153 }
154
155 static void
156 parse_line (unsigned char const *line, char **keyword, char **arg)
157 {
158   unsigned char const *p;
159   unsigned char const *keyword_start;
160   unsigned char const *arg_start;
161
162   *keyword = NULL;
163   *arg = NULL;
164
165   for (p = line; ISSPACE (*p); ++p)
166     ;
167
168   /* Ignore blank lines and shell-style comments.  */
169   if (*p == '\0' || *p == '#')
170     return;
171
172   keyword_start = p;
173
174   while (!ISSPACE (*p) && *p != '\0')
175     {
176       ++p;
177     }
178
179   *keyword = xstrndup ((const char *) keyword_start, p - keyword_start);
180   if (*p  == '\0')
181     return;
182
183   do
184     {
185       ++p;
186     }
187   while (ISSPACE (*p));
188
189   if (*p == '\0' || *p == '#')
190     return;
191
192   arg_start = p;
193
194   while (*p != '\0' && *p != '#')
195     ++p;
196
197   for (--p; ISSPACE (*p); --p)
198     {
199       /* empty */
200     }
201   ++p;
202
203   *arg = xstrndup ((const char *) arg_start, p - arg_start);
204 }
205
206 /* FIXME: Write a string to standard out, while watching for "dangerous"
207    sequences like unescaped : and = characters.  */
208
209 static void
210 append_quoted (const char *str)
211 {
212   int need_backslash = 1;
213
214   while (*str != '\0')
215     {
216       switch (*str)
217         {
218         case '\\':
219         case '^':
220           need_backslash = !need_backslash;
221           break;
222
223         case ':':
224         case '=':
225           if (need_backslash)
226             APPEND_CHAR ('\\');
227           /* Fall through */
228
229         default:
230           need_backslash = 1;
231           break;
232         }
233
234       APPEND_CHAR (*str);
235       ++str;
236     }
237 }
238
239 /* Read the file open on FP (with name FILENAME).  First, look for a
240    `TERM name' directive where name matches the current terminal type.
241    Once found, translate and accumulate the associated directives onto
242    the global obstack LSC_OBSTACK.  Give a diagnostic and return nonzero
243    upon failure (unrecognized keyword is the only way to fail here).
244    Return zero otherwise.  */
245
246 static int
247 dc_parse_stream (FILE *fp, const char *filename)
248 {
249   size_t line_number = 0;
250   char *line = NULL;
251   size_t line_chars_allocated = 0;
252   int state;
253   char *term;
254   int err = 0;
255
256   /* State for the parser.  */
257   enum states { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL };
258
259   state = ST_GLOBAL;
260
261   /* Get terminal type */
262   term = getenv ("TERM");
263   if (term == NULL || *term == '\0')
264     term = "none";
265
266   while (1)
267     {
268       int line_length;
269       char *keywd, *arg;
270       int unrecognized;
271
272       ++line_number;
273
274       if (fp)
275         {
276           line_length = getline (&line, &line_chars_allocated, fp);
277           if (line_length <= 0)
278             {
279               if (line)
280                 free (line);
281               break;
282             }
283         }
284       else
285         {
286           line = (char *) (G_line[line_number - 1]);
287           line_length = G_line_length[line_number - 1];
288           if (line_number > G_N_LINES)
289             break;
290         }
291
292       parse_line ((unsigned char *) line, &keywd, &arg);
293
294       if (keywd == NULL)
295         continue;
296
297       if (arg == NULL)
298         {
299           error (0, 0, _("%s:%lu: invalid line;  missing second token"),
300                  filename, (long unsigned) line_number);
301           err = 1;
302           free (keywd);
303           continue;
304         }
305
306       unrecognized = 0;
307       if (strcasecmp (keywd, "TERM") == 0)
308         {
309           if (STREQ (arg, term))
310             state = ST_TERMSURE;
311           else if (state != ST_TERMSURE)
312             state = ST_TERMNO;
313         }
314       else
315         {
316           if (state == ST_TERMSURE)
317             state = ST_TERMYES; /* Another TERM can cancel */
318
319           if (state != ST_TERMNO)
320             {
321               if (keywd[0] == '.')
322                 {
323                   APPEND_CHAR ('*');
324                   append_quoted (keywd);
325                   APPEND_CHAR ('=');
326                   append_quoted (arg);
327                   APPEND_CHAR (':');
328                 }
329               else if (keywd[0] == '*')
330                 {
331                   append_quoted (keywd);
332                   APPEND_CHAR ('=');
333                   append_quoted (arg);
334                   APPEND_CHAR (':');
335                 }
336               else if (strcasecmp (keywd, "OPTIONS") == 0
337                        || strcasecmp (keywd, "COLOR") == 0
338                        || strcasecmp (keywd, "EIGHTBIT") == 0)
339                 {
340                   /* Ignore.  */
341                 }
342               else
343                 {
344                   int i;
345
346                   for (i = 0; slack_codes[i] != NULL; ++i)
347                     if (strcasecmp (keywd, slack_codes[i]) == 0)
348                       break;
349
350                   if (slack_codes[i] != NULL)
351                     {
352                       APPEND_TWO_CHAR_STRING (ls_codes[i]);
353                       APPEND_CHAR ('=');
354                       append_quoted (arg);
355                       APPEND_CHAR (':');
356                     }
357                   else
358                     {
359                       unrecognized = 1;
360                     }
361                 }
362             }
363           else
364             {
365               unrecognized = 1;
366             }
367         }
368
369       if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
370         {
371           error (0, 0, _("%s:%lu: unrecognized keyword `%s'"),
372                  (filename ? filename : _("<internal>")),
373                  (long unsigned) line_number, keywd);
374           err = 1;
375         }
376
377       free (keywd);
378       if (arg)
379         free (arg);
380     }
381
382   return err;
383 }
384
385 static int
386 dc_parse_file (const char *filename)
387 {
388   FILE *fp;
389   int err;
390
391   if (STREQ (filename, "-"))
392     {
393       have_read_stdin = 1;
394       fp = stdin;
395     }
396   else
397     {
398       /* OPENOPTS is a macro.  It varies with the system.
399          Some systems distinguish between internal and
400          external text representations.  */
401
402       fp = fopen (filename, "r");
403       if (fp == NULL)
404         {
405           error (0, errno, "%s", filename);
406           return 1;
407         }
408     }
409
410   err = dc_parse_stream (fp, filename);
411
412   if (fp != stdin && fclose (fp) == EOF)
413     {
414       error (0, errno, "%s", filename);
415       return 1;
416     }
417
418   return err;
419 }
420
421 int
422 main (int argc, char **argv)
423 {
424   int err = 0;
425   int optc;
426   enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
427   int print_database = 0;
428
429   program_name = argv[0];
430   setlocale (LC_ALL, "");
431   bindtextdomain (PACKAGE, LOCALEDIR);
432   textdomain (PACKAGE);
433
434   while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
435     switch (optc)
436       {
437       case 'b': /* Bourne shell syntax.  */
438         syntax = SHELL_SYNTAX_BOURNE;
439         break;
440
441       case 'c': /* C shell syntax.  */
442         syntax = SHELL_SYNTAX_C;
443         break;
444
445       case 'p':
446         print_database = 1;
447         break;
448
449       case_GETOPT_HELP_CHAR;
450
451       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
452
453       default:
454         usage (1);
455       }
456
457   argc -= optind;
458   argv += optind;
459
460   /* It doesn't make sense to use --print with either of
461      --bourne or --c-shell.  */
462   if (print_database && syntax != SHELL_SYNTAX_UNKNOWN)
463     {
464       error (0, 0,
465              _("the options to output dircolors' internal database and\n\
466 to select a shell syntax are mutually exclusive"));
467       usage (1);
468     }
469
470   if (print_database && argc > 0)
471     {
472       error (0, 0,
473              _("no FILE arguments may be used with the option to output\n\
474 dircolors' internal database"));
475       usage (1);
476     }
477
478   if (!print_database && argc > 1)
479     {
480       error (0, 0, _("too many arguments"));
481       usage (1);
482     }
483
484   if (print_database)
485     {
486       int i;
487       for (i = 0; i < G_N_LINES; i++)
488         {
489           fwrite (G_line[i], 1, G_line_length[i], stdout);
490           fputc ('\n', stdout);
491         }
492     }
493   else
494     {
495       /* If shell syntax was not explicitly specified, try to guess it. */
496       if (syntax == SHELL_SYNTAX_UNKNOWN)
497         {
498           syntax = guess_shell_syntax ();
499           if (syntax == SHELL_SYNTAX_UNKNOWN)
500             {
501               error (EXIT_FAILURE, 0,
502          _("no SHELL environment variable, and no shell type option given"));
503             }
504         }
505
506       obstack_init (&lsc_obstack);
507       if (argc == 0)
508         err = dc_parse_stream (NULL, NULL);
509       else
510         err = dc_parse_file (argv[0]);
511
512       if (!err)
513         {
514           size_t len = obstack_object_size (&lsc_obstack);
515           char *s = obstack_finish (&lsc_obstack);
516           const char *prefix;
517           const char *suffix;
518
519           if (syntax == SHELL_SYNTAX_BOURNE)
520             {
521               prefix = "LS_COLORS='";
522               suffix = "';\nexport LS_COLORS\n";
523             }
524           else
525             {
526               prefix = "setenv LS_COLORS '";
527               suffix = "'\n";
528             }
529           fputs (prefix, stdout);
530           fwrite (s, 1, len, stdout);
531           fputs (suffix, stdout);
532         }
533     }
534
535   close_stdout ();
536
537   if (have_read_stdin && fclose (stdin) == EOF)
538     error (EXIT_FAILURE, errno, _("standard input"));
539
540   exit (err == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
541 }