mirror of
https://https.git.savannah.gnu.org/git/gettext.git
synced 2026-01-26 07:37:57 +00:00
* gettext-runtime/src/envsubst.c (main): Update copyright year in --version output. * gettext-runtime/src/gettext.c (main): Likewise. * gettext-runtime/src/gettext.sh.in (func_version): Likewise. * gettext-runtime/src/ngettext.c (main): Likewise. * gettext-runtime/src/printf_gettext.c (main): Likewise. * gettext-runtime/src/printf_ngettext.c (main): Likewise. * gettext-tools/src/cldr-plurals.c (main): Likewise. * gettext-tools/src/hostname.c (main): Likewise. * gettext-tools/src/msgattrib.c (main): Likewise. * gettext-tools/src/msgcat.c (main): Likewise. * gettext-tools/src/msgcmp.c (main): Likewise. * gettext-tools/src/msgcomm.c (main): Likewise. * gettext-tools/src/msgconv.c (main): Likewise. * gettext-tools/src/msgen.c (main): Likewise. * gettext-tools/src/msgexec.c (main): Likewise. * gettext-tools/src/msgfilter.c (main): Likewise. * gettext-tools/src/msgfmt.c (main): Likewise. * gettext-tools/src/msggrep.c (main): Likewise. * gettext-tools/src/msginit.c (main): Likewise. * gettext-tools/src/msgmerge.c (main): Likewise. * gettext-tools/src/msgpre.c (main): Likewise. * gettext-tools/src/msgunfmt.c (main): Likewise. * gettext-tools/src/msguniq.c (main): Likewise. * gettext-tools/src/recode-sr-latin.c (main): Likewise. * gettext-tools/src/spit.c (main): Likewise. * gettext-tools/src/spit.py.in (main): Likewise. * gettext-tools/src/urlget.c (main): Likewise. * gettext-tools/src/xgettext.c (main): Likewise. * gettext-tools/autotools/convert-archive.in (func_version): Likewise. * gettext-tools/autotools/autopoint.in (func_version): Likewise. * gettext-tools/wizard/gettextize.in (func_version): Likewise.
874 lines
27 KiB
C
874 lines
27 KiB
C
/* Extract some translations of a translation catalog.
|
|
Copyright (C) 2001-2026 Free Software Foundation, Inc.
|
|
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|
|
|
|
|
#include <config.h>
|
|
#include <alloca.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <locale.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
#if defined _MSC_VER || defined __MINGW32__
|
|
# include <io.h>
|
|
#endif
|
|
|
|
#include <fnmatch.h>
|
|
|
|
#include <textstyle.h>
|
|
|
|
#include <error.h>
|
|
#include "options.h"
|
|
#include "noreturn.h"
|
|
#include "closeout.h"
|
|
#include "dir-list.h"
|
|
#include "error-progname.h"
|
|
#include "progname.h"
|
|
#include "relocatable.h"
|
|
#include "basename-lgpl.h"
|
|
#include "message.h"
|
|
#include "read-catalog-file.h"
|
|
#include "read-po.h"
|
|
#include "read-properties.h"
|
|
#include "read-stringtable.h"
|
|
#include "write-catalog.h"
|
|
#include "write-po.h"
|
|
#include "write-properties.h"
|
|
#include "write-stringtable.h"
|
|
#include "xerror-handler.h"
|
|
#include "str-list.h"
|
|
#include "msgl-charset.h"
|
|
#include "xalloc.h"
|
|
#include "xmalloca.h"
|
|
#include "libgrep.h"
|
|
#include "propername.h"
|
|
#include "gettext.h"
|
|
|
|
#define _(str) gettext (str)
|
|
|
|
|
|
/* Force output of PO file even if empty. */
|
|
static int force_po;
|
|
|
|
/* Output only non-matching messages. */
|
|
static bool invert_match = false;
|
|
|
|
/* Selected source files. */
|
|
static string_list_ty *location_files;
|
|
|
|
/* Selected domain names. */
|
|
static string_list_ty *domain_names;
|
|
|
|
/* Task for each grep pass. */
|
|
struct grep_task {
|
|
matcher_t *matcher;
|
|
size_t pattern_count;
|
|
char *patterns;
|
|
size_t patterns_size;
|
|
bool case_insensitive;
|
|
void *compiled_patterns;
|
|
};
|
|
static struct grep_task grep_task[5];
|
|
|
|
/* Selected workflow flags. */
|
|
static string_list_ty *workflow_flags;
|
|
|
|
/* Selected sticky flags. */
|
|
static string_list_ty *sticky_flags;
|
|
|
|
|
|
/* Forward declaration of local functions. */
|
|
_GL_NORETURN_FUNC static void no_pass (int opt);
|
|
_GL_NORETURN_FUNC static void usage (int status);
|
|
static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp);
|
|
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
/* Set program name for messages. */
|
|
set_program_name (argv[0]);
|
|
error_print_progname = maybe_print_progname;
|
|
gram_max_allowed_errors = 20;
|
|
|
|
/* Set locale via LC_ALL. */
|
|
setlocale (LC_ALL, "");
|
|
|
|
/* Set the text message domain. */
|
|
bindtextdomain (PACKAGE, relocate (LOCALEDIR));
|
|
bindtextdomain ("gnulib", relocate (GNULIB_LOCALEDIR));
|
|
bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
|
|
textdomain (PACKAGE);
|
|
|
|
/* Ensure that write errors on stdout are detected. */
|
|
atexit (close_stdout);
|
|
|
|
/* Default values for command line options. */
|
|
bool do_help = false;
|
|
bool do_version = false;
|
|
char *output_file = NULL;
|
|
catalog_input_format_ty input_syntax = &input_format_po;
|
|
catalog_output_format_ty output_syntax = &output_format_po;
|
|
location_files = string_list_alloc ();
|
|
domain_names = string_list_alloc ();
|
|
workflow_flags = string_list_alloc ();
|
|
sticky_flags = string_list_alloc ();
|
|
for (size_t i = 0; i < 5; i++)
|
|
{
|
|
struct grep_task *gt = &grep_task[i];
|
|
gt->matcher = &matcher_grep;
|
|
gt->pattern_count = 0;
|
|
gt->patterns = NULL;
|
|
gt->patterns_size = 0;
|
|
gt->case_insensitive = false;
|
|
}
|
|
bool sort_by_filepos = false;
|
|
bool sort_by_msgid = false;
|
|
|
|
/* Parse command line options. */
|
|
BEGIN_ALLOW_OMITTING_FIELD_INITIALIZERS
|
|
static const struct program_option options[] =
|
|
{
|
|
{ "add-location", CHAR_MAX + 'n', optional_argument },
|
|
{ NULL, 'n', no_argument },
|
|
{ "color", CHAR_MAX + 9, optional_argument },
|
|
{ "comment", 'C', no_argument },
|
|
{ "directory", 'D', required_argument },
|
|
{ "domain", 'M', required_argument },
|
|
{ "escape", CHAR_MAX + 1, no_argument },
|
|
{ "extended-regexp", 'E', no_argument },
|
|
{ "extracted-comment", 'X', no_argument },
|
|
{ "file", 'f', required_argument },
|
|
{ "fixed-strings", 'F', no_argument },
|
|
{ "force-po", 0, no_argument, &force_po, 1 },
|
|
{ "help", 'h', no_argument },
|
|
{ "ignore-case", 'i', no_argument },
|
|
{ "indent", CHAR_MAX + 2, no_argument },
|
|
{ "invert-match", 'v', no_argument },
|
|
{ "location", 'N', required_argument },
|
|
{ "msgctxt", 'J', no_argument },
|
|
{ "msgid", 'K', no_argument },
|
|
{ "msgstr", 'T', no_argument },
|
|
{ "no-escape", CHAR_MAX + 3, no_argument },
|
|
{ "no-location", CHAR_MAX + 11, no_argument },
|
|
{ "no-wrap", CHAR_MAX + 6, no_argument },
|
|
{ "output-file", 'o', required_argument },
|
|
{ "properties-input", 'P', no_argument },
|
|
{ "properties-output", 'p', no_argument },
|
|
{ "regexp", 'e', required_argument },
|
|
{ "sort-by-file", CHAR_MAX + 4, no_argument },
|
|
{ "sort-output", CHAR_MAX + 5, no_argument },
|
|
{ "sticky-flag", 'S', required_argument },
|
|
{ "strict", CHAR_MAX + 12, no_argument },
|
|
{ "stringtable-input", CHAR_MAX + 7, no_argument },
|
|
{ "stringtable-output", CHAR_MAX + 8, no_argument },
|
|
{ "style", CHAR_MAX + 10, required_argument },
|
|
{ "version", 'V', no_argument },
|
|
{ "width", 'w', required_argument },
|
|
{ "workflow-flag", 'W', required_argument },
|
|
};
|
|
END_ALLOW_OMITTING_FIELD_INITIALIZERS
|
|
start_options (argc, argv, options, MOVE_OPTIONS_FIRST, 0);
|
|
{
|
|
int grep_pass = -1;
|
|
int opt;
|
|
while ((opt = get_next_option ()) != -1)
|
|
switch (opt)
|
|
{
|
|
case '\0': /* Long option with key == 0. */
|
|
break;
|
|
|
|
case 'C':
|
|
grep_pass = 3;
|
|
break;
|
|
|
|
case 'D':
|
|
dir_list_append (optarg);
|
|
break;
|
|
|
|
case 'e':
|
|
if (grep_pass < 0)
|
|
no_pass (opt);
|
|
{
|
|
struct grep_task *gt = &grep_task[grep_pass];
|
|
/* Append optarg and a newline to gt->patterns. */
|
|
size_t len = strlen (optarg);
|
|
gt->patterns =
|
|
(char *) xrealloc (gt->patterns, gt->patterns_size + len + 1);
|
|
memcpy (gt->patterns + gt->patterns_size, optarg, len);
|
|
gt->patterns_size += len;
|
|
*(gt->patterns + gt->patterns_size) = '\n';
|
|
gt->patterns_size += 1;
|
|
gt->pattern_count++;
|
|
}
|
|
break;
|
|
|
|
case 'E':
|
|
if (grep_pass < 0)
|
|
no_pass (opt);
|
|
grep_task[grep_pass].matcher = &matcher_egrep;
|
|
break;
|
|
|
|
case 'f':
|
|
if (grep_pass < 0)
|
|
no_pass (opt);
|
|
{
|
|
struct grep_task *gt = &grep_task[grep_pass];
|
|
|
|
/* Append the contents of the specified file to gt->patterns. */
|
|
FILE *fp = fopen (optarg, "r");
|
|
if (fp == NULL)
|
|
error (EXIT_FAILURE, errno,
|
|
_("error while opening \"%s\" for reading"), optarg);
|
|
|
|
while (!feof (fp))
|
|
{
|
|
char buf[4096];
|
|
size_t count = fread (buf, 1, sizeof buf, fp);
|
|
|
|
if (count == 0)
|
|
{
|
|
if (ferror (fp))
|
|
error (EXIT_FAILURE, errno,
|
|
_("error while reading \"%s\""), optarg);
|
|
/* EOF reached. */
|
|
break;
|
|
}
|
|
|
|
gt->patterns =
|
|
(char *) xrealloc (gt->patterns, gt->patterns_size + count);
|
|
memcpy (gt->patterns + gt->patterns_size, buf, count);
|
|
gt->patterns_size += count;
|
|
}
|
|
|
|
/* Append a final newline if file ended in a non-newline. */
|
|
if (gt->patterns_size > 0
|
|
&& *(gt->patterns + gt->patterns_size - 1) != '\n')
|
|
{
|
|
gt->patterns =
|
|
(char *) xrealloc (gt->patterns, gt->patterns_size + 1);
|
|
*(gt->patterns + gt->patterns_size) = '\n';
|
|
gt->patterns_size += 1;
|
|
}
|
|
|
|
fclose (fp);
|
|
gt->pattern_count++;
|
|
}
|
|
break;
|
|
|
|
case 'F':
|
|
if (grep_pass < 0)
|
|
no_pass (opt);
|
|
grep_task[grep_pass].matcher = &matcher_fgrep;
|
|
break;
|
|
|
|
case 'h':
|
|
do_help = true;
|
|
break;
|
|
|
|
case 'i':
|
|
if (grep_pass < 0)
|
|
no_pass (opt);
|
|
grep_task[grep_pass].case_insensitive = true;
|
|
break;
|
|
|
|
case 'J':
|
|
grep_pass = 0;
|
|
break;
|
|
|
|
case 'K':
|
|
grep_pass = 1;
|
|
break;
|
|
|
|
case 'M':
|
|
string_list_append (domain_names, optarg);
|
|
break;
|
|
|
|
case 'n': /* -n */
|
|
case CHAR_MAX + 'n': /* --add-location[={full|yes|file|never|no}] */
|
|
if (handle_filepos_comment_option (optarg))
|
|
usage (EXIT_FAILURE);
|
|
break;
|
|
|
|
case 'N':
|
|
string_list_append (location_files, optarg);
|
|
break;
|
|
|
|
case 'o':
|
|
output_file = optarg;
|
|
break;
|
|
|
|
case 'p':
|
|
output_syntax = &output_format_properties;
|
|
break;
|
|
|
|
case 'P':
|
|
input_syntax = &input_format_properties;
|
|
break;
|
|
|
|
case CHAR_MAX + 12: /* --strict */
|
|
message_print_style_uniforum ();
|
|
break;
|
|
|
|
case 'S':
|
|
string_list_append (sticky_flags, optarg);
|
|
break;
|
|
|
|
case 'T':
|
|
grep_pass = 2;
|
|
break;
|
|
|
|
case 'v':
|
|
invert_match = true;
|
|
break;
|
|
|
|
case 'V':
|
|
do_version = true;
|
|
break;
|
|
|
|
case 'w':
|
|
{
|
|
char *endp;
|
|
int value = strtol (optarg, &endp, 10);
|
|
if (endp != optarg)
|
|
message_page_width_set (value);
|
|
}
|
|
break;
|
|
|
|
case 'W':
|
|
string_list_append (workflow_flags, optarg);
|
|
break;
|
|
|
|
case 'X':
|
|
grep_pass = 4;
|
|
break;
|
|
|
|
case CHAR_MAX + 1:
|
|
message_print_style_escape (true);
|
|
break;
|
|
|
|
case CHAR_MAX + 2:
|
|
message_print_style_indent ();
|
|
break;
|
|
|
|
case CHAR_MAX + 3:
|
|
message_print_style_escape (false);
|
|
break;
|
|
|
|
case CHAR_MAX + 4:
|
|
sort_by_filepos = true;
|
|
break;
|
|
|
|
case CHAR_MAX + 5:
|
|
sort_by_msgid = true;
|
|
break;
|
|
|
|
case CHAR_MAX + 6: /* --no-wrap */
|
|
message_page_width_ignore ();
|
|
break;
|
|
|
|
case CHAR_MAX + 7: /* --stringtable-input */
|
|
input_syntax = &input_format_stringtable;
|
|
break;
|
|
|
|
case CHAR_MAX + 8: /* --stringtable-output */
|
|
output_syntax = &output_format_stringtable;
|
|
break;
|
|
|
|
case CHAR_MAX + 9: /* --color */
|
|
if (handle_color_option (optarg) || color_test_mode)
|
|
usage (EXIT_FAILURE);
|
|
break;
|
|
|
|
case CHAR_MAX + 10: /* --style */
|
|
handle_style_option (optarg);
|
|
break;
|
|
|
|
case CHAR_MAX + 11: /* --no-location */
|
|
message_print_style_filepos (filepos_comment_none);
|
|
break;
|
|
|
|
default:
|
|
usage (EXIT_FAILURE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Version information is requested. */
|
|
if (do_version)
|
|
{
|
|
printf ("%s (GNU %s) %s\n", last_component (program_name),
|
|
PACKAGE, VERSION);
|
|
/* xgettext: no-wrap */
|
|
printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
|
|
License GPLv3+: GNU GPL version 3 or later <%s>\n\
|
|
This is free software: you are free to change and redistribute it.\n\
|
|
There is NO WARRANTY, to the extent permitted by law.\n\
|
|
"),
|
|
"2001-2026", "https://gnu.org/licenses/gpl.html");
|
|
printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
|
|
/* Help is requested. */
|
|
if (do_help)
|
|
usage (EXIT_SUCCESS);
|
|
|
|
/* Test whether we have an .po file name as argument. */
|
|
const char *input_file = NULL;
|
|
if (optind == argc)
|
|
input_file = "-";
|
|
else if (optind + 1 == argc)
|
|
input_file = argv[optind];
|
|
else
|
|
{
|
|
error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
|
|
usage (EXIT_FAILURE);
|
|
}
|
|
|
|
/* Verify selected options. */
|
|
if (sort_by_msgid && sort_by_filepos)
|
|
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
|
|
"--sort-output", "--sort-by-file");
|
|
|
|
/* Compile the patterns. */
|
|
for (int grep_pass = 0; grep_pass < 5; grep_pass++)
|
|
{
|
|
struct grep_task *gt = &grep_task[grep_pass];
|
|
|
|
if (gt->pattern_count > 0)
|
|
{
|
|
if (gt->patterns_size > 0)
|
|
{
|
|
/* Strip trailing newline. */
|
|
assert (gt->patterns[gt->patterns_size - 1] == '\n');
|
|
gt->patterns_size--;
|
|
}
|
|
gt->compiled_patterns =
|
|
gt->matcher->compile (gt->patterns, gt->patterns_size,
|
|
gt->case_insensitive, false, false, '\n');
|
|
}
|
|
}
|
|
|
|
/* Read input file. */
|
|
msgdomain_list_ty *result = read_catalog_file (input_file, input_syntax);
|
|
|
|
if (grep_task[0].pattern_count > 0
|
|
|| grep_task[1].pattern_count > 0
|
|
|| grep_task[2].pattern_count > 0
|
|
|| grep_task[3].pattern_count > 0
|
|
|| grep_task[4].pattern_count > 0)
|
|
{
|
|
/* Warn if the current locale is not suitable for this PO file. */
|
|
compare_po_locale_charsets (result);
|
|
}
|
|
|
|
/* Select the messages. */
|
|
result = process_msgdomain_list (result);
|
|
|
|
/* Sort the results. */
|
|
if (sort_by_filepos)
|
|
msgdomain_list_sort_by_filepos (result);
|
|
else if (sort_by_msgid)
|
|
msgdomain_list_sort_by_msgid (result);
|
|
|
|
/* Write the merged message list out. */
|
|
msgdomain_list_print (result, output_file, output_syntax,
|
|
textmode_xerror_handler, force_po, false);
|
|
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
|
|
|
|
static void
|
|
no_pass (int opt)
|
|
{
|
|
error (EXIT_SUCCESS, 0,
|
|
_("option '%c' cannot be used before 'J' or 'K' or 'T' or 'C' or 'X' has been specified"),
|
|
opt);
|
|
usage (EXIT_FAILURE);
|
|
}
|
|
|
|
|
|
/* Display usage information and exit. */
|
|
static void
|
|
usage (int status)
|
|
{
|
|
if (status != EXIT_SUCCESS)
|
|
fprintf (stderr, _("Try '%s --help' for more information.\n"),
|
|
program_name);
|
|
else
|
|
{
|
|
printf (_("\
|
|
Usage: %s [OPTION] [INPUTFILE]\n\
|
|
"), program_name);
|
|
printf ("\n");
|
|
/* xgettext: no-wrap */
|
|
printf (_("\
|
|
Extracts all messages of a translation catalog that match a given pattern\n\
|
|
or belong to some given source files.\n\
|
|
"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Mandatory arguments to long options are mandatory for short options too.\n"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Input file location:\n"));
|
|
printf (_("\
|
|
INPUTFILE input PO file\n"));
|
|
printf (_("\
|
|
-D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
|
|
printf (_("\
|
|
If no input file is given or if it is -, standard input is read.\n"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Output file location:\n"));
|
|
printf (_("\
|
|
-o, --output-file=FILE write output to specified file\n"));
|
|
printf (_("\
|
|
The results are written to standard output if no output file is specified\n\
|
|
or if it is -.\n"));
|
|
printf ("\n");
|
|
/* xgettext: no-wrap */
|
|
printf (_("\
|
|
Message selection:\n\
|
|
[-N SOURCEFILE]... [-M DOMAINNAME]...\n\
|
|
[-J MSGCTXT-PATTERN] [-K MSGID-PATTERN] [-T MSGSTR-PATTERN]\n\
|
|
[-C COMMENT-PATTERN] [-X EXTRACTED-COMMENT-PATTERN]\n\
|
|
[-W WORKFLOW-FLAG] [-S STICKY-FLAG]\n\
|
|
A message is selected if it comes from one of the specified source files,\n\
|
|
or if it comes from one of the specified domains,\n\
|
|
or if -J is given and its context (msgctxt) matches MSGCTXT-PATTERN,\n\
|
|
or if -K is given and its key (msgid or msgid_plural) matches MSGID-PATTERN,\n\
|
|
or if -T is given and its translation (msgstr) matches MSGSTR-PATTERN,\n\
|
|
or if -C is given and the translator's comment matches COMMENT-PATTERN,\n\
|
|
or if -X is given and the extracted comment matches EXTRACTED-COMMENT-PATTERN,\n\
|
|
or if -W is given and its flags contain WORKFLOW-FLAG,\n\
|
|
or if -S is given and its flags contain STICKY-FLAG.\n\
|
|
\n\
|
|
When more than one selection criterion is specified, the set of selected\n\
|
|
messages is the union of the selected messages of each criterion.\n\
|
|
\n\
|
|
MSGCTXT-PATTERN or MSGID-PATTERN or MSGSTR-PATTERN or COMMENT-PATTERN or\n\
|
|
EXTRACTED-COMMENT-PATTERN syntax:\n\
|
|
[-E | -F] [-e PATTERN | -f FILE]...\n\
|
|
PATTERNs are basic regular expressions by default, or extended regular\n\
|
|
expressions if -E is given, or fixed strings if -F is given.\n\
|
|
\n\
|
|
-N, --location=SOURCEFILE select messages extracted from SOURCEFILE\n\
|
|
-M, --domain=DOMAINNAME select messages belonging to domain DOMAINNAME\n\
|
|
-J, --msgctxt start of patterns for the msgctxt\n\
|
|
-K, --msgid start of patterns for the msgid\n\
|
|
-T, --msgstr start of patterns for the msgstr\n\
|
|
-C, --comment start of patterns for the translator's comment\n\
|
|
-X, --extracted-comment start of patterns for the extracted comment\n\
|
|
-E, --extended-regexp PATTERN is an extended regular expression\n\
|
|
-F, --fixed-strings PATTERN is a set of newline-separated strings\n\
|
|
-e, --regexp=PATTERN use PATTERN as a regular expression\n\
|
|
-f, --file=FILE obtain PATTERN from FILE\n\
|
|
-i, --ignore-case ignore case distinctions\n\
|
|
-W, --workflow-flag=FLAG select messages with FLAG\n\
|
|
-S, --sticky-flag=FLAG select messages with FLAG\n\
|
|
-v, --invert-match output only the messages that do not match any\n\
|
|
selection criterion\n\
|
|
"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Input file syntax:\n"));
|
|
printf (_("\
|
|
-P, --properties-input input file is in Java .properties syntax\n"));
|
|
printf (_("\
|
|
--stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Output details:\n"));
|
|
printf (_("\
|
|
--color use colors and other text attributes always\n\
|
|
--color=WHEN use colors and other text attributes if WHEN.\n\
|
|
WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
|
|
printf (_("\
|
|
--style=STYLEFILE specify CSS style rule file for --color\n"));
|
|
printf (_("\
|
|
--no-escape do not use C escapes in output (default)\n"));
|
|
printf (_("\
|
|
--escape use C escapes in output, no extended chars\n"));
|
|
printf (_("\
|
|
--force-po write PO file even if empty\n"));
|
|
printf (_("\
|
|
--indent indented output style\n"));
|
|
printf (_("\
|
|
--no-location suppress '#: filename:line' lines\n"));
|
|
printf (_("\
|
|
-n, --add-location preserve '#: filename:line' lines (default)\n"));
|
|
printf (_("\
|
|
--strict strict Uniforum output style\n"));
|
|
printf (_("\
|
|
-p, --properties-output write out a Java .properties file\n"));
|
|
printf (_("\
|
|
--stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
|
|
printf (_("\
|
|
-w, --width=NUMBER set output page width\n"));
|
|
printf (_("\
|
|
--no-wrap do not break long message lines, longer than\n\
|
|
the output page width, into several lines\n"));
|
|
printf (_("\
|
|
--sort-output generate sorted output\n"));
|
|
printf (_("\
|
|
--sort-by-file sort output by file location\n"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Informative output:\n"));
|
|
printf (_("\
|
|
-h, --help display this help and exit\n"));
|
|
printf (_("\
|
|
-V, --version output version information and exit\n"));
|
|
printf ("\n");
|
|
/* TRANSLATORS: The first placeholder is the web address of the Savannah
|
|
project of this package. The second placeholder is the bug-reporting
|
|
email address for this package. Please add _another line_ saying
|
|
"Report translation bugs to <...>\n" with the address for translation
|
|
bugs (typically your translation team's web or email address). */
|
|
printf (_("\
|
|
Report bugs in the bug tracker at <%s>\n\
|
|
or by email to <%s>.\n"),
|
|
"https://savannah.gnu.org/projects/gettext",
|
|
"bug-gettext@gnu.org");
|
|
}
|
|
|
|
exit (status);
|
|
}
|
|
|
|
|
|
/* Return 1 if FILENAME is contained in a list of filename patterns,
|
|
0 otherwise. */
|
|
static bool
|
|
filename_list_match (const string_list_ty *slp, const char *filename)
|
|
{
|
|
for (size_t j = 0; j < slp->nitems; ++j)
|
|
if (fnmatch (slp->item[j], filename, FNM_PATHNAME) == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
/* Process a string STR of size LEN bytes through grep, and return true
|
|
if it matches. */
|
|
static bool
|
|
is_string_selected (int grep_pass, const char *str, size_t len)
|
|
{
|
|
const struct grep_task *gt = &grep_task[grep_pass];
|
|
|
|
if (gt->pattern_count > 0)
|
|
{
|
|
size_t match_size;
|
|
size_t match_offset =
|
|
gt->matcher->execute (gt->compiled_patterns, str, len,
|
|
&match_size, false);
|
|
return (match_offset != (size_t) -1);
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Return true if a message matches, considering only the positive selection
|
|
criteria and ignoring --invert-match. */
|
|
static bool
|
|
is_message_selected_no_invert (const message_ty *mp)
|
|
{
|
|
/* Test whether one of mp->filepos[] is selected. */
|
|
for (size_t i = 0; i < mp->filepos_count; i++)
|
|
if (filename_list_match (location_files, mp->filepos[i].file_name))
|
|
return true;
|
|
|
|
/* Test whether one of the workflow flags is selected. */
|
|
if (mp->is_fuzzy && string_list_member (workflow_flags, "fuzzy"))
|
|
return true;
|
|
|
|
/* Test whether one of the sticky flags is selected. */
|
|
/* Recognize flag "[no-]wrap". */
|
|
if ((mp->do_wrap == yes && string_list_member (sticky_flags, "wrap"))
|
|
|| (mp->do_wrap == no && string_list_member (sticky_flags, "no-wrap")))
|
|
return true;
|
|
/* Recognize flag "[no-]<language>-format". */
|
|
for (size_t i = 0; i < sticky_flags->nitems; i++)
|
|
{
|
|
const char *flag = sticky_flags->item[i];
|
|
size_t flag_len = strlen (flag);
|
|
if (flag_len >= 7 && memcmp (flag + flag_len - 7, "-format", 7) == 0)
|
|
{
|
|
flag_len -= 7;
|
|
bool has_no_prefix = (flag_len >= 3 && memcmp (flag, "no-", 3) == 0);
|
|
if (has_no_prefix)
|
|
{
|
|
flag += 3;
|
|
flag_len -= 3;
|
|
}
|
|
for (size_t j = 0; j < NFORMATS; j++)
|
|
if (strlen (format_language[j]) == flag_len
|
|
&& memcmp (format_language[j], flag, flag_len) == 0)
|
|
{
|
|
/* The value of the flag is stored in mp->is_format[j]. */
|
|
if (mp->is_format[j] == (has_no_prefix ? no : yes))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Test msgctxt using the --msgctxt arguments. */
|
|
if (mp->msgctxt != NULL
|
|
&& is_string_selected (0, mp->msgctxt, strlen (mp->msgctxt)))
|
|
return true;
|
|
|
|
/* Test msgid and msgid_plural using the --msgid arguments. */
|
|
if (is_string_selected (1, mp->msgid, strlen (mp->msgid)))
|
|
return true;
|
|
if (mp->msgid_plural != NULL
|
|
&& is_string_selected (1, mp->msgid_plural, strlen (mp->msgid_plural)))
|
|
return true;
|
|
|
|
/* Test msgstr using the --msgstr arguments. */
|
|
{
|
|
const char *msgstr = mp->msgstr;
|
|
size_t msgstr_len = mp->msgstr_len;
|
|
/* Process each NUL delimited substring separately. */
|
|
for (const char *p = msgstr; p < msgstr + msgstr_len; )
|
|
{
|
|
size_t length = strlen (p);
|
|
|
|
if (is_string_selected (2, p, length))
|
|
return true;
|
|
|
|
p += length + 1;
|
|
}
|
|
}
|
|
|
|
/* Test translator comments using the --comment arguments. */
|
|
if (grep_task[3].pattern_count > 0
|
|
&& mp->comment != NULL && mp->comment->nitems > 0)
|
|
{
|
|
size_t length;
|
|
{
|
|
length = 0;
|
|
for (size_t j = 0; j < mp->comment->nitems; j++)
|
|
length += strlen (mp->comment->item[j]) + 1;
|
|
}
|
|
char *total_comment = (char *) xmalloca (length);
|
|
|
|
{
|
|
char *q = total_comment;
|
|
for (size_t j = 0; j < mp->comment->nitems; j++)
|
|
{
|
|
size_t l = strlen (mp->comment->item[j]);
|
|
|
|
memcpy (q, mp->comment->item[j], l);
|
|
q += l;
|
|
*q++ = '\n';
|
|
}
|
|
if (q != total_comment + length)
|
|
abort ();
|
|
}
|
|
|
|
bool selected = is_string_selected (3, total_comment, length);
|
|
|
|
freea (total_comment);
|
|
|
|
if (selected)
|
|
return true;
|
|
}
|
|
|
|
/* Test extracted comments using the --extracted-comment arguments. */
|
|
if (grep_task[4].pattern_count > 0
|
|
&& mp->comment_dot != NULL && mp->comment_dot->nitems > 0)
|
|
{
|
|
size_t length;
|
|
{
|
|
length = 0;
|
|
for (size_t j = 0; j < mp->comment_dot->nitems; j++)
|
|
length += strlen (mp->comment_dot->item[j]) + 1;
|
|
}
|
|
char *total_comment = (char *) xmalloca (length);
|
|
|
|
{
|
|
char *q = total_comment;
|
|
for (size_t j = 0; j < mp->comment_dot->nitems; j++)
|
|
{
|
|
size_t l = strlen (mp->comment_dot->item[j]);
|
|
|
|
memcpy (q, mp->comment_dot->item[j], l);
|
|
q += l;
|
|
*q++ = '\n';
|
|
}
|
|
if (q != total_comment + length)
|
|
abort ();
|
|
}
|
|
|
|
bool selected = is_string_selected (4, total_comment, length);
|
|
|
|
freea (total_comment);
|
|
|
|
if (selected)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* Return true if a message matches. */
|
|
static bool
|
|
is_message_selected (const message_ty *mp)
|
|
{
|
|
/* Always keep the header entry. */
|
|
if (is_header (mp))
|
|
return true;
|
|
|
|
bool result = is_message_selected_no_invert (mp);
|
|
|
|
if (invert_match)
|
|
return !result;
|
|
else
|
|
return result;
|
|
}
|
|
|
|
|
|
static void
|
|
process_message_list (const char *domain, message_list_ty *mlp)
|
|
{
|
|
if (string_list_member (domain_names, domain))
|
|
/* Keep all the messages in the list. */
|
|
;
|
|
else
|
|
/* Keep only the selected messages. */
|
|
message_list_remove_if_not (mlp, is_message_selected);
|
|
}
|
|
|
|
|
|
static msgdomain_list_ty *
|
|
process_msgdomain_list (msgdomain_list_ty *mdlp)
|
|
{
|
|
for (size_t k = 0; k < mdlp->nitems; k++)
|
|
process_message_list (mdlp->item[k]->domain, mdlp->item[k]->messages);
|
|
|
|
return mdlp;
|
|
}
|