]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
xgettext: Add Ruby support.
authorBruno Haible <bruno@clisp.org>
Sun, 26 Apr 2020 22:19:07 +0000 (00:19 +0200)
committerBruno Haible <bruno@clisp.org>
Sun, 26 Apr 2020 22:23:15 +0000 (00:23 +0200)
* gettext-tools/src/x-ruby.h: New file.
* gettext-tools/src/x-ruby.c: New file.
* gettext-tools/src/xgettext.h (verbose): New declaration.
* gettext-tools/src/xgettext.c: Include x-ruby.h.
(verbose): New declaration.
(flag_table_ruby): New variable.
(long_options): Add '--verbose'.
(main): Update for Ruby. Handle '-v'/'--verbose' option.
(usage): Document the '-L Ruby' and '-v' options.
(xgettext_record_flag, language_to_extractor, extension_to_language): Update for
Ruby.
* gettext-tools/src/Makefile.am (noinst_HEADERS): Add x-ruby.h.
(xgettext_SOURCES): Add x-ruby.c.
* gettext-tools/src/FILES: Mention x-ruby.h, x-ruby.c.
* gettext-tools/tests/xgettext-ruby-1: New file.
* gettext-tools/tests/format-ruby-1: New file.
* gettext-tools/tests/format-ruby-2: New file.
* gettext-tools/tests/Makefile.am (TESTS): Add them.
* gettext-tools/doc/gettext.texi (Ruby): New section.
* gettext-tools/doc/xgettext.texi: Document the '-L Ruby' and '-v' options.
* HACKING: Document the recommended Ruby packages.
* NEWS: Mention the Ruby support.

14 files changed:
HACKING
NEWS
gettext-tools/doc/gettext.texi
gettext-tools/doc/xgettext.texi
gettext-tools/src/FILES
gettext-tools/src/Makefile.am
gettext-tools/src/x-ruby.c [new file with mode: 0644]
gettext-tools/src/x-ruby.h [new file with mode: 0644]
gettext-tools/src/xgettext.c
gettext-tools/src/xgettext.h
gettext-tools/tests/Makefile.am
gettext-tools/tests/format-ruby-1 [new file with mode: 0755]
gettext-tools/tests/format-ruby-2 [new file with mode: 0755]
gettext-tools/tests/xgettext-ruby-1 [new file with mode: 0755]

diff --git a/HACKING b/HACKING
index f953b68a4a494dc682406f1c7a3150e768722492..edc0b5862a6a9bdb65b70ebf8b2f9bf0eba7bb5d 100644 (file)
--- a/HACKING
+++ b/HACKING
@@ -142,6 +142,13 @@ are skipped. To this effect, you need to install also:
     + Homepage: http://www.php.net/
     + Ubuntu package: php
 
+  * Ruby
+    + Homepage: https://www.ruby-lang.org/en/
+    + Ubuntu package: ruby
+  * The ruby-gettext package
+    + Homepage: https://ruby-gettext.github.io/
+    + Ubuntu package: ruby-gettext
+
   * lua
     + Homepage: https://www.lua.org/
     + Ubuntu package: lua5.2 or lua5.1
diff --git a/NEWS b/NEWS
index 443a7c1a66828014b04c96dbcdaad525e5c3ac2a..5e5156dbbdcc1dc90c568db4b87d6d162ce714b1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,10 @@ Version 0.21 - April 2020
     o xgettext now recognizes text blocks as string literals.
   - JavaScript:
     xgettext parses JSX expressions more reliably.
+  - Ruby:
+    o xgettext now supports Ruby.
+    o 'msgfmt -c' now verifies the syntax of translations of Ruby format
+      strings.
 
 * Runtime behaviour:
   - On native Windows platforms, the directory that contains the message
index 669a550232589392c77f183c694d03d4fff3691d..5f4a88616bc2c12c2bf9a3b3798685a4f0195671 100644 (file)
@@ -79,7 +79,7 @@ This file provides documentation for GNU @code{gettext} utilities.
 It also serves as a reference for the free Translation Project.
 
 @copying
-Copyright (C) 1995-1998, 2001-2019 Free Software Foundation, Inc.
+Copyright (C) 1995-1998, 2001-2020 Free Software Foundation, Inc.
 
 This manual is free documentation.  It is dually licensed under the
 GNU FDL and the GNU GPL.  This means that you can redistribute this
@@ -114,7 +114,7 @@ A copy of the license is included in @ref{GNU GPL}.
 @page
 @vskip 0pt plus 1filll
 @c @insertcopying
-Copyright (C) 1995-1998, 2001-2019 Free Software Foundation, Inc.
+Copyright (C) 1995-1998, 2001-2020 Free Software Foundation, Inc.
 
 This manual is free documentation.  It is dually licensed under the
 GNU FDL and the GNU GPL.  This means that you can redistribute this
@@ -436,6 +436,7 @@ Individual Programming Languages
 * Tcl::                         Tcl - Tk's scripting language
 * Perl::                        Perl
 * PHP::                         PHP Hypertext Preprocessor
+* Ruby::                        Ruby
 * Pike::                        Pike
 * GCC-source::                  GNU Compiler Collection sources
 * Lua::                         Lua
