[gnome-builder] gettext: add a simple syntax check plugin



commit d620292add6438b5c679d447bff078c55ccc6702
Author: Daiki Ueno <dueno src gnome org>
Date:   Fri Feb 12 18:41:42 2016 +0900

    gettext: add a simple syntax check plugin
    
    This plugin performs syntax checks on translatable strings, using the
    --check option of xgettext:
    
http://www.gnu.org/software/gettext/manual/html_node/xgettext-Invocation.html#index-_002d_002dcheck_002c-xgettext-option
    
    It is helpful to keep the messages consistent with the Unicode character
    usage guidelines:
    https://wiki.gnome.org/Initiatives/GnomeGoals/UnicodeUsage
    
    https://bugzilla.gnome.org/show_bug.cgi?id=762062

 configure.ac                                      |    1 +
 plugins/Makefile.am                               |    1 +
 plugins/gettext/Makefile.am                       |   36 ++
 plugins/gettext/configure.ac                      |   12 +
 plugins/gettext/gettext-plugin.c                  |   30 ++
 plugins/gettext/gettext.plugin                    |    9 +
 plugins/gettext/ide-gettext-diagnostic-provider.c |  438 +++++++++++++++++++++
 plugins/gettext/ide-gettext-diagnostic-provider.h |   34 ++
 8 files changed, 561 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index c49a306..8c3c754 100644
--- a/configure.ac
+++ b/configure.ac
@@ -244,6 +244,7 @@ m4_include([plugins/devhelp/configure.ac])
 m4_include([plugins/file-search/configure.ac])
 m4_include([plugins/fpaste/configure.ac])
 m4_include([plugins/gcc/configure.ac])
+m4_include([plugins/gettext/configure.ac])
 m4_include([plugins/git/configure.ac])
 m4_include([plugins/gnome-code-assistance/configure.ac])
 m4_include([plugins/html-completion/configure.ac])
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 22e70c4..8bd3936 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -11,6 +11,7 @@ SUBDIRS = \
        file-search \
        fpaste \
        gcc \
+       gettext \
        git \
        gnome-code-assistance \
        html-completion \
