xgettext: Support per-file options

* autogen.sh (GNULIB_MODULES_TOOLS_FOR_SRC): Add getsubopt.
* gettext-tools/src/xgettext.c: Include <fnmatch.h>.
(long_options): Add --options.
(main): Handle --options.  Factor out language guessing to...
(filename_to_language): ...here.
(usage): Mention --options.
(enum suboption_type): New enum.
(suboption_tokens): New variable.
(struct suboption_ty, struct suboption_list_ty,
struct suboption_list_list_ty): New struct.
(suboption_list_destroy, suboption_list_insert,
suboption_list_list_destroy, suboption_list_list_insert): New function.
(suboptions): New variable.
This commit is contained in:
Daiki Ueno 2016-03-02 19:18:40 +09:00
parent b0af5330b3
commit de4796a11d
2 changed files with 191 additions and 27 deletions

View File

@ -200,6 +200,7 @@ if ! $skip_gnulib; then
gcd
getline
getopt-gnu
getsubopt
gettext-h
hash
html-styled-ostream

View File

@ -22,6 +22,7 @@
#include <ctype.h>
#include <errno.h>
#include <fnmatch.h>
#include <getopt.h>
#include <stdio.h>
#include <time.h>
@ -258,6 +259,7 @@ static const struct option long_options[] =
{ "no-location", no_argument, NULL, CHAR_MAX + 16 },
{ "no-wrap", no_argument, NULL, CHAR_MAX + 4 },
{ "omit-header", no_argument, &xgettext_omit_header, 1 },
{ "options", required_argument, NULL, 'O' },
{ "output", required_argument, NULL, 'o' },
{ "output-dir", required_argument, NULL, 'p' },
{ "package-name", required_argument, NULL, CHAR_MAX + 12 },
@ -278,6 +280,87 @@ static const struct option long_options[] =
};
enum suboption_type
{
OPT_FLAG,
OPT_KEYWORD,
OPT_LANGUAGE,
NSUBOPTS
};
static char * const suboption_tokens[] =
{
"flag",
"keyword",
"language",
NULL
};
struct suboption_ty
{
enum suboption_type type;
char *value;
};
struct suboption_list_ty
{
char *pattern;
struct suboption_ty *items;
size_t nitems;
size_t nitems_max;
};
struct suboption_list_list_ty
{
struct suboption_list_ty *items;
size_t nitems;
size_t nitems_max;
};
static void
suboption_list_destroy (struct suboption_list_ty *opts)
{
free (opts->items);
}
static void
suboption_list_insert (struct suboption_list_ty *opts, struct suboption_ty *opt)
{
if (opts->nitems == opts->nitems_max)
{
opts->nitems_max = opts->nitems_max * 2 + 1;
opts->items = xrealloc (opts->items,
opts->nitems_max * sizeof (struct suboption_ty));
}
memcpy (&opts->items[opts->nitems++], opt, sizeof (struct suboption_ty));
}
static void
suboption_list_list_destroy (struct suboption_list_list_ty *list)
{
size_t i;
for (i = 0; i < list->nitems; i++)
suboption_list_destroy (&list->items[i]);
free (list->items);
}
static void
suboption_list_list_insert (struct suboption_list_list_ty *list,
struct suboption_list_ty *opts)
{
if (list->nitems == list->nitems_max)
{
list->nitems_max = list->nitems_max * 2 + 1;
list->items = xrealloc (list->items,
list->nitems_max * sizeof (struct suboption_list_ty));
}
memcpy (&list->items[list->nitems++], opts,
sizeof (struct suboption_list_ty));
}
static struct suboption_list_list_ty suboptions;
/* The extractors must all be functions returning void and taking three
arguments designating the input stream and one message domain list argument
in which to add the messages. */
@ -314,6 +397,7 @@ static message_ty *construct_header (void);
static void finalize_header (msgdomain_list_ty *mdlp);
static extractor_ty language_to_extractor (const char *name);
static const char *extension_to_language (const char *extension);
static const char *filename_to_language (const char *filename);
int
@ -381,7 +465,7 @@ main (int argc, char *argv[])
init_flag_table_vala ();
while ((optchar = getopt_long (argc, argv,
"ac::Cd:D:eEf:Fhijk::l:L:m::M::no:p:sTVw:W:x:",
"ac::Cd:D:eEf:Fhijk::l:L:m::M::no:O:p:sTVw:W:x:",
long_options, NULL)) != EOF)
switch (optchar)
{
@ -518,6 +602,53 @@ main (int argc, char *argv[])
output_file = optarg;
break;
case 'O':
{
char *subopts = strchr (optarg, ':');
if (subopts != NULL)
{
char *pattern;
struct suboption_list_ty *opts;
pattern = xmalloc (subopts - optarg + 1);
memcpy (pattern, optarg, subopts - optarg);
pattern[subopts - optarg] = '\0';
opts = XZALLOC (struct suboption_list_ty);
opts->pattern = pattern;
subopts++;
while (*subopts != '\0')
{
int suboptchar;
char *value;
suboptchar = getsubopt (&subopts, suboption_tokens, &value);
switch (suboptchar)
{
case OPT_FLAG:
case OPT_KEYWORD:
case OPT_LANGUAGE:
{
struct suboption_ty opt;
opt.type = suboptchar;
opt.value = xstrdup (value);
suboption_list_insert (opts, &opt);
}
break;
default:
error (EXIT_FAILURE, 0, _("unknown sub-option for -O"));
break;
}
}
suboption_list_list_insert (&suboptions, opts);
}
}
break;
case 'p':
{
size_t len = strlen (optarg);
@ -872,12 +1003,14 @@ warning: ITS rule file '%s' does not exist"), explicit_its_filename);
}
else
{
const char *language_from_extension = NULL;
const char *language_from_filename = NULL;
const char *base;
char *reduced;
base = strrchr (filename, '/');
if (!base)
if (base)
base++;
else
base = filename;
reduced = xstrdup (base);
@ -886,31 +1019,14 @@ warning: ITS rule file '%s' does not exist"), explicit_its_filename);
&& memcmp (reduced + strlen (reduced) - 3, ".in", 3) == 0)
reduced[strlen (reduced) - 3] = '\0';
/* If no language is specified with -L, deduce it the extension. */
/* If no language is specified with -L, guess it from the
--options and the file name extension. */
if (language == NULL)
{
const char *p;
/* Work out what the file extension is. */
p = reduced + strlen (reduced);
for (; p > reduced && language_from_extension == NULL; p--)
{
if (*p == '.')
{
const char *extension = p + 1;
/* Derive the language from the extension, and
the extractor function from the language. */
language_from_extension =
extension_to_language (extension);
}
}
}
language_from_filename = filename_to_language (reduced);
/* If language is not determined from the file name
extension, check ITS locating rules. */
if (language_from_extension == NULL
&& strcmp (filename, "-") != 0)
if (language_from_filename == NULL && strcmp (filename, "-") != 0)
{
const char *its_basename;
@ -958,7 +1074,7 @@ warning: ITS rule file '%s' does not exist; check your gettext installation"),
if (its_rules == NULL)
{
if (language_from_extension == NULL)
if (language_from_filename == NULL)
{
const char *extension = strrchr (reduced, '.');
if (extension == NULL)
@ -967,11 +1083,11 @@ warning: ITS rule file '%s' does not exist; check your gettext installation"),
extension++;
error (0, 0, _("\
warning: file '%s' extension '%s' is unknown; will try C"), filename, extension);
language_from_extension = "C";
language_from_filename = "C";
}
this_file_extractor =
language_to_extractor (language_from_extension);
language_to_extractor (language_from_filename);
}
free (reduced);
@ -1033,6 +1149,8 @@ warning: file '%s' extension '%s' is unknown; will try C"), filename, extension)
for (i = 0; i < SIZEOF (its_dirs); i++)
free (its_dirs[i]);
suboption_list_list_destroy (&suboptions);
exit (EXIT_SUCCESS);
}
@ -1102,6 +1220,9 @@ Input file interpretation:\n"));
--from-code=NAME encoding of input files\n\
(except for Python, Tcl, Glade)\n"));
printf (_("\
-O, --options=PAT:OPTIONS specify options effective to files matching PAT\n\
(flag, keyword, language)\n"));
printf (_("\
By default the input files are assumed to be in ASCII.\n"));
printf ("\n");
printf (_("\
@ -4013,3 +4134,45 @@ extension_to_language (const char *extension)
return tp->language;
return NULL;
}
static const char *
filename_to_language (const char *filename)
{
const char *p;
size_t i;
for (i = 0; i < suboptions.nitems; i++)
{
struct suboption_list_ty *opts =
&suboptions.items[suboptions.nitems - 1 - i];
if (fnmatch (opts->pattern, filename, FNM_PATHNAME) == 0)
{
size_t j;
for (j = 0; j < opts->nitems; j++)
{
struct suboption_ty *opt = &opts->items[j];
if (opt->type == OPT_LANGUAGE)
return opt->value;
}
}
}
/* Work out what the file extension is. */
p = filename + strlen (filename);
for (; p > filename; p--)
{
if (*p == '.')
{
const char *extension = p + 1;
/* Derive the language from the extension, and
the extractor function from the language. */
return extension_to_language (extension);
}
}
return NULL;
}