@@ -9548,6 +9549,7 @@ that language, and to combine the resulting files using @code{msgcat}.
 * Tcl::                         Tcl - Tk's scripting language
 * Perl::                        Perl
 * PHP::                         PHP Hypertext Preprocessor
+* Ruby::                        Ruby
 * Pike::                        Pike
 * GCC-source::                  GNU Compiler Collection sources
 * Lua::                         Lua
@@ -12045,6 +12047,62 @@ On platforms without gettext, the functions are not available.
 
 An example is available in the @file{examples} directory: @code{hello-php}.
 
+@node Ruby
+@subsection Ruby
+@cindex Ruby
+
+@table @asis
+@item RPMs
+ruby, ruby-gettext
+
+@item Ubuntu packages
+ruby, ruby-gettext
+
+@item File extension
+@code{rb}
+
+@item String syntax
+@code{"abc"}, @code{'abc'}, @code{%q/abc/} etc.,
+@code{%q(abc)}, @code{%q[abc]}, @code{%q@{abc@}}
+
+@item gettext shorthand
+@code{_("abc")}
+
+@item gettext/ngettext functions
+@code{gettext}, @code{ngettext}
+
+@item textdomain
+---
+
+@item bindtextdomain
+@code{bindtextdomain} function
+
+@item setlocale
+---
+
+@item Prerequisite
+@code{require 'gettext'}
+@code{include GetText}
+
+@item Use or emulate GNU gettext
+emulate
+
+@item Extractor
+@code{xgettext}
+
+@item Formatting with positions
+@code{sprintf("%2$d %1$d", x, y)}
+@*@code{"%@{new@} replaces %@{old@}" % @{:old => oldvalue, :new => newvalue@}}
+
+@item Portability
+fully portable
+
+@item po-mode marking
+---
+@end table
+
+@c An example is available in the @file{examples} directory: @code{hello-ruby}.
+
 @node Pike
 @subsection Pike
 @cindex Pike
index f12af6acab90f1b7d3efdb1def172410422df1ea..b2c6374fa6466144d3beacea3923980ae9095e16 100644 (file)
@@ -1,5 +1,5 @@
 @c This file is part of the GNU gettext manual.
-@c Copyright (C) 1995-2019 Free Software Foundation, Inc.
+@c Copyright (C) 1995-2020 Free Software Foundation, Inc.
 @c See the file gettext.texi for copying conditions.
 
 @pindex xgettext
@@ -76,9 +76,9 @@ Specifies the language of the input files.  The supported languages
 are @code{C}, @code{C++}, @code{ObjectiveC}, @code{PO}, @code{Shell},
 @code{Python}, @code{Lisp}, @code{EmacsLisp}, @code{librep}, @code{Scheme},
 @code{Smalltalk}, @code{Java}, @code{JavaProperties}, @code{C#}, @code{awk},
-@code{YCP}, @code{Tcl}, @code{Perl}, @code{PHP}, @code{GCC-source},
-@code{NXStringTable}, @code{RST}, @code{RSJ}, @code{Glade}, @code{Lua},
-@code{JavaScript}, @code{Vala}, @code{GSettings}, @code{Desktop}.
+@code{YCP}, @code{Tcl}, @code{Perl}, @code{PHP}, @code{Ruby},
+@code{GCC-source}, @code{NXStringTable}, @code{RST}, @code{RSJ}, @code{Glade},
+@code{Lua}, @code{JavaScript}, @code{Vala}, @code{GSettings}, @code{Desktop}.
 
 @item -C
 @itemx --c++
@@ -642,4 +642,10 @@ Display this help and exit.
 @opindex --version@r{, @code{xgettext} option}
 Output version information and exit.
 
+@item -v
+@itemx --verbose
+@opindex -v@r{, @code{xgettext} option}
+@opindex --verbose@r{, @code{xgettext} option}
+Increase verbosity level.
+
 @end table
index 54b4b4c82963778858be7cfd59087812351c154a..5f7d21d9a7defec3352edc05bdc18c9e2dc43a9a 100644 (file)
@@ -362,6 +362,9 @@ msgl-check.c
 | x-php.h
 | x-php.c
 |               String extractor for PHP.
+| x-ruby.h
+| x-ruby.c
+|               String extractor for Ruby.
 | x-rst.h
 | x-rst.c
 |               String extractor from .rst files, for Object Pascal.
index 3349b9acb190abd4f584084ac7383d4649c6df46..875eb4eb04590e363c7cb81c9ff6360ce9ba1a3c 100644 (file)
@@ -59,7 +59,7 @@ rc-str-list.h xg-pos.h xg-encoding.h xg-mixed-string.h xg-arglist-context.h \
 xg-arglist-callshape.h xg-arglist-parser.h xg-message.h \
 x-c.h x-po.h x-sh.h x-python.h x-lisp.h x-elisp.h x-librep.h \
 x-scheme.h x-smalltalk.h x-java.h x-properties.h x-csharp.h x-awk.h x-ycp.h \
-x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-lua.h \
+x-tcl.h x-perl.h x-php.h x-ruby.h x-stringtable.h x-rst.h x-glade.h x-lua.h \
 x-javascript.h x-vala.h x-gsettings.h x-desktop.h x-appdata.h
 
 EXTRA_DIST += FILES project-id
@@ -206,6 +206,7 @@ xgettext_SOURCES += \
   x-tcl.c \
   x-perl.c \
   x-php.c \
+  x-ruby.c \
   x-rst.c \
   x-lua.c \
   x-javascript.c \
diff --git a/gettext-tools/src/x-ruby.c b/gettext-tools/src/x-ruby.c
new file mode 100644 (file)
index 0000000..e371439
--- /dev/null
@@ -0,0 +1,249 @@
+/* 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 *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);
+}
diff --git a/gettext-tools/src/x-ruby.h b/gettext-tools/src/x-ruby.h
new file mode 100644 (file)
index 0000000..6f29feb
--- /dev/null
@@ -0,0 +1,50 @@
+/* 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/>.  */
+
+
+#include <stdio.h>
+
+#include "message.h"
+#include "xg-arglist-context.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define EXTENSIONS_RUBY \
+  { "rb",     "Ruby"  },                                                \
+
+#define SCANNERS_RUBY \
+  { "Ruby",             NULL, extract_ruby,                             \
+                        &flag_table_ruby, &formatstring_ruby, NULL },   \
+
+extern void extract_ruby (const char *real_filename,
+                          const char *logical_filename,
+                          flag_context_list_table_ty *flag_table,
+                          msgdomain_list_ty *mdlp);
+
+extern void x_ruby_keyword (const char *keyword);
+extern void x_ruby_extract_all (void);
+
+extern void init_flag_table_ruby (void);
+
+
+#ifdef __cplusplus
+}
+#endif
index 3c85c1f03e98b1fcf54c4386f0c4964f663df90e..72b6e2b08ab50dd76871cf33981bb8729adffe4e 100644 (file)
 #include "x-tcl.h"
 #include "x-perl.h"
 #include "x-php.h"