diff --git a/plugins/gettext/Makefile.am b/plugins/gettext/Makefile.am
new file mode 100644
index 0000000..8fd97f0
--- /dev/null
+++ b/plugins/gettext/Makefile.am
@@ -0,0 +1,36 @@
+if ENABLE_GETTEXT_PLUGIN
+
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libgettext-plugin.la
+dist_plugin_DATA = gettext.plugin
+
+libgettext_plugin_la_SOURCES = \
+       ide-gettext-diagnostic-provider.c \
+       ide-gettext-diagnostic-provider.h \
+       gettext-plugin.c \
+       $(NULL)
+
+libgettext_plugin_la_CFLAGS = \
+       $(LIBIDE_CFLAGS) \
+       $(GETTEXT_CFLAGS) \
+       -I$(top_srcdir)/libide \
+       -I$(top_srcdir)/contrib/egg \
+       $(NULL)
+
+libgettext_plugin_la_LIBADD = \
+       $(NULL)
+
+libgettext_plugin_la_LDFLAGS = \
+       $(GETTEXT_LDFLAGS) \
+       $(OPTIMIZE_LDFLAGS) \
+       -avoid-version \
+       -module \
+       $(NULL)
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/gettext/configure.ac b/plugins/gettext/configure.ac
new file mode 100644
index 0000000..890057d
--- /dev/null
+++ b/plugins/gettext/configure.ac
@@ -0,0 +1,12 @@
+# --enable-gettext-plugin=yes/no
+AC_ARG_ENABLE([gettext-plugin],
+              [AS_HELP_STRING([--enable-gettext-plugin=@<:@auto/yes/no@:>@],
+                              [Build with support for the gettext compiler toolchain.])],
+              [enable_gettext_plugin=$enableval],
+              [enable_gettext_plugin=yes])
+
+# for if ENABLE_GETTEXT_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_GETTEXT_PLUGIN, test x$enable_gettext_plugin != xno)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/gettext/Makefile])
diff --git a/plugins/gettext/gettext-plugin.c b/plugins/gettext/gettext-plugin.c
new file mode 100644
index 0000000..f5eda57
--- /dev/null
+++ b/plugins/gettext/gettext-plugin.c
@@ -0,0 +1,30 @@
+/* ide-gettext-diagnostic-provider.c
+ *
+ * Copyright (C) 2016 Daiki Ueno <dueno src gnome org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <libpeas/peas.h>
+#include <ide.h>
+
+#include "ide-gettext-diagnostic-provider.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_DIAGNOSTIC_PROVIDER,
+                                              IDE_TYPE_GETTEXT_DIAGNOSTIC_PROVIDER);
+}
diff --git a/plugins/gettext/gettext.plugin b/plugins/gettext/gettext.plugin
new file mode 100644
index 0000000..14458b6
--- /dev/null
+++ b/plugins/gettext/gettext.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Module=gettext-plugin
+Name=Gettext
+Description=Provides integration with Gettext
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2015 Christian Hergert
+Builtin=true
+X-Diagnostic-Provider-Languages=c,chdr,cpp,js,python,vala
+X-Diagnostic-Provider-Languages-Priority=100
diff --git a/plugins/gettext/ide-gettext-diagnostic-provider.c 
b/plugins/gettext/ide-gettext-diagnostic-provider.c
new file mode 100644
index 0000000..a3eacf9
--- /dev/null
+++ b/plugins/gettext/ide-gettext-diagnostic-provider.c
@@ -0,0 +1,438 @@
+/* ide-gettext-diagnostic-provider.c
+ *
+ * Copyright (C) 2016 Daiki Ueno <dueno src gnome org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-gettext-diagnostic-provider"
+
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "egg-task-cache.h"
+
+#include "ide-context.h"
+#include "ide-diagnostic.h"
+#include "ide-diagnostics.h"
+#include "ide-file.h"
+#include "ide-gettext-diagnostic-provider.h"
+#include "ide-source-location.h"
+#include "ide-unsaved-file.h"
+#include "ide-unsaved-files.h"
+
+struct _IdeGettextDiagnostics
+{
+  GObject parent_instance;
+  IdeDiagnostics *diagnostics;
+  guint64 sequence;
+};
+
+struct _IdeGettextDiagnosticsClass
+{
+  GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (IdeGettextDiagnostics, ide_gettext_diagnostics, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_DIAGNOSTICS,
+  PROP_SEQUENCE,
+  LAST_PROP
+};
+
+static GParamSpec *diagnostics_pspecs[LAST_PROP] = { 0 };
+
+static void
+ide_gettext_diagnostics_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  IdeGettextDiagnostics *self = IDE_GETTEXT_DIAGNOSTICS (object);
+
+  switch (prop_id)
+    {
+    case PROP_DIAGNOSTICS:
+      self->diagnostics = g_value_dup_boxed (value);
+      break;
+
+    case PROP_SEQUENCE:
+      self->sequence = g_value_get_uint64 (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+ide_gettext_diagnostics_finalize (GObject *object)
+{
+  IdeGettextDiagnostics *self = IDE_GETTEXT_DIAGNOSTICS (object);
+
+  ide_diagnostics_unref (self->diagnostics);
+
+  G_OBJECT_CLASS (ide_gettext_diagnostics_parent_class)->finalize (object);
+}
+
+static void
+ide_gettext_diagnostics_class_init (IdeGettextDiagnosticsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  object_class->set_property = ide_gettext_diagnostics_set_property;
+  object_class->finalize = ide_gettext_diagnostics_finalize;
+
+  diagnostics_pspecs[PROP_DIAGNOSTICS] =
+    g_param_spec_boxed ("diagnostics", NULL, NULL,
+                        IDE_TYPE_DIAGNOSTICS,
+                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE);
+  diagnostics_pspecs[PROP_SEQUENCE] =
+    g_param_spec_uint64 ("sequence", NULL, NULL,
+                         0, G_MAXUINT64, 0,
+                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE);
+
+  g_object_class_install_properties (object_class,
+                                     LAST_PROP,
+                                     diagnostics_pspecs);
+}
+
+static void
+ide_gettext_diagnostics_init (IdeGettextDiagnostics *self)
+{
+}
+
+struct _IdeGettextDiagnosticProvider
+{
+  IdeObject parent_instance;
+  EggTaskCache *diagnostics_cache;
+};
+
+struct _IdeGettextDiagnosticProviderClass
+{
+  IdeObjectClass parent_class;
+};
+
+static void diagnostic_provider_iface_init (IdeDiagnosticProviderInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeGettextDiagnosticProvider,
+                        ide_gettext_diagnostic_provider,
+                        IDE_TYPE_OBJECT,
+                        0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_DIAGNOSTIC_PROVIDER,
+                                               diagnostic_provider_iface_init))
+
+typedef struct
+{
+  IdeFile *file;
+  IdeUnsavedFile *unsaved_file;
+} TranslationUnit;
+
+static void
+translation_unit_free (TranslationUnit *unit)
+{
+  g_object_unref (unit->file);
+  ide_unsaved_file_unref (unit->unsaved_file);
+  g_free (unit);
+}
+
+static IdeUnsavedFile *
+get_unsaved_file (IdeGettextDiagnosticProvider *self,
+                  IdeFile                      *file)
+{
+  IdeUnsavedFiles *unsaved_files;
+  IdeContext *context;
+  g_autoptr(GPtrArray) array = NULL;
+  guint index;
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  unsaved_files = ide_context_get_unsaved_files (context);
+
+  array = ide_unsaved_files_to_array (unsaved_files);
+  for (index = 0; index < array->len; index++)
+    {
+      IdeUnsavedFile *unsaved_file = g_ptr_array_index (array, index);
+      if (g_file_equal (ide_unsaved_file_get_file (unsaved_file),
+                        ide_file_get_file (file)))
+        return ide_unsaved_file_ref (unsaved_file);
+    }
+
+  return NULL;
+}
+
+static void
+get_diagnostics_cb (GObject      *source_object,
+                    GAsyncResult *res,
+                    gpointer      user_data)
+{
+  EggTaskCache *cache = EGG_TASK_CACHE (source_object);
+  g_autoptr(GTask) task = user_data;
+  IdeGettextDiagnostics *diags;
+  GError *error = NULL;
+
+  diags = egg_task_cache_get_finish (cache, res, &error);
+  if (!diags)
+    g_task_return_error (task, error);
+  else
+    g_task_return_pointer (task, diags, g_object_unref);
+}
+
+static void
+ide_gettext_diagnostic_provider_diagnose_async (IdeDiagnosticProvider *provider,
+                                                IdeFile               *file,
+                                                GCancellable          *cancellable,
+                                                GAsyncReadyCallback    callback,
+                                                gpointer               user_data)
+{
+  IdeGettextDiagnosticProvider *self = (IdeGettextDiagnosticProvider *)provider;
+  g_autoptr(IdeUnsavedFile) unsaved_file = NULL;
+  IdeGettextDiagnostics *cached;
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_GETTEXT_DIAGNOSTIC_PROVIDER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  unsaved_file = get_unsaved_file (self, file);
+  if ((cached = egg_task_cache_peek (self->diagnostics_cache, file)) &&
+      (cached->sequence >= ide_unsaved_file_get_sequence (unsaved_file)))
+    {
+      g_task_return_pointer (task, g_object_ref (cached), g_object_unref);
+      return;
+    }
+
+  egg_task_cache_get_async (self->diagnostics_cache,
+                            file,
+                            TRUE,
+                            cancellable,
+                            get_diagnostics_cb,
+                            g_object_ref (task));
+}
+
+static IdeDiagnostics *
+ide_gettext_diagnostic_provider_diagnose_finish (IdeDiagnosticProvider  *provider,
+                                                 GAsyncResult           *result,
+                                                 GError                **error)
+{
+  GTask *task = (GTask *)result;
+  g_autoptr(IdeGettextDiagnostics) object = NULL;
+
+  g_return_val_if_fail (IDE_IS_GETTEXT_DIAGNOSTIC_PROVIDER (provider), NULL);
+  g_return_val_if_fail (G_IS_TASK (task), NULL);
+
+  object = g_task_propagate_pointer (task, error);
+  if (!object)
+    return NULL;
+
+  return ide_diagnostics_ref (object->diagnostics);
+}
+
+static void
+diagnostic_provider_iface_init (IdeDiagnosticProviderInterface *iface)
+{
+  iface->diagnose_async = ide_gettext_diagnostic_provider_diagnose_async;
+  iface->diagnose_finish = ide_gettext_diagnostic_provider_diagnose_finish;
+}
+
+static void
+ide_gettext_diagnostic_provider_finalize (GObject *object)
+{
+  IdeGettextDiagnosticProvider *self = IDE_GETTEXT_DIAGNOSTIC_PROVIDER (object);
+
+  g_object_unref (self->diagnostics_cache);
+
+  G_OBJECT_CLASS (ide_gettext_diagnostic_provider_parent_class)->finalize (object);
+}
+
+static void
+ide_gettext_diagnostic_provider_class_init (IdeGettextDiagnosticProviderClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  object_class->finalize = ide_gettext_diagnostic_provider_finalize;
+}
+
+static void
+subprocess_wait_cb (GObject      *source_object,
+                    GAsyncResult *res,
+                    gpointer      user_data)
+{
+  GSubprocess *subprocess = G_SUBPROCESS (source_object);
+  g_autoptr(GTask) task = user_data;
+  TranslationUnit *unit = g_task_get_task_data (task);
+  GPtrArray *array;
+  IdeGettextDiagnostics *diags;
+  GInputStream *stderr_input;
+  GDataInputStream *stderr_data_input;
+  g_autofree gchar *input_prefix = NULL;
+  GError *error = NULL;
+
+  if (!g_subprocess_wait_finish (subprocess, res, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  array = g_ptr_array_new_with_free_func ((GDestroyNotify)ide_diagnostic_unref);
+  if (g_subprocess_get_exit_status (subprocess) == 0)
+    goto out;
+
+  stderr_input = g_subprocess_get_stderr_pipe (subprocess);
+  stderr_data_input = g_data_input_stream_new (stderr_input);
+  g_object_unref (stderr_input);
+
+  input_prefix = g_strdup_printf ("%s:", ide_unsaved_file_get_temp_path (unit->unsaved_file));
+  for (;;)
+    {
+      g_autofree gchar *line;
+      gsize length;
+
+      error = NULL;
+      line = g_data_input_stream_read_line (stderr_data_input,
+                                            &length,
+                                            g_task_get_cancellable (task),
+                                            &error);
+      if (!line)
+        break;
+
+      if (g_str_has_prefix (line, input_prefix))
+        {
+          gchar *p = line + strlen (input_prefix);
+
+          if (g_ascii_isdigit (*p))
+            {
+              gulong line_number = strtoul (p, &p, 10);
+              IdeSourceLocation *loc;
+              IdeDiagnostic *diag;
+
+              loc = ide_source_location_new (unit->file,
+                                             line_number - 1,
+                                             0,
+                                             0);
+              diag = ide_diagnostic_new (IDE_DIAGNOSTIC_WARNING,
+                                         g_strstrip (p + 1),
+                                         loc);
+              g_ptr_array_add (array, diag);
+            }
+        }
+    }
+
+ out:
+  diags = g_object_new (IDE_TYPE_GETTEXT_DIAGNOSTICS,
+                        "diagnostics", ide_diagnostics_new (array),
+                        "sequence", ide_unsaved_file_get_sequence (unit->unsaved_file),
+                        NULL);
+  g_task_return_pointer (task, diags, g_object_unref);
+}
+
+static const gchar *
+id_to_xgettext_language (const gchar *id)
+{
+  static const struct {
+    const gchar *id;
+    const gchar *lang;
+  } id_to_lang[] = {
+    { "awk", "awk" },
+    { "c", "C" },
+    { "chdr", "C" },
+    { "cpp", "C++" },
+    { "js", "JavaScript" },
+    { "lisp", "Lisp" },
+    { "objc", "ObjectiveC" },
+    { "perl", "Perl" },
+    { "php", "PHP" },
+    { "python", "Python" },
+    { "sh", "Shell" },
+    { "tcl", "Tcl" },
+    { "vala", "Vala" }
+  };
+  gsize i;
+
+  for (i = 0; i < G_N_ELEMENTS (id_to_lang); i++)
+    if (strcmp (id, id_to_lang[i].id) == 0)
+      return id_to_lang[i].lang;
+
+  return NULL;
+}
+
+static void
+populate_cache (EggTaskCache  *cache,
+                gconstpointer  key,
+                GTask         *task,
+                gpointer       user_data)
+{
+  IdeFile *file = (IdeFile *)key;
+  IdeGettextDiagnosticProvider *self = user_data;
+  g_autoptr(IdeUnsavedFile) unsaved_file = get_unsaved_file (self, file);
+  GtkSourceLanguage *language = ide_file_get_language (file);
+  const gchar *language_id = gtk_source_language_get_id (language);
+  g_autoptr(GSubprocess) subprocess = NULL;
+  TranslationUnit *unit;
+  GError *error = NULL;
+
+  if (!ide_unsaved_file_persist (unsaved_file,
+                                 g_task_get_cancellable (task),
+                                 &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE
+                                 | G_SUBPROCESS_FLAGS_STDOUT_PIPE
+                                 | G_SUBPROCESS_FLAGS_STDERR_PIPE,
+                                 &error,
+                                 "xgettext",
+                                 "--check=ellipsis-unicode",
+                                 "--check=quote-unicode",
+                                 "--check=space-ellipsis",
+                                 "-k_",
+                                 "-kN_",
+                                 "-L", id_to_xgettext_language (language_id),
+                                 "-o" "-",
+                                 ide_unsaved_file_get_temp_path (unsaved_file),
+                                 NULL);
+  if (!subprocess)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  unit = g_new0 (TranslationUnit, 1);
+  unit->file = g_object_ref (file);
+  unit->unsaved_file = ide_unsaved_file_ref (unsaved_file);
+  g_task_set_task_data (task, unit, (GDestroyNotify) translation_unit_free);
+
+  g_subprocess_wait_async (subprocess,
+                           g_task_get_cancellable (task),
+                           subprocess_wait_cb,
+                           task);
+}
+
+static void
+ide_gettext_diagnostic_provider_init (IdeGettextDiagnosticProvider *self)
+{
+  self->diagnostics_cache = egg_task_cache_new ((GHashFunc)ide_file_hash,
+                                                (GEqualFunc)ide_file_equal,
+                                                g_object_ref,
+                                                g_object_unref,
+                                                g_object_ref,
+                                                g_object_unref,
+                                                20 * 1000L,
+                                                populate_cache,
+                                                self,
+                                                NULL);
+}
diff --git a/plugins/gettext/ide-gettext-diagnostic-provider.h 
b/plugins/gettext/ide-gettext-diagnostic-provider.h
new file mode 100644
index 0000000..fbdcbf0
--- /dev/null
+++ b/plugins/gettext/ide-gettext-diagnostic-provider.h
@@ -0,0 +1,34 @@
+/* ide-gettext-diagnostic-provider.h
+ *
+ * Copyright (C) 2016 Daiki Ueno <dueno src gnome org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_GETTEXT_DIAGNOSTIC_PROVIDER_H
+#define IDE_GETTEXT_DIAGNOSTIC_PROVIDER_H
+
+#include "ide-diagnostic-provider.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_GETTEXT_DIAGNOSTICS (ide_gettext_diagnostics_get_type ())
+G_DECLARE_FINAL_TYPE (IdeGettextDiagnostics, ide_gettext_diagnostics, IDE, GETTEXT_DIAGNOSTICS, GObject)
+
+#define IDE_TYPE_GETTEXT_DIAGNOSTIC_PROVIDER (ide_gettext_diagnostic_provider_get_type ())
+G_DECLARE_FINAL_TYPE (IdeGettextDiagnosticProvider, ide_gettext_diagnostic_provider, IDE, 
GETTEXT_DIAGNOSTIC_PROVIDER, IdeObject)
+
+G_END_DECLS
+
+#endif /* IDE_GETTEXT_DIAGNOSTIC_PROVIDER_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]