1 /*
2   command line option processing
3 
4   Copyright (C) 2000-2003 David Necas (Yeti) <yeti@physics.muni.cz>
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of version 2 of the GNU General Public License as published
8   by the Free Software Foundation.
9 
10   This program is distributed in the hope that it will be useful, but WITHOUT
11   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13   more details.
14 
15   You should have received a copy of the GNU General Public License along
16   with this program; if not, write to the Free Software Foundation, Inc.,
17   59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
18 */
19 #include "common.h"
20 
21 #ifdef HAVE_GETOPT_H
22 # include <getopt.h>
23 #else /* HAVE_GETOPT_H */
24 # include "getopt.h"
25 #endif /* HAVE_GETOPT_H */
26 
27 #ifdef HAVE_WORDEXP
28 # ifdef HAVE_WORDEXP_H
29 #  include <wordexp.h>
30 # else /* HAVE_WORDEXP_H */
31 /* don't declare all the stuff from wordexp.h when not present, it would
32    probably not work anyway---just don't use it */
33 #  undef HAVE_WORDEXP
34 # endif /* HAVE_WORDEXP_H */
35 #endif /* HAVE_WORDEXP */
36 
37 typedef void (* ReportFunc)(void);
38 
39 /* Program behaviour: enca or enconv. */
40 typedef enum {
41   BEHAVE_ENCA,
42   BEHAVE_ENCONV
43 } ProgramBehaviour;
44 
45 /* Settings. */
46 char *program_name = NULL;
47 static ProgramBehaviour behaviour = BEHAVE_ENCA;
48 Options options;
49 
50 /* Environment variable containing default options. */
51 static const char *ENCA_ENV_VAR = "ENCAOPT";
52 
53 /* Environment variable containing default target charset (think recode). */
54 static const char *RECODE_CHARSET_VAR = "DEFAULT_CHARSET";
55 
56 /* Default option values. */
57 static const Options DEFAULTS = {
58   0, /* verbosity_level */
59   NULL, /* language */
60   OTYPE_HUMAN, /* output_type */
61   { ENCA_CS_UNKNOWN, 0 }, /* target_enc */
62   NULL, /* target_enc_str */
63   -1, /* prefix_filename */
64 };
65 
66 extern const char *const COPYING_text[];
67 extern const char *const    HELP_text[];
68 
69 /* Version/copyright text. */
70 static const char *VERSION_TEXT = /* {{{ */
71 "Features: "
72 #ifdef HAVE_LIBRECODE
73 "+"
74 #else /* HAVE_LIBRECODE */
75 "-"
76 #endif /* HAVE_LIBRECODE */
77 "librecode-interface "
78 
79 #ifdef HAVE_GOOD_ICONV
80 "+"
81 #else /* HAVE_GOOD_ICONV */
82 "-"
83 #endif /* HAVE_GOOD_ICONV */
84 "iconv-interface "
85 
86 #ifdef ENABLE_EXTERNAL
87 "+"
88 #else /* ENABLE_EXTERNAL */
89 "-"
90 #endif /* ENABLE_EXTERNAL */
91 "external-converter "
92 
93 #ifdef HAVE_SETLOCALE
94 "+"
95 #else /* HAVE_SETLOCALE */
96 "-"
97 #endif /* HAVE_SETLOCALE */
98 "language-detection "
99 
100 #ifdef HAVE_LOCALE_ALIAS
101 "+"
102 #else /* HAVE_LOCALE_ALIAS */
103 "-"
104 #endif /* HAVE_LOCALE_ALIAS */
105 "locale-alias "
106 
107 #ifdef HAVE_NL_LANGINFO
108 "+"
109 #else /* HAVE_NL_LANGINFO */
110 "-"
111 #endif /* HAVE_NL_LANGINFO */
112 "target-charset-auto "
113 
114 #ifdef HAVE_WORDEXP
115 "+"
116 #else /* HAVE_WORDEXP */
117 "-"
118 #endif /* HAVE_WORDEXP */
119 "ENCAOPT ";
120 /* }}} */
121 
122 static const char *COPYRIGHT_TEXT = /* {{{ */
123 "Copyright (C) 2000-2005 David Necas (Yeti) (<yeti@physics.muni.cz>),\n"
124 "              2005 Zuxy Meng (<zuxy.meng@gmail.com>).\n"
125 "\n"
126 PACKAGE_NAME
127 " is free software; it can be copied and/or modified under the terms of\n"
128 "version 2 of GNU General Public License, run `enca --license' to see the full\n"
129 "license text.  There is NO WARRANTY; not even for MERCHANTABILITY or FITNESS\n"
130 "FOR A PARTICULAR PURPOSE.";
131 /* }}} */
132 
133 /* Local prototypes. */
134 static char**     interpret_opt          (int argc,
135                                           char *argv[],
136                                           int cmdl_argc);
137 static int        prepend_env            (int argc,
138                                           char *argv[],
139                                           int *newargc,
140                                           char *(*newargv[]));
141 static OutputType optchar_to_otype       (const char c);
142 static void       set_otype_from_name    (const char *otname);
143 static void       set_program_behaviour  (void);
144 static int        parse_arg_x            (const char *s);
145 static int        add_parsed_converters  (const char *list);
146 static void       print_some_list        (const char *listname);
147 static char**     make_filelist          (const int n,
148                                           char *argvrest[]);
149 static int        prefix_filename        (int pfx);
150 #ifndef HAVE_PROGRAM_INVOCATION_SHORT_NAME
151 # define program_invocation_short_name strip_path(argv[0])
152 static char*      strip_path             (const char *fullpath);
153 #endif /* not HAVE_PROGRAM_INVOCATION_SHORT_NAME */
154 static void       print_version          (void);
155 static void       print_all_charsets     (void);
156 static void       print_builtin_charsets (void);
157 static void       print_surfaces         (void);
158 static void       print_languages        (void);
159 static void       print_lists            (void);
160 static void       print_names            (void);
161 static void       print_charsets         (int only_builtin);
162 static void       print_text_and_exit    (const char *const *text,
163                                           int exitcode);
164 
165 /* merge all sources of options (ENCAOPT and command line arguments) and
166    process them
167    returns list of file to process (or NULL for stdin) */
168 char**
process_opt(const int argc,char * argv[])169 process_opt(const int argc, char *argv[])
170 {
171   int newargc;
172   char **newargv;
173   char **flist;
174 
175   /* Assign defaults. */
176   options = DEFAULTS;
177 
178   program_name = program_invocation_short_name;
179   set_program_behaviour();
180 
181 #ifdef ENABLE_EXTERNAL
182   set_external_converter(DEFAULT_EXTERNAL_CONVERTER);
183 #endif /* ENABLE_EXTERNAL */
184 
185   /* Prepend options in $ENCAOPT. */
186   prepend_env(argc, argv, &newargc, &newargv);
187 
188   /* Interpret them. */
189   flist = interpret_opt(newargc, newargv, argc);
190 
191   /* prefix result with file name iff we are about to process stdin or the
192      file list contains only one file and we don't print result */
193   if (prefix_filename(-1) == -1) {
194     if ((flist == NULL || flist[1] == NULL)
195         && options.output_type != OTYPE_DETAILS)
196       prefix_filename(0);
197     else
198       prefix_filename(1);
199   }
200 
201   return flist;
202 }
203 
204 /* process options, return file list (i.e. all remaining arguments)
205 
206    FIXME: this function is infinitely ugly */
207 static char**
interpret_opt(int argc,char * argv[],int cmdl_argc)208 interpret_opt(int argc, char *argv[], int cmdl_argc)
209 {
210   /* Short command line options. */
211   static const char *short_options =
212     "cC:deE:fgGhil:L:mn:pPrsvVx:";
213 
214   /* Long `GNU style' command line options {{{. */
215   static const struct option long_options[] = {
216     { "auto-convert", no_argument, NULL, 'c' },
217     { "convert-to", required_argument, NULL, 'x' },
218     { "cstocs-name", no_argument, NULL, 's' },
219     { "details", no_argument, NULL, 'd' },
220     { "enca-name", no_argument, NULL, 'e' },
221     { "external-converter-program", required_argument, NULL, 'E' },
222     { "guess", no_argument, NULL, 'g' },
223     { "help", no_argument, NULL, 'h' },
224     { "human-readable", no_argument, NULL, 'f' },
225     { "iconv-name", no_argument, NULL, 'i' },
226     { "language", required_argument, NULL, 'L' },
227     { "license", no_argument, NULL, 'G' },
228     { "list", required_argument, NULL, 'l' },
229     { "mime-name", no_argument, NULL, 'm' },
230     { "name", required_argument, NULL, 'n' },
231     { "no-filename", no_argument, NULL, 'P' },
232     { "rfc1345-name", no_argument, NULL, 'r' },
233     { "try-converters", required_argument, NULL, 'C' },
234     { "verbose", no_argument, NULL, 'V' },
235     { "version", no_argument, NULL, 'v' },
236     { "with-filename", no_argument, NULL, 'p' },
237     { NULL, 0, NULL, 0 }
238   };
239   /* }}} */
240 
241   int c;
242   char **filelist;
243   int otype_set = 0; /* Whether output type was explicitely set. */
244 
245   /* Process options. */
246   opterr = 0; /* Getopt() shouldn't print errors, we do it ourself. */
247   while ((c = getopt_long(argc, argv, short_options,
248                           long_options, NULL)) != -1) {
249     switch (c) {
250       case '?': /* Unknown option. */
251       fprintf(stderr, "%s: Unknown option -%c%s.\n"
252                       "Run `%s --help' to get brief help.\n",
253                       program_name, optopt,
254                       optopt == '\0' ? " or misspelt/ambiguous long option"
255                                      : "",
256                       program_name);
257       exit(EXIT_TROUBLE);
258       break;
259 
260       case ':': /* Missing paramter. */
261       fprintf(stderr, "%s: Option -%c requires an argument.\n"
262                       "Run `%s --help' to get brief help.\n",
263                       program_name, optopt, program_name);
264       exit(EXIT_TROUBLE);
265       break;
266 
267       case 'h': /* Help (and exit). */
268       print_text_and_exit(HELP_text, EXIT_SUCCESS);
269       break;
270 
271       case 'v': /* Version (and exit). */
272       print_version();
273       exit(EXIT_SUCCESS);
274       break;
275 
276       case 'G': /* License (and exit). */
277       print_text_and_exit(COPYING_text, EXIT_SUCCESS);
278       break;
279 
280       case 'l': /* Print required list (and exit). */
281       print_some_list(optarg);
282       exit(EXIT_SUCCESS);
283       break;
284 
285       case 'd': /* Detailed output. */
286       case 'e': /* Canonical name. */
287       case 'f': /* Full (descriptive) output. */
288       case 'i': /* Iconv name. */
289       case 'm': /* MIME name. */
290       case 'r': /* RFC 1345 name as output. */
291       case 's': /* Cstocs name as output. */
292       options.output_type = optchar_to_otype(c);
293       otype_set = 1;
294       break;
295 
296       case 'n': /* Output type by name. */
297       set_otype_from_name(optarg);
298       otype_set = 1;
299       break;
300 
301       case 'p': /* Prefix filename on. */
302       case 'P': /* Prefix filename off. */
303       prefix_filename(islower(c));
304       break;
305 
306       case 'g': /* Behave enca. */
307       behaviour = BEHAVE_ENCA;
308       break;
309 
310       case 'c': /* Behave enconv. */
311       behaviour = BEHAVE_ENCONV;
312       break;
313 
314       case 'V': /* Increase verbosity level. */
315       options.verbosity_level++;
316       break;
317 
318       case 'x': /* Convert to. */
319       options.output_type = OTYPE_CONVERT;
320       parse_arg_x(optarg);
321       otype_set = 1;
322       break;
323 
324       case 'L': /* Language. */
325       options.language = optarg;
326       break;
327 
328       case 'C': /* Add converters to converter list. */
329       add_parsed_converters(optarg);
330       break;
331 
332       case 'E': /* Converter name. */
333 #ifdef ENABLE_EXTERNAL
334       set_external_converter(optarg);
335 #else /* ENABLE_EXTERNAL */
336       fprintf(stderr, "%s: Cannot set external converter.\n"
337                       "Enca was built without support "
338                       "for external converters.\n",
339                       program_name);
340 #endif /* ENABLE_EXTERNAL */
341       break;
342 
343       default:
344       abort();
345       break;
346     }
347   }
348 
349   /* Set and initialize language. */
350   options.language = detect_lang(options.language);
351   if (options.language == NULL) {
352     fprintf(stderr, "%s: Cannot determine (or understand) "
353                     "your language preferences.\n"
354                     "Please use `-L language', or `-L none' if your language is not supported\n"
355                     "(only a few multibyte encodings can be recognized then).\n"
356                     "Run `%s --list languages' to get a list of supported languages.\n",
357                     program_name, program_name);
358     exit(EXIT_TROUBLE);
359   }
360 
361   /* Behaviour. */
362   /* With an explicit output type doesn't matter how we were called. */
363   if (otype_set) {
364     behaviour = BEHAVE_ENCA;
365     if (options.output_type == OTYPE_CONVERT
366         && options.verbosity_level > 2)
367       fprintf(stderr, "Explicitly specified target charset: %s\n",
368                       options.target_enc_str);
369   }
370 
371   switch (behaviour) {
372     case BEHAVE_ENCA:
373     /* Nothing special here. */
374     break;
375 
376     case BEHAVE_ENCONV:
377     {
378       const char *charset;
379 
380       /* Try recode's default target charset. */
381       charset = getenv(RECODE_CHARSET_VAR);
382       if (charset != NULL) {
383         if (options.verbosity_level > 2)
384           fprintf(stderr, "Inherited recode's %s target charset: %s\n",
385                           RECODE_CHARSET_VAR, charset);
386       }
387       else {
388         /* Then locale native charset. */
389         charset = get_lang_codeset();
390         assert(charset != NULL);
391       }
392 
393       parse_arg_x(charset);
394     }
395     if (options.target_enc_str[0] == '\0') {
396       fprintf(stderr, "%s: Cannot detect native charset for locale %s.\n"
397                       "You have to use the `-x' option "
398                       "or the %s environment variable "
399                       "to set the target encoding manually.\n",
400                       program_name,
401                       options.language,
402                       RECODE_CHARSET_VAR);
403       exit(EXIT_TROUBLE);
404     }
405     options.output_type = OTYPE_CONVERT;
406     break;
407 
408     default:
409     abort();
410     break;
411   }
412 
413   /* Set up default list of converters. */
414   if (add_parsed_converters(NULL) == 0)
415     add_parsed_converters(DEFAULT_CONVERTER_LIST);
416 
417   /* Create file list from remaining options. */
418   filelist = make_filelist(argc-optind, argv+optind);
419   /* When run without any arguments and input is a tty, print help. */
420   if (filelist == NULL && enca_isatty(STDIN_FILENO) && cmdl_argc == 1)
421     print_text_and_exit(HELP_text, EXIT_SUCCESS);
422 
423 #ifdef ENABLE_EXTERNAL
424   if (options.output_type == OTYPE_CONVERT
425       && external_converter_listed()
426       && !check_external_converter())
427     exit(EXIT_TROUBLE);
428 #endif
429 
430   return filelist;
431 }
432 
433 /* prepend parsed contents of environment variable containing default options
434    (ENCAOPT) before command line arguments (but after argv[0]) and return the
435    new list of arguments in newargv (its length is newargc) */
436 static int
prepend_env(int argc,char * argv[],int * newargc,char * (* newargv[]))437 prepend_env(int argc,
438             char *argv[],
439             int *newargc,
440             char *(*newargv[]))
441 #ifdef HAVE_WORDEXP
442 {
443   char *msg;
444   char *encaenv;
445   wordexp_t encaenv_parsed;
446   size_t i;
447 
448   *newargc = argc;
449   *newargv = argv;
450   /* Fetch value of ENCA_ENV_VAR, if set. */
451   encaenv = getenv(ENCA_ENV_VAR);
452   if (encaenv == NULL)
453     return 0;
454 
455   /* Parse encaenv. */
456   if ((i = wordexp(encaenv, &encaenv_parsed, WRDE_NOCMD)) != 0) {
457     switch (i) {
458       case WRDE_NOSPACE:
459       wordfree(&encaenv_parsed);
460       fprintf(stderr, "%s: Cannot allocate memory.\n",
461                       program_name);
462       exit(EXIT_TROUBLE);
463       break;
464 
465       case WRDE_BADCHAR:
466       msg = "invalid characters";
467       break;
468 
469       case WRDE_CMDSUB:
470       msg = "command substitution is disabled";
471       break;
472 
473       case WRDE_SYNTAX:
474       msg = "syntax error";
475       break;
476 
477       default:
478       msg = NULL;
479       break;
480     }
481     fprintf(stderr, "%s: Cannot parse value of %s (",
482                     program_name, ENCA_ENV_VAR);
483     if (msg == NULL)
484       fprintf(stderr, "error %zd", i);
485     else
486       fprintf(stderr, "%s", msg);
487 
488     fputs("), ignoring it\n", stderr);
489 
490     return 1;
491   }
492 
493   /* create newargv starting from argv[0], then encaenv_parsed, and last rest
494      of argv; note we copy addresses, not strings themselves from argv */
495   *newargc = argc + encaenv_parsed.we_wordc;
496   *newargv = (char**)enca_malloc((*newargc)*sizeof(char*));
497   (*newargv)[0] = argv[0];
498 
499   for (i = 0; i < encaenv_parsed.we_wordc; i++)
500     (*newargv)[i+1] = enca_strdup(encaenv_parsed.we_wordv[i]);
501 
502   for (i = 1; i < (size_t)argc; i++)
503     (*newargv)[i + encaenv_parsed.we_wordc] = argv[i];
504 
505   /* Free memory. */
506   wordfree(&encaenv_parsed);
507 
508   return 0;
509 }
510 #else /* HAVE_WORDEXP */
511 {
512   char *encaenv;
513   size_t nitems;
514   size_t i, state;
515   const char *p;
516 
517   *newargc = argc;
518   *newargv = argv;
519   /* Fetch value of ENCA_ENV_VAR, if set. */
520   encaenv = getenv(ENCA_ENV_VAR);
521   if (encaenv == NULL)
522     return 0;
523 
524   /* Count the number of tokens in ENCA_ENV_VAR. */
525   encaenv = enca_strdup(encaenv);
526   nitems = 0;
527   state = 0;
528   for (i = 0; encaenv[i] != '\0'; i++) {
529     if (state == 0) {
530       if (!isspace(encaenv[i]))
531         nitems += ++state;
532     }
533     else {
534       if (isspace(encaenv[i])) {
535         encaenv[i] = '\0';
536         state = 0;
537       }
538     }
539   }
540 
541   /* Extend argv[].  (see above) */
542   *newargc = argc + nitems;
543   *newargv = (char**)enca_malloc((*newargc)*sizeof(char*));
544   (*newargv)[0] = argv[0];
545 
546   p = encaenv;
547   for (i = 0; i < nitems; i++) {
548     while (isspace(*p))
549       p++;
550 
551     (*newargv)[i+1] = enca_strdup(p);
552 
553     while (*p != '\0')
554       p++;
555     p++;
556   }
557   enca_free(encaenv);
558 
559   for (i = 1; i < argc; i++)
560     (*newargv)[i + nitems] = argv[i];
561 
562   return 0;
563 }
564 #endif /* HAVE_WORDEXP */
565 
566 /* Return output type appropriate for given option character. */
567 static OutputType
optchar_to_otype(const char c)568 optchar_to_otype(const char c)
569 {
570   switch (c) {
571     case 'd': return OTYPE_DETAILS; /* Detailed output. */
572     case 'e': return OTYPE_CANON;   /* Enca's name. */
573     case 'f': return OTYPE_HUMAN;   /* Full (descriptive) output. */
574     case 'i': return OTYPE_ICONV;   /* Iconv name. */
575     case 'r': return OTYPE_RFC1345; /* RFC 1345 name as output */
576     case 's': return OTYPE_CS2CS;   /* Cstocs name as output. */
577     case 'm': return OTYPE_MIME;    /* Preferred MIME name as output. */
578   }
579 
580   abort();
581   return 0;
582 }
583 
584 /* if otname represents a valid output type name, assign it to *otype,
585    otherwise do nothing
586    when gets NULL as the name, prints list of valid names instead */
587 static void
set_otype_from_name(const char * otname)588 set_otype_from_name(const char *otname)
589 {
590   /* Abbreviations table stores pointers, we need something to point to. */
591   static const OutputType OTS[] = {
592     OTYPE_DETAILS,
593     OTYPE_CANON,
594     OTYPE_HUMAN,
595     OTYPE_RFC1345,
596     OTYPE_ICONV,
597     OTYPE_CS2CS,
598     OTYPE_MIME,
599     OTYPE_ALIASES
600   };
601 
602   /* Output type names. */
603   static const Abbreviation OTNAMES[] =
604   {
605     { "aliases", OTS+7 },
606     { "cstocs", OTS+4 },
607     { "details", OTS },
608     { "enca", OTS+1 },
609     { "human-readable", OTS+2 },
610     { "iconv", OTS+5 },
611     { "mime", OTS+6 },
612     { "rfc1345", OTS+3 },
613   };
614 
615   const Abbreviation *p;
616 
617   p = expand_abbreviation(otname, OTNAMES,
618                           sizeof(OTNAMES)/sizeof(Abbreviation),
619                           "output type");
620   if (p != NULL)
621     options.output_type = *(OutputType*)p->data;
622 }
623 
624 /* parse -x argument, assign output encoding */
625 static int
parse_arg_x(const char * s)626 parse_arg_x(const char *s)
627 {
628   /* Encoding separator for -x argument. */
629   static const char XENC_SEPARATOR[] = "..";
630   static const size_t XENC_SEPARATOR_LEN = sizeof(XENC_SEPARATOR);
631 
632   /* Strip leading `..' if present. */
633   if (strncmp(s, XENC_SEPARATOR, XENC_SEPARATOR_LEN) == 0)
634     s += XENC_SEPARATOR_LEN;
635 
636   /* Assign target encoding. */
637   enca_free(options.target_enc_str);
638   options.target_enc_str = enca_strdup(s);
639 
640   /* We have to check for `..CHARSET/SURFACE..CHARSET2/SURFACE2' which would
641    * enca_parse_encoding_name() split as
642    * charset = CHARSET
643    * surfaces = SURFACE..CHARSET2, SURFACE2
644    * which is aboviously not what we want. */
645   if (enca_strstr(s, XENC_SEPARATOR) == NULL)
646     options.target_enc = enca_parse_encoding_name(s);
647   else {
648     options.target_enc.charset = ENCA_CS_UNKNOWN;
649     options.target_enc.surface = 0;
650   }
651 
652   return 0;
653 }
654 
655 /* add comma separated list of converters to list of converters
656    returns zero on success, nonzero otherwise
657    when list is NULL return number of successfully added converters instead */
658 static int
add_parsed_converters(const char * list)659 add_parsed_converters(const char *list)
660 {
661   /* Converter list separator for -E argument. */
662   static const char CONVERTER_SEPARATOR = ',';
663 
664   char *s;
665   char *p_c,*p_c1;
666   static int nc = 0;
667 
668   if (list == NULL)
669     return nc;
670 
671   s = enca_strdup(list);
672   /* Add converter names one by one. */
673   p_c = s;
674   while ((p_c1 = strchr(p_c, CONVERTER_SEPARATOR)) != NULL) {
675     *p_c1++ = '\0';
676     if (add_converter(p_c) == 0) nc++;
677     p_c = p_c1;
678   }
679   if (add_converter(p_c) == 0) nc++;
680   enca_free(s);
681 
682   return 0;
683 }
684 
685 /* create NULL-terminated file list from remaining fields in argv[]
686    and return it */
687 static char**
make_filelist(const int n,char * argvrest[])688 make_filelist(const int n, char *argvrest[])
689 {
690   int i;
691   char **flist = NULL;
692 
693   /* Accept `-' as stdin. */
694   if (n == 0
695       || (n == 1 && strcmp(argvrest[0], "-") == 0))
696     return NULL;
697 
698   flist = (char**)enca_malloc((n+1)*sizeof(char*));
699   for (i = 0; i < n; i++) flist[i] = enca_strdup(argvrest[i]);
700   flist[n] = NULL;
701 
702   return flist;
703 }
704 
705 static int
prefix_filename(int pfx)706 prefix_filename(int pfx) {
707   if (pfx != -1)
708     options.prefix_filename = pfx;
709   return options.prefix_filename;
710 }
711 
712 /* prints some list user asked for (--list) -- just calls appropriate
713  * functions from appropriate module (with funny abbreviation expansion)
714  * when listname is NULL prints list of available lists instead */
715 static void
print_some_list(const char * listname)716 print_some_list(const char *listname)
717 {
718   /* ISO C forbids initialization between function pointers and void*
719      so we use one more level of indirection to comply (and hope gracious
720      complier will forgive us our sins, amen) */
721   static const ReportFunc printer_bics = print_builtin_charsets;
722   static const ReportFunc printer_conv = print_converter_list;
723   static const ReportFunc printer_char = print_all_charsets;
724   static const ReportFunc printer_lang = print_languages;
725   static const ReportFunc printer_list = print_lists;
726   static const ReportFunc printer_name = print_names;
727   static const ReportFunc printer_surf = print_surfaces;
728 
729   /* List names and pointers to pointers to list-printers. */
730   static const Abbreviation LISTS[] = {
731     { "built-in-charsets", &printer_bics },
732     { "converters", &printer_conv },
733     { "charsets", &printer_char },
734     { "languages", &printer_lang },
735     { "lists", &printer_list },
736     { "names", &printer_name },
737     { "surfaces", &printer_surf },
738   };
739 
740   const Abbreviation *p = NULL;
741   ReportFunc list_printer; /* Pointer to list printing functions. */
742 
743   /* Get the abbreviation data. */
744   p = expand_abbreviation(listname, LISTS,
745                           sizeof(LISTS)/sizeof(Abbreviation),
746                           "list");
747 
748   /* p can be NULL in weird situations, e.g. when print_some_list() was
749    * called recursively by itself through print_lists.
750    * In all cases, return. */
751   if (p == NULL)
752     return;
753 
754   list_printer = *(ReportFunc*)p->data;
755   list_printer();
756 }
757 
758 #ifndef HAVE_PROGRAM_INVOCATION_SHORT_NAME
759 /* Create and return string containing only last component of path fullpath. */
760 static char*
strip_path(const char * fullpath)761 strip_path(const char *fullpath)
762 {
763   char *p;
764 
765   p = strrchr(fullpath, '/');
766   if (p == NULL)
767     p = (char*)fullpath;
768   else
769     p++;
770 
771   return enca_strdup(p);
772 }
773 #endif
774 
775 /* Print version information. */
776 static void
print_version(void)777 print_version(void)
778 {
779   printf("%s %s\n\n%s\n\n%s\n", PACKAGE_TARNAME, PACKAGE_VERSION, VERSION_TEXT, COPYRIGHT_TEXT);
780 }
781 
782 /**
783  * Prints builtin charsets.
784  * Must be of type ReportFunc.
785  **/
786 static void
print_builtin_charsets(void)787 print_builtin_charsets(void)
788 {
789   print_charsets(1);
790 }
791 
792 /**
793  * Prints all charsets.
794  * Must be of type ReportFunc.
795  **/
796 static void
print_all_charsets(void)797 print_all_charsets(void)
798 {
799   print_charsets(0);
800 }
801 
802 /**
803  * Prints list of charsets using name style from options.output_type.
804  *
805  * It prints all charsets, except:
806  * - charsets without given name, and
807  * - charsets without UCS-2 map when only_builtin is set.
808  **/
809 static void
print_charsets(int only_builtin)810 print_charsets(int only_builtin)
811 {
812   size_t ncharsets, i;
813 
814   ncharsets = enca_number_of_charsets();
815   for (i = 0; i < ncharsets; i++) {
816     if (only_builtin && !enca_charset_has_ucs2_map(i))
817       continue;
818 
819     switch (options.output_type) {
820       case OTYPE_ALIASES:
821       print_aliases(i);
822       break;
823 
824       case OTYPE_CANON:
825       case OTYPE_CONVERT:
826       puts(enca_charset_name(i, ENCA_NAME_STYLE_ENCA));
827       break;
828 
829       case OTYPE_HUMAN:
830       case OTYPE_DETAILS:
831       puts(enca_charset_name(i, ENCA_NAME_STYLE_HUMAN));
832       break;
833 
834       case OTYPE_RFC1345:
835       puts(enca_charset_name(i, ENCA_NAME_STYLE_RFC1345));
836       break;
837 
838       case OTYPE_CS2CS:
839       if (enca_charset_name(i, ENCA_NAME_STYLE_CSTOCS) != NULL)
840         puts(enca_charset_name(i, ENCA_NAME_STYLE_CSTOCS));
841       break;
842 
843       case OTYPE_ICONV:
844       if (enca_charset_name(i, ENCA_NAME_STYLE_ICONV) != NULL)
845         puts(enca_charset_name(i, ENCA_NAME_STYLE_ICONV));
846       break;
847 
848       case OTYPE_MIME:
849       if (enca_charset_name(i, ENCA_NAME_STYLE_MIME) != NULL)
850         puts(enca_charset_name(i, ENCA_NAME_STYLE_MIME));
851       break;
852 
853       default:
854       abort();
855       break;
856     }
857   }
858 }
859 
860 /**
861  * Prints all aliases of given charset.
862  **/
863 void
print_aliases(size_t cs)864 print_aliases(size_t cs)
865 {
866   size_t i, na;
867   const char **aliases = enca_get_charset_aliases(cs, &na);
868 
869   for (i = 0; i < na; i++)
870     printf("%s ", aliases[i]);
871 
872   putchar('\n');
873   enca_free(aliases);
874 }
875 
876 /**
877  * Prints all [public] surfaces.
878  * Must be of type ReportFunc.
879  **/
880 static void
print_surfaces(void)881 print_surfaces(void)
882 {
883   EncaNameStyle ns;
884   char *s;
885   unsigned int i;
886 
887   /* Only these two know surfaces. */
888   if (options.output_type == OTYPE_HUMAN)
889     ns = ENCA_NAME_STYLE_HUMAN;
890   else
891     ns = ENCA_NAME_STYLE_ENCA;
892 
893   for (i = 1; i != 0; i <<= 1) {
894     s = enca_get_surface_name(i, ns);
895     if (s != NULL && s[0] != '\0') {
896       fputs(s, stdout);
897       if (ns == ENCA_NAME_STYLE_ENCA)
898         putchar('\n');
899       enca_free(s);
900     }
901   }
902 }
903 
904 /* Magically print the list of lists. */
905 static void
print_lists(void)906 print_lists(void)
907 {
908   print_some_list(NULL);
909 }
910 
911 /**
912  * Prints all languages list of charsets of each.
913  * Must be of type ReportFunc.
914  * Quite illogically affected by options.output_type: it changes *language*
915  * name style, instead of charset name style.
916  **/
917 static void
print_languages(void)918 print_languages(void)
919 {
920   size_t nl, nc, i, j, maxlen;
921   const char **l;
922   int *c;
923   int english;
924 
925   l = enca_get_languages(&nl);
926 
927   english = options.output_type == OTYPE_HUMAN
928             || options.output_type == OTYPE_DETAILS;
929   /* Find max. language name length for English. */
930   maxlen = 0;
931   if (english) {
932     for (i = 0; i < nl; i++) {
933       j = strlen(enca_language_english_name(l[i]));
934       if (j > maxlen)
935         maxlen = j;
936     }
937   }
938 
939   /* Print the names. */
940   for (i = 0; i < nl; i++) {
941     if (english)
942       printf("%*s:", (int)maxlen, enca_language_english_name(l[i]));
943     else
944       printf("%s:", l[i]);
945     c = enca_get_language_charsets(l[i], &nc);
946     for (j = 0; j < nc; j++)
947       printf(" %s", enca_charset_name(c[j], ENCA_NAME_STYLE_ENCA));
948 
949     putchar('\n');
950     enca_free(c);
951   }
952   enca_free(l);
953 }
954 
955 /**
956  * Prints list of all encoding name styles.
957  * Must be of type ReportFunc.
958  **/
959 static void
print_names(void)960 print_names(void)
961 {
962   set_otype_from_name(NULL);
963 }
964 
965 /**
966  * Print some text (help, copying, ...) and exit with given code.
967  **/
968 static void
print_text_and_exit(const char * const * text,int exitcode)969 print_text_and_exit(const char *const *text, int exitcode)
970 {
971   assert(text);
972 
973   for (; *text; text++)
974     puts(*text);
975 
976   exit(exitcode);
977 }
978 
979 /**
980  * set_program_behaviour:
981  *
982  * Sets behaviour according to name we were called.
983  **/
984 static void
set_program_behaviour(void)985 set_program_behaviour(void)
986 {
987   static const char enca_name[] = "enca";
988   static const char enconv_name[] = "enconv";
989   static const size_t nenca = sizeof(enca_name) - 1;
990   static const size_t nenconv = sizeof(enconv_name) - 1;
991 
992   if (strncmp(program_name, enca_name, nenca) == 0
993       && !isalpha(program_name[nenca])) {
994     behaviour = BEHAVE_ENCA;
995     return;
996   }
997 
998   if (strncmp(program_name, enconv_name, nenconv) == 0
999       && !isalpha(program_name[nenconv])) {
1000     behaviour = BEHAVE_ENCONV;
1001     return;
1002   }
1003 }
1004 
1005 /* vim: ts=2
1006  */
1007