1 /* GNU gettext - internationalization aids
2 Copyright (C) 1997-1998, 2000-2007, 2009-2020 Free Software Foundation, Inc.
3
4 This file was written by Peter Miller <millerp@canb.auug.org.au>
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 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #include <getopt.h>
24 #include <limits.h>
25 #include <locale.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28
29 #include <textstyle.h>
30
31 #include "noreturn.h"
32 #include "closeout.h"
33 #include "dir-list.h"
34 #include "str-list.h"
35 #include "file-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
55 /* A convenience macro. I don't like writing gettext() every time. */
56 #define _(str) gettext (str)
57
58
59 /* Force output of PO file even if empty. */
60 static int force_po;
61
62 /* Target encoding. */
63 static const char *to_code;
64
65 /* Long options. */
66 static const struct option long_options[] =
67 {
68 { "add-location", optional_argument, NULL, 'n' },
69 { "color", optional_argument, NULL, CHAR_MAX + 5 },
70 { "directory", required_argument, NULL, 'D' },
71 { "escape", no_argument, NULL, 'E' },
72 { "files-from", required_argument, NULL, 'f' },
73 { "force-po", no_argument, &force_po, 1 },
74 { "help", no_argument, NULL, 'h' },
75 { "indent", no_argument, NULL, 'i' },
76 { "no-escape", no_argument, NULL, 'e' },
77 { "no-location", no_argument, NULL, CHAR_MAX + 7 },
78 { "no-wrap", no_argument, NULL, CHAR_MAX + 2 },
79 { "omit-header", no_argument, NULL, CHAR_MAX + 1 },
80 { "output", required_argument, NULL, 'o' }, /* for backward compatibility */
81 { "output-file", required_argument, NULL, 'o' },
82 { "properties-input", no_argument, NULL, 'P' },
83 { "properties-output", no_argument, NULL, 'p' },
84 { "sort-by-file", no_argument, NULL, 'F' },
85 { "sort-output", no_argument, NULL, 's' },
86 { "strict", no_argument, NULL, 'S' },
87 { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 },
88 { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 },
89 { "style", required_argument, NULL, CHAR_MAX + 6 },
90 { "to-code", required_argument, NULL, 't' },
91 { "unique", no_argument, NULL, 'u' },
92 { "version", no_argument, NULL, 'V' },
93 { "width", required_argument, NULL, 'w' },
94 { "more-than", required_argument, NULL, '>' },
95 { "less-than", required_argument, NULL, '<' },
96 { NULL, 0, NULL, 0 }
97 };
98
99
100 /* Forward declaration of local functions. */
101 _GL_NORETURN_FUNC static void usage (int status);
102
103
104 int
main(int argc,char * argv[])105 main (int argc, char *argv[])
106 {
107 int cnt;
108 int optchar;
109 bool do_help = false;
110 bool do_version = false;
111 msgdomain_list_ty *result;
112 catalog_input_format_ty input_syntax = &input_format_po;
113 catalog_output_format_ty output_syntax = &output_format_po;
114 bool sort_by_msgid = false;
115 bool sort_by_filepos = false;
116 const char *files_from = NULL;
117 string_list_ty *file_list;
118 char *output_file = NULL;
119
120 /* Set program name for messages. */
121 set_program_name (argv[0]);
122 error_print_progname = maybe_print_progname;
123
124 /* Set locale via LC_ALL. */
125 setlocale (LC_ALL, "");
126
127 /* Set the text message domain. */
128 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
129 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
130 textdomain (PACKAGE);
131
132 /* Ensure that write errors on stdout are detected. */
133 atexit (close_stdout);
134
135 /* Set default values for variables. */
136 more_than = -1;
137 less_than = -1;
138 use_first = false;
139
140 while ((optchar = getopt_long (argc, argv, "<:>:D:eEf:Fhino:pPst:uVw:",
141 long_options, NULL)) != EOF)
142 switch (optchar)
143 {
144 case '\0': /* Long option. */
145 break;
146
147 case '>':
148 {
149 int value;
150 char *endp;
151 value = strtol (optarg, &endp, 10);
152 if (endp != optarg)
153 more_than = value;
154 }
155 break;
156
157 case '<':
158 {
159 int value;
160 char *endp;
161 value = strtol (optarg, &endp, 10);
162 if (endp != optarg)
163 less_than = value;
164 }
165 break;
166
167 case 'D':
168 dir_list_append (optarg);
169 break;
170
171 case 'e':
172 message_print_style_escape (false);
173 break;
174
175 case 'E':
176 message_print_style_escape (true);
177 break;
178
179 case 'f':
180 files_from = optarg;
181 break;
182
183 case 'F':
184 sort_by_filepos = true;
185 break;
186
187 case 'h':
188 do_help = true;
189 break;
190
191 case 'i':
192 message_print_style_indent ();
193 break;
194
195 case 'n':
196 if (handle_filepos_comment_option (optarg))
197 usage (EXIT_FAILURE);
198 break;
199
200 case 'o':
201 output_file = optarg;
202 break;
203
204 case 'p':
205 output_syntax = &output_format_properties;
206 break;
207
208 case 'P':
209 input_syntax = &input_format_properties;
210 break;
211
212 case 's':
213 sort_by_msgid = true;
214 break;
215
216 case 'S':
217 message_print_style_uniforum ();
218 break;
219
220 case 't':
221 to_code = optarg;
222 break;
223
224 case 'u':
225 less_than = 2;
226 break;
227
228 case 'V':
229 do_version = true;
230 break;
231
232 case 'w':
233 {
234 int value;
235 char *endp;
236 value = strtol (optarg, &endp, 10);
237 if (endp != optarg)
238 message_page_width_set (value);
239 }
240 break;
241
242 case CHAR_MAX + 1:
243 omit_header = true;
244 break;
245
246 case CHAR_MAX + 2: /* --no-wrap */
247 message_page_width_ignore ();
248 break;
249
250 case CHAR_MAX + 3: /* --stringtable-input */
251 input_syntax = &input_format_stringtable;
252 break;
253
254 case CHAR_MAX + 4: /* --stringtable-output */
255 output_syntax = &output_format_stringtable;
256 break;
257
258 case CHAR_MAX + 5: /* --color */
259 if (handle_color_option (optarg) || color_test_mode)
260 usage (EXIT_FAILURE);
261 break;
262
263 case CHAR_MAX + 6: /* --style */
264 handle_style_option (optarg);
265 break;
266
267 case CHAR_MAX + 7: /* --no-location */
268 message_print_style_filepos (filepos_comment_none);
269 break;
270
271 default:
272 usage (EXIT_FAILURE);
273 /* NOTREACHED */
274 }
275
276 /* Version information requested. */
277 if (do_version)
278 {
279 printf ("%s (GNU %s) %s\n", last_component (program_name),
280 PACKAGE, VERSION);
281 /* xgettext: no-wrap */
282 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
283 License GPLv3+: GNU GPL version 3 or later <%s>\n\
284 This is free software: you are free to change and redistribute it.\n\
285 There is NO WARRANTY, to the extent permitted by law.\n\
286 "),
287 "1995-2020", "https://gnu.org/licenses/gpl.html");
288 printf (_("Written by %s.\n"), proper_name ("Peter Miller"));
289 exit (EXIT_SUCCESS);
290 }
291
292 /* Help is requested. */
293 if (do_help)
294 usage (EXIT_SUCCESS);
295
296 /* Verify selected options. */
297 if (sort_by_msgid && sort_by_filepos)
298 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
299 "--sort-output", "--sort-by-file");
300
301 /* Determine list of files we have to process. */
302 if (files_from != NULL)
303 file_list = read_names_from_file (files_from);
304 else
305 file_list = string_list_alloc ();
306 /* Append names from command line. */
307 for (cnt = optind; cnt < argc; ++cnt)
308 string_list_append_unique (file_list, argv[cnt]);
309
310 /* Test whether sufficient input files were given. */
311 if (file_list->nitems < 2)
312 {
313 error (EXIT_SUCCESS, 0, _("at least two files must be specified"));
314 usage (EXIT_FAILURE);
315 }
316
317 /* Default the message selection criteria, and check them for sanity. */
318 if (more_than < 0)
319 more_than = (less_than < 0 ? 1 : 0);
320 if (less_than < 0)
321 less_than = INT_MAX;
322 if (more_than >= less_than || less_than < 2)
323 error (EXIT_FAILURE, 0,
324 _("impossible selection criteria specified (%d < n < %d)"),
325 more_than, less_than);
326
327 /* Read input files, then filter, convert and merge messages. */
328 allow_duplicates = true;
329 msgcomm_mode = true;
330 result = catenate_msgdomain_list (file_list, input_syntax, to_code);
331
332 string_list_free (file_list);
333
334 /* Sorting the list of messages. */
335 if (sort_by_filepos)
336 msgdomain_list_sort_by_filepos (result);
337 else if (sort_by_msgid)
338 msgdomain_list_sort_by_msgid (result);
339
340 /* Write the PO file. */
341 msgdomain_list_print (result, output_file, output_syntax, force_po, false);
342
343 exit (error_message_count > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
344 }
345
346
347 /* Display usage information and exit. */
348 static void
usage(int status)349 usage (int status)
350 {
351 if (status != EXIT_SUCCESS)
352 fprintf (stderr, _("Try '%s --help' for more information.\n"),
353 program_name);
354 else
355 {
356 printf (_("\
357 Usage: %s [OPTION] [INPUTFILE]...\n\
358 "), program_name);
359 printf ("\n");
360 /* xgettext: no-wrap */
361 printf (_("\
362 Find messages which are common to two or more of the specified PO files.\n\
363 By using the --more-than option, greater commonality may be requested\n\
364 before messages are printed. Conversely, the --less-than option may be\n\
365 used to specify less commonality before messages are printed (i.e.\n\
366 --less-than=2 will only print the unique messages). Translations,\n\
367 comments and extracted comments will be preserved, but only from the first\n\
368 PO file to define them. File positions from all PO files will be\n\
369 cumulated.\n\
370 "));
371 printf ("\n");
372 printf (_("\
373 Mandatory arguments to long options are mandatory for short options too.\n"));
374 printf ("\n");
375 printf (_("\
376 Input file location:\n"));
377 printf (_("\
378 INPUTFILE ... input files\n"));
379 printf (_("\
380 -f, --files-from=FILE get list of input files from FILE\n"));
381 printf (_("\
382 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
383 printf (_("\
384 If input file is -, standard input is read.\n"));
385 printf ("\n");
386 printf (_("\
387 Output file location:\n"));
388 printf (_("\
389 -o, --output-file=FILE write output to specified file\n"));
390 printf (_("\
391 The results are written to standard output if no output file is specified\n\
392 or if it is -.\n"));
393 printf ("\n");
394 printf (_("\
395 Message selection:\n"));
396 printf (_("\
397 -<, --less-than=NUMBER print messages with less than this many\n\
398 definitions, defaults to infinite if not set\n"));
399 printf (_("\
400 ->, --more-than=NUMBER print messages with more than this many\n\
401 definitions, defaults to 1 if not set\n"));
402 printf (_("\
403 -u, --unique shorthand for --less-than=2, requests\n\
404 that only unique messages be printed\n"));
405 printf ("\n");
406 printf (_("\
407 Input file syntax:\n"));
408 printf (_("\
409 -P, --properties-input input files are in Java .properties syntax\n"));
410 printf (_("\
411 --stringtable-input input files are in NeXTstep/GNUstep .strings\n\
412 syntax\n"));
413 printf ("\n");
414 printf (_("\
415 Output details:\n"));
416 printf (_("\
417 --color use colors and other text attributes always\n\
418 --color=WHEN use colors and other text attributes if WHEN.\n\
419 WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
420 printf (_("\
421 --style=STYLEFILE specify CSS style rule file for --color\n"));
422 printf (_("\
423 -e, --no-escape do not use C escapes in output (default)\n"));
424 printf (_("\
425 -E, --escape use C escapes in output, no extended chars\n"));
426 printf (_("\
427 --force-po write PO file even if empty\n"));
428 printf (_("\
429 -i, --indent write the .po file using indented style\n"));
430 printf (_("\
431 --no-location do not write '#: filename:line' lines\n"));
432 printf (_("\
433 -n, --add-location generate '#: filename:line' lines (default)\n"));
434 printf (_("\
435 --strict write out strict Uniforum conforming .po file\n"));
436 printf (_("\
437 -p, --properties-output write out a Java .properties file\n"));
438 printf (_("\
439 --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
440 printf (_("\
441 -w, --width=NUMBER set output page width\n"));
442 printf (_("\
443 --no-wrap do not break long message lines, longer than\n\
444 the output page width, into several lines\n"));
445 printf (_("\
446 -s, --sort-output generate sorted output\n"));
447 printf (_("\
448 -F, --sort-by-file sort output by file location\n"));
449 printf (_("\
450 --omit-header don't write header with 'msgid \"\"' entry\n"));
451 printf ("\n");
452 printf (_("\
453 Informative output:\n"));
454 printf (_("\
455 -h, --help display this help and exit\n"));
456 printf (_("\
457 -V, --version output version information and exit\n"));
458 printf ("\n");
459 /* TRANSLATORS: The first placeholder is the web address of the Savannah
460 project of this package. The second placeholder is the bug-reporting
461 email address for this package. Please add _another line_ saying
462 "Report translation bugs to <...>\n" with the address for translation
463 bugs (typically your translation team's web or email address). */
464 printf(_("\
465 Report bugs in the bug tracker at <%s>\n\
466 or by email to <%s>.\n"),
467 "https://savannah.gnu.org/projects/gettext",
468 "bug-gettext@gnu.org");
469 }
470
471 exit (status);
472 }
473