1 /* xgettext Ruby backend.
2 Copyright (C) 2020 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2020.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21
22 /* Specification. */
23 #include "x-ruby.h"
24
25 #include <errno.h>
26 #include <stdbool.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "message.h"
32 #include "sh-quote.h"
33 #include "spawn-pipe.h"
34 #include "wait-process.h"
35 #include "xvasprintf.h"
36 #include "x-po.h"
37 #include "xgettext.h"
38 #include "xg-message.h"
39 #include "c-strstr.h"
40 #include "read-catalog-abstract.h"
41 #include "error.h"
42 #include "gettext.h"
43
44 /* A convenience macro. I don't like writing gettext() every time. */
45 #define _(str) gettext (str)
46
47 /* The Ruby syntax is defined in
48 https://ruby-doc.org/core-2.7.1/doc/syntax_rdoc.html
49 https://ruby-doc.org/core-2.7.1/doc/syntax/comments_rdoc.html
50 https://ruby-doc.org/core-2.7.1/doc/syntax/literals_rdoc.html
51 We don't parse Ruby directly, but instead rely on the 'rxgettext' program
52 from https://github.com/ruby-gettext/gettext . */
53
54
55 /* ====================== Keyword set customization. ====================== */
56
57 /* This function currently has no effect. */
58 void
x_ruby_extract_all(void)59 x_ruby_extract_all (void)
60 {
61 }
62
63 /* This function currently has no effect. */
64 void
x_ruby_keyword(const char * keyword)65 x_ruby_keyword (const char *keyword)
66 {
67 }
68
69 /* This function currently has no effect. */
70 void
init_flag_table_ruby(void)71 init_flag_table_ruby (void)
72 {
73 }
74
75
76 /* ========================= Extracting strings. ========================== */
77
78 void
extract_ruby(const char * real_filename,const char * logical_filename,flag_context_list_table_ty * flag_table,msgdomain_list_ty * mdlp)79 extract_ruby (const char *real_filename, const char *logical_filename,
80 flag_context_list_table_ty *flag_table,
81 msgdomain_list_ty *mdlp)
82 {
83 const char *progname = "rxgettext";
84 char *dummy_filename;
85 msgdomain_list_ty *mdlp2;
86 int pass;
87
88 dummy_filename = xasprintf (_("(output from '%s')"), progname);
89
90 /* Invoke rgettext twice:
91 1. to get the messages, without ruby-format flags.
92 2. to get the 'xgettext:' comments that guide us while adding
93 [no-]ruby-format flags. */
94 mdlp2 = msgdomain_list_alloc (true);
95 for (pass = 0; pass < 2; pass++)
96 {
97 char *argv[4];
98 unsigned int i;
99 pid_t child;
100 int fd[1];
101 FILE *fp;
102 int exitstatus;
103
104 /* Prepare arguments. */
105 argv[0] = (char *) progname;
106 i = 1;
107
108 if (pass > 0)
109 argv[i++] = (char *) "--add-comments=xgettext:";
110 else
111 {
112 if (add_all_comments)
113 argv[i++] = (char *) "--add-comments";
114 else if (comment_tag != NULL)
115 argv[i++] = xasprintf ("--add-comments=%s", comment_tag);
116 }
117
118 argv[i++] = (char *) real_filename;
119
120 argv[i] = NULL;
121
122 if (verbose)
123 {
124 char *command = shell_quote_argv (argv);
125 error (0, 0, "%s", command);
126 free (command);
127 }
128
129 child = create_pipe_in (progname, progname, argv,
130 DEV_NULL, false, true, true, fd);
131
132 fp = fdopen (fd[0], "r");
133 if (fp == NULL)
134 error (EXIT_FAILURE, errno, _("fdopen() failed"));
135
136 /* Read the resulting PO file. */
137 extract_po (fp, dummy_filename, dummy_filename, flag_table,
138 pass == 0 ? mdlp : mdlp2);
139
140 fclose (fp);
141
142 /* Remove zombie process from process list, and retrieve exit status. */
143 exitstatus =
144 wait_subprocess (child, progname, false, false, true, true, NULL);
145 if (exitstatus != 0)
146 error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"),
147 progname, exitstatus);
148 }
149
150 /* Add [no-]ruby-format flags and process 'xgettext:' comments.
151 This processing is similar to the one done in remember_a_message(). */
152 if (mdlp->nitems == 1 && mdlp2->nitems == 1)
153 {
154 message_list_ty *mlp = mdlp->item[0]->messages;
155 message_list_ty *mlp2 = mdlp2->item[0]->messages;
156 size_t j;
157
158 for (j = 0; j < mlp->nitems; j++)
159 {
160 message_ty *mp = mlp->item[j];
161
162 if (!is_header (mp))
163 {
164 /* Find 'xgettext:' comments and apply them to mp. */
165 message_ty *mp2 =
166 message_list_search (mlp2, mp->msgctxt, mp->msgid);
167
168 if (mp2 != NULL && mp2->comment_dot != NULL)
169 {
170 string_list_ty *mp2_comment_dot = mp2->comment_dot;
171 size_t k;
172
173 for (k = 0; k < mp2_comment_dot->nitems; k++)
174 {
175 const char *s = mp2_comment_dot->item[k];
176
177 /* To reduce the possibility of unwanted matches we do a
178 two step match: the line must contain 'xgettext:' and
179 one of the possible format description strings. */
180 const char *t = c_strstr (s, "xgettext:");
181 if (t != NULL)
182 {
183 bool tmp_fuzzy;
184 enum is_format tmp_format[NFORMATS];
185 struct argument_range tmp_range;
186 enum is_wrap tmp_wrap;
187 enum is_syntax_check tmp_syntax_check[NSYNTAXCHECKS];
188 bool interesting;
189 size_t i;
190
191 t += strlen ("xgettext:");
192
193 po_parse_comment_special (t, &tmp_fuzzy, tmp_format,
194 &tmp_range, &tmp_wrap,
195 tmp_syntax_check);
196
197 interesting = false;
198 for (i = 0; i < NFORMATS; i++)
199 if (tmp_format[i] != undecided)
200 {
201 mp->is_format[i] = tmp_format[i];
202 interesting = true;
203 }
204 if (has_range_p (tmp_range))
205 {
206 intersect_range (mp, &tmp_range);
207 interesting = true;
208 }
209 if (tmp_wrap != undecided)
210 {
211 mp->do_wrap = tmp_wrap;
212 interesting = true;
213 }
214 for (i = 0; i < NSYNTAXCHECKS; i++)
215 if (tmp_syntax_check[i] != undecided)
216 {
217 mp->do_syntax_check[i] = tmp_syntax_check[i];
218 interesting = true;
219 }
220
221 /* If the "xgettext:" marker was followed by an
222 interesting keyword, and we updated our
223 is_format/do_wrap variables, eliminate the comment
224 from the #. comments. */
225 if (interesting)
226 if (mp->comment_dot != NULL)
227 {
228 const char *removed =
229 string_list_remove (mp->comment_dot, s);
230
231 if (removed != NULL)
232 free ((char *) removed);
233 }
234 }
235 }
236 }
237
238 /* Now evaluate the consequences of the 'xgettext:' comments, */
239 decide_is_format (mp);
240 decide_do_wrap (mp);
241 decide_syntax_check (mp);
242 }
243 }
244 }
245
246 msgdomain_list_free (mdlp2);
247
248 free (dummy_filename);
249 }
250