[gnome-builder/wip/libide] Add Python scripting support



commit f9f6d5b2eaa079a1124cedb79619aac09df01e00
Author: Garrett Regier <garrettregier gmail com>
Date:   Wed Feb 18 17:08:54 2015 -0800

    Add Python scripting support
    
    Signed-off-by: Garrett Regier <garrettregier gmail com>

 build/autotools/autoconf.d/50_dependencies.post-am |   12 +-
 examples/scripts/README.md                         |    8 +-
 examples/scripts/search-provider.py                |   26 ++
 libide/Makefile.am                                 |    3 +
 libide/ide-script-manager.c                        |    5 +-
 libide/ide.c                                       |    6 +
 libide/pygobject/ide-pygobject-script.c            |  299 ++++++++++++++++++++
 libide/pygobject/ide-pygobject-script.h            |   32 ++
 8 files changed, 384 insertions(+), 7 deletions(-)
---
diff --git a/build/autotools/autoconf.d/50_dependencies.post-am 
b/build/autotools/autoconf.d/50_dependencies.post-am
index ecbc31b..faefbcc 100644
--- a/build/autotools/autoconf.d/50_dependencies.post-am
+++ b/build/autotools/autoconf.d/50_dependencies.post-am
@@ -3,6 +3,7 @@ m4_define([glib_required_version], [2.43.4])
 m4_define([gtksourceview_required_version], [3.15.4])
 m4_define([ggit_required_version], [0.0.24])
 m4_define([gjs_required_version], [1.42.0])
+m4_define([pygobject_required_version], [3.0.0])
 
 PKG_CHECK_MODULES(BUILDER, [gtk+-3.0 >= gtk_required_version
                             gio-2.0 >= glib_required_version
@@ -15,7 +16,16 @@ PKG_CHECK_MODULES(LIBIDE, [gio-2.0 >= glib_required_version
                            gtksourceview-3.0 >= gtksourceview_required_version
                            libgit2-glib-1.0 >= ggit_required_version
                            gjs-1.0 >= gjs_required_version
-                           gjs-internals-1.0 >= gjs_required_version])
+                           gjs-internals-1.0 >= gjs_required_version
+                           pygobject-3.0 >= pygobject_required_version])
 
+AC_PATH_TOOL(PYTHON3_CONFIG, "python3-config")
+if test -z "${PYTHON3_CONFIG}"; then
+  AC_MSG_ERROR([Failed to locate python3-config.])
+fi
+
+LIBIDE_CFLAGS="${LIBIDE_CFLAGS} `${PYTHON3_CONFIG} --cflags`"
+LIBIDE_LIBS="${LIBIDE_LIBS} `${PYTHON3_CONFIG} --libs`"
+LIBIDE_LDFLAGS="${LIBIDE_LDFLAGS} `${PYTHON3_CONFIG} --ldflags`"
 
 GOBJECT_INTROSPECTION_CHECK([1.30.0])
diff --git a/examples/scripts/README.md b/examples/scripts/README.md
index 54b5405..8840790 100644
--- a/examples/scripts/README.md
+++ b/examples/scripts/README.md
@@ -4,8 +4,8 @@ This directory contains various examples you can take inspiration from when
 writing your own IDE extensions.
 
 Extensions are placed in ~/.config/gnome-builder/scripts/. Currently,
-JavaScript is supported. However, more languages may be added in the future
-based on demands.
+JavaScript and Python 3 are supported. However, more languages may be
+added in the future based on demands.
 
