(main): Use fputs, not puts. Avoids spurious newline.
[platform/upstream/coreutils.git] / src / dircolors.c
1 /* FIXME: accept, but ignore EIGHTBIT option
2    FIXME: embed contents of default mapping file
3    FIXME: add option to print that default mapping?
4  */
5 /* dircolors - parse a Slackware-style DIR_COLORS file.
6    Copyright (C) 1994, 1995 H. Peter Anvin
7    Copyright (C) 1996 Free Software Foundation, Inc.
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
12 any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <ctype.h>
28 #include <getopt.h>
29 #include <stdio.h>
30
31 #include "system.h"
32 #include "error.h"
33
34 char *xmalloc ();
35
36 #define USER_FILE ".dir_colors" /* Versus user's home directory */
37 #define SYSTEM_FILE "DIR_COLORS" /* System-wide file in directory SYSTEM_DIR
38                                      (defined on the cc command line).  */
39
40 #define STRINGLEN 2048          /* Max length of a string */
41
42 enum modes { MO_SH, MO_CSH, MO_KSH, MO_ZSH, MO_UNKNOWN, MO_ERR };
43
44 /* FIXME: associate these arrays? */
45 static const char *const shells[] =
46 { "sh", "ash", "csh", "tcsh", "bash", "ksh", "zsh", NULL };
47
48 static const int shell_mode[] =
49 { MO_SH, MO_SH, MO_CSH, MO_CSH, MO_KSH, MO_KSH, MO_ZSH };
50
51 /* Parser needs these state variables.  */
52 enum states { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL };
53
54 /* FIXME: associate with ls_codes? */
55 static const char *const slack_codes[] =
56 {
57   "NORMAL", "NORM", "FILE", "DIR", "LNK", "LINK",
58   "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK",
59   "CHR", "CHAR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE", "END",
60   "ENDCODE", NULL
61 };
62
63 static const char *const ls_codes[] =
64 {
65   "no", "no", "fi", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
66   "so", "bd", "bd", "cd", "cd", "ex", "lc", "lc", "rc", "rc", "ec", "ec"
67 };
68
69 enum color_opts { col_yes, col_no, col_tty };
70
71 static struct option const long_options[] =
72   {
73     {"ash", no_argument, NULL, 'a'},
74     {"bash", no_argument, NULL, 'b'},
75     {"csh", no_argument, NULL, 'c'},
76     {"help", no_argument, NULL, 'h'},
77     {"no-path", no_argument, NULL, 'P'},
78     {"sh", no_argument, NULL, 's'},
79     {"tcsh", no_argument, NULL, 't'},
80     {"version", no_argument, NULL, 'v'},
81     {"zsh", no_argument, NULL, 'z'},
82   };
83
84 char *program_name;
85
86 static void
87 usage (int status)
88 {
89   if (status != 0)
90     fprintf (stderr, _("Try `%s --help' for more information.\n"),
91              program_name);
92   else
93     {
94       printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name);
95       printf (_("\
96   -h, --help        display this help and exit\n\
97   -P, --no-path     do not look for shell in PATH\n\
98       --version     output version information and exit\n\
99 Determine format of output:\n\
100   -a, --ash         assume ash shell\n\
101   -b, --bash        assume bash shell\n\
102   -c, --csh         assume csh shell\n\
103   -s, --sh          assume Bourne shell\n\
104   -t, --tcsh        assume tcsh shell\n\
105   -z, --zsh         assume zsh shell\n"));
106     }
107
108   exit (status);
109 }
110
111 static int
112 figure_mode (void)
113 {
114   char *shell, *shellv;
115   int i;
116
117   shellv = getenv ("SHELL");
118   if (shellv == NULL || *shellv == '\0')
119     error (1, 0, _("\
120 No SHELL variable, and no mode option specified"));
121
122   shell = strrchr (shellv, '/');
123   if (shell != NULL)
124     ++shell;
125   else
126     shell = shellv;
127
128   for (i = 0; shells[i]; ++i)
129     if (strcmp (shell, shells[i]) == 0)
130       return shell_mode[i];
131
132   error (1, 0, _("Unknown shell `%s'\n"), shell);
133   /* NOTREACHED */
134 }
135
136 static void
137 parse_line (char **keyword, char **arg, char *line)
138 {
139   char *p;
140
141   *keyword = *arg = "";
142
143   for (p = line; isspace (*p); ++p)
144     ;
145
146   if (*p == '\0' || *p == '#')
147     return;
148
149   *keyword = p;
150
151   while (!isspace (*p))
152     if (*p++ == '\0')
153       return;
154
155   *p++ = '\0';
156
157   while (isspace (*p))
158     ++p;
159
160   if (*p == '\0' || *p == '#')
161     return;
162
163   *arg = p;
164
165   while (*p != '\0' && *p != '#')
166     ++p;
167   for (--p; isspace (*p); --p)
168     ;
169   ++p;
170
171   *p = '\0';
172 }
173
174 /* Write a string to standard out, while watching for "dangerous"
175    sequences like unescaped : and = characters.  */
176
177 static void
178 put_seq (const char *str, char follow)
179 {
180   int danger = 1;
181
182   while (*str != '\0')
183     {
184       switch (*str)
185         {
186         case '\\':
187         case '^':
188           danger = !danger;
189           break;
190
191         case ':':
192         case '=':
193           if (danger)
194             putchar ('\\');
195           /* Fall through */
196
197         default:
198           danger = 1;
199           break;
200         }
201
202       putchar (*str++);
203     }
204
205   putchar (follow);             /* The character that ends the sequence.  */
206 }
207
208 int
209 main (int argc, char *argv[])
210 {
211   char *p, *q;
212   int optc;
213   int mode = MO_UNKNOWN;
214   FILE *fp = NULL;
215   char *term;
216   int state;
217
218   char line[STRINGLEN];
219   char useropts[2048] = "";
220   char *keywd, *arg;
221
222   int color_opt = col_no;       /* Assume --color=no */
223
224   int no_path = 0;              /* Do not search PATH */
225   int do_help = 0;
226   int do_version = 0;
227
228   const char *copt;
229   char *input_file;
230
231   program_name = argv[0];
232   setlocale (LC_ALL, "");
233   bindtextdomain (PACKAGE, LOCALEDIR);
234   textdomain (PACKAGE);
235
236   /* Parse command line.  */
237
238   while ((optc = getopt_long (argc, argv, "abhckPstz", long_options, NULL))
239          != EOF)
240     switch (optc)
241       {
242       case 'a':
243       case 's': /* Plain sh mode */
244         mode = MO_SH;
245         break;
246
247       case 'c':
248       case 't':
249         mode = MO_CSH;
250         break;
251
252       case 'b':
253       case 'k':
254         mode = MO_KSH;
255         break;
256
257       case 'h':
258         do_help = 1;
259         break;
260
261       case 'z':
262         mode = MO_ZSH;
263         break;
264
265       case 'P':
266         no_path = 1;
267         break;
268
269       case 'v':
270         do_version = 1;
271         break;
272
273       default:
274         usage (1);
275       }
276
277   if (do_version)
278     {
279       printf ("%s - %s\n", program_name, PACKAGE_VERSION);
280       exit (0);
281     }
282
283   if (do_help)
284     usage (0);
285
286   /* Use shell to determine mode, if not already done. */
287   if (mode == MO_UNKNOWN)
288     mode = figure_mode ();
289
290   /* Open dir_colors file */
291   if (optind == argc)
292     {
293       p = getenv ("HOME");
294       if (p != NULL && *p != '\0')
295         {
296           /* Note: deliberate leak.  It's not worth freeing this.  */
297           input_file = xmalloc (strlen (p) + 1
298                                 + strlen (USER_FILE) + 1);
299           stpcpy (stpcpy (stpcpy (input_file, p), "/"), USER_FILE);
300           fp = fopen (input_file, "r");
301         }
302
303       if (fp == NULL)
304         {
305           /* Note: deliberate leak.  It's not worth freeing this.  */
306           input_file = xmalloc (strlen (SHAREDIR) + 1
307                                 + strlen (USER_FILE) + 1);
308           stpcpy (stpcpy (stpcpy (input_file, SHAREDIR), "/"),
309                   SYSTEM_FILE);
310           fp = fopen (input_file, "r");
311         }
312     }
313   else
314     {
315       input_file = argv[optind];
316       fp = fopen (input_file, "r");
317     }
318
319   if (fp == NULL)
320     error (1, errno, _("while opening input file `%s'"), input_file);
321
322   /* Get terminal type */
323   term = getenv ("TERM");
324   if (term == NULL || *term == '\0')
325     term = "none";
326
327   /* Write out common start */
328   switch (mode)
329     {
330     case MO_CSH:
331       puts ("set noglob;\n\
332 setenv LS_COLORS \':");
333       break;
334     case MO_SH:
335     case MO_KSH:
336     case MO_ZSH:
337       fputs ("LS_COLORS=\'", stdout);
338       break;
339     }
340
341   state = ST_GLOBAL;
342
343   /* FIXME: use getline */
344   while (fgets (line, STRINGLEN, fp) != NULL )
345     {
346       parse_line (&keywd, &arg, line);
347       if (*keywd != '\0')
348         {
349           if (strcasecmp (keywd, "TERM") == 0)
350             {
351               if (strcmp (arg, term) == 0)
352                 state = ST_TERMSURE;
353               else if (state != ST_TERMSURE)
354                 state = ST_TERMNO;
355             }
356           else
357             {
358               if (state == ST_TERMSURE)
359                 state = ST_TERMYES; /* Another TERM can cancel */
360
361               if (state != ST_TERMNO)
362                 {
363                   if (keywd[0] == '.')
364                     {
365                       putchar ('*');
366                       put_seq (keywd, '=');
367                       put_seq (arg, ':');
368                     }
369                   else if (keywd[0] == '*')
370                     {
371                       put_seq (keywd, '=');
372                       put_seq (arg, ':');
373                     }
374                   else if (strcasecmp(keywd, "OPTIONS") == 0)
375                     {
376                       strcat (useropts, " ");
377                       strcat (useropts, arg);
378                     }
379                   else if (strcasecmp(keywd, "COLOR") == 0)
380                     {
381                       switch (arg[0])
382                         {
383                         case 'a':
384                         case 'y':
385                         case '1':
386                           color_opt = col_yes;
387                           break;
388
389                         case 'n':
390                         case '0':
391                           color_opt = col_no;
392                           break;
393
394                         case 't':
395                           color_opt = col_tty;
396                           break;
397
398                         default:
399                           error (0, 0, _("Unknown COLOR option `%s'\n"), arg);
400                           break;
401                         }
402                     }
403                   else
404                     {
405                       int i;
406
407                       for (i = 0; slack_codes[i] != NULL; ++i)
408                         if (strcasecmp (keywd, slack_codes[i]) == 0)
409                           break;
410
411                       if (slack_codes[i] != NULL)
412                         {
413                           printf ("%s=", ls_codes[i]);
414                           put_seq (arg, ':');
415                         }
416                       else
417                         error (0, 0, _("Unknown keyword %s\n"), keywd);
418                     }
419                 }
420             }
421         }
422     }
423
424   fclose (fp);
425
426   /* Decide on the options.  */
427   switch (color_opt)
428     {
429     case col_yes:
430       copt = "--color=yes";
431       break;
432
433     case col_no:
434       copt = "--color=no";
435       break;
436
437     case col_tty:
438       copt = "--color=tty";
439       break;
440     }
441
442   /* Find ls in the path.  */
443   if (no_path == 0)
444     {
445       no_path = 1;              /* Assume we won't find one.  */
446
447       p = getenv ("PATH");
448       if (p != NULL && *p != '\0')
449         {
450           while (*p != '\0')
451             {
452               while (*p == ':')
453                 ++p;
454
455               if (*p != '/')    /* Skip relative path entries.  */
456                 while (*p != '\0' && *p != ':')
457                   ++p;
458               else
459                 {
460                   q = line;
461                   while (*p != '\0' && *p != ':')
462                     *q++ = *p++;
463                   /* Make sure it ends in slash.  */
464                   if (*(q-1) != '/' )
465                     *q++ = '/';
466
467                   strcpy (q, "ls");
468                   if (access (line, X_OK) == 0)
469                     {
470                       no_path = 0; /* Found it.  */
471                       break;
472                     }
473                 }
474             }
475         }
476     }
477
478   /* Write it out.  */
479   switch (mode)
480     {
481     case MO_SH:
482       if (no_path)
483         printf ("\';\n\
484 export LS_COLORS;\n\
485 LS_OPTIONS='%s%s';\n\
486 export LS_OPTIONS;\n\
487 ls () { ( exec ls $LS_OPTIONS \"$@\" ) };\n\
488 dir () { ( exec dir $LS_OPTIONS \"$@\" ) };\n\
489 vdir () { ( exec vdir $LS_OPTIONS \"$@\" ) };\n\
490 d () { dir \"$@\" ; };\n\
491 v () { vdir \"$@\" ; };\n", copt, useropts);
492       else
493         printf ("\';\n\
494 export LS_COLORS;\n\
495 LS_OPTIONS='%s%s';\n\
496 ls () { %s $LS_OPTIONS \"$@\" ; };\n\
497 dir () { %s $LS_OPTIONS --format=vertical \"$@\" ; };\n\
498 vdir () { %s $LS_OPTIONS --format=long \"$@\" ; };\n\
499 d () { dir \"$@\" ; };\n\
500 v () { vdir \"$@\" ; };\n", copt, useropts, line, line, line);
501       break;
502
503     case MO_CSH:
504       if (no_path)
505         printf ("\';\n\
506 setenv LS_OPTIONS '%s%s';\n\
507 alias ls \'ls $LS_OPTIONS\';\n\
508 alias dir \'dir $LS_OPTIONS\';\n\
509 alias vdir \'vdir $LS_OPTIONS\';\n\
510 alias d dir;\n\
511 alias v vdir;\n\
512 unset noglob;\n", copt, useropts);
513       else
514         printf ("\';\n\
515 setenv LS_OPTIONS '%s%s';\n\
516 alias ls \'%s $LS_OPTIONS\';\n\
517 alias dir \'%s $LS_OPTIONS --format=vertical\';\n\
518 alias vdir \'%s $LS_OPTIONS --format=long\';\n\
519 alias d dir;\n\
520 alias v vdir;\n\
521 unset noglob;\n", copt, useropts, line, line, line);
522       break;
523
524     case MO_KSH:
525       if (no_path)
526         printf ("\';\n\
527 export LS_COLORS;\n\
528 LS_OPTIONS='%s%s';\n\
529 export LS_OPTIONS;\n\
530 alias ls=\'ls $LS_OPTIONS\';\n\
531 alias dir=\'dir $LS_OPTIONS\';\n\
532 alias vdir=\'vdir $LS_OPTIONS\';\n\
533 alias d=dir;\n\
534 alias v=vdir;\n", copt, useropts);
535       else
536         printf ("\';\n\
537 export LS_COLORS;\n\
538 LS_OPTIONS='%s%s';\n\
539 export LS_OPTIONS;\n\
540 alias ls=\'%s $LS_OPTIONS\';\n\
541 alias dir=\'%s $LS_OPTIONS --format=vertical\';\n\
542 alias vdir=\'%s $LS_OPTIONS --format=long\';\n\
543 alias d=dir;\n\
544 alias v=vdir;\n", copt, useropts, line, line, line);
545       break;
546
547     case MO_ZSH:
548       if (no_path)
549         printf ("\';\n\
550 export LS_COLORS;\n\
551 LS_OPTIONS=(%s%s);\n\
552 export LS_OPTIONS;\n\
553 alias ls=\'ls $LS_OPTIONS\';\n\
554 alias dir=\'dir $LS_OPTIONS\';\n\
555 alias vdir=\'vdir $LS_OPTIONS\';\n\
556 alias d=dir;\n\
557 alias v=vdir;\n", copt, useropts);
558       else
559         printf ("\';\n\
560 export LS_COLORS;\n\
561 LS_OPTIONS=(%s%s);\n\
562 export LS_OPTIONS;\n\
563 alias ls=\'%s $LS_OPTIONS\';\n\
564 alias dir=\'%s $LS_OPTIONS --format=vertical\';\n\
565 alias vdir=\'%s $LS_OPTIONS --format=long\';\n\
566 alias d=dir;\n\
567 alias v=vdir;\n", copt, useropts, line, line, line);
568       break;
569     }
570
571   exit (0);
572 }