[gnome-builder/wip/chergert/templates: 69/70] wip: templates



commit fa9712b17f55c080bb13c415e5794ca690bb74e5
Author: Christian Hergert <christian hergert me>
Date:   Mon Jan 4 04:35:33 2016 -0800

    wip: templates
    
    start on some template ideas. this uses mako for the moment, via the C API.
    I'm not sure that is what I want to do long term, but figured I'd do a
    code drop so people can see what I'm working on.

 configure.ac                                       |    7 +
 libide/Makefile.am                                 |    7 +
 libide/ide.h                                       |    3 +
 libide/template/example.tmpl                       |  193 ++++++++
 libide/template/ide-project-template.c             |  133 ++++++
 libide/template/ide-project-template.h             |   67 +++
 libide/template/ide-template-mako.c                |  258 +++++++++++
 libide/template/ide-template-private.h             |   51 +++
 libide/template/ide-template-state.c               |  160 +++++++
 libide/template/ide-template-state.h               |   49 ++
 libide/template/ide-template.c                     |  473 ++++++++++++++++++++
 libide/template/ide-template.h                     |   59 +++
 plugins/Makefile.am                                |    2 +
 plugins/create-project/Makefile.am                 |   46 ++
 plugins/create-project/configure.ac                |   12 +
 plugins/create-project/create-project.plugin       |   10 +
 plugins/create-project/gbp-create-project-plugin.c |   30 ++
 plugins/create-project/gbp-create-project-tool.c   |  358 +++++++++++++++
 plugins/create-project/gbp-create-project-tool.h   |   32 ++
 .../gbp-create-project.gresource.xml               |    5 +
 plugins/library-template/Makefile.am               |   15 +
 plugins/library-template/configure.ac              |   11 +
 plugins/library-template/library-template.plugin   |    9 +
 .../library-template/library_template/__init__.py  |   63 +++
 24 files changed, 2053 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 6d86eff..adc2b3d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -231,6 +231,7 @@ m4_include([plugins/c-pack/configure.ac])
 m4_include([plugins/clang/configure.ac])
 m4_include([plugins/command-bar/configure.ac])
 m4_include([plugins/contributing/configure.ac])
+m4_include([plugins/create-project/configure.ac])
 m4_include([plugins/ctags/configure.ac])
 m4_include([plugins/devhelp/configure.ac])
 m4_include([plugins/file-search/configure.ac])
@@ -241,6 +242,7 @@ m4_include([plugins/gnome-code-assistance/configure.ac])
 m4_include([plugins/html-completion/configure.ac])
 m4_include([plugins/html-preview/configure.ac])
 m4_include([plugins/jedi/configure.ac])
+m4_include([plugins/library-template/configure.ac])
 m4_include([plugins/mingw/configure.ac])
 m4_include([plugins/project-tree/configure.ac])
 m4_include([plugins/python-gi-imports-completion/configure.ac])
@@ -496,6 +498,7 @@ echo "  GNOME Code Assistance ................ : ${enable_gnome_code_assistance_
 echo "  HTML Autocompletion .................. : ${enable_html_completion_plugin}"
 echo "  HTML and Markdown Preview ............ : ${enable_html_preview_plugin}"
 echo "  MinGW ................................ : ${enable_mingw_plugin}"
+echo "  Project Creation ..................... : ${enable_create_project_plugin}"
 echo "  Project Tree ......................... : ${enable_project_tree_plugin}"
 echo "  Python GObject Introspection ......... : ${enable_python_gi_imports_completion_plugin}"
 echo "  Python Jedi Autocompletion ........... : ${enable_jedi_plugin}"
@@ -508,4 +511,8 @@ echo "  Terminal ............................. : ${enable_terminal_plugin}"
 echo "  Vala Language Pack ................... : ${enable_vala_pack_plugin}"
 echo "  XML Language Pack .................... : ${enable_xml_pack_plugin}"
 echo ""
+echo " Templates"
+echo ""
+echo "  Library .............................. : ${enable_library_template_plugin}"
+echo ""
 
diff --git a/libide/Makefile.am b/libide/Makefile.am
index eef1348..b2902cd 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -237,6 +237,13 @@ libide_1_0_la_public_sources = \
        ide.h \
        local/ide-local-device.c \
        local/ide-local-device.h \
+       template/ide-template.c \
+       template/ide-template.h \
+       template/ide-template-mako.c \
+       template/ide-template-state.c \
+       template/ide-template-state.h \
+       template/ide-project-template.c \
+       template/ide-project-template.h \
        util/ide-file-manager.c \
        util/ide-file-manager.h \
        $(NULL)
diff --git a/libide/ide.h b/libide/ide.h
index 948fd8b..4b50032 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -126,6 +126,9 @@ G_BEGIN_DECLS
 #include "doap/ide-doap.h"
 #include "local/ide-local-device.h"
 #include "search/ide-omni-search-row.h"
+#include "template/ide-project-template.h"
+#include "template/ide-template-state.h"
+#include "template/ide-template.h"
 #include "util/ide-file-manager.h"
 #include "util/ide-gdk.h"
 #include "util/ide-gtk.h"
