-/* FIXME: accept, but ignore EIGHTBIT option
- FIXME: embed contents of default mapping file
- FIXME: add option to print that default mapping?
-
- Only distinction necessary: Bourne vs. C-shell
- */
-/* dircolors - parse a Slackware-style DIR_COLORS file.
+/* dircolors - output commands to set the LS_COLOR environment variable
Copyright (C) 1994, 1995 H. Peter Anvin
Copyright (C) 1996 Free Software Foundation, Inc.
# include <config.h>
#endif
+#include <sys/types.h>
#include <ctype.h>
#include <getopt.h>
#include <stdio.h>
#include "system.h"
+#include "getline.h"
+#include "long-options.h"
#include "error.h"
+#include "obstack.h"
+#include "dircolors.h"
-char *xmalloc ();
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
-#define USER_FILE ".dir_colors" /* Versus user's home directory */
-#define SYSTEM_FILE "DIR_COLORS" /* System-wide file in directory SYSTEM_DIR
- (defined on the cc command line). */
+#ifndef STDC_HEADERS
+void free ();
+#endif
-#define STRINGLEN 2048 /* Max length of a string */
+char *xmalloc ();
+char *basename ();
+char *strndup();
-enum modes { MO_SH, MO_CSH, MO_KSH, MO_ZSH, MO_UNKNOWN, MO_ERR };
+enum Shell_syntax
+{
+ SHELL_SYNTAX_BOURNE,
+ SHELL_SYNTAX_C,
+ SHELL_SYNTAX_UNKNOWN
+};
-/* FIXME: associate these arrays? */
-static const char *const shells[] =
-{ "sh", "ash", "csh", "tcsh", "bash", "ksh", "zsh", NULL };
+#define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
+#define APPEND_TWO_CHAR_STRING(S) \
+ do \
+ { \
+ APPEND_CHAR (S[0]); \
+ APPEND_CHAR (S[1]); \
+ } \
+ while (0)
-static const int shell_mode[] =
-{ MO_SH, MO_SH, MO_CSH, MO_CSH, MO_KSH, MO_KSH, MO_ZSH };
+/* Accumulate in this obstack the value for the LS_COLORS environment
+ variable. */
+static struct obstack lsc_obstack;
-/* Parser needs these state variables. */
-enum states { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL };
+/* Nonzero if the input file was the standard input. */
+static int have_read_stdin;
/* FIXME: associate with ls_codes? */
static const char *const slack_codes[] =
static struct option const long_options[] =
{
- {"ash", no_argument, NULL, 'a'},
- {"bash", no_argument, NULL, 'b'},
+ {"bourne-shell", no_argument, NULL, 'b'},
+ {"sh", no_argument, NULL, 'b'},
{"csh", no_argument, NULL, 'c'},
+ {"c-shell", no_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'},
- {"sh", no_argument, NULL, 's'},
- {"tcsh", no_argument, NULL, 't'},
+ {"print-database", no_argument, NULL, 'p'},
{"version", no_argument, NULL, 'v'},
- {"zsh", no_argument, NULL, 'z'},
+ {NULL, 0, NULL, 0}
};
char *program_name;
{
printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
printf (_("\
- -h, --help display this help and exit\n\
- --version output version information and exit\n\
+Output commands to set the LS_COLORS environment variable.\n\
+\n\
Determine format of output:\n\
- -a, --ash assume ash shell\n\
- -b, --bash assume bash shell\n\
- -c, --csh assume csh shell\n\
- -s, --sh assume Bourne shell\n\
- -t, --tcsh assume tcsh shell\n\
- -z, --zsh assume zsh shell\n"));
+ -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS\n\
+ -c, --csh, --c-shell output C shell code to set LS_COLORS\n\
+ -p, --print-database output defaults\n\
+ --help display this help and exit\n\
+ --version output version information and exit\n\
+\n\
+If FILE is specified, read it to determine which colors to use for which\n\
+file types and extensions. Otherwise, a precompiled database is used.\n\
+For details on the format of these files, run `dircolors --print-database'.\n\
+"));
+ puts (_("\nReport bugs to <fileutils-bugs@gnu.ai.mit.edu>."));
}
- exit (status);
+ exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
-/* If SHELL is csh or tcsh, assume C-shell. Else Bourne shell. */
-/* FIXME: rename functon. */
+static void *
+xstrndup (const char *s, size_t n)
+{
+ char *new = strndup (s, n);
+ if (new == NULL)
+ error (EXIT_FAILURE, 0, _("virtual memory exhausted"));
+ return new;
+}
-static int
-figure_mode (void)
+/* If the SHELL environment variable is set to `csh' or `tcsh,'
+ assume C shell. Else Bourne shell. */
+
+static enum Shell_syntax
+guess_shell_syntax (void)
{
- char *shell, *shellv;
- int i;
+ char *shell;
- shellv = getenv ("SHELL");
- if (shellv == NULL || *shellv == '\0')
- error (1, 0, _("\
-No SHELL variable, and no mode option specified"));
+ shell = getenv ("SHELL");
+ if (shell == NULL || *shell == '\0')
+ return SHELL_SYNTAX_UNKNOWN;
- shell = strrchr (shellv, '/');
- if (shell != NULL)
- ++shell;
- else
- shell = shellv;
+ shell = basename (shell);
- for (i = 0; shells[i]; ++i)
- if (strcmp (shell, shells[i]) == 0)
- return shell_mode[i];
+ if (STREQ (shell, "csh") || STREQ (shell, "tcsh"))
+ return SHELL_SYNTAX_C;
- error (1, 0, _("Unknown shell `%s'\n"), shell);
- /* NOTREACHED */
+ return SHELL_SYNTAX_BOURNE;
}
static void
-parse_line (char **keyword, char **arg, char *line)
+parse_line (const char *line, char **keyword, char **arg)
{
- char *p;
+ const char *p;
+ const char *keyword_start;
+ const char *arg_start;
- *keyword = *arg = "";
+ *keyword = NULL;
+ *arg = NULL;
for (p = line; isspace (*p); ++p)
;
+ /* Ignore blank lines and shell-style comments. */
if (*p == '\0' || *p == '#')
return;
- *keyword = p;
+ keyword_start = p;
- while (!isspace (*p))
- if (*p++ == '\0')
- return;
+ while (!isspace (*p) && *p != '\0')
+ {
+ ++p;
+ }
- *p++ = '\0';
+ *keyword = xstrndup (keyword_start, p - keyword_start);
+ if (*p == '\0')
+ return;
- while (isspace (*p))
- ++p;
+ do
+ {
+ ++p;
+ }
+ while (isspace (*p));
if (*p == '\0' || *p == '#')
return;
- *arg = p;
+ arg_start = p;
while (*p != '\0' && *p != '#')
++p;
+
for (--p; isspace (*p); --p)
- ;
+ {
+ /* empty */
+ }
++p;
- *p = '\0';
+ *arg = xstrndup (arg_start, p - arg_start);
}
-/* Write a string to standard out, while watching for "dangerous"
+/* FIXME: Write a string to standard out, while watching for "dangerous"
sequences like unescaped : and = characters. */
static void
-put_seq (const char *str, char follow)
+append_quoted (const char *str)
{
- int danger = 1;
+ int need_backslash = 1;
while (*str != '\0')
{
{
case '\\':
case '^':
- danger = !danger;
+ need_backslash = !need_backslash;
break;
case ':':
case '=':
- if (danger)
- putchar ('\\');
+ if (need_backslash)
+ APPEND_CHAR ('\\');
/* Fall through */
default:
- danger = 1;
+ need_backslash = 1;
break;
}
- putchar (*str++);
+ APPEND_CHAR (*str);
+ ++str;
}
-
- putchar (follow); /* The character that ends the sequence. */
}
-int
-main (int argc, char *argv[])
+/* Read the file open on FP (with name FILENAME). First, look for a
+ `TERM name' directive where name matches the current terminal type.
+ Once found, translate and accumulate the associated directives onto
+ the global obstack LSC_OBSTACK. Give a diagnostic and return nonzero
+ upon failure (unrecognized keyword is the only way to fail here).
+ Return zero otherwise. */
+
+static int
+dc_parse_stream (FILE *fp, const char *filename)
{
- char *p;
- int optc;
- int mode = MO_UNKNOWN;
- FILE *fp = NULL;
- char *term;
+ size_t line_number = 0;
+ char *line = NULL;
+ size_t line_chars_allocated = 0;
int state;
+ char *term;
+ int err = 0;
- char line[STRINGLEN];
- char useropts[2048] = "";
- char *keywd, *arg;
+ /* State for the parser. */
+ enum states { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL };
- int do_help = 0;
- int do_version = 0;
+ state = ST_GLOBAL;
- char *input_file;
+ /* Get terminal type */
+ term = getenv ("TERM");
+ if (term == NULL || *term == '\0')
+ term = "none";
+
+ while (1)
+ {
+ int line_length;
+ char *keywd, *arg;
+ int unrecognized;
+
+ ++line_number;
+
+ if (fp)
+ {
+ line_length = getline (&line, &line_chars_allocated, fp);
+ if (line_length <= 0)
+ {
+ if (line)
+ free (line);
+ break;
+ }
+ }
+ else
+ {
+ line = (char *) (G_line[line_number - 1]);
+ line_length = G_line_length[line_number - 1];
+ if (line_number > G_N_LINES)
+ break;
+ }
+
+ parse_line (line, &keywd, &arg);
+
+ if (keywd == NULL)
+ continue;
+
+ unrecognized = 0;
+ if (strcasecmp (keywd, "TERM") == 0)
+ {
+ if (strcmp (arg, term) == 0)
+ state = ST_TERMSURE;
+ else if (state != ST_TERMSURE)
+ state = ST_TERMNO;
+ }
+ else
+ {
+ if (state == ST_TERMSURE)
+ state = ST_TERMYES; /* Another TERM can cancel */
+
+ if (state != ST_TERMNO)
+ {
+ if (keywd[0] == '.')
+ {
+ APPEND_CHAR ('*');
+ append_quoted (keywd);
+ APPEND_CHAR ('=');
+ append_quoted (arg);
+ APPEND_CHAR (':');
+ }
+ else if (keywd[0] == '*')
+ {
+ append_quoted (keywd);
+ APPEND_CHAR ('=');
+ append_quoted (arg);
+ APPEND_CHAR (':');
+ }
+ else if (strcasecmp (keywd, "OPTIONS") == 0
+ || strcasecmp (keywd, "COLOR") == 0
+ || strcasecmp (keywd, "EIGHTBIT") == 0)
+ {
+ /* Ignore. */
+ }
+ else
+ {
+ int i;
+
+ for (i = 0; slack_codes[i] != NULL; ++i)
+ if (strcasecmp (keywd, slack_codes[i]) == 0)
+ break;
+
+ if (slack_codes[i] != NULL)
+ {
+ APPEND_TWO_CHAR_STRING (ls_codes[i]);
+ APPEND_CHAR ('=');
+ append_quoted (arg);
+ APPEND_CHAR (':');
+ }
+ else
+ {
+ unrecognized = 1;
+ }
+ }
+ }
+ else
+ {
+ unrecognized = 1;
+ }
+ }
+
+ if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES))
+ {
+ error (0, 0, _("%s:%lu: unrecognized keyword `%s'"),
+ filename, (long unsigned) line_number, keywd);
+ err = 1;
+ }
+
+ free (keywd);
+ if (arg)
+ free (arg);
+ }
+
+ return err;
+}
+
+static int
+dc_parse_file (const char *filename)
+{
+ FILE *fp;
+ int err;
+
+ if (strcmp (filename, "-") == 0)
+ {
+ have_read_stdin = 1;
+ fp = stdin;
+ }
+ else
+ {
+ /* OPENOPTS is a macro. It varies with the system.
+ Some systems distinguish between internal and
+ external text representations. */
+
+ fp = fopen (filename, "r");
+ if (fp == NULL)
+ {
+ error (0, errno, "%s", filename);
+ return 1;
+ }
+ }
+
+ err = dc_parse_stream (fp, filename);
+
+ if (fp != stdin && fclose (fp) == EOF)
+ {
+ error (0, errno, "%s", filename);
+ return 1;
+ }
+
+ return err;
+}
+
+int
+main (int argc, char **argv)
+{
+ int err = 0;
+ int optc;
+ enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN;
+ int print_database = 0;
program_name = argv[0];
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
- /* Parse command line. */
+ parse_long_options (argc, argv, "dircolors", GNU_PACKAGE, VERSION, usage);
- while ((optc = getopt_long (argc, argv, "abhckPstz", long_options, NULL))
- != EOF)
+ while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1)
switch (optc)
{
- case 'a':
- case 's': /* Plain sh mode */
- mode = MO_SH;
- break;
-
- case 'c':
- case 't':
- mode = MO_CSH;
+ case 'b': /* Bourne shell syntax. */
+ syntax = SHELL_SYNTAX_BOURNE;
break;
- case 'b':
- case 'k':
- mode = MO_KSH;
+ case 'c': /* C shell syntax. */
+ syntax = SHELL_SYNTAX_C;
break;
- case 'h':
- do_help = 1;
- break;
-
- case 'z':
- mode = MO_ZSH;
- break;
-
- case 'v':
- do_version = 1;
+ case 'p':
+ print_database = 1;
break;
default:
usage (1);
}
- if (do_version)
+ argc -= optind;
+ argv += optind;
+
+ /* It doesn't make sense to use --print with either of
+ --bourne or --c-shell. */
+ if (print_database && syntax != SHELL_SYNTAX_UNKNOWN)
{
- printf ("%s - %s\n", program_name, PACKAGE_VERSION);
- exit (0);
+ error (0, 0,
+ _("the options to output dircolors' internal database and\n\
+to select a shell syntax are mutually exclusive"));
+ usage (1);
}
- if (do_help)
- usage (0);
-
- /* Use shell to determine mode, if not already done. */
- if (mode == MO_UNKNOWN)
- mode = figure_mode ();
+ if (print_database && argc > 0)
+ {
+ error (0, 0,
+ _("no FILE arguments may be used with the option to output\n\
+dircolors' internal database"));
+ usage (1);
+ }
- /* Open dir_colors file */
- if (optind == argc)
+ if (!print_database && argc > 1)
{
- p = getenv ("HOME");
- if (p != NULL && *p != '\0')
- {
- /* Note: deliberate leak. It's not worth freeing this. */
- input_file = xmalloc (strlen (p) + 1
- + strlen (USER_FILE) + 1);
- stpcpy (stpcpy (stpcpy (input_file, p), "/"), USER_FILE);
- fp = fopen (input_file, "r");
- }
+ error (0, 0, _("too many arguments"));
+ usage (1);
+ }
- if (fp == NULL)
+ if (print_database)
+ {
+ int i;
+ for (i = 0; i < G_N_LINES; i++)
{
- /* Note: deliberate leak. It's not worth freeing this. */
- input_file = xmalloc (strlen (SHAREDIR) + 1
- + strlen (USER_FILE) + 1);
- stpcpy (stpcpy (stpcpy (input_file, SHAREDIR), "/"),
- SYSTEM_FILE);
- fp = fopen (input_file, "r");
+ fwrite (G_line[i], 1, G_line_length[i], stdout);
+ fputc ('\n', stdout);
}
}
else
{
- input_file = argv[optind];
- fp = fopen (input_file, "r");
- }
-
- if (fp == NULL)
- error (1, errno, _("while opening input file `%s'"), input_file);
-
- /* Get terminal type */
- term = getenv ("TERM");
- if (term == NULL || *term == '\0')
- term = "none";
-
- /* Write out common start */
- switch (mode)
- {
- case MO_CSH:
- puts ("set noglob;\n\
-setenv LS_COLORS \':");
- break;
- case MO_SH:
- case MO_KSH:
- case MO_ZSH:
- fputs ("LS_COLORS=\'", stdout);
- break;
- }
+ /* If shell syntax was not explicitly specified, try to guess it. */
+ if (syntax == SHELL_SYNTAX_UNKNOWN)
+ {
+ syntax = guess_shell_syntax ();
+ if (syntax == SHELL_SYNTAX_UNKNOWN)
+ {
+ error (EXIT_FAILURE, 0,
+ _("no SHELL environment variable, and no shell type option given"));
+ }
+ }
- state = ST_GLOBAL;
+ obstack_init (&lsc_obstack);
+ if (argc == 0)
+ err = dc_parse_stream (NULL, NULL);
+ else
+ err = dc_parse_file (argv[0]);
- /* FIXME: use getline */
- while (fgets (line, STRINGLEN, fp) != NULL)
- {
- parse_line (&keywd, &arg, line);
- if (*keywd != '\0')
+ if (!err)
{
- if (strcasecmp (keywd, "TERM") == 0)
+ size_t len = obstack_object_size (&lsc_obstack);
+ char *s = obstack_finish (&lsc_obstack);
+ const char *prefix;
+ const char *suffix;
+
+ if (syntax == SHELL_SYNTAX_BOURNE)
{
- if (strcmp (arg, term) == 0)
- state = ST_TERMSURE;
- else if (state != ST_TERMSURE)
- state = ST_TERMNO;
+ prefix = "LS_COLORS='";
+ suffix = "';\nexport LS_COLORS\n";
}
else
{
- if (state == ST_TERMSURE)
- state = ST_TERMYES; /* Another TERM can cancel */
-
- if (state != ST_TERMNO)
- {
- if (keywd[0] == '.')
- {
- putchar ('*');
- put_seq (keywd, '=');
- put_seq (arg, ':');
- }
- else if (keywd[0] == '*')
- {
- put_seq (keywd, '=');
- put_seq (arg, ':');
- }
- else if (strcasecmp (keywd, "OPTIONS") == 0)
- {
- strcat (useropts, " ");
- strcat (useropts, arg);
- }
- else if (strcasecmp (keywd, "COLOR") == 0)
- {
- /* FIXME: ignored now. */
- }
- else
- {
- int i;
-
- for (i = 0; slack_codes[i] != NULL; ++i)
- if (strcasecmp (keywd, slack_codes[i]) == 0)
- break;
-
- if (slack_codes[i] != NULL)
- {
- printf ("%s=", ls_codes[i]);
- put_seq (arg, ':');
- }
- else
- error (0, 0, _("Unknown keyword %s\n"), keywd);
- }
- }
+ prefix = "setenv LS_COLORS '";
+ suffix = "'\n";
}
+ fputs (prefix, stdout);
+ fwrite (s, 1, len, stdout);
+ fputs (suffix, stdout);
}
}
- fclose (fp);
+ if (fclose (stdout) == EOF)
+ error (EXIT_FAILURE, errno, _("write error"));
- /* Write it out. */
- switch (mode)
- {
- case MO_SH:
- case MO_KSH:
- case MO_ZSH:
- printf ("\';\nexport LS_COLORS;\n");
- break;
-
- case MO_CSH:
- printf ("\';\nunset noglob;\n");
- break;
-
- default:
- abort ();
- }
+ if (have_read_stdin && fclose (stdin) == EOF)
+ error (EXIT_FAILURE, errno, _("standard input"));
- exit (0);
+ exit (err == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}