-Simply place a `*.js` file in the proper directory, and it will be loaded
-when the `Ide.Context` is initialized.
+Simply place a `*.js` or `*.py` file in the proper directory, and it will
+be loaded when the `Ide.Context` is initialized.
diff --git a/examples/scripts/search-provider.py b/examples/scripts/search-provider.py
new file mode 100644
index 0000000..c20dc26
--- /dev/null
+++ b/examples/scripts/search-provider.py
@@ -0,0 +1,26 @@
+# ~/.config/gnome-builder/scripts/script1.py
+
+from gi.repository import GObject, Ide
+
+# just some examples of things you can access
+Project = Context.get_project()
+BuildSystem = Context.get_build_system()
+Vcs = Context.get_vcs()
+
+# get a handle to the search engine
+SearchEngine = Context.get_search_engine()
+
+# create a custom provider subclass
+class MySearchProvider(Ide.SearchProvider):
+    # perform the search operation. can be asynchronous
+    def do_populate(self, search_context, search_terms, max_results, cancellable):
+        result = Ide.SearchResult(Context, 'weeee', 'more weeeeeee', 0.5)
+        search_context.add_result(self, result)
+        search_context.provider_completed(self)
+
+    # this is what is shown in the search ui describing the action
+    def do_get_verb(self):
+        return "foobar"
+
+# add our custom provider to the search engine
+SearchEngine.add_provider(MySearchProvider(context=Context))
diff --git a/libide/Makefile.am b/libide/Makefile.am
index ce6acee..203b2f3 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -138,6 +138,8 @@ libide_1_0_la_public_sources = \
        libide/ide.h \
        libide/local/ide-local-device.c \
        libide/local/ide-local-device.h \
+       libide/pygobject/ide-pygobject-script.c \
+       libide/pygobject/ide-pygobject-script.h \
        libide/python/ide-python-indenter.c \
        libide/python/ide-python-indenter.h \
        libide/python/ide-python-language.c \
@@ -193,6 +195,7 @@ libide_1_0_la_includes = \
        -I$(top_srcdir)/libide/gjs \
        -I$(top_srcdir)/libide/gsettings \
        -I$(top_srcdir)/libide/local \
+       -I$(top_srcdir)/libide/pygobject \
        -I$(top_srcdir)/libide/python \
        -I$(top_srcdir)/libide/tasks \
        -I$(top_srcdir)/libide/xml \
diff --git a/libide/ide-script-manager.c b/libide/ide-script-manager.c
index a65c119..7828d8a 100644
--- a/libide/ide-script-manager.c
+++ b/libide/ide-script-manager.c
@@ -146,9 +146,10 @@ allow_file (const gchar *name)
   /* NOTE:
    *
    * Add your allowed suffix here if you are adding a new scripting language
-   * (ie: python, etc)
+   * (ie: Lua, etc)
    */
-  return g_str_has_suffix (name, ".js");
+  return g_str_has_suffix (name, ".js") ||
+         g_str_has_suffix (name, ".py");
 }
 
 static void
diff --git a/libide/ide.c b/libide/ide.c
index a309b53..9fb7cf9 100644
--- a/libide/ide.c
+++ b/libide/ide.c
@@ -34,6 +34,7 @@
 #include "ide-git-vcs.h"
 #include "ide-gjs-script.h"
 #include "ide-gsettings-file-settings.h"
+#include "ide-pygobject-script.h"
 #include "ide-python-language.h"
 #include "ide-search-provider.h"
 #include "ide-xml-language.h"
@@ -112,6 +113,11 @@ ide_init_ctor (void)
                                   IDE_SCRIPT_EXTENSION_POINT".gjs",
                                   -100);
 