+#include "x-ruby.h"
 #include "x-stringtable.h"
 #include "x-rst.h"
 #include "x-glade.h"
@@ -166,6 +167,9 @@ static catalog_output_format_ty output_syntax = &output_format_po;
 /* If nonzero omit header with information about this run.  */
 int xgettext_omit_header;
 
+/* Be more verbose.  */
+int verbose = 0;
+
 /* Table of flag_context_list_ty tables.  */
 static flag_context_list_table_ty flag_table_c;
 static flag_context_list_table_ty flag_table_cxx_qt;
@@ -186,6 +190,7 @@ static flag_context_list_table_ty flag_table_ycp;
 static flag_context_list_table_ty flag_table_tcl;
 static flag_context_list_table_ty flag_table_perl;
 static flag_context_list_table_ty flag_table_php;
+static flag_context_list_table_ty flag_table_ruby;
 static flag_context_list_table_ty flag_table_lua;
 static flag_context_list_table_ty flag_table_javascript;
 static flag_context_list_table_ty flag_table_vala;
@@ -210,7 +215,7 @@ static locating_rule_list_ty *its_locating_rules;
   "  <its:translateRule selector=\"/*\" translate=\"no\"/>" \
   "</its:rules>"
 
-/* If nonzero add comments used by itstool. */
+/* If nonzero add comments used by itstool.  */
 static bool add_itstool_comments = false;
 
 /* Long options.  */
@@ -263,6 +268,7 @@ static const struct option long_options[] =
   { "stringtable-output", no_argument, NULL, CHAR_MAX + 7 },
   { "style", required_argument, NULL, CHAR_MAX + 15 },
   { "trigraphs", no_argument, NULL, 'T' },
+  { "verbose", no_argument, NULL, 'v' },
   { "version", no_argument, NULL, 'V' },
   { "width", required_argument, NULL, 'w' },
   { NULL, 0, NULL, 0 }
@@ -373,12 +379,13 @@ main (int argc, char *argv[])
   init_flag_table_tcl ();
   init_flag_table_perl ();
   init_flag_table_php ();
+  init_flag_table_ruby ();
   init_flag_table_lua ();
   init_flag_table_javascript ();
   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:p:sTvVw:W:x:",
                                  long_options, NULL)) != EOF)
     switch (optchar)
       {
@@ -399,6 +406,7 @@ main (int argc, char *argv[])
         x_tcl_extract_all ();
         x_perl_extract_all ();
         x_php_extract_all ();
+        x_ruby_extract_all ();
         x_lua_extract_all ();
         x_javascript_extract_all ();
         x_vala_extract_all ();
@@ -478,6 +486,7 @@ main (int argc, char *argv[])
         x_tcl_keyword (optarg);
         x_perl_keyword (optarg);
         x_php_keyword (optarg);
+        x_ruby_keyword (optarg);
         x_lua_keyword (optarg);
         x_javascript_keyword (optarg);
         x_vala_keyword (optarg);
@@ -541,6 +550,10 @@ main (int argc, char *argv[])
         x_c_trigraphs ();
         break;
 
+      case 'v':
+        verbose++;
+        break;
+
       case 'V':
         do_version = true;
         break;
@@ -1073,8 +1086,8 @@ Choice of input file language:\n"));
                                 (C, C++, ObjectiveC, PO, Shell, Python, Lisp,\n\
                                 EmacsLisp, librep, Scheme, Smalltalk, Java,\n\
                                 JavaProperties, C#, awk, YCP, Tcl, Perl, PHP,\n\
-                                GCC-source, NXStringTable, RST, RSJ, Glade,\n\
-                                Lua, JavaScript, Vala, Desktop)\n"));
+                                Ruby, GCC-source, NXStringTable, RST, RSJ,\n\
+                                Glade, Lua, JavaScript, Vala, Desktop)\n"));
       printf (_("\
   -C, --c++                   shorthand for --language=C++\n"));
       printf (_("\
@@ -1218,6 +1231,8 @@ Informative output:\n"));
   -h, --help                  display this help and exit\n"));
       printf (_("\
   -V, --version               output version information and exit\n"));