diff --git a/libide/template/example.tmpl b/libide/template/example.tmpl
new file mode 100644
index 0000000..f8befaa
--- /dev/null
+++ b/libide/template/example.tmpl
@@ -0,0 +1,193 @@
+% define NAME name|strup|delimit("-","_")
+% include "configure.ac.basic"
+
+AC_PREREQ([2.69])
+
+
+dnl ***********************************************************************
+dnl Versioning Information
+dnl ***********************************************************************
+m4_define([major_version],[3])
+m4_define([minor_version],[19])
+m4_define([micro_version],[0])
+m4_define([version],[major_version.minor_version.micro_version])
+m4_define([interface_age],[0])
+m4_define([bugreport_url], [{{bugreport_url}}])
+m4_define([debug_default],
+          [m4_if(m4_eval(minor_version % 2), [1], [yes], [minimum])])
+
+
+dnl ***********************************************************************
+dnl Initialize Autoconf
+dnl ***********************************************************************
+AC_INIT([{{name}}],[version],[bugreport_url])
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_SRCDIR([configure.ac])
+AC_CONFIG_MACRO_DIR([build-aux])
+AC_CONFIG_AUX_DIR([build-aux])
+AC_SUBST(ACLOCAL_AMFLAGS, "-I build-aux")
+AC_CANONICAL_HOST
+
+
+dnl ***********************************************************************
+dnl Initialize Automake
+dnl ***********************************************************************
+AM_SILENT_RULES([yes])
+AM_INIT_AUTOMAKE([1.11 foreign subdir-objects tar-ustar no-dist-gzip dist-xz])
+AM_MAINTAINER_MODE([enable])
+
+
+dnl ***********************************************************************
+dnl Internationalization
+dnl ***********************************************************************
+IT_PROG_INTLTOOL([0.50.1])
+GETTEXT_PACKAGE=AC_PACKAGE_TARNAME
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"], [GETTEXT package name])
+AM_GLIB_GNU_GETTEXT
+
+
+dnl ***********************************************************************
+dnl Check for Required Programs
+dnl ***********************************************************************
+AC_PROG_CC
+##if cplusplus_enabled
+AC_PROG_CXX
+##endif
+AC_PROG_INSTALL
+PKG_PROG_PKG_CONFIG([0.22])
+AC_PATH_PROG([GLIB_GENMARSHAL], [glib-genmarshal])
+AC_PATH_PROG([GLIB_MKENUMS], [glib-mkenums])
+AC_PATH_PROG([GLIB_COMPILE_RESOURCES], [glib-compile-resources])
+GOBJECT_INTROSPECTION_CHECK([1.42.0])
+GLIB_GSETTINGS
+##if vala_enabled
+VAPIGEN_CHECK
+##endif
+##if appstream_enabled
+APPSTREAM_XML
+##endif
+
+
+##if std_version
+dnl ***********************************************************************
+dnl Ensure {{std_version}} is Supported
+dnl ***********************************************************************
+AX_CHECK_COMPILE_FLAG([-std={{std_version}}],
+                      [CFLAGS="$CFLAGS -std={{std_version}}"],
+                      [AC_MSG_ERROR([C compiler cannot compile {{std_version}} code])])
+##endif
+
+
+##if cplusplus_enabled
+##if std_version == "c11"
+dnl ***********************************************************************
+dnl Ensure C++11 is Supported
+dnl ***********************************************************************
+AX_CXX_COMPILE_STDCXX_11([noext], [mandatory])
+##endif
+##endif
+
+
+dnl ***********************************************************************
+dnl Setup Debug and Tracing Support
+dnl ***********************************************************************
+AC_ARG_ENABLE(debug,
+              AS_HELP_STRING([--enable-debug=@<:@no/minimum/yes@:>@],
+                             [turn on debugging @<:@default=debug_default@:>@]),
+              ,
+              enable_debug=debug_default)
+AS_CASE(["$enable_debug"],
+        [yes],[
+            DEBUG_CFLAGS="$DEBUG_CFLAGS -O0"
+            DEBUG_CFLAGS="$DEBUG_CFLAGS -g"
+        ],
+        [minimum],[
+            DEBUG_CFLAGS="$DEBUG_CFLAGS -DG_DISABLE_CAST_CHECKS"
+        ],
+        [no],[
+            DEBUG_CFLAGS="$DEBUG_CFLAGS -DG_DISABLE_ASSERT"
+            DEBUG_CFLAGS="$DEBUG_CFLAGS -DG_DISABLE_CHECKS"
+            DEBUG_CFLAGS="$DEBUG_CFLAGS -DG_DISABLE_CAST_CHECKS"
+        ],
+        [])
+AC_SUBST(DEBUG_CFLAGS)
+
+
+dnl ***********************************************************************
+dnl Check for Required Packages
+dnl ***********************************************************************
+##if glib_min_version
+m4_define([glib_required_version], [{{glib_min_version}}])
+##else
+m4_define([glib_required_version], [2.47.0])
+##endif
+
+PKG_CHECK_MODULES({{NAME}}, [glib_required_version])
+
+
+dnl ***********************************************************************
+dnl Initialize Libtool
+dnl ***********************************************************************
+LT_PREREQ([2.2])
+LT_INIT
+
+
+dnl ***********************************************************************
+dnl Additional C Compiler Flags
+dnl ***********************************************************************
+AX_CHECK_COMPILE_FLAG([-Werror=unknown-warning-option], [
+       ax_compiler_flags_test="-Werror=unknown-warning-option"
+],[
+       ax_compiler_flags_test=""
+])
+AX_APPEND_COMPILE_FLAGS([ \
+       -Wall \
+       -Wcast-align \
+       -Wdeclaration-after-statement \
+       -Wempty-body \
+       -Werror=format-security \
+       -Werror=format=2 \
+       -Wextra \
+       -Wformat \
+       -Wformat-nonliteral \
+       -Wformat-security \
+       -Winit-self \
+       -Wmisleading-indentation \
+       -Wmissing-include-dirs \
+       -Wshift-negative-value \
+       -Wnested-externs \
+       -Wno-missing-field-initializers \
+       -Wno-sign-compare \
+       -Wno-strict-aliasing \
+       -Wno-uninitialized \
+       -Wno-unused-parameter \
+       -Wpointer-arith \
+       -Wredundant-decls \
+       -Wreturn-type \
+       -Wshadow \
+       -Wswitch-default \
+       -Wswitch-enum \
+       -Wundef \
+       -Wuninitialized \
+], [], [$ax_compiler_flags_test])
+AC_C_CONST
+
+
+dnl ***********************************************************************
+dnl Process .in Files
+dnl ***********************************************************************
+AC_CONFIG_FILES([
+       Makefile
+       build-aux/Makefile
+])
+AC_OUTPUT
+
+
+echo ""
+echo " ${PACKAGE} - ${VERSION}"
+echo ""
+echo " Developer Options"
+echo ""
+echo "  Enable Debug ......................... : ${enable_debug}"
+echo ""
diff --git a/libide/template/ide-project-template.c b/libide/template/ide-project-template.c
new file mode 100644
index 0000000..ede6cde
--- /dev/null
+++ b/libide/template/ide-project-template.c
@@ -0,0 +1,133 @@
+/* ide-project-template.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * 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 "ide-project-template.h"
+
+G_DEFINE_INTERFACE (IdeProjectTemplate, ide_project_template, G_TYPE_OBJECT)
+
+static void
+ide_project_template_default_init (IdeProjectTemplateInterface *iface)
+{
+}
+
+gchar *
+ide_project_template_get_id (IdeProjectTemplate *self)
+{
+  g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+  return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_id (self);
+}
+
+gchar *
+ide_project_template_get_name (IdeProjectTemplate *self)
+{
+  g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+  return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_name (self);
+}
+
+gchar *
+ide_project_template_get_description (IdeProjectTemplate *self)
+{
+  g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+  return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_description (self);
+}
+
+/**
+ * ide_project_template_get_widget:
+ * @self: An #IdeProjectTemplate
+ *
+ * Get's the configuration widget for the template if there is one.
+ *
+ * Returns: (transfer none): A #GtkWidget.
+ */
+GtkWidget *
+ide_project_template_get_widget (IdeProjectTemplate *self)
+{
+  g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+  return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_widget (self);
+}
+
+/**
+ * ide_project_template_get_languages:
+ * @self: an #IdeProjectTemplate
+ *
+ * Gets the list of languages that this template can support when generating
+ * the project.
+ *
+ * Returns: (transfer full): A newly allocated, NULL terminated list of
+ *   supported languages.
+ */
+gchar **
+ide_project_template_get_languages (IdeProjectTemplate *self)
+{
+  g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+  return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_languages (self);
+}
+
+gchar *
+ide_project_template_get_icon_name (IdeProjectTemplate *self)
+{
+  g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+  return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_icon_name (self);
+}
+
+/**
+ * ide_project_template_expand_async:
+ * @self: an #IdeProjectTemplate
+ * @params: (element-type utf8 GLib.Variant): A hashtable of template parameters.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @callback: the callback for the asynchronous operation.
+ * @user_data: user data for @callback.
+ *
+ * Asynchronously requests expansion of the template.
+ *
+ * This may involve creating files and directories on disk as well as
+ * expanding files based on the contents of @params.
+ *
+ * It is expected that this method is only called once on an #IdeProjectTemplate.
+ */
+void
+ide_project_template_expand_async (IdeProjectTemplate  *self,
+                                   GHashTable          *params,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_PROJECT_TEMPLATE (self));
+  g_return_if_fail (params != NULL);
+  g_return_if_fail (g_hash_table_contains (params, "name"));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_PROJECT_TEMPLATE_GET_IFACE (self)->expand_async (self, params, cancellable, callback, user_data);
+}
+
+gboolean
+ide_project_template_expand_finish (IdeProjectTemplate  *self,
+                                    GAsyncResult        *result,
+                                    GError             **error)
+{
+  g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->expand_finish (self, result, error);
+}
diff --git a/libide/template/ide-project-template.h b/libide/template/ide-project-template.h
new file mode 100644
index 0000000..031370d
--- /dev/null
+++ b/libide/template/ide-project-template.h
@@ -0,0 +1,67 @@
+/* ide-project-template.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * 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_PROJECT_TEMPLATE_H
+#define IDE_PROJECT_TEMPLATE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PROJECT_TEMPLATE (ide_project_template_get_type())
+
+G_DECLARE_INTERFACE (IdeProjectTemplate, ide_project_template, IDE, PROJECT_TEMPLATE, GObject)
+
+struct _IdeProjectTemplateInterface
+{
+  GTypeInterface parent;
+
+  gchar      *(*get_id)          (IdeProjectTemplate   *self);
+  gchar      *(*get_name)        (IdeProjectTemplate   *self);
+  gchar      *(*get_description) (IdeProjectTemplate   *self);
+  GtkWidget  *(*get_widget)      (IdeProjectTemplate   *self);
+  gchar     **(*get_languages)   (IdeProjectTemplate   *self);
+  gchar      *(*get_icon_name)   (IdeProjectTemplate   *self);
+  void        (*expand_async)    (IdeProjectTemplate   *self,
+                                  GHashTable           *params,
+                                  GCancellable         *cancellable,
+                                  GAsyncReadyCallback   callback,
+                                  gpointer              user_data);
+  gboolean    (*expand_finish)   (IdeProjectTemplate   *self,
+                                  GAsyncResult         *result,
+                                  GError              **error);
+};
+
+gchar      *ide_project_template_get_id          (IdeProjectTemplate  *self);
+gchar      *ide_project_template_get_name        (IdeProjectTemplate  *self);
+gchar      *ide_project_template_get_description (IdeProjectTemplate  *self);
+GtkWidget  *ide_project_template_get_widget      (IdeProjectTemplate  *self);
+gchar     **ide_project_template_get_languages   (IdeProjectTemplate  *self);
+gchar      *ide_project_template_get_icon_name   (IdeProjectTemplate  *self);
+void        ide_project_template_expand_async    (IdeProjectTemplate   *self,
+                                                  GHashTable           *params,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+gboolean    ide_project_template_expand_finish   (IdeProjectTemplate   *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_PROJECT_TEMPLATE_H */
diff --git a/libide/template/ide-template-mako.c b/libide/template/ide-template-mako.c
new file mode 100644
index 0000000..be3eb62
--- /dev/null
+++ b/libide/template/ide-template-mako.c
@@ -0,0 +1,258 @@
+/* ide-template-parse.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 <Python.h>
+#include "frameobject.h"
+
+#include "ide-template.h"
+#include "ide-template-private.h"
+
+static void
+set_pyerror (GError **error)
+{
+  PyObject *pyerror = PyErr_Occurred ();
+
+  if (pyerror != NULL)
+    {
+      PyObject *repr = PyObject_Str (pyerror);
+
+      if (repr != NULL && PyUnicode_Check (repr))
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_FAILED,
+                       "%s",
+                       PyUnicode_AsUTF8AndSize (repr, NULL));
+        }
+
+      Py_XDECREF (repr);
+    }
+
+  if (error && !*error)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "An unknown error occurred when parsing the template");
+    }
+}
+
+gboolean
+ide_template_parse (IdeTemplate  *self,
+                    GBytes       *data,
+                    GError      **error)
+{
+  IdeTemplatePrivate *priv;
+  PyGILState_STATE gstate;
+  PyObject *module = NULL;
+  PyObject *klass = NULL;
+  PyObject *instance = NULL;
+  PyObject *args = NULL;
+  PyObject *kwargs = NULL;
+  const gchar *buf;
+  gboolean ret = FALSE;
+  gsize len;
+
+  g_return_val_if_fail (IDE_IS_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (data != NULL, FALSE);
+
+  priv = IDE_TEMPLATE_GET_PRIVATE (self);
+
+  buf = g_bytes_get_data (data, &len);
+
+  if (len >= G_MAXINT)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_DATA,
+                   "File is too large to map into template");
+      return FALSE;
+    }
+
+  gstate = PyGILState_Ensure ();
+
+  if (NULL == (module = PyImport_ImportModule ("mako.template")) ||
+      !PyModule_Check (module) ||
+      NULL == (klass = PyObject_GetAttrString (module, "Template")) ||
+      !PyType_Check (klass) ||
+      NULL == (args = PyTuple_New (0)) ||
+      !PyTuple_Check (args) ||
+      NULL == (kwargs = Py_BuildValue ("{s:s#}", "text", buf, (int)len)) ||
+      !PyDict_Check (kwargs) ||
+      !PyCallable_Check (klass) ||
+      NULL == (instance = PyObject_Call (klass, args, kwargs)))
+    goto cleanup;
+
+  g_assert (PyType_Ready ((PyTypeObject *)klass) == 0);
+  g_assert (PyObject_IsInstance (instance, klass));
+
+cleanup:
+
+  if (instance != NULL)
+    {
+      priv->pytemplate = instance;
+      instance = NULL;
+      ret = TRUE;
+    }
+  else
+    {
+      set_pyerror (error);
+    }
+
+  Py_XDECREF (args);
+  Py_XDECREF (kwargs);
+  Py_XDECREF (instance);
+  Py_XDECREF (klass);
+  Py_XDECREF (module);
+
+  PyGILState_Release (gstate);
+
+  return ret;
+}
+
+static PyObject *
+g_variant_to_PyObject (GVariant *variant)
+{
+  if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING))
+    {
+      const gchar *str;
+      gsize len;
+
+      str = g_variant_get_string (variant, &len);
+      return PyUnicode_FromStringAndSize (str, len);
+    }
+
+  return NULL;
+}
+
+static PyObject *
+g_value_to_PyObject (const GValue *value)
+{
+  if (G_VALUE_HOLDS_STRING (value))
+    return PyUnicode_FromString (g_value_get_string (value) ?: "");
+
+  if (G_VALUE_HOLDS_VARIANT (value))
+    return g_variant_to_PyObject (g_value_get_variant (value));
+
+  return NULL;
+}
+
+static void
+state_foreach_cb (gpointer key,
+                  gpointer value,
+                  gpointer user_data)
+{
+  PyObject *dict = user_data;
+  PyObject *pyvalue = NULL;
+  const gchar *var_name = key;
+  const GValue *var_value = value;
+
+  pyvalue = g_value_to_PyObject (var_value);
+
+  if (pyvalue != NULL)
+    {
+      PyDict_SetItemString (dict, var_name, pyvalue);
+      Py_DECREF (pyvalue);
+    }
+}
+
+static PyObject *
+state_to_pydict (IdeTemplateState *state)
+{
+  PyObject *dict;
+
+  g_assert (IDE_IS_TEMPLATE_STATE (state));
+
+  dict = PyDict_New ();
+
+  ide_template_state_foreach (state, state_foreach_cb, dict);
+
+  return dict;
+}
+
+gboolean
+ide_template_expand (IdeTemplate       *self,
+                     IdeTemplateState  *state,
+                     GOutputStream     *stream,
+                     GError           **error)
+{
+  IdeTemplatePrivate *priv;
+  PyGILState_STATE gstate;
+  PyObject *kwargs = NULL;
+  PyObject *result = NULL;
+  PyObject *method = NULL;
+  PyObject *args = NULL;
+  const char *str;
+  Py_ssize_t len;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (IDE_IS_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (IDE_IS_TEMPLATE_STATE (state), FALSE);
+  g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE);
+
+  priv = IDE_TEMPLATE_GET_PRIVATE (self);
+
+  if (priv->pytemplate == NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_INITIALIZED,
+                   "IdeTemplate has not been compiled");
+      return FALSE;
+    }
+
+  gstate = PyGILState_Ensure ();
+
+  kwargs = state_to_pydict (state);
+  g_assert (kwargs != NULL);
+  g_assert (PyDict_Check (kwargs));
+
+  method = PyObject_GetAttrString (priv->pytemplate, "render");
+  g_assert (method != NULL);
+  g_assert (PyCallable_Check (method));
+
+  //args = Py_BuildValue ("(O)", priv->pytemplate);
+  args = PyTuple_New (0);
+  g_assert (args != NULL);
+  g_assert (PyTuple_Check (args));
+
+  result = PyObject_Call (method, args, kwargs);
+
+  if (result == NULL)
+    {
+      set_pyerror (error);
+      goto cleanup;
+    }
+
+  g_assert (PyUnicode_Check (result));
+
+  str = PyUnicode_AsUTF8AndSize (result, &len);
+
+  if (g_output_stream_write_all (stream, str, len, NULL, NULL, error))
+    ret = TRUE;
+
+cleanup:
+  Py_XDECREF (args);
+  Py_XDECREF (kwargs);
+  Py_XDECREF (method);
+  Py_XDECREF (result);
+
+  PyGILState_Release (gstate);
+
+  return ret;
+}
diff --git a/libide/template/ide-template-private.h b/libide/template/ide-template-private.h
new file mode 100644
index 0000000..c0040a7
--- /dev/null
+++ b/libide/template/ide-template-private.h
@@ -0,0 +1,51 @@
+/* ide-template-private.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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_TEMPLATE_PRIVATE_H
+#define IDE_TEMPLATE_PRIVATE_H
+
+#include <Python.h>
+
+#include "ide-template.h"
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+  gchar           *path;
+  GBytes          *data;
+  GError          *compile_error;
+  PyObject        *pytemplate;
+  guint            compiled : 1;
+  guint            compile_failed : 1;
+} IdeTemplatePrivate;
+
+gboolean ide_template_parse  (IdeTemplate       *self,
+                              GBytes            *input,
+                              GError           **error);
+gboolean ide_template_expand (IdeTemplate       *self,
+                              IdeTemplateState  *state,
+                              GOutputStream     *stream,
+                              GError           **error);
+
+#define IDE_TEMPLATE_GET_PRIVATE(self) \
+  (G_TYPE_INSTANCE_GET_PRIVATE(self, IDE_TYPE_TEMPLATE, IdeTemplatePrivate))
+
+G_END_DECLS
+
+#endif /* IDE_TEMPLATE_PRIVATE_H */
diff --git a/libide/template/ide-template-state.c b/libide/template/ide-template-state.c
new file mode 100644
index 0000000..19a4f44
--- /dev/null
+++ b/libide/template/ide-template-state.c
@@ -0,0 +1,160 @@
+/* ide-template-state.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 "ide-template-state.h"
+
+struct _IdeTemplateState
+{
+  GObject     parent_instance;
+  GHashTable *state;
+};
+
+G_DEFINE_TYPE (IdeTemplateState, ide_template_state, G_TYPE_OBJECT)
+
+static void
+_g_value_free (gpointer data)
+{
+  GValue *value = data;
+
+  if (value != NULL && value->g_type)
+    {
+      g_value_unset (value);
+      g_slice_free (GValue, value);
+    }
+}
+
+static void
+ide_template_state_finalize (GObject *object)
+{
+  IdeTemplateState *self = (IdeTemplateState *)object;
+
+  g_clear_pointer (&self->state, g_hash_table_unref);
+
+  G_OBJECT_CLASS (ide_template_state_parent_class)->finalize (object);
+}
+
+static void
+ide_template_state_class_init (IdeTemplateStateClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_template_state_finalize;
+}
+
+static void
+ide_template_state_init (IdeTemplateState *self)
+{
+  self->state = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, _g_value_free);
+}
+
+void
+ide_template_state_add_value (IdeTemplateState *self,
+                              const gchar      *key,
+                              const GValue     *value)
+{
+  GValue *val;
+
+  g_return_if_fail (IDE_IS_TEMPLATE_STATE (self));
+  g_return_if_fail (key != NULL);
+
+  val = g_slice_new0 (GValue);
+  g_value_init (val, G_VALUE_TYPE (value));
+  g_value_copy (value, val);
+
+  g_hash_table_insert (self->state, g_strdup (key), val);
+}
+
+void
+ide_template_state_add_string (IdeTemplateState *self,
+                               const gchar      *key,
+                               const gchar      *value)
+{
+  GValue *val;
+
+  g_return_if_fail (IDE_IS_TEMPLATE_STATE (self));
+  g_return_if_fail (key != NULL);
+
+  val = g_slice_new0 (GValue);
+  g_value_init (val, G_TYPE_STRING);
+  g_value_set_string (val, value);
+
+  g_hash_table_insert (self->state, g_strdup (key), val);
+}
+
+void
+ide_template_state_add_variant (IdeTemplateState *self,
+                                const gchar      *key,
+                                GVariant         *value)
+{
+  GValue *val;
+
+  g_return_if_fail (IDE_IS_TEMPLATE_STATE (self));
+  g_return_if_fail (key != NULL);
+
+  val = g_slice_new0 (GValue);
+  g_value_init (val, G_TYPE_VARIANT);
+  g_value_set_variant (val, value);
+
+  g_hash_table_insert (self->state, g_strdup (key), val);
+}
+
+const GValue *
+ide_template_state_lookup (IdeTemplateState *self,
+                           const gchar      *key)
+{
+  g_return_val_if_fail (IDE_IS_TEMPLATE_STATE (self), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  return g_hash_table_lookup (self->state, key);
+}
+
+IdeTemplateState *
+ide_template_state_new (void)
+{
+  return g_object_new (IDE_TYPE_TEMPLATE_STATE, NULL);
+}
+
+/**
+ * ide_template_state_foreach:
+ * @self: an #IdeTemplateState
+ * @callback: (scope call): A callback to execute for every key/value pair
+ * @callback_data: user data for @calback
+ *
+ * Calls @callback for every key/value pair in @self.
+ *
+ * The key is a UTF-8 string which should not be modified or freed.
+ * The value is a #GValue which should not be modified or freed.
+ */
+void
+ide_template_state_foreach (IdeTemplateState *self,
+                            GHFunc            callback,
+                            gpointer          callback_data)
+{
+  g_return_if_fail (IDE_IS_TEMPLATE_STATE (self));
+  g_return_if_fail (callback != NULL);
+
+  g_hash_table_foreach (self->state, callback, callback_data);
+}
+
+gsize
+ide_template_state_size (IdeTemplateState *self)
+{
+  g_return_val_if_fail (IDE_IS_TEMPLATE_STATE (self), 0);
+
+  return g_hash_table_size (self->state);
+}
diff --git a/libide/template/ide-template-state.h b/libide/template/ide-template-state.h
new file mode 100644
index 0000000..2e93291
--- /dev/null
+++ b/libide/template/ide-template-state.h
@@ -0,0 +1,49 @@
+/* ide-template-state.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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_TEMPLATE_STATE_H
+#define IDE_TEMPLATE_STATE_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TEMPLATE_STATE (ide_template_state_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeTemplateState, ide_template_state, IDE, TEMPLATE_STATE, GObject)
+
+IdeTemplateState *ide_template_state_new         (void);
+void              ide_template_state_add_value   (IdeTemplateState *self,
+                                                  const gchar      *key,
+                                                  const GValue     *value);
+void              ide_template_state_add_string  (IdeTemplateState *self,
+                                                  const gchar      *key,
+                                                  const gchar      *value);
+void              ide_template_state_add_variant (IdeTemplateState *self,
+                                                  const gchar      *key,
+                                                  GVariant         *value);
+const GValue      *ide_template_state_lookup     (IdeTemplateState *self,
+                                                  const gchar      *key);
+void               ide_template_state_foreach    (IdeTemplateState *self,
+                                                  GHFunc            callback,
+                                                  gpointer          callback_data);
+gsize              ide_template_state_size       (IdeTemplateState *self);
+
+G_END_DECLS
+
+#endif /* IDE_TEMPLATE_STATE_H */
diff --git a/libide/template/ide-template.c b/libide/template/ide-template.c
new file mode 100644
index 0000000..54d0a23
--- /dev/null
+++ b/libide/template/ide-template.c
@@ -0,0 +1,473 @@
+/* ide-template.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 "ide-template.h"
+#include "ide-template-private.h"
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeTemplate, ide_template, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_PATH,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_template_set_path (IdeTemplate *self,
+                       const gchar *path)
+{
+  IdeTemplatePrivate *priv = ide_template_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_TEMPLATE (self));
+  g_return_if_fail (path != NULL);
+
+  if (0 != g_strcmp0 (path, priv->path))
+    {
+      g_free (priv->path);
+      priv->path = g_strdup (path);
+    }
+}
+
+static void
+ide_template_finalize (GObject *object)
+{
+  IdeTemplate *self = (IdeTemplate *)object;
+  IdeTemplatePrivate *priv = ide_template_get_instance_private (self);
+
+  g_clear_pointer (&priv->path, g_free);
+  g_clear_pointer (&priv->compile_error, g_error_free);
+
+  G_OBJECT_CLASS (ide_template_parent_class)->finalize (object);
+}
+
+static void
+ide_template_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  IdeTemplate *self = IDE_TEMPLATE (object);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      g_value_set_string (value, ide_template_get_path (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_template_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  IdeTemplate *self = IDE_TEMPLATE (object);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      ide_template_set_path (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_template_class_init (IdeTemplateClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_template_finalize;
+  object_class->get_property = ide_template_get_property;
+  object_class->set_property = ide_template_set_property;
+
+  properties [PROP_PATH] =
+    g_param_spec_string ("path",
+                         "Path",
+                         "Path",
+                         NULL,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_EXPLICIT_NOTIFY |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_template_init (IdeTemplate *self)
+{
+}
+
+static void
+ide_template_compile_worker (GTask        *task,
+                             gpointer      source_object,
+                             gpointer      task_data,
+                             GCancellable *cancellable)
+{
+  IdeTemplate *self = source_object;
+  IdeTemplatePrivate *priv = ide_template_get_instance_private (self);
+  g_autoptr(GBytes) bytes = NULL;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IDE_IS_TEMPLATE (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (priv->path == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_INVALID_FILENAME,
+                               "No template path was specified.");
+      return;
+    }
+
+  if (g_str_has_prefix (priv->path, "resource://"))
+    {
+      bytes = g_resources_lookup_data (priv->path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
+
+      if (bytes == NULL)
+        {
+          g_task_return_error (task, error);
+          return;
+        }
+    }
+  else
+    {
+      gchar *contents = NULL;
+      gsize length = 0;
+
+      if (!g_file_get_contents (priv->path, &contents, &length, &error))
+        {
+          g_task_return_error (task, error);
+          return;
+        }
+
+      bytes = g_bytes_new_take (contents, length);
+    }
+
+  if (!ide_template_parse (self, bytes, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  priv->compiled = TRUE;
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_template_compile_async (IdeTemplate         *self,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  IdeTemplatePrivate *priv = ide_template_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_TEMPLATE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  if (priv->compiled)
+    {
+      if (priv->compile_failed)
+        g_task_return_error (task, g_error_copy (priv->compile_error));
+      else
+        g_task_return_boolean (task, TRUE);
+      return;
+    }
+
+  g_task_run_in_thread (task, ide_template_compile_worker);
+}
+
+static gboolean
+ide_template_compile_finish (IdeTemplate   *self,
+                             GAsyncResult  *result,
+                             GError       **error)
+{
+  IdeTemplatePrivate *priv = ide_template_get_instance_private (self);
+  GError *local_error = NULL;
+
+  g_assert (IDE_IS_TEMPLATE (self));
+  g_assert (G_IS_TASK (result));
+
+  priv->compiled = TRUE;
+
+  if (!g_task_propagate_boolean (G_TASK (result), &local_error))
+    {
+      priv->compile_failed = TRUE;
+      priv->compile_error = g_error_copy (local_error);
+      g_propagate_error (error, local_error);
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+ide_template_expand_worker (GTask        *task,
+                            gpointer      source_object,
+                            gpointer      task_data,
+                            GCancellable *cancellable)
+{
+  IdeTemplate *self = source_object;
+  IdeTemplateState *state;
+  GOutputStream *stream;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IDE_IS_TEMPLATE (self));
+
+  state = g_object_get_data (G_OBJECT (task), "STATE");
+  stream = g_object_get_data (G_OBJECT (task), "STREAM");
+
+  g_assert (IDE_IS_TEMPLATE_STATE (state));
+  g_assert (G_IS_OUTPUT_STREAM (stream));
+
+  if (!ide_template_expand (self, state, stream, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_template_expand_compile_cb (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer      user_data)
+{
+  IdeTemplate *self = (IdeTemplate *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_TEMPLATE (self));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_template_compile_finish (self, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_run_in_thread (task, ide_template_expand_worker);
+}
+
+void
+ide_template_expand_async (IdeTemplate         *self,
+                           IdeTemplateState    *state,
+                           GOutputStream       *stream,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  IdeTemplatePrivate *priv = ide_template_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_TEMPLATE (self));
+  g_return_if_fail (!state || IDE_IS_TEMPLATE_STATE (state));
+  g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  if (priv->compile_failed)
+    {
+      g_task_return_error (task, g_error_copy (priv->compile_error));
+      return;
+    }
+
+  g_object_set_data_full (G_OBJECT (task), "STATE", g_object_ref (state), g_object_unref);
+  g_object_set_data_full (G_OBJECT (task), "STREAM", g_object_ref (stream), g_object_unref);
+
+  if (priv->pytemplate == NULL)
+    {
+      ide_template_compile_async (self,
+                                  cancellable,
+                                  ide_template_expand_compile_cb,
+                                  g_object_ref (task));
+    }
+  else
+    {
+      g_task_run_in_thread (task, ide_template_expand_worker);
+    }
+}
+
+gboolean
+ide_template_expand_finish (IdeTemplate   *self,
+                            GAsyncResult  *result,
+                            GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_template_expand_file_expand_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  IdeTemplate *self = (IdeTemplate *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_TEMPLATE (self));
+
+  if (!ide_template_expand_finish (self, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_template_expand_file_replace_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  GFile *file = (GFile *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GFileOutputStream) stream = NULL;
+  IdeTemplate *self;
+  IdeTemplateState *state;
+  GError *error = NULL;
+
+  g_assert (G_IS_FILE (file));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  stream = g_file_replace_finish (file, result, &error);
+
+  if (stream == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  self = g_task_get_source_object (task);
+  g_assert (IDE_IS_TEMPLATE (self));
+
+  state = g_task_get_task_data (task);
+  g_assert (!state || IDE_IS_TEMPLATE_STATE (state));
+
+  ide_template_expand_async (self,
+                             state,
+                             G_OUTPUT_STREAM (stream),
+                             g_task_get_cancellable (task),
+                             ide_template_expand_file_expand_cb,
+                             g_object_ref (task));
+}
+
+/**
+ * ide_template_expand_file_async:
+ * @self: an #IdeTemplate.
+ * @state: (nullable): an #IdeTemplateState or %NULL
+ * @file: a #GFile
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: the callback to execute upon completion
+ * @user_data: user data for @callback
+ *
+ * This function works like ide_template_expand_async() except that the
+ * contents are written into @file, which is also replaced asynchronously.
+ *
+ * Call ide_template_expand_file_finish() from @callback to check for any
+ * errors expanding the template.
+ */
+void
+ide_template_expand_file_async (IdeTemplate         *self,
+                                IdeTemplateState    *state,
+                                GFile               *file,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_TEMPLATE (self));
+  g_return_if_fail (!state || IDE_IS_TEMPLATE_STATE (state));
+  g_return_if_fail (G_IS_FILE (file));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  if (state != NULL)
+    g_task_set_task_data (task, g_object_ref (state), g_object_unref);
+
+  g_file_replace_async (file,
+                        NULL,
+                        FALSE,
+                        G_FILE_CREATE_REPLACE_DESTINATION,
+                        G_PRIORITY_DEFAULT,
+                        cancellable,
+                        ide_template_expand_file_replace_cb,
+                        g_object_ref (task));
+}
+
+gboolean
+ide_template_expand_file_finish (IdeTemplate   *self,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+IdeTemplate *
+ide_template_new (const gchar *path)
+{
+  return g_object_new (IDE_TYPE_TEMPLATE,
+                       "path", path,
+                       NULL);
+}
+
+/**
+ * ide_template_get_path:
+ * @self: an #IdeTemplate
+ *
+ * Gets the path of the template. If the template is a resource, the path
+ * begins with resource:///.
+ */
+const gchar *
+ide_template_get_path (IdeTemplate *self)
+{
+  IdeTemplatePrivate *priv = ide_template_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_TEMPLATE (self), NULL);
+
+  return priv->path;
+}
diff --git a/libide/template/ide-template.h b/libide/template/ide-template.h
new file mode 100644
index 0000000..b6be07f
--- /dev/null
+++ b/libide/template/ide-template.h
@@ -0,0 +1,59 @@
+/* ide-template.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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_TEMPLATE_H
+#define IDE_TEMPLATE_H
+
+#include <gio/gio.h>
+
+#include "ide-template-state.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TEMPLATE (ide_template_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeTemplate, ide_template, IDE, TEMPLATE, GObject)
+
+struct _IdeTemplateClass
+{
+  GObjectClass parent;
+};
+
+const gchar *ide_template_get_path           (IdeTemplate          *self);
+void         ide_template_expand_async       (IdeTemplate          *self,
+                                              IdeTemplateState     *state,
+                                              GOutputStream        *stream,
+                                              GCancellable         *cancellable,
+                                              GAsyncReadyCallback   callback,
+                                              gpointer              user_data);
+gboolean     ide_template_expand_finish      (IdeTemplate          *self,
+                                              GAsyncResult         *result,
+                                              GError              **error);
+void         ide_template_expand_file_async  (IdeTemplate          *self,
+                                              IdeTemplateState     *state,
+                                              GFile                *file,
+                                              GCancellable         *cancellable,
+                                              GAsyncReadyCallback   callback,
+                                              gpointer              user_data);
+gboolean     ide_template_expand_file_finish (IdeTemplate          *self,
+                                              GAsyncResult         *result,
+                                              GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_TEMPLATE_H */
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 7e9628a..b316c78 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -5,6 +5,7 @@ SUBDIRS = \
        command-bar \
        contributing \
        c-pack \
