blob: 115e5691f3108c9876b17e4473349f9d9a7a1deb [file] [log] [blame]
/* xgettext Ruby backend.
Copyright (C) 2020 Free Software Foundation, Inc.
Written by Bruno Haible <bruno@clisp.org>, 2020.
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/>. */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
/* Specification. */
#include "x-ruby.h"
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "message.h"
#include "sh-quote.h"
#include "spawn-pipe.h"
#include "wait-process.h"
#include "xvasprintf.h"
#include "x-po.h"
#include "xgettext.h"
#include "xg-message.h"
#include "c-strstr.h"
#include "read-catalog-abstract.h"
#include "error.h"
#include "gettext.h"
/* A convenience macro. I don't like writing gettext() every time. */
#define _(str) gettext (str)
/* The Ruby syntax is defined in
https://ruby-doc.org/core-2.7.1/doc/syntax_rdoc.html
https://ruby-doc.org/core-2.7.1/doc/syntax/comments_rdoc.html
https://ruby-doc.org/core-2.7.1/doc/syntax/literals_rdoc.html
We don't parse Ruby directly, but instead rely on the 'rxgettext' program
from https://github.com/ruby-gettext/gettext . */
/* ====================== Keyword set customization. ====================== */
/* This function currently has no effect. */
void
x_ruby_extract_all (void)
{
}
/* This function currently has no effect. */
void
x_ruby_keyword (const char *keyword)
{
}
/* This function currently has no effect. */
void
init_flag_table_ruby (void)
{
}
/* ========================= Extracting strings. ========================== */
void
extract_ruby (const char *found_in_dir, const char *real_filename,
const char *logical_filename,
flag_context_list_table_ty *flag_table,
msgdomain_list_ty *mdlp)
{
const char *progname = "rxgettext";
char *dummy_filename;
msgdomain_list_ty *mdlp2;
int pass;
dummy_filename = xasprintf (_("(output from '%s')"), progname);
/* Invoke rgettext twice:
1. to get the messages, without ruby-format flags.
2. to get the 'xgettext:' comments that guide us while adding
[no-]ruby-format flags. */
mdlp2 = msgdomain_list_alloc (true);
for (pass = 0; pass < 2; pass++)
{
const char *argv[4];
unsigned int i;
pid_t child;
int fd[1];
FILE *fp;
int exitstatus;
/* Prepare arguments. */
argv[0] = progname;
i = 1;
if (pass > 0)
argv[i++] = "--add-comments=xgettext:";
else
{
if (add_all_comments)
argv[i++] = "--add-comments";
else if (comment_tag != NULL)
argv[i++] = xasprintf ("--add-comments=%s", comment_tag);
}
argv[i++] = logical_filename;
argv[i] = NULL;
if (verbose)
{
char *command = shell_quote_argv (argv);
error (0, 0, "%s", command);
free (command);
}
child = create_pipe_in (progname, progname, argv, found_in_dir,
DEV_NULL, false, true, true, fd);
fp = fdopen (fd[0], "r");
if (fp == NULL)
error (EXIT_FAILURE, errno, _("fdopen() failed"));
/* Read the resulting PO file. */
extract_po (fp, dummy_filename, dummy_filename, flag_table,
pass == 0 ? mdlp : mdlp2);
fclose (fp);
/* Remove zombie process from process list, and retrieve exit status. */
exitstatus =
wait_subprocess (child, progname, false, false, true, true, NULL);
if (exitstatus != 0)
error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"),
progname, exitstatus);
}
/* Add [no-]ruby-format flags and process 'xgettext:' comments.
This processing is similar to the one done in remember_a_message(). */
if (mdlp->nitems == 1 && mdlp2->nitems == 1)
{
message_list_ty *mlp = mdlp->item[0]->messages;
message_list_ty *mlp2 = mdlp2->item[0]->messages;
size_t j;
for (j = 0; j < mlp->nitems; j++)
{
message_ty *mp = mlp->item[j];
if (!is_header (mp))
{
/* Find 'xgettext:' comments and apply them to mp. */
message_ty *mp2 =
message_list_search (mlp2, mp->msgctxt, mp->msgid);
if (mp2 != NULL && mp2->comment_dot != NULL)
{
string_list_ty *mp2_comment_dot = mp2->comment_dot;
size_t k;
for (k = 0; k < mp2_comment_dot->nitems; k++)
{
const char *s = mp2_comment_dot->item[k];
/* To reduce the possibility of unwanted matches we do a
two step match: the line must contain 'xgettext:' and
one of the possible format description strings. */
const char *t = c_strstr (s, "xgettext:");
if (t != NULL)
{
bool tmp_fuzzy;
enum is_format tmp_format[NFORMATS];
struct argument_range tmp_range;
enum is_wrap tmp_wrap;
enum is_syntax_check tmp_syntax_check[NSYNTAXCHECKS];
bool interesting;
size_t i;
t += strlen ("xgettext:");
po_parse_comment_special (t, &tmp_fuzzy, tmp_format,
&tmp_range, &tmp_wrap,
tmp_syntax_check);
interesting = false;
for (i = 0; i < NFORMATS; i++)
if (tmp_format[i] != undecided)
{
mp->is_format[i] = tmp_format[i];
interesting = true;
}
if (has_range_p (tmp_range))
{
intersect_range (mp, &tmp_range);
interesting = true;
}
if (tmp_wrap != undecided)
{
mp->do_wrap = tmp_wrap;
interesting = true;
}
for (i = 0; i < NSYNTAXCHECKS; i++)
if (tmp_syntax_check[i] != undecided)
{
mp->do_syntax_check[i] = tmp_syntax_check[i];
interesting = true;
}
/* If the "xgettext:" marker was followed by an
interesting keyword, and we updated our
is_format/do_wrap variables, eliminate the comment
from the #. comments. */
if (interesting)
if (mp->comment_dot != NULL)
{
const char *removed =
string_list_remove (mp->comment_dot, s);
if (removed != NULL)
free ((char *) removed);
}
}
}
}
/* Now evaluate the consequences of the 'xgettext:' comments, */
decide_is_format (mp);
decide_do_wrap (mp);
decide_syntax_check (mp);
}
}
}
msgdomain_list_free (mdlp2);
free (dummy_filename);
}