+      printf (_("\
+  -v, --verbose               increase verbosity level\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
@@ -1555,6 +1570,11 @@ xgettext_record_flag (const char *optionstring)
                                                     name_start, name_end,
                                                     argnum, value, pass);
                     break;
+                  case format_ruby:
+                    flag_context_list_table_insert (&flag_table_ruby, 0,
+                                                    name_start, name_end,
+                                                    argnum, value, pass);
+                    break;
                   case format_gcc_internal:
                     flag_context_list_table_insert (&flag_table_gcc_internal, 0,
                                                     name_start, name_end,
@@ -2126,6 +2146,7 @@ language_to_extractor (const char *name)
     SCANNERS_TCL
     SCANNERS_PERL
     SCANNERS_PHP
+    SCANNERS_RUBY
     SCANNERS_STRINGTABLE
     SCANNERS_RST
     SCANNERS_GLADE
@@ -2217,6 +2238,7 @@ extension_to_language (const char *extension)
     EXTENSIONS_TCL
     EXTENSIONS_PERL
     EXTENSIONS_PHP
+    EXTENSIONS_RUBY
     EXTENSIONS_STRINGTABLE
     EXTENSIONS_RST
     EXTENSIONS_GLADE
index c6db3614807b77381e444bbd7de618bf04c3dc84..c6c98da558b52b8a395da3e61b2812d74c8f3860 100644 (file)
@@ -1,5 +1,5 @@
 /* xgettext common functions.
-   Copyright (C) 2001-2003, 2005-2006, 2008-2009, 2011, 2013-2014, 2018 Free Software Foundation, Inc.
+   Copyright (C) 2001-2003, 2005-2006, 2008-2009, 2011, 2013-2014, 2018, 2020 Free Software Foundation, Inc.
    Written by Peter Miller <millerp@canb.auug.org.au>
    and Bruno Haible <haible@clisp.cons.org>, 2001.
 
@@ -50,6 +50,9 @@ extern const char *msgstr_suffix;
    If false, keep the header entry present in the input.  */
 extern int xgettext_omit_header;
 
+/* Be more verbose.  */
+extern int verbose;
+
 extern enum is_syntax_check default_syntax_check[NSYNTAXCHECKS];
 
 /* Language dependent format string parser.
index 055b92c74e38129e049ca4a4defbea3910949b70..14272f4b801d09c3439b0b8e528273834500a69e 100644 (file)
@@ -108,6 +108,7 @@ TESTS = gettext-1 gettext-2 \
        xgettext-rst-1 xgettext-rst-2 \
        xgettext-python-1 xgettext-python-2 xgettext-python-3 \
        xgettext-python-4 \
+       xgettext-ruby-1 \
        xgettext-scheme-1 xgettext-scheme-2 xgettext-scheme-3 \
        xgettext-scheme-4 \
        xgettext-sh-1 xgettext-sh-2 xgettext-sh-3 xgettext-sh-4 xgettext-sh-5 \
@@ -146,6 +147,7 @@ TESTS = gettext-1 gettext-2 \
        format-perl-mixed-1 format-perl-mixed-2 \
        format-qt-1 format-qt-2 \
        format-qt-plural-1 format-qt-plural-2 \
+       format-ruby-1 format-ruby-2 \
        format-scheme-1 format-scheme-2 \
        format-sh-1 format-sh-2 \
        format-tcl-1 format-tcl-2 \
diff --git a/gettext-tools/tests/format-ruby-1 b/gettext-tools/tests/format-ruby-1
new file mode 100755 (executable)
index 0000000..2294b33
--- /dev/null
@@ -0,0 +1,304 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test recognition of Ruby format strings.
+
+(rxgettext --version) >/dev/null 2>/dev/null \
+  || { echo "Skipping test: rxgettext not found"; Exit 77; }
+
+cat <<\EOF > f-r-1.data
+# Valid: no argument
+"abc%%"
+# Valid: one string argument (unnumbered)
+"abc%s"
+# Valid: one string argument (numbered)
+"abc%1$s"
+# Valid: one string argument (named)
+"abc%<foo>s"
+# Valid: one string argument (named)
+"abc%{foo}"
+# Valid: one escaped string argument (unnumbered)
+"abc%p"
+# Valid: one escaped string argument (numbered)
+"abc%1$p"
+# Valid: one escaped string argument (named)
+"abc%<foo>p"
+# Valid: one character argument (unnumbered)
+"abc%c"
+# Valid: one character argument (numbered)
+"abc%1$c"
+# Valid: one character argument (named)
+"abc%<foo>c"
+# Valid: one integer argument (unnumbered)
+"abc%d"
+# Valid: one integer argument (numbered)
+"abc%1$d"
+# Valid: one integer argument (named)
+"abc%<foo>d"
+# Valid: one integer argument (unnumbered)
+"abc%i"
+# Valid: one integer argument (numbered)
+"abc%1$i"
+# Valid: one integer argument (named)
+"abc%<foo>i"
+# Valid: one integer argument (unnumbered)
+"abc%u"
+# Valid: one integer argument (numbered)
+"abc%1$u"
+# Valid: one integer argument (named)
+"abc%<foo>u"
+# Valid: one integer argument (unnumbered)
+"abc%o"
+# Valid: one integer argument (numbered)
+"abc%1$o"
+# Valid: one integer argument (named)
+"abc%<foo>o"
+# Valid: one integer argument (unnumbered)
+"abc%x"
+# Valid: one integer argument (numbered)
+"abc%1$x"
+# Valid: one integer argument (named)
+"abc%<foo>x"
+# Valid: one integer argument (unnumbered)
+"abc%X"
+# Valid: one integer argument (numbered)
+"abc%1$X"
+# Valid: one integer argument (named)
+"abc%<foo>X"
+# Valid: one integer argument (unnumbered)
+"abc%b"
+# Valid: one integer argument (numbered)
+"abc%1$b"
+# Valid: one integer argument (named)
+"abc%<foo>b"
+# Valid: one integer argument (unnumbered)
+"abc%B"
+# Valid: one integer argument (numbered)
+"abc%1$B"
+# Valid: one integer argument (named)
+"abc%<foo>B"
+# Valid: one floating-point argument (unnumbered)
+"abc%f"
+# Valid: one floating-point argument (numbered)
+"abc%1$f"
+# Valid: one floating-point argument (named)
+"abc%<foo>f"
+# Valid: one floating-point argument (unnumbered)
+"abc%g"
+# Valid: one floating-point argument (numbered)
+"abc%1$g"
+# Valid: one floating-point argument (named)
+"abc%<foo>g"
+# Valid: one floating-point argument (unnumbered)
+"abc%G"
+# Valid: one floating-point argument (numbered)
+"abc%1$G"
+# Valid: one floating-point argument (named)
+"abc%<foo>G"
+# Valid: one floating-point argument (unnumbered)
+"abc%e"
+# Valid: one floating-point argument (numbered)
+"abc%1$e"
+# Valid: one floating-point argument (named)
+"abc%<foo>e"
+# Valid: one floating-point argument (unnumbered)
+"abc%E"
+# Valid: one floating-point argument (numbered)
+"abc%1$E"
+# Valid: one floating-point argument (named)
+"abc%<foo>E"
+# Valid: one floating-point argument (unnumbered)
+"abc%a"
+# Valid: one floating-point argument (numbered)
+"abc%1$a"
+# Valid: one floating-point argument (named)
+"abc%<foo>a"
+# Valid: one floating-point argument (unnumbered)
+"abc%A"
+# Valid: one floating-point argument (numbered)
+"abc%1$A"
+# Valid: one floating-point argument (named)
+"abc%<foo>A"
+# Valid: one argument with flags (unnumbered)
+"abc%0#g"
+# Valid: one argument with flags (numbered)
+"abc%1$0#g"
+# Valid: one argument with flags (numbered)
+"abc%0#1$g"
+# Valid: one argument with flags (named)
+"abc%<foo>0#g"
+# Valid: one argument with flags (named)
+"abc%0#<foo>g"
+# Valid: one argument with width (unnumbered)
+"abc%2g"
+# Valid: one argument with width (numbered)
+"abc%1$2g"
+# Valid: one argument with width (named)
+"abc%<foo>2g"
+# Valid: one argument with width (named)
+"abc%2<foo>g"
+# Valid: one argument with width (unnumbered)
+"abc%*g"
+# Valid: one argument with width (numbered)
+"abc%2$*1$g"
+# Valid: one argument with precision (unnumbered)
+"abc%.4g"
+# Valid: one argument with precision (numbered)
+"abc%1$.4g"
+# Valid: one argument with precision (named)
+"abc%<foo>.4g"
+# Valid: one argument with precision (named)
+"abc%.4<foo>g"
+# Valid: one argument with precision (unnumbered)
+"abc%.*g"
+# Valid: one argument with precision (numbered)
+"abc%2$.*1$g"
+# Valid: one argument with width and precision (unnumbered)
+"abc%14.4g"
+# Valid: one argument with width and precision (numbered)
+"abc%1$14.4g"
+# Valid: one argument with width and precision (named)
+"abc%<foo>14.4g"
+# Valid: one argument with width and precision (named)
+"abc%14<foo>.4g"
+# Valid: one argument with width and precision (named)
+"abc%14.4<foo>g"
+# Valid: one argument with width and precision (unnumbered)
+"abc%14.*g"
+# Valid: one argument with width and precision (numbered)
+"abc%2$14.*1$g"
+# Valid: one argument with width and precision (unnumbered)
+"abc%*.4g"
+# Valid: one argument with width and precision (numbered)
+"abc%2$*1$.4g"
+# Valid: one argument with width and precision (unnumbered)
+"abc%*.*g"
+# Valid: one argument with width and precision (numbered)
+"abc%3$*1$.*2$g"
+# Invalid: unterminated directive
+"abc%"
+# Invalid: unterminated name
+"abc%<value"
+# Invalid: unterminated name
+"abc%{value"
+# Invalid: unterminated directive
+"abc%<value>"
+# Invalid: unknown format specifier (unnumbered)
+"abc%y"
+# Invalid: mixing unnumbered and numbered in the same directive
+"abc%2$*g"
+# Invalid: mixing unnumbered and numbered in the same directive
+"abc%*1$g"
+# Invalid: mixing unnumbered and numbered in the same directive
+"abc%2$.*g"
+# Invalid: mixing unnumbered and numbered in the same directive
+"abc%.*1$g"
+# Invalid: mixing unnumbered and numbered in different directives
+"abc%d%2$g"
+# Invalid: mixing unnumbered and numbered in different directives
+"abc%1$d%g"
+# Invalid: mixing unnumbered and named in the same directive
+"abc%*<foo>g"
+# Invalid: mixing unnumbered and named in the same directive
+"abc%.*<foo>g"
+# Invalid: mixing unnumbered and named in different directives
+"abc%d%<foo>g"
+# Invalid: mixing unnumbered and named in different directives
+"abc%<foo>d%g"
+# Invalid: mixing numbered and named in the same directive
+"abc%*1$<foo>g"
+# Invalid: mixing numbered and named in the same directive
+"abc%.*1$<foo>g"
+# Invalid: mixing numbered and named in different directives
+"abc%2$d%<foo>g"
+# Invalid: mixing numbered and named in different directives
+"abc%<foo>d%2$g"
+# Invalid: flags after width (unnumbered)
+"abc%*0g"
+# Invalid: flags after width (numbered)
+"abc%2$*1$0g"
+# Invalid: flags after precision (unnumbered)
+"abc%.*0g"
+# Invalid: flags after precision (numbered)
+"abc%2$.*1$0g"
+# Invalid: width after precision (unnumbered)
+"abc%.*14g"
+# Invalid: width after precision (unnumbered)
+"abc%.4*g"
+# Invalid: width after precision (unnumbered)
+"abc%.**g"
+# Invalid: width after precision (numbered)
+"abc%2$.*1$14g"
+# Invalid: width after precision (numbered)
+"abc%2$.4*1$g"
+# Invalid: width after precision (numbered)
+"abc%3$.*1$*2$g"
+# Invalid: twice width (unnumbered)
+"abc%2*g"
+# Invalid: twice width (unnumbered)
+"abc%*2g"
+# Invalid: twice width (numbered)
+"abc%2$2*1$g"
+# Invalid: twice width (numbered)
+"abc%2$*1$2g"
+# Invalid: twice precision (unnumbered)
+"abc%.4.2g"
+# Invalid: twice precision (numbered)
+"abc%1$.4.2g"
+# Valid: three arguments
+"abc%d%u%u"
+# Valid: an unused argument
+"abc%2$d%3$u"
+# Valid: a named argument
+"abc%<value>d"
+# Valid: a named argument
+"abc%{value}"
+# Valid: an empty name
+"abc%<>d"
+# Valid: an empty name
+"abc%{}"
+# Valid: ignored named argument
+"abc%<dummy>%"
+# Valid: three arguments, two with equal names
+"abc%<addr>4x,%<char>c,%<addr>u"
+# Invalid: argument with conflicting types
+"abc%<addr>4x,%<char>c,%<addr>s"
+# Valid: no conflict
+"abc%<addr>s,%{addr}"
+EOF
+
+: ${XGETTEXT=xgettext}
+n=0
+while read comment; do
+  read string
+  n=`expr $n + 1`
+  cat <<EOF > f-r-1-$n.in
+gettext(${string});
+EOF
+  ${XGETTEXT} -L Ruby -o f-r-1-$n.po f-r-1-$n.in || Exit 1
+  test -f f-r-1-$n.po || Exit 1
+  fail=
+  if echo "$comment" | grep 'Valid:' > /dev/null; then
+    if grep ruby-format f-r-1-$n.po > /dev/null; then
+      :
+    else
+      fail=yes
+    fi
+  else
+    if grep ruby-format f-r-1-$n.po > /dev/null; then
+      fail=yes
+    else
+      :
+    fi
+  fi
+  if test -n "$fail"; then
+    echo "Format string recognition error:" 1>&2
+    cat f-r-1-$n.in 1>&2
+    echo "Got:" 1>&2
+    cat f-r-1-$n.po 1>&2
+    Exit 1
+  fi
+  rm -f f-r-1-$n.in f-r-1-$n.po
+done < f-r-1.data
+
+Exit 0
diff --git a/gettext-tools/tests/format-ruby-2 b/gettext-tools/tests/format-ruby-2
new file mode 100755 (executable)
index 0000000..cce98e9
--- /dev/null
@@ -0,0 +1,361 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test checking of Ruby format strings.
+
+cat <<\EOF > f-r-2.data
+# Valid: %% doesn't count
+msgid  "abc%%def"
+msgstr "xyz"
+# Invalid: invalid msgstr
+msgid  "abc%%def"
+msgstr "xyz%"
+# Valid: same arguments, with different widths (argument list)
+msgid  "abc%2sdef"
+msgstr "xyz%3s"
+# Valid: same arguments, with different widths (argument list)
+msgid  "abc%2sdef"
+msgstr "xyz%1$3s"
+# Valid: same arguments, with different widths (named)
+msgid  "abc%<foo>2sdef"
+msgstr "xyz%<foo>3s"
+# Valid: same arguments, with different widths (named)
+msgid  "abc%<date>5s%<time>4s"
+msgstr "xyz%<date>4s%<time>5s"
+# Invalid: too few arguments (argument list)
+msgid  "abc%sdef%u"
+msgstr "xyz%s"
+# Invalid: too few arguments (named)
+msgid  "abc%<foo>sdef%<bar>u"
+msgstr "xyz%<foo>s"
+# Invalid: too many arguments (argument list)
+msgid  "abc%udef"
+msgstr "xyz%uvw%c"
+# Invalid: too many arguments (named)
+msgid  "abc%<foo>udef"
+msgstr "xyz%<foo>uvw%<bar>c"
+# Valid: permutation (argument list)
+msgid  "abc%3$d%1$c%2$sdef"
+msgstr "xyz%2$s%1$c%3$d"
+# Valid: permutation (named)
+msgid  "abc%<3>d%<1>c%<2>sdef"
+msgstr "xyz%<2>s%<1>c%<3>d"
+# Invalid: missing argument (named)
+msgid  "abc%<2>sdef%<1>u"
+msgstr "xyz%<1>u"
+# Invalid: missing argument (named)
+msgid  "abc%<1>sdef%<2>u"
+msgstr "xyz%<2>u"
+# Invalid: added argument (named)
+msgid  "abc%<foo>udef"
+msgstr "xyz%<foo>uvw%<char>c"
+# Invalid: added argument (named)
+msgid  "abc%<foo>udef"
+msgstr "xyz%<zoo>cvw%<foo>u"
+# Invalid: unnamed vs. named arguments
+msgid  "abc%sdef"
+msgstr "xyz%<value>s"
+# Invalid: named vs. unnamed arguments
+msgid  "abc%{value}def"
+msgstr "xyz%s"
+# Valid: unnumbered vs. numbered arguments
+msgid  "abc%sdef%d"
+msgstr "xyz%2$duvw%1$s"
+# Valid: numbered vs. unnumbered arguments
+msgid  "abc%1$sdef%2$d"
+msgstr "xyz%suvw%d"
+# Valid: type compatibility (argument list)
+msgid  "abc%d"
+msgstr "xyz%i"
+# Valid: type compatibility (argument list)
+msgid  "abc%d"
+msgstr "xyz%u"
+# Valid: type compatibility (argument list)
+msgid  "abc%d"
+msgstr "xyz%o"
+# Valid: type compatibility (argument list)
+msgid  "abc%d"
+msgstr "xyz%x"
+# Valid: type compatibility (argument list)
+msgid  "abc%d"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid  "abc%d"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid  "abc%d"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid  "abc%i"
+msgstr "xyz%u"
+# Valid: type compatibility (argument list)
+msgid  "abc%i"
+msgstr "xyz%o"
+# Valid: type compatibility (argument list)
+msgid  "abc%i"
+msgstr "xyz%x"
+# Valid: type compatibility (argument list)
+msgid  "abc%i"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid  "abc%i"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid  "abc%i"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid  "abc%u"
+msgstr "xyz%o"
+# Valid: type compatibility (argument list)
+msgid  "abc%u"
+msgstr "xyz%x"
+# Valid: type compatibility (argument list)
+msgid  "abc%u"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid  "abc%u"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid  "abc%u"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid  "abc%o"
+msgstr "xyz%x"
+# Valid: type compatibility (argument list)
+msgid  "abc%o"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid  "abc%o"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid  "abc%o"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid  "abc%x"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid  "abc%x"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid  "abc%x"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid  "abc%X"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid  "abc%X"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid  "abc%b"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid  "abc%f"
+msgstr "xyz%g"
+# Valid: type compatibility (argument list)
+msgid  "abc%f"
+msgstr "xyz%G"
+# Valid: type compatibility (argument list)
+msgid  "abc%f"
+msgstr "xyz%e"
+# Valid: type compatibility (argument list)
+msgid  "abc%f"
+msgstr "xyz%E"
+# Valid: type compatibility (argument list)
+msgid  "abc%f"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid  "abc%f"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid  "abc%g"
+msgstr "xyz%G"
+# Valid: type compatibility (argument list)
+msgid  "abc%g"
+msgstr "xyz%e"
+# Valid: type compatibility (argument list)
+msgid  "abc%g"
+msgstr "xyz%E"
+# Valid: type compatibility (argument list)
+msgid  "abc%g"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid  "abc%g"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid  "abc%G"
+msgstr "xyz%e"
+# Valid: type compatibility (argument list)
+msgid  "abc%G"
+msgstr "xyz%E"
+# Valid: type compatibility (argument list)
+msgid  "abc%G"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid  "abc%G"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid  "abc%e"
+msgstr "xyz%E"
+# Valid: type compatibility (argument list)
+msgid  "abc%e"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid  "abc%e"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid  "abc%E"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid  "abc%E"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid  "abc%a"
+msgstr "xyz%A"
+# Valid: type compatibility (named)
+msgid  "abc%<foo>d"
+msgstr "xyz%<foo>x"
+# Valid: type compatibility (named)
+msgid  "abc%<foo>s"
+msgstr "xyz%{foo}"
+# Valid: type compatibility (named)
+msgid  "abc%{foo}"
+msgstr "xyz%<foo>s"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%c"
+msgstr "xyz%s"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%c"
+msgstr "xyz%.0s"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%c"
+msgstr "xyz%p"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%c"
+msgstr "xyz%i"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%c"
+msgstr "xyz%e"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%s"
+msgstr "xyz%p"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%s"
+msgstr "xyz%i"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%.0s"
+msgstr "xyz%i"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%s"
+msgstr "xyz%e"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%.0s"
+msgstr "xyz%e"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%p"
+msgstr "xyz%i"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%p"
+msgstr "xyz%e"
+# Invalid: type incompatibility (argument list)
+msgid  "abc%i"
+msgstr "xyz%e"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>c"
+msgstr "xyz%<foo>s"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>c"
+msgstr "xyz%{foo}"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>c"
+msgstr "xyz%<foo>.0s"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>c"
+msgstr "xyz%.0{foo}"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>c"
+msgstr "xyz%<foo>p"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>c"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>c"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>s"
+msgstr "xyz%<foo>p"
+# Invalid: type incompatibility (named)
+msgid  "abc%{foo}"
+msgstr "xyz%<foo>p"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>s"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid  "abc%{foo}"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>.0s"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid  "abc%.0{foo}"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>s"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid  "abc%{foo}"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>.0s"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid  "abc%.0{foo}"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>p"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>p"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid  "abc%<foo>i"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility for width (argument list)
+msgid  "abc%g%*g"
+msgstr "xyz%*g%g"
+EOF
+
+: ${MSGFMT=msgfmt}
+n=0
+while read comment; do
+  read msgid_line
+  read msgstr_line
+  n=`expr $n + 1`
+  cat <<EOF > f-r-2-$n.po
+#, ruby-format
+${msgid_line}
+${msgstr_line}
+EOF
+  fail=
+  if echo "$comment" | grep 'Valid:' > /dev/null; then
+    if ${MSGFMT} --check-format -o f-r-2-$n.mo f-r-2-$n.po; then
+      :
+    else
+      fail=yes
+    fi
+  else
+    ${MSGFMT} --check-format -o f-r-2-$n.mo f-r-2-$n.po 2> /dev/null
+    if test $? = 1; then
+      :
+    else
+      fail=yes
+    fi
+  fi
+  if test -n "$fail"; then
+    echo "Format string checking error:" 1>&2
+    cat f-r-2-$n.po 1>&2
+    Exit 1
+  fi
+  rm -f f-r-2-$n.po f-r-2-$n.mo
+done < f-r-2.data
+
+Exit 0
diff --git a/gettext-tools/tests/xgettext-ruby-1 b/gettext-tools/tests/xgettext-ruby-1
new file mode 100755 (executable)
index 0000000..dd8e68b
--- /dev/null
@@ -0,0 +1,68 @@
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test of Ruby support.
+
+(rxgettext --version) >/dev/null 2>/dev/null \
+  || { echo "Skipping test: rxgettext not found"; Exit 77; }
+
+cat <<\EOF > xg-ru-1.rb
+_("abc")
+# Some comment.
+_("abc%%def")
+_("abc%{foo}def")
+# xgettext: no-ruby-format
+_("abc%{bar}def")
+_("some %d people")
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --add-comments --no-location -o xg-ru-1.tmp xg-ru-1.rb 2>xg-ru-1.err
+test $? = 0 || { cat xg-ru-1.err; Exit 1; }
+func_filter_POT_Creation_Date xg-ru-1.tmp xg-ru-1.pot
+
+cat <<\EOF > xg-ru-1.ok
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "abc"
+msgstr ""
+
+#. Some comment.
+#, ruby-format
+msgid "abc%%def"
+msgstr ""
+
+#, ruby-format
+msgid "abc%{foo}def"
+msgstr ""
+
+#, no-ruby-format
+msgid "abc%{bar}def"
+msgstr ""
+
+#, ruby-format
+msgid "some %d people"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-ru-1.ok xg-ru-1.pot
+result=$?
+
+exit $result