split: port ‘split -n N /dev/null’ better to macOS
[coreutils.git] / src / dircolors.c
blobf95524b67f24ac5bd7852dd2da047483e506ace2
1 /* dircolors - output commands to set the LS_COLOR environment variable
2 Copyright (C) 1996-2023 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 3 of the License, or
8 (at your option) 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, see <https://www.gnu.org/licenses/>. */
18 #include <config.h>
20 #include <sys/types.h>
21 #include <fnmatch.h>
22 #include <getopt.h>
24 #include "system.h"
25 #include "dircolors.h"
26 #include "c-strcase.h"
27 #include "die.h"
28 #include "error.h"
29 #include "obstack.h"
30 #include "quote.h"
31 #include "stdio--.h"
33 /* The official name of this program (e.g., no 'g' prefix). */
34 #define PROGRAM_NAME "dircolors"
36 #define AUTHORS proper_name ("H. Peter Anvin")
38 #define obstack_chunk_alloc malloc
39 #define obstack_chunk_free free
41 enum Shell_syntax
43 SHELL_SYNTAX_BOURNE,
44 SHELL_SYNTAX_C,
45 SHELL_SYNTAX_UNKNOWN
48 #define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
50 /* Accumulate in this obstack the value for the LS_COLORS environment
51 variable. */
52 static struct obstack lsc_obstack;
54 static char const *const slack_codes[] =
56 "NORMAL", "NORM", "FILE", "RESET", "DIR", "LNK", "LINK",
57 "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK",
58 "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE",
59 "END", "ENDCODE", "SUID", "SETUID", "SGID", "SETGID", "STICKY",
60 "OTHER_WRITABLE", "OWR", "STICKY_OTHER_WRITABLE", "OWT", "CAPABILITY",
61 "MULTIHARDLINK", "CLRTOEOL", NULL
64 static char const *const ls_codes[] =
66 "no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
67 "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec",
68 "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh", "cl", NULL
70 static_assert (ARRAY_CARDINALITY (slack_codes) == ARRAY_CARDINALITY (ls_codes));
72 /* Whether to output escaped ls color codes for display. */
73 static bool print_ls_colors;
75 /* For long options that have no equivalent short option, use a
76 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
77 enum
79 PRINT_LS_COLORS_OPTION = CHAR_MAX + 1,
82 static struct option const long_options[] =
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 {"print-ls-colors", no_argument, NULL, PRINT_LS_COLORS_OPTION},
90 {GETOPT_HELP_OPTION_DECL},
91 {GETOPT_VERSION_OPTION_DECL},
92 {NULL, 0, NULL, 0}
95 void
96 usage (int status)
98 if (status != EXIT_SUCCESS)
99 emit_try_help ();
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 --print-ls-colors output fully escaped colors for display\n\
111 "), stdout);
112 fputs (HELP_OPTION_DESCRIPTION, stdout);
113 fputs (VERSION_OPTION_DESCRIPTION, stdout);
114 fputs (_("\
116 If FILE is specified, read it to determine which colors to use for which\n\
117 file types and extensions. Otherwise, a precompiled database is used.\n\
118 For details on the format of these files, run 'dircolors --print-database'.\n\
119 "), stdout);
120 emit_ancillary_info (PROGRAM_NAME);
123 exit (status);
126 /* If the SHELL environment variable is set to 'csh' or 'tcsh,'
127 assume C shell. Else Bourne shell. */
129 static enum Shell_syntax
130 guess_shell_syntax (void)
132 char *shell;
134 shell = getenv ("SHELL");
135 if (shell == NULL || *shell == '\0')
136 return SHELL_SYNTAX_UNKNOWN;
138 shell = last_component (shell);
140 if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
141 return SHELL_SYNTAX_C;
143 return SHELL_SYNTAX_BOURNE;
146 static void
147 parse_line (char const *line, char **keyword, char **arg)
149 char const *p;
150 char const *keyword_start;
151 char const *arg_start;
153 *keyword = NULL;
154 *arg = NULL;
156 for (p = line; isspace (to_uchar (*p)); ++p)
157 continue;
159 /* Ignore blank lines and shell-style comments. */
160 if (*p == '\0' || *p == '#')
161 return;
163 keyword_start = p;
165 while (!isspace (to_uchar (*p)) && *p != '\0')
167 ++p;
170 *keyword = ximemdup0 (keyword_start, p - keyword_start);
171 if (*p == '\0')
172 return;
176 ++p;
178 while (isspace (to_uchar (*p)));
180 if (*p == '\0' || *p == '#')
181 return;
183 arg_start = p;
185 while (*p != '\0' && *p != '#')
186 ++p;
188 for (--p; isspace (to_uchar (*p)); --p)
189 continue;
190 ++p;
192 *arg = ximemdup0 (arg_start, p - arg_start);
195 /* Accumulate STR to LS_COLORS data.
196 If outputting shell syntax, then escape appropriately. */
198 static void
199 append_quoted (char const *str)
201 bool need_backslash = true;
203 while (*str != '\0')
205 if (! print_ls_colors)
206 switch (*str)
208 case '\'':
209 APPEND_CHAR ('\'');
210 APPEND_CHAR ('\\');
211 APPEND_CHAR ('\'');
212 need_backslash = true;
213 break;
215 case '\\':
216 case '^':
217 need_backslash = !need_backslash;
218 break;
220 case ':':
221 case '=':
222 if (need_backslash)
223 APPEND_CHAR ('\\');
224 FALLTHROUGH;
226 default:
227 need_backslash = true;
228 break;
231 APPEND_CHAR (*str);
232 ++str;
236 /* Accumulate entry to LS_COLORS data.
237 Use shell syntax unless PRINT_LS_COLORS is set. */
239 static void
240 append_entry (char prefix, char const *item, char const *arg)
242 if (print_ls_colors)
244 append_quoted ("\x1B[");
245 append_quoted (arg);
246 APPEND_CHAR ('m');
248 if (prefix)
249 APPEND_CHAR (prefix);
250 append_quoted (item);
251 APPEND_CHAR (print_ls_colors ? '\t' : '=');
252 append_quoted (arg);
253 if (print_ls_colors)
254 append_quoted ("\x1B[0m");
255 APPEND_CHAR (print_ls_colors ? '\n' : ':');
258 /* Read the file open on FP (with name FILENAME). First, look for a
259 'TERM name' directive where name matches the current terminal type.
260 Once found, translate and accumulate the associated directives onto
261 the global obstack LSC_OBSTACK. Give a diagnostic
262 upon failure (unrecognized keyword is the only way to fail here).
263 Return true if successful. */
265 static bool
266 dc_parse_stream (FILE *fp, char const *filename)
268 size_t line_number = 0;
269 char const *next_G_line = G_line;
270 char *input_line = NULL;
271 size_t input_line_size = 0;
272 char const *line;
273 char const *term;
274 char const *colorterm;
275 bool ok = true;
277 /* State for the parser. */
278 enum { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL } state = ST_GLOBAL;
280 /* Get terminal type */
281 term = getenv ("TERM");
282 if (term == NULL || *term == '\0')
283 term = "none";
285 /* Also match $COLORTERM. */
286 colorterm = getenv ("COLORTERM");
287 if (colorterm == NULL)
288 colorterm = ""; /* Doesn't match default "?*" */
290 while (true)
292 char *keywd, *arg;
293 bool unrecognized;
295 ++line_number;
297 if (fp)
299 if (getline (&input_line, &input_line_size, fp) <= 0)
301 free (input_line);
302 break;
304 line = input_line;
306 else
308 if (next_G_line == G_line + sizeof G_line)
309 break;
310 line = next_G_line;
311 next_G_line += strlen (next_G_line) + 1;
314 parse_line (line, &keywd, &arg);
316 if (keywd == NULL)
317 continue;
319 if (arg == NULL)
321 error (0, 0, _("%s:%lu: invalid line; missing second token"),
322 quotef (filename), (unsigned long int) line_number);
323 ok = false;
324 free (keywd);
325 continue;
328 unrecognized = false;
329 if (c_strcasecmp (keywd, "TERM") == 0)
331 if (state != ST_TERMSURE)
332 state = fnmatch (arg, term, 0) == 0 ? ST_TERMSURE : ST_TERMNO;
334 else if (c_strcasecmp (keywd, "COLORTERM") == 0)
336 if (state != ST_TERMSURE)
337 state = fnmatch (arg, colorterm, 0) == 0 ? ST_TERMSURE : ST_TERMNO;
339 else
341 if (state == ST_TERMSURE)
342 state = ST_TERMYES; /* Another {COLOR,}TERM can cancel. */
344 if (state != ST_TERMNO)
346 if (keywd[0] == '.')
347 append_entry ('*', keywd, arg);
348 else if (keywd[0] == '*')
349 append_entry (0, keywd, arg);
350 else if (c_strcasecmp (keywd, "OPTIONS") == 0
351 || c_strcasecmp (keywd, "COLOR") == 0
352 || c_strcasecmp (keywd, "EIGHTBIT") == 0)
354 /* Ignore. */
356 else
358 int i;
360 for (i = 0; slack_codes[i] != NULL; ++i)
361 if (c_strcasecmp (keywd, slack_codes[i]) == 0)
362 break;
364 if (slack_codes[i] != NULL)
365 append_entry (0, ls_codes[i], arg);
366 else
367 unrecognized = true;
370 else
371 unrecognized = true;
374 if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
376 error (0, 0, _("%s:%lu: unrecognized keyword %s"),
377 (filename ? quotef (filename) : _("<internal>")),
378 (unsigned long int) line_number, keywd);
379 ok = false;
382 free (keywd);
383 free (arg);
386 return ok;
389 static bool
390 dc_parse_file (char const *filename)
392 bool ok;
394 if (! STREQ (filename, "-") && freopen (filename, "r", stdin) == NULL)
396 error (0, errno, "%s", quotef (filename));
397 return false;
400 ok = dc_parse_stream (stdin, filename);
402 if (fclose (stdin) != 0)
404 error (0, errno, "%s", quotef (filename));
405 return false;
408 return ok;
412 main (int argc, char **argv)
414 bool ok = true;
415 int optc;
416 enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
417 bool print_database = false;
419 initialize_main (&argc, &argv);
420 set_program_name (argv[0]);
421 setlocale (LC_ALL, "");
422 bindtextdomain (PACKAGE, LOCALEDIR);
423 textdomain (PACKAGE);
425 atexit (close_stdout);
427 while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
428 switch (optc)
430 case 'b': /* Bourne shell syntax. */
431 syntax = SHELL_SYNTAX_BOURNE;
432 break;
434 case 'c': /* C shell syntax. */
435 syntax = SHELL_SYNTAX_C;
436 break;
438 case 'p':
439 print_database = true;
440 break;
442 case PRINT_LS_COLORS_OPTION:
443 print_ls_colors = true;
444 break;
446 case_GETOPT_HELP_CHAR;
448 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
450 default:
451 usage (EXIT_FAILURE);
454 argc -= optind;
455 argv += optind;
457 /* It doesn't make sense to use --print with either of
458 --bourne or --c-shell. */
459 if ((print_database | print_ls_colors) && syntax != SHELL_SYNTAX_UNKNOWN)
461 error (0, 0,
462 _("the options to output non shell syntax,\n"
463 "and to select a shell syntax are mutually exclusive"));
464 usage (EXIT_FAILURE);
467 if (print_database && print_ls_colors)
469 error (0, 0,
470 _("options --print-database and --print-ls-colors "
471 "are mutually exclusive"));
472 usage (EXIT_FAILURE);
475 if ((!print_database) < argc)
477 error (0, 0, _("extra operand %s"),
478 quote (argv[!print_database]));
479 if (print_database)
480 fprintf (stderr, "%s\n",
481 _("file operands cannot be combined with "
482 "--print-database (-p)"));
483 usage (EXIT_FAILURE);
486 if (print_database)
488 char const *p = G_line;
489 while (p - G_line < sizeof G_line)
491 puts (p);
492 p += strlen (p) + 1;
495 else
497 /* If shell syntax was not explicitly specified, try to guess it. */
498 if (syntax == SHELL_SYNTAX_UNKNOWN && ! print_ls_colors)
500 syntax = guess_shell_syntax ();
501 if (syntax == SHELL_SYNTAX_UNKNOWN)
503 die (EXIT_FAILURE, 0,
504 _("no SHELL environment variable, and no shell type option given"));
508 obstack_init (&lsc_obstack);
509 if (argc == 0)
510 ok = dc_parse_stream (NULL, NULL);
511 else
512 ok = dc_parse_file (argv[0]);
514 if (ok)
516 size_t len = obstack_object_size (&lsc_obstack);
517 char *s = obstack_finish (&lsc_obstack);
518 char const *prefix;
519 char const *suffix;
521 if (syntax == SHELL_SYNTAX_BOURNE)
523 prefix = "LS_COLORS='";
524 suffix = "';\nexport LS_COLORS\n";
526 else
528 prefix = "setenv LS_COLORS '";
529 suffix = "'\n";
531 if (! print_ls_colors)
532 fputs (prefix, stdout);
533 fwrite (s, 1, len, stdout);
534 if (! print_ls_colors)
535 fputs (suffix, stdout);
539 return ok ? EXIT_SUCCESS : EXIT_FAILURE;