1 /* Converts a translation catalog to a different character encoding.
2    Copyright (C) 2001-2007, 2009-2010, 2012, 2014, 2016, 2018-2020 Free Software
3    Foundation, Inc.
4    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
5 
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
18 
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include <getopt.h>
25 #include <limits.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <locale.h>
29 
30 #include <textstyle.h>
31 
32 #include "noreturn.h"
33 #include "closeout.h"
34 #include "dir-list.h"
35 #include "error.h"
36 #include "error-progname.h"
37 #include "progname.h"
38 #include "relocatable.h"
39 #include "basename-lgpl.h"
40 #include "message.h"
41 #include "read-catalog.h"
42 #include "read-po.h"
43 #include "read-properties.h"
44 #include "read-stringtable.h"
45 #include "write-catalog.h"
46 #include "write-po.h"
47 #include "write-properties.h"
48 #include "write-stringtable.h"
49 #include "msgl-iconv.h"
50 #include "localcharset.h"
51 #include "propername.h"
52 #include "gettext.h"
53 
54 #define _(str) gettext (str)
55 
56 
57 /* Force output of PO file even if empty.  */
58 static int force_po;
59 
60 /* Target encoding.  */
61 static const char *to_code;
62 
63 /* Long options.  */
64 static const struct option long_options[] =
65 {
66   { "add-location", optional_argument, NULL, 'n' },
67   { "color", optional_argument, NULL, CHAR_MAX + 4 },
68   { "directory", required_argument, NULL, 'D' },
69   { "escape", no_argument, NULL, 'E' },
70   { "force-po", no_argument, &force_po, 1 },
71   { "help", no_argument, NULL, 'h' },
72   { "indent", no_argument, NULL, 'i' },
73   { "no-escape", no_argument, NULL, 'e' },
74   { "no-location", no_argument, NULL, CHAR_MAX + 6 },
75   { "no-wrap", no_argument, NULL, CHAR_MAX + 1 },
76   { "output-file", required_argument, NULL, 'o' },
77   { "properties-input", no_argument, NULL, 'P' },
78   { "properties-output", no_argument, NULL, 'p' },
79   { "sort-by-file", no_argument, NULL, 'F' },
80   { "sort-output", no_argument, NULL, 's' },
81   { "strict", no_argument, NULL, 'S' },
82   { "stringtable-input", no_argument, NULL, CHAR_MAX + 2 },
83   { "stringtable-output", no_argument, NULL, CHAR_MAX + 3 },
84   { "style", required_argument, NULL, CHAR_MAX + 5 },
85   { "to-code", required_argument, NULL, 't' },
86   { "version", no_argument, NULL, 'V' },
87   { "width", required_argument, NULL, 'w' },
88   { NULL, 0, NULL, 0 }
89 };
90 
91 
92 /* Forward declaration of local functions.  */
93 _GL_NORETURN_FUNC static void usage (int status);
94 
95 
96 int
main(int argc,char ** argv)97 main (int argc, char **argv)
98 {
99   int opt;
100   bool do_help;
101   bool do_version;
102   char *output_file;
103   const char *input_file;
104   msgdomain_list_ty *result;
105   catalog_input_format_ty input_syntax = &input_format_po;
106   catalog_output_format_ty output_syntax = &output_format_po;
107   bool sort_by_filepos = false;
108   bool sort_by_msgid = false;
109 
110   /* Set program name for messages.  */
111   set_program_name (argv[0]);
112   error_print_progname = maybe_print_progname;
113 
114   /* Set locale via LC_ALL.  */
115   setlocale (LC_ALL, "");
116 
117   /* Set the text message domain.  */
118   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
119   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
120   textdomain (PACKAGE);
121 
122   /* Ensure that write errors on stdout are detected.  */
123   atexit (close_stdout);
124 
125   /* Set default values for variables.  */
126   do_help = false;
127   do_version = false;
128   output_file = NULL;
129   input_file = NULL;
130 
131   while ((opt = getopt_long (argc, argv, "D:eEFhin:o:pPst:Vw:", long_options,
132                              NULL))
133          != EOF)
134     switch (opt)
135       {
136       case '\0':                /* Long option.  */
137         break;
138 
139       case 'D':
140         dir_list_append (optarg);
141         break;
142 
143       case 'e':
144         message_print_style_escape (false);
145         break;
146 
147       case 'E':
148         message_print_style_escape (true);
149         break;
150 
151       case 'F':
152         sort_by_filepos = true;
153         break;
154 
155       case 'h':
156         do_help = true;
157         break;
158 
159       case 'i':
160         message_print_style_indent ();
161         break;
162 
163       case 'n':
164         if (handle_filepos_comment_option (optarg))
165           usage (EXIT_FAILURE);
166         break;
167 
168       case 'o':
169         output_file = optarg;
170         break;
171 
172       case 'p':
173         output_syntax = &output_format_properties;
174         break;
175 
176       case 'P':
177         input_syntax = &input_format_properties;
178         break;
179 
180       case 's':
181         sort_by_msgid = true;
182         break;
183 
184       case 'S':
185         message_print_style_uniforum ();
186         break;
187 
188       case 't':
189         to_code = optarg;
190         break;
191 
192       case 'V':
193         do_version = true;
194         break;
195 
196       case 'w':
197         {
198           int value;
199           char *endp;
200           value = strtol (optarg, &endp, 10);
201           if (endp != optarg)
202             message_page_width_set (value);
203         }
204         break;
205 
206       case CHAR_MAX + 1: /* --no-wrap */
207         message_page_width_ignore ();
208         break;
209 
210       case CHAR_MAX + 2: /* --stringtable-input */
211         input_syntax = &input_format_stringtable;
212         break;
213 
214       case CHAR_MAX + 3: /* --stringtable-output */
215         output_syntax = &output_format_stringtable;
216         break;
217 
218       case CHAR_MAX + 4: /* --color */
219         if (handle_color_option (optarg) || color_test_mode)
220           usage (EXIT_FAILURE);
221         break;
222 
223       case CHAR_MAX + 5: /* --style */
224         handle_style_option (optarg);
225         break;
226 
227       case CHAR_MAX + 6: /* --no-location */
228         message_print_style_filepos (filepos_comment_none);
229         break;
230 
231       default:
232         usage (EXIT_FAILURE);
233         break;
234       }
235 
236   /* Version information is requested.  */
237   if (do_version)
238     {
239       printf ("%s (GNU %s) %s\n", last_component (program_name),
240               PACKAGE, VERSION);
241       /* xgettext: no-wrap */
242       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
243 License GPLv3+: GNU GPL version 3 or later <%s>\n\
244 This is free software: you are free to change and redistribute it.\n\
245 There is NO WARRANTY, to the extent permitted by law.\n\
246 "),
247               "2001-2020", "https://gnu.org/licenses/gpl.html");
248       printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
249       exit (EXIT_SUCCESS);
250     }
251 
252   /* Help is requested.  */
253   if (do_help)
254     usage (EXIT_SUCCESS);
255 
256   /* Test whether we have an .po file name as argument.  */
257   if (optind == argc)
258     input_file = "-";
259   else if (optind + 1 == argc)
260     input_file = argv[optind];
261   else
262     {
263       error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
264       usage (EXIT_FAILURE);
265     }
266 
267   /* Verify selected options.  */
268   if (sort_by_msgid && sort_by_filepos)
269     error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
270            "--sort-output", "--sort-by-file");
271 
272   /* Default for target encoding is current locale's encoding.  */
273   if (to_code == NULL)
274     to_code = locale_charset ();
275 
276   /* Read input file.  */
277   result = read_catalog_file (input_file, input_syntax);
278 
279   /* Convert if and only if the output syntax supports different encodings.  */
280   if (!output_syntax->requires_utf8)
281     result = iconv_msgdomain_list (result, to_code, true, input_file);
282 
283   /* Sort the results.  */
284   if (sort_by_filepos)
285     msgdomain_list_sort_by_filepos (result);
286   else if (sort_by_msgid)
287     msgdomain_list_sort_by_msgid (result);
288 
289   /* Write the merged message list out.  */
290   msgdomain_list_print (result, output_file, output_syntax, force_po, false);
291 
292   exit (EXIT_SUCCESS);
293 }
294 
295 
296 /* Display usage information and exit.  */
297 static void
usage(int status)298 usage (int status)
299 {
300   if (status != EXIT_SUCCESS)
301     fprintf (stderr, _("Try '%s --help' for more information.\n"),
302              program_name);
303   else
304     {
305       printf (_("\
306 Usage: %s [OPTION] [INPUTFILE]\n\
307 "), program_name);
308       printf ("\n");
309       printf (_("\
310 Converts a translation catalog to a different character encoding.\n\
311 "));
312       printf ("\n");
313       printf (_("\
314 Mandatory arguments to long options are mandatory for short options too.\n"));
315       printf ("\n");
316       printf (_("\
317 Input file location:\n"));
318       printf (_("\
319   INPUTFILE                   input PO file\n"));
320       printf (_("\
321   -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
322       printf (_("\
323 If no input file is given or if it is -, standard input is read.\n"));
324       printf ("\n");
325       printf (_("\
326 Output file location:\n"));
327       printf (_("\
328   -o, --output-file=FILE      write output to specified file\n"));
329       printf (_("\
330 The results are written to standard output if no output file is specified\n\
331 or if it is -.\n"));
332       printf ("\n");
333       printf (_("\
334 Conversion target:\n"));
335       printf (_("\
336   -t, --to-code=NAME          encoding for output\n"));
337       printf (_("\
338 The default encoding is the current locale's encoding.\n"));
339       printf ("\n");
340       printf (_("\
341 Input file syntax:\n"));
342       printf (_("\
343   -P, --properties-input      input file is in Java .properties syntax\n"));
344       printf (_("\
345       --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
346       printf ("\n");
347       printf (_("\
348 Output details:\n"));
349       printf (_("\
350       --color                 use colors and other text attributes always\n\
351       --color=WHEN            use colors and other text attributes if WHEN.\n\
352                               WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
353       printf (_("\
354       --style=STYLEFILE       specify CSS style rule file for --color\n"));
355       printf (_("\
356   -e, --no-escape             do not use C escapes in output (default)\n"));
357       printf (_("\
358   -E, --escape                use C escapes in output, no extended chars\n"));
359       printf (_("\
360       --force-po              write PO file even if empty\n"));
361       printf (_("\
362   -i, --indent                indented output style\n"));
363       printf (_("\
364       --no-location           suppress '#: filename:line' lines\n"));
365       printf (_("\
366   -n, --add-location          preserve '#: filename:line' lines (default)\n"));
367       printf (_("\
368       --strict                strict Uniforum output style\n"));
369       printf (_("\
370   -p, --properties-output     write out a Java .properties file\n"));
371       printf (_("\
372       --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
373       printf (_("\
374   -w, --width=NUMBER          set output page width\n"));
375       printf (_("\
376       --no-wrap               do not break long message lines, longer than\n\
377                               the output page width, into several lines\n"));
378       printf (_("\
379   -s, --sort-output           generate sorted output\n"));
380       printf (_("\
381   -F, --sort-by-file          sort output by file location\n"));
382       printf ("\n");
383       printf (_("\
384 Informative output:\n"));
385       printf (_("\
386   -h, --help                  display this help and exit\n"));
387       printf (_("\
388   -V, --version               output version information and exit\n"));
389       printf ("\n");
390       /* TRANSLATORS: The first placeholder is the web address of the Savannah
391          project of this package.  The second placeholder is the bug-reporting
392          email address for this package.  Please add _another line_ saying
393          "Report translation bugs to <...>\n" with the address for translation
394          bugs (typically your translation team's web or email address).  */
395       printf(_("\
396 Report bugs in the bug tracker at <%s>\n\
397 or by email to <%s>.\n"),
398              "https://savannah.gnu.org/projects/gettext",
399              "bug-gettext@gnu.org");
400     }
401 
402   exit (status);
403 }
404