+  g_io_extension_point_implement (IDE_SCRIPT_EXTENSION_POINT,
+                                  IDE_TYPE_PYGOBJECT_SCRIPT,
+                                  IDE_SCRIPT_EXTENSION_POINT".py",
+                                  -100);
+
   g_io_extension_point_implement (IDE_SEARCH_PROVIDER_EXTENSION_POINT,
                                   IDE_TYPE_GIT_SEARCH_PROVIDER,
                                   IDE_SEARCH_PROVIDER_EXTENSION_POINT".git",
diff --git a/libide/pygobject/ide-pygobject-script.c b/libide/pygobject/ide-pygobject-script.c
new file mode 100644
index 0000000..a2fa998
--- /dev/null
+++ b/libide/pygobject/ide-pygobject-script.c
@@ -0,0 +1,299 @@
+/* ide-pygobject-script.c
+ *
+ * Copyright (C) 2015 Garrett Regier <garrettregier gmail com>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser 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 "ide-context.h"
+#include "ide-pygobject-script.h"
+
+/* _POSIX_C_SOURCE is defined in Python.h and in limits.h included by
+ * glib-object.h, so we unset it here to avoid a warning. Yep, that's bad.
+ */
+#undef _POSIX_C_SOURCE
+#include <pygobject.h>
+
+#include <glib/gi18n.h>
+
+struct _IdePyGObjectScript
+{
+  IdeScript parent_instance;
+};
+
+static PyThreadState *py_thread_state = NULL;
+
+static void async_initable_iface_init (GAsyncInitableIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdePyGObjectScript, ide_pygobject_script, IDE_TYPE_SCRIPT, 0,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+                                               async_initable_iface_init))
+
+static gboolean
+init_pygobject (void)
+{
+  PyGILState_STATE state = 0;
+  long hexversion;
+  gboolean must_finalize_python = FALSE;
+  static gboolean initialized = FALSE;
+  static gboolean success = FALSE;
+
+  if (initialized)
+    return success;
+
+  initialized = TRUE;
+
+  /* Python initialization */
+  if (Py_IsInitialized ())
+    {
+      state = PyGILState_Ensure ();
+    }
+  else
+    {
+      Py_InitializeEx (FALSE);
+      must_finalize_python = TRUE;
+    }
+
+  hexversion = PyLong_AsLong (PySys_GetObject ((char *) "hexversion"));
+
+#if PY_VERSION_HEX < 0x03000000
+  if (hexversion >= 0x03000000)
+#else
+  if (hexversion < 0x03000000)
+#endif
+    {
+      g_critical ("Attempting to mix incompatible Python versions");
+      return FALSE;
+    }
+
+  /* Initialize PyGObject */
+  pygobject_init (3, 0, 0);
+
+  if (PyErr_Occurred ())
+    {
+      g_warning ("PyGObject initialization failed");
+      PyErr_Print ();
+      return FALSE;
+    }
+
+  /* Initialize support for threads */
+  pyg_enable_threads ();
+  PyEval_InitThreads ();
+
+  /* Only redirect warnings when pygobject was not already initialized */
+  if (!must_finalize_python)
+    pyg_disable_warning_redirections ();
+
+  if (!must_finalize_python)
+    PyGILState_Release (state);
+  else
+    py_thread_state = PyEval_SaveThread ();
+
+  success = TRUE;
+  return TRUE;
+}
+
+static void
+ide_pygobject_script_load (IdeScript *script)
+{
+  IdePyGObjectScript *self = (IdePyGObjectScript *)script;
+  IdeContext *context;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(gchar) contents = NULL;
+  g_autoptr(gchar) path = NULL;
+  g_autoptr(GFile) parent = NULL;
+  g_autoptr(gchar) parent_path = NULL;
+  GFile *file;
+  PyObject *globals = NULL;
+  PyObject *builtins_module;
+  PyObject *module_dir = NULL;
+  PyObject *retval;
+  PyObject *pycontext = NULL;
+  PyObject *code;
+  PyGILState_STATE state;
+
+  g_return_if_fail (IDE_IS_PYGOBJECT_SCRIPT (self));
+
+  file = ide_script_get_file (IDE_SCRIPT (script));
+
+  if (!file)
+    {
+      g_warning (_("Attempt to load a PyGObject script with no filename."));
+      return;
+    }
+
+  path = g_file_get_basename (file);
+
+  if (!g_file_load_contents (file, NULL, &contents, NULL, NULL, &error))
+    {
+      g_warning ("%s", error->message);
+      return;
+    }
+
+  if (!init_pygobject ())
+    return;
+
+  state = PyGILState_Ensure ();
+
+  globals = PyDict_New ();
+  if (globals == NULL)
+    goto out;
+
+  builtins_module = PyImport_ImportModule ("builtins");
+  if (builtins_module == NULL)
+    goto out;
+
+  if (PyDict_SetItemString (globals, "__builtins__", builtins_module) != 0)
+    goto out;
+
+  parent = g_file_get_parent (file);
+  parent_path = g_file_get_path (parent);
+  module_dir = PyUnicode_FromString (parent_path);
+
+  if (PyDict_SetItemString (globals, "module_dir", module_dir) != 0)
+    goto out;
+
+  retval = PyRun_String ("import signal\n"
+                         "import sys\n"
+                         "if module_dir not in sys.path:\n"
+                         "    sys.path.insert(0, module_dir)\n"
+                         "\n"
+                         "signal.signal(signal.SIGINT, signal.SIG_DFL)\n",
+                         Py_file_input,
+                         globals, globals);
+
+  if (PyDict_DelItemString (globals, "module_dir") != 0)
+    goto out;
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  pycontext = pygobject_new (G_OBJECT (context));
+  if (pycontext == NULL)
+    goto out;
+
+  if (PyDict_SetItemString (globals, "Context", pycontext) != 0)
+    goto out;
+
+  code = Py_CompileString (contents, path, Py_file_input);
+  if (code == NULL)
+    goto out;
+
+  retval = PyEval_EvalCode (code, globals, globals);
+  Py_XDECREF (retval);
+
+out:
+
+  Py_XDECREF (code);
+  Py_XDECREF (pycontext);
+  Py_XDECREF (module_dir);
+  Py_XDECREF (globals);
+
+  if (PyErr_Occurred ())
+    PyErr_Print ();
+
+  PyGILState_Release (state);
+}
+
+static void
+ide_pygobject_script_unload (IdeScript *self)
+{
+  g_return_if_fail (IDE_IS_PYGOBJECT_SCRIPT (self));
+}
+
+static void
+ide_pygobject_script_class_init (IdePyGObjectScriptClass *klass)
+{
+  IdeScriptClass *script_class = IDE_SCRIPT_CLASS (klass);
+
+  script_class->load = ide_pygobject_script_load;
+  script_class->unload = ide_pygobject_script_unload;
+}
+
+static void
+ide_pygobject_script_init (IdePyGObjectScript *self)
+{
+}
+
+static void
+ide_pygobject_script_init_async (GAsyncInitable      *initable,
+                           gint                 io_priority,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  IdePyGObjectScript *self = (IdePyGObjectScript *)initable;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(gchar) path = NULL;
+  GFile *file;
+
+  g_return_if_fail (IDE_IS_PYGOBJECT_SCRIPT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  file = ide_script_get_file (IDE_SCRIPT (self));
+
+  if (!file)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_INVALID_FILENAME,
+                               _("The filename for the script was not provided."));
+      return;
+    }
+
+  path = g_file_get_path (file);
+
+  if (!path)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_INVALID_FILENAME,
+                               _("The script must be on a local filesystem."));
+      return;
+    }
+
+  if (!g_str_has_suffix (path, ".py"))
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_NOT_SUPPORTED,
+                               _("The script \"%s\" is not a PyGObject file."),
+                               path);
+      return;
+    }
+
+  ide_script_load (IDE_SCRIPT (self));
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+ide_pygobject_script_init_finish (GAsyncInitable  *initable,
+                            GAsyncResult    *result,
+                            GError         **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_PYGOBJECT_SCRIPT (initable), FALSE);
+  g_return_val_if_fail (G_IS_TASK (task), FALSE);
+
+  return g_task_propagate_boolean (task, error);
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *iface)
+{
+  iface->init_async = ide_pygobject_script_init_async;
+  iface->init_finish = ide_pygobject_script_init_finish;
+}
diff --git a/libide/pygobject/ide-pygobject-script.h b/libide/pygobject/ide-pygobject-script.h
new file mode 100644
index 0000000..fa059f2
--- /dev/null
+++ b/libide/pygobject/ide-pygobject-script.h
@@ -0,0 +1,32 @@
+/* ide-pygobject-script.h
+ *
+ * Copyright (C) 2015 Garrett Regier <garrettregier gmail com>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser 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_PYGOBJECT_SCRIPT_H
+#define IDE_PYGOBJECT_SCRIPT_H
+
+#include "ide-script.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PYGOBJECT_SCRIPT (ide_pygobject_script_get_type())
+
+G_DECLARE_FINAL_TYPE (IdePyGObjectScript, ide_pygobject_script, IDE, PYGOBJECT_SCRIPT, IdeScript)
+
+G_END_DECLS
+
+#endif /* IDE_PYGOBJECT_SCRIPT_H */


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