/* xgettext Ruby backend. Copyright (C) 2020 Free Software Foundation, Inc. Written by Bruno Haible , 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* Specification. */ #include "x-ruby.h" #include #include #include #include #include #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 *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++) { char *argv[4]; unsigned int i; pid_t child; int fd[1]; FILE *fp; int exitstatus; /* Prepare arguments. */ argv[0] = (char *) progname; i = 1; if (pass > 0) argv[i++] = (char *) "--add-comments=xgettext:"; else { if (add_all_comments) argv[i++] = (char *) "--add-comments"; else if (comment_tag != NULL) argv[i++] = xasprintf ("--add-comments=%s", comment_tag); } argv[i++] = (char *) real_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, 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); }