1 /* Remove, select or merge duplicate translations.
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 "str-list.h"
36 #include "error.h"
37 #include "error-progname.h"
38 #include "progname.h"
39 #include "relocatable.h"
40 #include "basename-lgpl.h"
41 #include "message.h"
42 #include "read-catalog.h"
43 #include "read-po.h"
44 #include "read-properties.h"
45 #include "read-stringtable.h"
46 #include "write-catalog.h"
47 #include "write-po.h"
48 #include "write-properties.h"
49 #include "write-stringtable.h"
50 #include "msgl-cat.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 + 5 },
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 + 7 },
75   { "no-wrap", no_argument, NULL, CHAR_MAX + 2 },
76   { "output-file", required_argument, NULL, 'o' },
77   { "properties-input", no_argument, NULL, 'P' },
78   { "properties-output", no_argument, NULL, 'p' },
79   { "repeated", no_argument, NULL, 'd' },
80   { "sort-by-file", no_argument, NULL, 'F' },
81   { "sort-output", no_argument, NULL, 's' },
82   { "strict", no_argument, NULL, 'S' },
83   { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 },
84   { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 },
85   { "style", required_argument, NULL, CHAR_MAX + 6 },
86   { "to-code", required_argument, NULL, 't' },
87   { "unique", no_argument, NULL, 'u' },
88   { "use-first", no_argument, NULL, CHAR_MAX + 1 },
89   { "version", no_argument, NULL, 'V' },
90   { "width", required_argument, NULL, 'w' },
91   { NULL, 0, NULL, 0 }
92 };
93 
94 
95 /* Forward declaration of local functions.  */
96 _GL_NORETURN_FUNC static void usage (int status);
97 
98 
99 int
main(int argc,char ** argv)100 main (int argc, char **argv)
101 {
102   int optchar;
103   bool do_help;
104   bool do_version;
105   char *output_file;
106   const char *input_file;
107   string_list_ty *file_list;
108   msgdomain_list_ty *result;
109   catalog_input_format_ty input_syntax = &input_format_po;
110   catalog_output_format_ty output_syntax = &output_format_po;
111   bool sort_by_msgid = false;
112   bool sort_by_filepos = false;
113 
114   /* Set program name for messages.  */
115   set_program_name (argv[0]);
116   error_print_progname = maybe_print_progname;
117 
118   /* Set locale via LC_ALL.  */
119   setlocale (LC_ALL, "");
120 
121   /* Set the text message domain.  */
122   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
123   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
124   textdomain (PACKAGE);
125 
126   /* Ensure that write errors on stdout are detected.  */
127   atexit (close_stdout);
128 
129   /* Set default values for variables.  */
130   do_help = false;
131   do_version = false;
132   output_file = NULL;
133   input_file = NULL;
134   more_than = 0;
135   less_than = INT_MAX;
136   use_first = false;
137 
138   while ((optchar = getopt_long (argc, argv, "dD:eEFhino:pPst:uVw:",
139                                  long_options, NULL)) != EOF)
140     switch (optchar)
141       {
142       case '\0':                /* Long option.  */
143         break;
144 
145       case 'd':
146         more_than = 1;
147         less_than = INT_MAX;
148         break;
149 
150       case 'D':
151         dir_list_append (optarg);
152         break;
153 
154       case 'e':
155         message_print_style_escape (false);
156         break;
157 
158       case 'E':
159         message_print_style_escape (true);
160         break;
161 
162       case 'F':
163         sort_by_filepos = true;
164         break;
165 
166       case 'h':
167         do_help = true;
168         break;
169 
170       case 'i':
171         message_print_style_indent ();
172         break;
173 
174       case 'n':
175         if (handle_filepos_comment_option (optarg))
176           usage (EXIT_FAILURE);
177         break;
178 
179       case 'o':
180         output_file = optarg;
181         break;
182 
183       case 'p':
184         output_syntax = &output_format_properties;
185         break;
186 
187       case 'P':
188         input_syntax = &input_format_properties;
189         break;
190 
191       case 's':
192         sort_by_msgid = true;
193         break;
194 
195       case 'S':
196         message_print_style_uniforum ();
197         break;
198 
199       case 't':
200         to_code = optarg;
201         break;
202 
203       case 'u':
204         more_than = 0;
205         less_than = 2;
206         break;
207 
208       case 'V':
209         do_version = true;
210         break;
211 
212       case 'w':
213         {
214           int value;
215           char *endp;
216           value = strtol (optarg, &endp, 10);
217           if (endp != optarg)
218             message_page_width_set (value);
219         }
220         break;
221 
222       case CHAR_MAX + 1:
223         use_first = true;
224         break;
225 
226       case CHAR_MAX + 2: /* --no-wrap */
227         message_page_width_ignore ();
228         break;
229 
230       case CHAR_MAX + 3: /* --stringtable-input */
231         input_syntax = &input_format_stringtable;
232         break;
233 
234       case CHAR_MAX + 4: /* --stringtable-output */
235         output_syntax = &output_format_stringtable;
236         break;
237 
238       case CHAR_MAX + 5: /* --color */
239         if (handle_color_option (optarg) || color_test_mode)
240           usage (EXIT_FAILURE);
241         break;
242 
243       case CHAR_MAX + 6: /* --style */
244         handle_style_option (optarg);
245         break;
246 
247       case CHAR_MAX + 7: /* --no-location */
248         message_print_style_filepos (filepos_comment_none);
249         break;
250 
251       default:
252         usage (EXIT_FAILURE);
253         /* NOTREACHED */
254       }
255 
256   /* Version information requested.  */
257   if (do_version)
258     {
259       printf ("%s (GNU %s) %s\n", last_component (program_name),
260               PACKAGE, VERSION);
261       /* xgettext: no-wrap */
262       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
263 License GPLv3+: GNU GPL version 3 or later <%s>\n\
264 This is free software: you are free to change and redistribute it.\n\
265 There is NO WARRANTY, to the extent permitted by law.\n\
266 "),
267               "2001-2020", "https://gnu.org/licenses/gpl.html");
268       printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
269       exit (EXIT_SUCCESS);
270     }
271 
272   /* Help is requested.  */
273   if (do_help)
274     usage (EXIT_SUCCESS);
275 
276   /* Test whether we have an .po file name as argument.  */
277   if (optind == argc)
278     input_file = "-";
279   else if (optind + 1 == argc)
280     input_file = argv[optind];
281   else
282     {
283       error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
284       usage (EXIT_FAILURE);
285     }
286 
287   /* Verify selected options.  */
288   if (sort_by_msgid && sort_by_filepos)
289     error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
290            "--sort-output", "--sort-by-file");
291 
292   /* Determine list of files we have to process: a single file.  */
293   file_list = string_list_alloc ();
294   string_list_append (file_list, input_file);
295 
296   /* Read input files, then filter, convert and merge messages.  */
297   allow_duplicates = true;
298   result = catenate_msgdomain_list (file_list, input_syntax, to_code);
299 
300   string_list_free (file_list);
301 
302   /* Sorting the list of messages.  */
303   if (sort_by_filepos)
304     msgdomain_list_sort_by_filepos (result);
305   else if (sort_by_msgid)
306     msgdomain_list_sort_by_msgid (result);
307 
308   /* Write the PO file.  */
309   msgdomain_list_print (result, output_file, output_syntax, force_po, false);
310 
311   exit (error_message_count > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
312 }
313 
314 
315 /* Display usage information and exit.  */
316 static void
usage(int status)317 usage (int status)
318 {
319   if (status != EXIT_SUCCESS)
320     fprintf (stderr, _("Try '%s --help' for more information.\n"),
321              program_name);
322   else
323     {
324       printf (_("\
325 Usage: %s [OPTION] [INPUTFILE]\n\
326 "), program_name);
327       printf ("\n");
328       /* xgettext: no-wrap */
329       printf (_("\
330 Unifies duplicate translations in a translation catalog.\n\
331 Finds duplicate translations of the same message ID.  Such duplicates are\n\
332 invalid input for other programs like msgfmt, msgmerge or msgcat.  By\n\
333 default, duplicates are merged together.  When using the --repeated option,\n\
334 only duplicates are output, and all other messages are discarded.  Comments\n\
335 and extracted comments will be cumulated, except that if --use-first is\n\
336 specified, they will be taken from the first translation.  File positions\n\
337 will be cumulated.  When using the --unique option, duplicates are discarded.\n\
338 "));
339       printf ("\n");
340       printf (_("\
341 Mandatory arguments to long options are mandatory for short options too.\n"));
342       printf ("\n");
343       printf (_("\
344 Input file location:\n"));
345       printf (_("\
346   INPUTFILE                   input PO file\n"));
347       printf (_("\
348   -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
349       printf (_("\
350 If no input file is given or if it is -, standard input is read.\n"));
351       printf ("\n");
352       printf (_("\
353 Output file location:\n"));
354       printf (_("\
355   -o, --output-file=FILE      write output to specified file\n"));
356       printf (_("\
357 The results are written to standard output if no output file is specified\n\
358 or if it is -.\n"));
359       printf ("\n");
360       printf (_("\
361 Message selection:\n"));
362       printf (_("\
363   -d, --repeated              print only duplicates\n"));
364       printf (_("\
365   -u, --unique                print only unique messages, discard duplicates\n"));
366       printf ("\n");
367       printf (_("\
368 Input file syntax:\n"));
369       printf (_("\
370   -P, --properties-input      input file is in Java .properties syntax\n"));
371       printf (_("\
372       --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
373       printf ("\n");
374       printf (_("\
375 Output details:\n"));
376       printf (_("\
377   -t, --to-code=NAME          encoding for output\n"));
378       printf (_("\
379       --use-first             use first available translation for each\n\
380                               message, don't merge several translations\n"));
381       printf (_("\
382       --color                 use colors and other text attributes always\n\
383       --color=WHEN            use colors and other text attributes if WHEN.\n\
384                               WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
385       printf (_("\
386       --style=STYLEFILE       specify CSS style rule file for --color\n"));
387       printf (_("\
388   -e, --no-escape             do not use C escapes in output (default)\n"));
389       printf (_("\
390   -E, --escape                use C escapes in output, no extended chars\n"));
391       printf (_("\
392       --force-po              write PO file even if empty\n"));
393       printf (_("\
394   -i, --indent                write the .po file using indented style\n"));
395       printf (_("\
396       --no-location           do not write '#: filename:line' lines\n"));
397       printf (_("\
398   -n, --add-location          generate '#: filename:line' lines (default)\n"));
399       printf (_("\
400       --strict                write out strict Uniforum conforming .po file\n"));
401       printf (_("\
402   -p, --properties-output     write out a Java .properties file\n"));
403       printf (_("\
404       --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
405       printf (_("\
406   -w, --width=NUMBER          set output page width\n"));
407       printf (_("\
408       --no-wrap               do not break long message lines, longer than\n\
409                               the output page width, into several lines\n"));
410       printf (_("\
411   -s, --sort-output           generate sorted output\n"));
412       printf (_("\
413   -F, --sort-by-file          sort output by file location\n"));
414       printf ("\n");
415       printf (_("\
416 Informative output:\n"));
417       printf (_("\
418   -h, --help                  display this help and exit\n"));
419       printf (_("\
420   -V, --version               output version information and exit\n"));
421       printf ("\n");
422       /* TRANSLATORS: The first placeholder is the web address of the Savannah
423          project of this package.  The second placeholder is the bug-reporting
424          email address for this package.  Please add _another line_ saying
425          "Report translation bugs to <...>\n" with the address for translation
426          bugs (typically your translation team's web or email address).  */
427       printf(_("\
428 Report bugs in the bug tracker at <%s>\n\
429 or by email to <%s>.\n"),
430              "https://savannah.gnu.org/projects/gettext",
431              "bug-gettext@gnu.org");
432     }
433 
434   exit (status);
435 }
436 
437