* NEWS: Add a line for 6.5-cvs.
[coreutils.git] / src / dircolors.c
blobfc9319b60ed5b0b546a429e4b9c818daa53d6222
1 /* dircolors - output commands to set the LS_COLOR environment variable
2 Copyright (C) 1996-2006 Free Software Foundation, Inc.
3 Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000 H. Peter Anvin
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
19 #include <config.h>
21 #include <sys/types.h>
22 #include <getopt.h>
23 #include <stdio.h>
25 #include "system.h"
26 #include "dircolors.h"
27 #include "error.h"
28 #include "getline.h"
29 #include "obstack.h"
30 #include "quote.h"
31 #include "strcase.h"
32 #include "xstrndup.h"
34 /* The official name of this program (e.g., no `g' prefix). */
35 #define PROGRAM_NAME "dircolors"
37 #define AUTHORS "H. Peter Anvin"
39 #define obstack_chunk_alloc malloc
40 #define obstack_chunk_free free
42 enum Shell_syntax
44 SHELL_SYNTAX_BOURNE,
45 SHELL_SYNTAX_C,
46 SHELL_SYNTAX_UNKNOWN
49 #define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
50 #define APPEND_TWO_CHAR_STRING(S) \
51 do \
52 { \
53 APPEND_CHAR (S[0]); \
54 APPEND_CHAR (S[1]); \
55 } \
56 while (0)
58 /* Accumulate in this obstack the value for the LS_COLORS environment
59 variable. */
60 static struct obstack lsc_obstack;
62 static const char *const slack_codes[] =
64 "NORMAL", "NORM", "FILE", "DIR", "LNK", "LINK",
65 "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK",
66 "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE",
67 "END", "ENDCODE", "SUID", "SETUID", "SGID", "SETGID", "STICKY",
68 "OTHER_WRITABLE", "OWR", "STICKY_OTHER_WRITABLE", "OWT", NULL
71 static const char *const ls_codes[] =
73 "no", "no", "fi", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
74 "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec",
75 "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", NULL
77 #define array_len(Array) (sizeof (Array) / sizeof *(Array))
78 verify (array_len (slack_codes) == array_len (ls_codes));
80 static struct option const long_options[] =
82 {"bourne-shell", no_argument, NULL, 'b'},
83 {"sh", no_argument, NULL, 'b'},
84 {"csh", no_argument, NULL, 'c'},
85 {"c-shell", no_argument, NULL, 'c'},
86 {"print-database", no_argument, NULL, 'p'},
87 {GETOPT_HELP_OPTION_DECL},
88 {GETOPT_VERSION_OPTION_DECL},
89 {NULL, 0, NULL, 0}
92 char *program_name;
94 void
95 usage (int status)
97 if (status != EXIT_SUCCESS)
98 fprintf (stderr, _("Try `%s --help' for more information.\n"),
99 program_name);
100 else
102 printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
103 fputs (_("\
104 Output commands to set the LS_COLORS environment variable.\n\
106 Determine format of output:\n\
107 -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS\n\
108 -c, --csh, --c-shell output C shell code to set LS_COLORS\n\
109 -p, --print-database output defaults\n\
110 "), stdout);
111 fputs (HELP_OPTION_DESCRIPTION, stdout);
112 fputs (VERSION_OPTION_DESCRIPTION, stdout);
113 fputs (_("\
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 "), stdout);
119 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
122 exit (status);
125 /* If the SHELL environment variable is set to `csh' or `tcsh,'
126 assume C shell. Else Bourne shell. */
128 static enum Shell_syntax
129 guess_shell_syntax (void)
131 char *shell;
133 shell = getenv ("SHELL");
134 if (shell == NULL || *shell == '\0')
135 return SHELL_SYNTAX_UNKNOWN;
137 shell = last_component (shell);
139 if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
140 return SHELL_SYNTAX_C;
142 return SHELL_SYNTAX_BOURNE;
145 static void
146 parse_line (char const *line, char **keyword, char **arg)
148 char const *p;
149 char const *keyword_start;
150 char const *arg_start;
152 *keyword = NULL;
153 *arg = NULL;
155 for (p = line; isspace (to_uchar (*p)); ++p)
156 continue;
158 /* Ignore blank lines and shell-style comments. */
159 if (*p == '\0' || *p == '#')
160 return;
162 keyword_start = p;
164 while (!isspace (to_uchar (*p)) && *p != '\0')
166 ++p;
169 *keyword = xstrndup (keyword_start, p - keyword_start);
170 if (*p == '\0')
171 return;
175 ++p;
177 while (isspace (to_uchar (*p)));
179 if (*p == '\0' || *p == '#')
180 return;
182 arg_start = p;
184 while (*p != '\0' && *p != '#')
185 ++p;
187 for (--p; isspace (to_uchar (*p)); --p)
188 continue;
189 ++p;
191 *arg = xstrndup (arg_start, p - arg_start);
194 /* FIXME: Write a string to standard out, while watching for "dangerous"
195 sequences like unescaped : and = characters. */
197 static void
198 append_quoted (const char *str)
200 bool need_backslash = true;
202 while (*str != '\0')
204 switch (*str)
206 case '\'':
207 APPEND_CHAR ('\'');
208 APPEND_CHAR ('\\');
209 APPEND_CHAR ('\'');
210 need_backslash = true;
211 break;
213 case '\\':
214 case '^':
215 need_backslash = !need_backslash;
216 break;
218 case ':':
219 case '=':
220 if (need_backslash)
221 APPEND_CHAR ('\\');
222 /* Fall through */
224 default:
225 need_backslash = true;
226 break;
229 APPEND_CHAR (*str);
230 ++str;
234 /* Read the file open on FP (with name FILENAME). First, look for a
235 `TERM name' directive where name matches the current terminal type.
236 Once found, translate and accumulate the associated directives onto
237 the global obstack LSC_OBSTACK. Give a diagnostic
238 upon failure (unrecognized keyword is the only way to fail here).
239 Return true if successful. */
241 static bool
242 dc_parse_stream (FILE *fp, const char *filename)
244 size_t line_number = 0;
245 char const *next_G_line = G_line;
246 char *input_line = NULL;
247 size_t input_line_size = 0;
248 char const *line;
249 char *term;
250 bool ok = true;
252 /* State for the parser. */
253 enum { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL } state = ST_GLOBAL;
255 /* Get terminal type */
256 term = getenv ("TERM");
257 if (term == NULL || *term == '\0')
258 term = "none";
260 while (1)
262 char *keywd, *arg;
263 bool unrecognized;
265 ++line_number;
267 if (fp)
269 if (getline (&input_line, &input_line_size, fp) <= 0)
271 free (input_line);
272 break;
274 line = input_line;
276 else
278 if (next_G_line == G_line + sizeof G_line)
279 break;
280 line = next_G_line;
281 next_G_line += strlen (next_G_line) + 1;
284 parse_line (line, &keywd, &arg);
286 if (keywd == NULL)
287 continue;
289 if (arg == NULL)
291 error (0, 0, _("%s:%lu: invalid line; missing second token"),
292 filename, (unsigned long int) line_number);
293 ok = false;
294 free (keywd);
295 continue;
298 unrecognized = false;
299 if (strcasecmp (keywd, "TERM") == 0)
301 if (STREQ (arg, term))
302 state = ST_TERMSURE;
303 else if (state != ST_TERMSURE)
304 state = ST_TERMNO;
306 else
308 if (state == ST_TERMSURE)
309 state = ST_TERMYES; /* Another TERM can cancel */
311 if (state != ST_TERMNO)
313 if (keywd[0] == '.')
315 APPEND_CHAR ('*');
316 append_quoted (keywd);
317 APPEND_CHAR ('=');
318 append_quoted (arg);
319 APPEND_CHAR (':');
321 else if (keywd[0] == '*')
323 append_quoted (keywd);
324 APPEND_CHAR ('=');
325 append_quoted (arg);
326 APPEND_CHAR (':');
328 else if (strcasecmp (keywd, "OPTIONS") == 0
329 || strcasecmp (keywd, "COLOR") == 0
330 || strcasecmp (keywd, "EIGHTBIT") == 0)
332 /* Ignore. */
334 else
336 int i;
338 for (i = 0; slack_codes[i] != NULL; ++i)
339 if (strcasecmp (keywd, slack_codes[i]) == 0)
340 break;
342 if (slack_codes[i] != NULL)
344 APPEND_TWO_CHAR_STRING (ls_codes[i]);
345 APPEND_CHAR ('=');
346 append_quoted (arg);
347 APPEND_CHAR (':');
349 else
351 unrecognized = true;
355 else
357 unrecognized = true;
361 if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
363 error (0, 0, _("%s:%lu: unrecognized keyword %s"),
364 (filename ? quote (filename) : _("<internal>")),
365 (unsigned long int) line_number, keywd);
366 ok = false;
369 free (keywd);
370 free (arg);
373 return ok;
376 static bool
377 dc_parse_file (const char *filename)
379 bool ok;
381 if (! STREQ (filename, "-") && freopen (filename, "r", stdin) == NULL)
383 error (0, errno, "%s", filename);
384 return false;
387 ok = dc_parse_stream (stdin, filename);
389 if (fclose (stdin) != 0)
391 error (0, errno, "%s", quote (filename));
392 return false;
395 return ok;
399 main (int argc, char **argv)
401 bool ok = true;
402 int optc;
403 enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
404 bool print_database = false;
406 initialize_main (&argc, &argv);
407 program_name = argv[0];
408 setlocale (LC_ALL, "");
409 bindtextdomain (PACKAGE, LOCALEDIR);
410 textdomain (PACKAGE);
412 atexit (close_stdout);
414 while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
415 switch (optc)
417 case 'b': /* Bourne shell syntax. */
418 syntax = SHELL_SYNTAX_BOURNE;
419 break;
421 case 'c': /* C shell syntax. */
422 syntax = SHELL_SYNTAX_C;
423 break;
425 case 'p':
426 print_database = true;
427 break;
429 case_GETOPT_HELP_CHAR;
431 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
433 default:
434 usage (EXIT_FAILURE);
437 argc -= optind;
438 argv += optind;
440 /* It doesn't make sense to use --print with either of
441 --bourne or --c-shell. */
442 if (print_database && syntax != SHELL_SYNTAX_UNKNOWN)
444 error (0, 0,
445 _("the options to output dircolors' internal database and\n\
446 to select a shell syntax are mutually exclusive"));
447 usage (EXIT_FAILURE);
450 if (!print_database < argc)
452 error (0, 0, _("extra operand %s"), quote (argv[!print_database]));
453 if (print_database)
454 fprintf (stderr, "%s\n",
455 _("File operands cannot be combined with "
456 "--print-database (-p)."));
457 usage (EXIT_FAILURE);
460 if (print_database)
462 char const *p = G_line;
463 while (p < G_line + sizeof G_line)
465 puts (p);
466 p += strlen (p) + 1;
469 else
471 /* If shell syntax was not explicitly specified, try to guess it. */
472 if (syntax == SHELL_SYNTAX_UNKNOWN)
474 syntax = guess_shell_syntax ();
475 if (syntax == SHELL_SYNTAX_UNKNOWN)
477 error (EXIT_FAILURE, 0,
478 _("no SHELL environment variable, and no shell type option given"));
482 obstack_init (&lsc_obstack);
483 if (argc == 0)
484 ok = dc_parse_stream (NULL, NULL);
485 else
486 ok = dc_parse_file (argv[0]);
488 if (ok)
490 size_t len = obstack_object_size (&lsc_obstack);
491 char *s = obstack_finish (&lsc_obstack);
492 const char *prefix;
493 const char *suffix;
495 if (syntax == SHELL_SYNTAX_BOURNE)
497 prefix = "LS_COLORS='";
498 suffix = "';\nexport LS_COLORS\n";
500 else
502 prefix = "setenv LS_COLORS '";
503 suffix = "'\n";
505 fputs (prefix, stdout);
506 fwrite (s, 1, len, stdout);
507 fputs (suffix, stdout);
511 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);