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