+       create-project \
        ctags \
        devhelp \
        file-search \
@@ -15,6 +16,7 @@ SUBDIRS = \
        html-completion \
        html-preview \
        jedi \
+       library-template \
        project-tree \
        python-gi-imports-completion \
        mingw \
diff --git a/plugins/create-project/Makefile.am b/plugins/create-project/Makefile.am
new file mode 100644
index 0000000..f840f31
--- /dev/null
+++ b/plugins/create-project/Makefile.am
@@ -0,0 +1,46 @@
+if ENABLE_CREATE_PROJECT_PLUGIN
+
+DISTCLEANFILES =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libcreate-project-plugin.la
+dist_plugin_DATA = create-project.plugin
+
+libcreate_project_plugin_la_SOURCES = \
+       gbp-create-project-plugin.c \
+       gbp-create-project-tool.c \
+       gbp-create-project-tool.h \
+       $(NULL)
+
+nodist_libcreate_project_plugin_la_SOURCES = \
+       gbp-create-project-resources.c \
+       gbp-create-project-resources.h
+
+libcreate_project_plugin_la_CFLAGS = \
+       $(LIBIDE_CFLAGS) \
+       $(OPTIMIZE_CFLAGS) \
+       -I$(top_srcdir)/libide \
+       -I$(top_srcdir)/contrib/egg \
+       $(NULL)
+
+libcreate_project_plugin_la_LDFLAGS = \
+       $(OPTIMIZE_LDFLAGS) \
+       -avoid-version \
+       -module \
+       -export-regex peas_register_types \
+       $(NULL)
+
+glib_resources_c = gbp-create-project-resources.c
+glib_resources_h = gbp-create-project-resources.h
+glib_resources_xml = gbp-create-project.gresource.xml
+glib_resources_namespace = gbp_create_project
+include $(top_srcdir)/build/autotools/Makefile.am.gresources
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/create-project/configure.ac b/plugins/create-project/configure.ac
new file mode 100644
index 0000000..6491ff2
--- /dev/null
+++ b/plugins/create-project/configure.ac
@@ -0,0 +1,12 @@
+# --enable-create-project-plugin=yes/no
+AC_ARG_ENABLE([create-project-plugin],
+              [AS_HELP_STRING([--enable-create-project-plugin=@<:@yes/no@:>@],
+                              [Build with support for creating projects.])],
+              [enable_create_project_plugin=$enableval],
+              [enable_create_project_plugin=yes])
+
+# for if ENABLE_CREATE_PROJECT_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_CREATE_PROJECT_PLUGIN, test x$enable_create_project_plugin != xno)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/create-project/Makefile])
diff --git a/plugins/create-project/create-project.plugin b/plugins/create-project/create-project.plugin
new file mode 100644
index 0000000..afc340d
--- /dev/null
+++ b/plugins/create-project/create-project.plugin
@@ -0,0 +1,10 @@
+[Plugin]
+Module=create-project-plugin
+Name=Create Project
+Description=Create projects with Builder
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2015 Christian Hergert
+Builtin=true
+Hidden=true
+X-Tool-Name=create-project
+X-Tool-Description=Create a new project
diff --git a/plugins/create-project/gbp-create-project-plugin.c 
b/plugins/create-project/gbp-create-project-plugin.c
new file mode 100644
index 0000000..70e02bb
--- /dev/null
+++ b/plugins/create-project/gbp-create-project-plugin.c
@@ -0,0 +1,30 @@
+/* gbp-create-project-plugin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * 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 <ide.h>
+#include <libpeas/peas.h>
+
+#include "gbp-create-project-tool.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_APPLICATION_TOOL,
+                                              GBP_TYPE_CREATE_PROJECT_TOOL);
+}
diff --git a/plugins/create-project/gbp-create-project-tool.c 
b/plugins/create-project/gbp-create-project-tool.c
new file mode 100644
index 0000000..6b71322
--- /dev/null
+++ b/plugins/create-project/gbp-create-project-tool.c
@@ -0,0 +1,358 @@
+/* gbp-create-project-tool.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <ctype.h>
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include "gbp-create-project-tool.h"
+
+struct _GbpCreateProjectTool
+{
+  GObject            parent_instance;
+  gboolean           list_templates;
+  gchar            **args;
+  gchar             *template;
+  PeasExtensionSet  *templates;
+};
+
+static void application_tool_iface_init (IdeApplicationToolInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GbpCreateProjectTool, gbp_create_project_tool, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_APPLICATION_TOOL,
+                                               application_tool_iface_init))
+
+static void
+gbp_create_project_tool_constructed (GObject *object)
+{
+  GbpCreateProjectTool *self = (GbpCreateProjectTool *)object;
+
+  self->templates = peas_extension_set_new (peas_engine_get_default (),
+                                            IDE_TYPE_PROJECT_TEMPLATE,
+                                            NULL);
+
+  G_OBJECT_CLASS (gbp_create_project_tool_parent_class)->constructed (object);
+}
+
+static void
+gbp_create_project_tool_finalize (GObject *object)
+{
+  GbpCreateProjectTool *self = (GbpCreateProjectTool *)object;
+
+  g_clear_object (&self->templates);
+  g_clear_pointer (&self->args, g_strfreev);
+  g_clear_pointer (&self->template, g_free);
+
+  G_OBJECT_CLASS (gbp_create_project_tool_parent_class)->finalize (object);
+}
+
+static void
+gbp_create_project_tool_class_init (GbpCreateProjectToolClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = gbp_create_project_tool_constructed;
+  object_class->finalize = gbp_create_project_tool_finalize;
+}
+
+static void
+gbp_create_project_tool_init (GbpCreateProjectTool *self)
+{
+}
+
+static void
+print_template (PeasExtensionSet *set,
+                PeasPluginInfo   *plugin_info,
+                PeasExtension    *exten,
+                gpointer          user_data)
+{
+  IdeProjectTemplate *template = (IdeProjectTemplate *)exten;
+  GbpCreateProjectTool *self = user_data;
+  gchar *id;
+
+  g_assert (IDE_IS_PROJECT_TEMPLATE (template));
+  g_assert (GBP_IS_CREATE_PROJECT_TOOL (self));
+
+  id = ide_project_template_get_id (template);
+  g_print ("  %s\n", id);
+  g_free (id);
+}
+
+static void
+gbp_create_project_tool_list_templates (GbpCreateProjectTool *self)
+{
+  g_assert (GBP_IS_CREATE_PROJECT_TOOL (self));
+
+  g_print ("\n");
+  peas_extension_set_foreach (self->templates, print_template, self);
+  g_print ("\n");
+}
+
+static gboolean
+gbp_create_project_tool_parse (GbpCreateProjectTool  *self,
+                               GError               **error)
+{
+  g_autoptr(GOptionContext) context = NULL;
+  GOptionEntry entries[] = {
+    { "list-templates", 'l', 0, G_OPTION_ARG_NONE, &self->list_templates,
+      N_("List available templates") },
+    { "template", 't', 0, G_OPTION_ARG_STRING, &self->template,
+      N_("Project template to generate") },
+    { NULL }
+  };
+
+  g_assert (GBP_IS_CREATE_PROJECT_TOOL (self));
+
+  context = g_option_context_new (_("create-project [OPTION...] PROJECT_NAME"));
+  g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
+
+  if (!g_option_context_parse_strv (context, &self->args, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+typedef struct
+{
+  const gchar        *id;
+  IdeProjectTemplate *result;
+} TemplateLookup;
+
+static void
+find_template_cb (PeasExtensionSet *set,
+                  PeasPluginInfo   *plugin_info,
+                  PeasExtension    *exten,
+                  gpointer          user_data)
+{
+  TemplateLookup *lookup = user_data;
+  g_autofree gchar *id = NULL;
+
+  if (lookup->result != NULL)
+    return;
+
+  id = ide_project_template_get_id (IDE_PROJECT_TEMPLATE (exten));
+  if (ide_str_equal0 (id, lookup->id))
+    lookup->result = IDE_PROJECT_TEMPLATE (exten);
+}
+
+static IdeProjectTemplate *
+find_template (GbpCreateProjectTool *self)
+{
+  TemplateLookup lookup;
+
+  g_assert (GBP_IS_CREATE_PROJECT_TOOL (self));
+  g_assert (self->template != NULL);
+
+  lookup.id = self->template;
+  lookup.result = NULL;
+
+  peas_extension_set_foreach (self->templates, find_template_cb, &lookup);
+
+  return lookup.result;
+}
+
+static gboolean
+validate_name (GbpCreateProjectTool  *self,
+               const gchar           *name,
+               GError               **error)
+{
+  for (; *name; name = g_utf8_next_char (name))
+    {
+      gunichar ch = g_utf8_get_char (name);
+
+      switch (ch)
+        {
+        default:
+          if (isascii (ch))
+            continue;
+          /* Fall through */
+        case '=':
+        case ':':
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_DATA,
+                       _("Filename must be ascii and may not contain : or ="));
+          return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
+static void
+extract_cb (GObject      *object,
+            GAsyncResult *result,
+            gpointer      user_data)
+{
+  IdeProjectTemplate *template = (IdeProjectTemplate *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_PROJECT_TEMPLATE (template));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_project_template_expand_finish (template, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_return_int (task, 0);
+}
+
+static gboolean
+extract_params (GbpCreateProjectTool  *self,
+                GHashTable            *params,
+                GError               **error)
+{
+  gint i;
+
+  g_assert (GBP_IS_CREATE_PROJECT_TOOL (self));
+  g_assert (params != NULL);
+
+  if (self->args && g_strv_length (self->args) > 2)
+    {
+      for (i = 2; self->args [i]; i++)
+        {
+          const gchar *arg = self->args [i];
+          const gchar *eq;
+
+          if ((eq = strchr (arg, '=')) != NULL)
+            {
+              g_autofree gchar *value = NULL;
+              gchar *key;
+              GVariant *var;
+
+              key = g_strndup (arg, (eq - arg));
+              value = g_strdup (eq + 1);
+
+              var = g_variant_parse (NULL, value, NULL, NULL, NULL);
+              if (var == NULL)
+                var = g_variant_new_string (value);
+
+              g_hash_table_insert (params, key, g_variant_ref_sink (var));
+            }
+        }
+    }
+
+  return TRUE;
+}
+
+static void
+gbp_create_project_tool_run_async (IdeApplicationTool  *tool,
+                                   const gchar * const *arguments,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  GbpCreateProjectTool *self = (GbpCreateProjectTool *)tool;
+  IdeProjectTemplate *template;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GHashTable) params = NULL;
+  const gchar *name;
+  GError *error = NULL;
+
+  g_assert (GBP_IS_CREATE_PROJECT_TOOL (self));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  /* pretend that "create-project" is argv[0] */
+  self->args = g_strdupv ((gchar **)&arguments[1]);
+
+  if (!gbp_create_project_tool_parse (self, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  if (self->list_templates)
+    {
+      gbp_create_project_tool_list_templates (self);
+      g_task_return_int (task, 0);
+      return;
+    }
+
+  if (!self->args || g_strv_length (self->args) < 2)
+    {
+      g_printerr (_("Please specify a project name.\n"));
+      g_task_return_int (task, 1);
+      return;
+    }
+
+  name = self->args [1];
+
+  if (!validate_name (self, name, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      g_task_return_error (task, error);
+      return;
+    }
+
+  if (!self->template || !(template = find_template (self)))
+    {
+      g_printerr (_("Please specify a project template.\n"));
+      gbp_create_project_tool_list_templates (self);
+      g_task_return_int (task, 1);
+      return;
+    }
+
+  params = g_hash_table_new_full (g_str_hash,
+                                  g_str_equal,
+                                  g_free,
+                                  (GDestroyNotify)g_variant_unref);
+
+  if (!extract_params (self, params, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_hash_table_insert (params,
+                       g_strdup ("name"),
+                       g_variant_ref_sink (g_variant_new_string (name)));
+
+  ide_project_template_expand_async (template,
+                                     params,
+                                     NULL,
+                                     extract_cb,
+                                     g_object_ref (task));
+}
+
+static gint
+gbp_create_project_tool_run_finish (IdeApplicationTool  *tool,
+                                    GAsyncResult        *result,
+                                    GError             **error)
+{
+  g_assert (GBP_IS_CREATE_PROJECT_TOOL (tool));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_int (G_TASK (result), error);
+}
+
+static void
+application_tool_iface_init (IdeApplicationToolInterface *iface)
+{
+  iface->run_async = gbp_create_project_tool_run_async;
+  iface->run_finish = gbp_create_project_tool_run_finish;
+}
diff --git a/plugins/create-project/gbp-create-project-tool.h 
b/plugins/create-project/gbp-create-project-tool.h
new file mode 100644
index 0000000..1fa888e
--- /dev/null
+++ b/plugins/create-project/gbp-create-project-tool.h
@@ -0,0 +1,32 @@
+/* gbp-create-project-tool.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * 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 GBP_CREATE_PROJECT_TOOL_H
+#define GBP_CREATE_PROJECT_TOOL_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_CREATE_PROJECT_TOOL (gbp_create_project_tool_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpCreateProjectTool, gbp_create_project_tool, GBP, CREATE_PROJECT_TOOL, GObject)
+
+G_END_DECLS
+
+#endif /* GBP_CREATE_PROJECT_TOOL_H */
diff --git a/plugins/create-project/gbp-create-project.gresource.xml 
b/plugins/create-project/gbp-create-project.gresource.xml
new file mode 100644
index 0000000..7c5c913
--- /dev/null
+++ b/plugins/create-project/gbp-create-project.gresource.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/builder/plugins/create-project-plugin">
+  </gresource>
+</gresources>
diff --git a/plugins/library-template/Makefile.am b/plugins/library-template/Makefile.am
new file mode 100644
index 0000000..bff686c
--- /dev/null
+++ b/plugins/library-template/Makefile.am
@@ -0,0 +1,15 @@
+if ENABLE_LIBRARY_TEMPLATE_PLUGIN
+
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+dist_plugin_DATA = library-template.plugin
+
+moduledir = $(libdir)/gnome-builder/plugins/library_template
+dist_module_DATA = library_template/__init__.py
+
+endif
+
+GITIGNOREFILES = library_template/__pycache__
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/library-template/configure.ac b/plugins/library-template/configure.ac
new file mode 100644
index 0000000..82ef9a4
--- /dev/null
+++ b/plugins/library-template/configure.ac
@@ -0,0 +1,11 @@
+AC_ARG_ENABLE([library-template-plugin],
+              [AS_HELP_STRING([--enable-library-template-plugin=@<:@auto/yes/no@:>@],
+                              [Build with support for creating library projects.])],
+              [enable_library_template_plugin=$enableval],
+              [enable_library_template_plugin=yes])
+
+# for if ENABLE_LIBRARY_TEMPLATE_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_LIBRARY_TEMPLATE_PLUGIN, test x$enable_library_template_plugin = xyes)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/library-template/Makefile])
diff --git a/plugins/library-template/library-template.plugin 
b/plugins/library-template/library-template.plugin
new file mode 100644
index 0000000..9908d0d
--- /dev/null
+++ b/plugins/library-template/library-template.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Module=library_template
+Loader=python3
+Name=Library Template
+Description=Provides templates for creating libraries
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2015 Christian Hergert
+Builtin=true
+Hidden=true
diff --git a/plugins/library-template/library_template/__init__.py 
b/plugins/library-template/library_template/__init__.py
new file mode 100644
index 0000000..39c842b
--- /dev/null
+++ b/plugins/library-template/library_template/__init__.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+
+#
+# library_template.py
+#
+# Copyright (C) 2015 Christian Hergert <chris dronelabs com>
+#
+# 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/>.
+#
+
+from gi.repository import Gio
+from gi.repository import GObject
+from gi.repository import Ide
+
+class LibraryProjectTemplate(GObject.Object, Ide.ProjectTemplate):
+    def do_get_id(self):
+        return 'library'
+
+    def do_get_icon_name(self):
+        return 'template-library-symbolic'
+
+    def do_get_name(self):
+        return _("Library")
+
+    def do_get_description(self):
+        return _("Create a project containing a shared library")
+
+    def do_get_languages(self):
+        return ['C']
+
+    def do_get_widget(self):
+        return None
+
+    def do_expand_async(self, params, cancellable, callback, user_data):
+        task = Gio.Task.new(self, cancellable, callback)
+
+        """
+        outFile = Gio.File.new_for_path("/home/christian/test.tmpl.out")
+
+        state = Ide.TemplateState()
+        for k,v in params.items():
+            state.add_variant(k, v)
+
+        tmpl = Ide.Template(path="/home/christian/test.tmpl")
+        tmpl.expand_file_async(state, outFile, cancellable, lambda *_: task.return_boolean(True))
+        """
+
+        task.return_boolean(True)
+
+    def do_expand_finish(self, result):
+        return result.propagate_boolean()
+


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