[gnome-builder/wip/chergert/templates] wip on templating ideas



commit 00fe667400babf2d2a59fec02abd2d88f95fda53
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jan 6 02:27:15 2016 -0800

    wip on templating ideas

 configure.ac                           |    3 +
 contrib/Makefile.am                    |    1 +
 contrib/tmpl/Makefile.am               |   42 ++++
 contrib/tmpl/configure.ac.tmpl         |  203 ++++++++++++++++++
 contrib/tmpl/tmpl-error.c              |   25 +++
 contrib/tmpl/tmpl-error.h              |   39 ++++
 contrib/tmpl/tmpl-expr-node.c          |   84 ++++++++
 contrib/tmpl/tmpl-expr-node.h          |   36 ++++
 contrib/tmpl/tmpl-expr.c               |   71 +++++++
 contrib/tmpl/tmpl-expr.h               |   40 ++++
 contrib/tmpl/tmpl-lexer.c              |  165 +++++++++++++++
 contrib/tmpl/tmpl-lexer.h              |   47 ++++
 contrib/tmpl/tmpl-node.c               |  166 +++++++++++++++
 contrib/tmpl/tmpl-node.h               |   63 ++++++
 contrib/tmpl/tmpl-parser.c             |  267 ++++++++++++++++++++++++
 contrib/tmpl/tmpl-parser.h             |   48 +++++
 contrib/tmpl/tmpl-scope.c              |   57 +++++
 contrib/tmpl/tmpl-scope.h              |   35 +++
 contrib/tmpl/tmpl-template-locator.c   |  197 +++++++++++++++++
 contrib/tmpl/tmpl-template-locator.h   |   57 +++++
 contrib/tmpl/tmpl-template.c           |  359 ++++++++++++++++++++++++++++++++
 contrib/tmpl/tmpl-template.h           |   66 ++++++
 contrib/tmpl/tmpl-text-node.c          |   77 +++++++
 contrib/tmpl/tmpl-text-node.h          |   37 ++++
 contrib/tmpl/tmpl-token-input-stream.c |  307 +++++++++++++++++++++++++++
 contrib/tmpl/tmpl-token-input-stream.h |   43 ++++
 contrib/tmpl/tmpl-token.c              |  147 +++++++++++++
 contrib/tmpl/tmpl-token.h              |   54 +++++
 contrib/tmpl/tmpl.c                    |   74 +++++++
 contrib/tmpl/tmpl.h                    |   35 +++
 30 files changed, 2845 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 53b5a39..8d50780 100644
--- a/configure.ac
+++ b/configure.ac
@@ -181,6 +181,8 @@ PKG_CHECK_MODULES(PYGOBJECT,[pygobject-3.0 >= pygobject_required_version],
                             [have_pygobject=no])
 PKG_CHECK_MODULES(RG,       [gtk+-3.0 >= gtk_required_version])
 PKG_CHECK_MODULES(SEARCH,   [glib-2.0 >= glib_required_version])
+PKG_CHECK_MODULES(TMPL,     [gio-2.0 >= glib_required_version
+                             gio-unix-2.0 >= glib_required_version])
 PKG_CHECK_MODULES(XML,      [gio-2.0 >= glib_required_version
                              libxml-2.0 >= libxml_required_version])
 
@@ -421,6 +423,7 @@ AC_CONFIG_FILES([
        contrib/nautilus/Makefile
        contrib/rg/Makefile
        contrib/search/Makefile
+       contrib/tmpl/Makefile
        contrib/xml/Makefile
 
        libide/ide-debug.h
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index d01f238..dcfefa4 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -6,6 +6,7 @@ SUBDIRS = \
        nautilus \
        rg \
        search \
+       tmpl \
        xml \
        $(NULL)
 
diff --git a/contrib/tmpl/Makefile.am b/contrib/tmpl/Makefile.am
new file mode 100644
index 0000000..4e1589d
--- /dev/null
+++ b/contrib/tmpl/Makefile.am
@@ -0,0 +1,42 @@
+pkglibdir = $(libdir)/gnome-builder
+pkglib_LTLIBRARIES = libtemplate-glib.la
+
+libtemplate_glib_la_SOURCES = \
+       tmpl-error.c \
+       tmpl-error.h \
+       tmpl-expr.c \
+       tmpl-expr.h \
+       tmpl-expr-node.c \
+       tmpl-expr-node.h \
+       tmpl-lexer.c \
+       tmpl-lexer.h \
+       tmpl-node.c \
+       tmpl-node.h \
+       tmpl-parser.c \
+       tmpl-parser.h \
+       tmpl-scope.c \
+       tmpl-scope.h \
+       tmpl-template.c \
+       tmpl-template.h \
+       tmpl-template-locator.c \
+       tmpl-template-locator.h \
+       tmpl-text-node.c \
+       tmpl-text-node.h \
+       tmpl-token.c \
+       tmpl-token.h \
+       tmpl-token-input-stream.c \
+       tmpl-token-input-stream.h \
+       tmpl.h
+
+libtemplate_glib_la_LIBADD = $(TMPL_LIBS)
+libtemplate_glib_la_CFLAGS = $(TMPL_CFLAGS)
+libtemplate_glib_la_CPPFLAGS = -DTMPL_COMPILATION
+
+
+noinst_PROGRAMS = tmpl
+
+tmpl_SOURCES = tmpl.c
+tmpl_LDADD = libtemplate-glib.la
+tmpl_CFLAGS = $(TMPL_CFLAGS)
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/tmpl/configure.ac.tmpl b/contrib/tmpl/configure.ac.tmpl
new file mode 100644
index 0000000..40db4c9
--- /dev/null
+++ b/contrib/tmpl/configure.ac.tmpl
@@ -0,0 +1,203 @@
+AC_PREREQ([{{autoconf_required_version}}])
+
+dnl ***********************************************************************
+dnl Define Versioning Information
+dnl ***********************************************************************
+m4_define([major_version],[{{version_major}}])
+m4_define([minor_version],[{{version_minor}}])
+m4_define([micro_version],[{{version_micro}}])
+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 Export version information
+dnl ***********************************************************************
+MAJOR_VERSION=major_version
+MINOR_VERSION=minor_version
+MICRO_VERSION=micro_version
+AC_SUBST(MAJOR_VERSION)
+AC_SUBST(MINOR_VERSION)
+AC_SUBST(MICRO_VERSION)
+
+
+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 enable_cplusplus}}
+AC_PROG_CXX
+{{endif}}
+AC_PROG_INSTALL
+AC_PATH_PROG([GLIB_GENMARSHAL], [glib-genmarshal])
+AC_PATH_PROG([GLIB_MKENUMS], [glib-mkenums])
+AC_PATH_PROG([GLIB_COMPILE_RESOURCES], [glib-compile-resources])
+GLIB_GSETTINGS
+PKG_PROG_PKG_CONFIG([0.22])
+{{if enable_gobject_introspection}}
+GOBJECT_INTROSPECTION_CHECK([1.42.0])
+{{endif}}
+{{if enable_vala}}
+AM_PROG_VALAC
+VAPIGEN_CHECK
+{{endif}}
+{{if enable_appstream}}
+APPSTREAM_XML
+{{endif}}
+
+
+{{if enable_c11}}
+dnl ***********************************************************************
+dnl Ensure C11 is Supported
+dnl ***********************************************************************
+AX_CHECK_COMPILE_FLAG([-std=gnu11],
+                      [CFLAGS="$CFLAGS -std=gnu11"],
+                      [AC_MSG_ERROR([C compiler cannot compile GNU C11 code])])
+
+
+{{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 ***********************************************************************
+m4_define([glib_required_version],[{{glib_required_version}}])
+
+PKG_CHECK_MODULES({{name|strup|delimit("- ",'_')}}, [
+       gobject-2.0 >= glib_required_version
+       gio-2.0 >= glib_required_version
+])
+
+
+dnl ***********************************************************************
+dnl Initialize Libtool
+dnl ***********************************************************************
+LT_PREREQ([2.2])
+LT_INIT
+
+
+{{if enable_compiler_flags}}
+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
+
+
+{{endif}}
+{{if enable_gtk_doc}}
+dnl ***********************************************************************
+dnl Support for gtk-doc Documentation Engine
+dnl ***********************************************************************
+GTK_DOC_CHECK([1.11],[--flavour no-tmpl])
+AM_CONDITIONAL(ENABLE_GTK_DOC, test "x$enable_gtk_doc" = "xyes")
+AC_ARG_ENABLE(doc-cross-references,
+              AS_HELP_STRING([--disable-doc-cross-references],
+                             [cross reference symbols from other libraries @<:@default=yes@:>@]),
+              enable_doc_cross_references=$enableval,
+              enable_doc_cross_references=yes)
+AM_CONDITIONAL(ENABLE_DOC_CROSS_REFERENCES, test x$enable_doc_cross_references != xno)
+
+
+{{endif}}
+dnl ***********************************************************************
+dnl Process .in Files
+dnl ***********************************************************************
+AC_CONFIG_FILES([
+       Makefile
+])
+AC_OUTPUT
+
+
+echo ""
+echo " ${PACKAGE} - ${VERSION}"
+echo ""
diff --git a/contrib/tmpl/tmpl-error.c b/contrib/tmpl/tmpl-error.c
new file mode 100644
index 0000000..3306183
--- /dev/null
+++ b/contrib/tmpl/tmpl-error.c
@@ -0,0 +1,25 @@
+/* tmpl-error.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 "tmpl-error.h"
+
+GQuark
+tmpl_error_quark (void)
+{
+  return g_quark_from_static_string ("tmpl-error");
+}
diff --git a/contrib/tmpl/tmpl-error.h b/contrib/tmpl/tmpl-error.h
new file mode 100644
index 0000000..fd03d8f
--- /dev/null
+++ b/contrib/tmpl/tmpl-error.h
@@ -0,0 +1,39 @@
+/* tmpl-error.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 TMPL_ERROR_H
+#define TMPL_ERROR_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define TMPL_ERROR (tmpl_error_quark())
+
+typedef enum
+{
+  TMPL_ERROR_INVALID_STATE = 1,
+  TMPL_ERROR_TEMPLATE_NOT_FOUND,
+  TMPL_ERROR_CIRCULAR_INCLUDE,
+} TmplError;
+
+GQuark tmpl_error_quark (void);
+
+G_END_DECLS
+
+#endif /* TMPL_ERROR_H */
diff --git a/contrib/tmpl/tmpl-expr-node.c b/contrib/tmpl/tmpl-expr-node.c
new file mode 100644
index 0000000..04777a3
--- /dev/null
+++ b/contrib/tmpl/tmpl-expr-node.c
@@ -0,0 +1,84 @@
+/* tmpl-expr-node.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 "tmpl-expr-node.h"
+
+struct _TmplExprNode
+{
+  TmplNode  parent_instance;
+  TmplExpr *expr;
+};
+
+G_DEFINE_TYPE (TmplExprNode, tmpl_expr_node, TMPL_TYPE_NODE)
+
+static void
+tmpl_expr_node_finalize (GObject *object)
+{
+  TmplExprNode *self = (TmplExprNode *)object;
+
+  g_clear_pointer (&self->expr, tmpl_expr_free);
+
+  G_OBJECT_CLASS (tmpl_expr_node_parent_class)->finalize (object);
+}
+
+static void
+tmpl_expr_node_class_init (TmplExprNodeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = tmpl_expr_node_finalize;
+}
+
+static void
+tmpl_expr_node_init (TmplExprNode *self)
+{
+}
+
+/**
+ * tmpl_expr_node_new:
+ * @expr: (transfer full): The expression
+ *
+ * Creates a new node, stealing the reference to @expr.
+ *
+ * Returns: (transfer full): A #TmplExprNode
+ */
+TmplNode *
+tmpl_expr_node_new (TmplExpr *expr)
+{
+  TmplExprNode *self;
+
+  self = g_object_new (TMPL_TYPE_EXPR_NODE, NULL);
+  self->expr = expr;
+
+  return TMPL_NODE (self);
+}
+
+/**
+ * tmpl_expr_node_get_expr:
+ *
+ * Gets the expression.
+ *
+ * Returns: (transfer none): A #TmplExpr.
+ */
+TmplExpr *
+tmpl_expr_node_get_expr (TmplExprNode *self)
+{
+  g_return_val_if_fail (TMPL_IS_EXPR_NODE (self), NULL);
+
+  return self->expr;
+}
diff --git a/contrib/tmpl/tmpl-expr-node.h b/contrib/tmpl/tmpl-expr-node.h
new file mode 100644
index 0000000..4ba2910
--- /dev/null
+++ b/contrib/tmpl/tmpl-expr-node.h
@@ -0,0 +1,36 @@
+/* tmpl-expr-node.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 TMPL_EXPR_NODE_H
+#define TMPL_EXPR_NODE_H
+
+#include "tmpl-expr.h"
+#include "tmpl-node.h"
+
+G_BEGIN_DECLS
+
+#define TMPL_TYPE_EXPR_NODE (tmpl_expr_node_get_type())
+
+G_DECLARE_FINAL_TYPE (TmplExprNode, tmpl_expr_node, TMPL, EXPR_NODE, TmplNode)
+
+TmplNode *tmpl_expr_node_new      (TmplExpr     *expr);
+TmplExpr *tmpl_expr_node_get_expr (TmplExprNode *self);
+
+G_END_DECLS
+
+#endif /* TMPL_EXPR_NODE_H */
diff --git a/contrib/tmpl/tmpl-expr.c b/contrib/tmpl/tmpl-expr.c
new file mode 100644
index 0000000..5472c45
--- /dev/null
+++ b/contrib/tmpl/tmpl-expr.c
@@ -0,0 +1,71 @@
+/* tmpl-expr.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 "tmpl-expr.h"
+
+struct _TmplExpr
+{
+  void *dum;
+};
+
+TmplExpr *
+tmpl_expr_from_string (const gchar  *expr,
+                       GError      **error)
+{
+  TmplExpr *self;
+
+  self = g_slice_new0 (TmplExpr);
+
+  /* TODO: finish up EBNF for GJS/Python like dot expressions */
+  /* TODO: simple function calls for processing based on G-I */
+
+  return self;
+}
+
+void
+tmpl_expr_free (TmplExpr *self)
+{
+  if (self != NULL)
+    {
+      g_slice_free (TmplExpr, self);
+    }
+}
+
+gboolean
+tmpl_expr_eval (TmplExpr   *self,
+                TmplScope  *scope,
+                GValue     *return_value,
+                GError    **error)
+{
+  TmplScope *local = NULL;
+
+  g_return_val_if_fail (self != NULL, FALSE);
+  g_return_val_if_fail (return_value != NULL, FALSE);
+
+  if (scope == NULL)
+    scope = local = tmpl_scope_new ();
+
+  /* XXX: temp */
+  g_value_init (return_value, G_TYPE_BOOLEAN);
+  g_value_set_boolean (return_value, TRUE);
+
+  if (local != NULL)
+    tmpl_scope_free (scope);
+
+  return TRUE;
+}
diff --git a/contrib/tmpl/tmpl-expr.h b/contrib/tmpl/tmpl-expr.h
new file mode 100644
index 0000000..eb4b3d9
--- /dev/null
+++ b/contrib/tmpl/tmpl-expr.h
@@ -0,0 +1,40 @@
+/* tmpl-expr.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 TMPL_EXPR_H
+#define TMPL_EXPR_H
+
+#include <glib-object.h>
+
+#include "tmpl-scope.h"
+
+G_BEGIN_DECLS
+
+typedef struct _TmplExpr TmplExpr;
+
+TmplExpr *tmpl_expr_from_string (const gchar  *str,
+                                 GError      **error);
+gboolean  tmpl_expr_eval        (TmplExpr     *self,
+                                 TmplScope    *scope,
+                                 GValue       *return_value,
+                                 GError      **error);
+void      tmpl_expr_free        (TmplExpr     *self);
+
+G_END_DECLS
+
+#endif /* TMPL_EXPR_H */
diff --git a/contrib/tmpl/tmpl-lexer.c b/contrib/tmpl/tmpl-lexer.c
new file mode 100644
index 0000000..eb55522
--- /dev/null
+++ b/contrib/tmpl/tmpl-lexer.c
@@ -0,0 +1,165 @@
+/* tmpl-lexer.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 "tmpl-error.h"
+#include "tmpl-lexer.h"
+#include "tmpl-template-locator.h"
+#include "tmpl-token-input-stream.h"
+
+struct _TmplLexer
+{
+  GQueue               *stream_stack;
+  TmplTemplateLocator  *locator;
+  GHashTable           *circular;
+};
+
+G_DEFINE_POINTER_TYPE (TmplLexer, tmpl_lexer)
+
+TmplLexer *
+tmpl_lexer_new (GInputStream        *stream,
+                TmplTemplateLocator *locator)
+{
+  TmplLexer *self;
+
+  g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL);
+  g_return_val_if_fail (!locator || TMPL_IS_TEMPLATE_LOCATOR (locator), NULL);
+
+  self = g_slice_new0 (TmplLexer);
+  self->stream_stack = g_queue_new ();
+  self->locator = locator ? g_object_ref (locator) : tmpl_template_locator_new ();
+  self->circular = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  g_queue_push_head (self->stream_stack, tmpl_token_input_stream_new (stream));
+
+  return self;
+}
+
+void
+tmpl_lexer_free (TmplLexer *self)
+{
+  if (self != NULL)
+    {
+      const GList *iter;
+
+      for (iter = self->stream_stack->head; iter != NULL; iter = iter->next)
+        {
+          TmplTokenInputStream *stream = iter->data;
+
+          g_object_unref (stream);
+        }
+
+      g_clear_pointer (&self->circular, g_hash_table_unref);
+      g_clear_pointer (&self->stream_stack, g_queue_free);
+      g_clear_object (&self->locator);
+      g_slice_free (TmplLexer, self);
+    }
+}
+
+/**
+ * tmpl_lexer_next:
+ * @self: A #TmplLexer.
+ * @token: (out) (transfer full): A location for a #TmplToken.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @error: A location for a #GError or %NULL.
+ *
+ * Reads the next token.
+ *
+ * It is possible for %FALSE to be returned and @error left unset.
+ * This happens at the end of the stream.
+ *
+ * Returns: %TRUE if @token was set, otherwise %FALSE.
+ */
+gboolean
+tmpl_lexer_next (TmplLexer     *self,
+                 TmplToken    **token,
+                 GCancellable  *cancellable,
+                 GError       **error)
+{
+  TmplTokenInputStream *stream;
+  GError *local_error = NULL;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (self != NULL, FALSE);
+  g_return_val_if_fail (token != NULL, FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  while ((stream = g_queue_peek_head (self->stream_stack)))
+    {
+      if (!(*token = tmpl_token_input_stream_read_token (stream, cancellable, &local_error)))
+        {
+          /*
+           * If we finished this stream, try to move to the next one.
+           */
+          if (local_error == NULL)
+            {
+              g_queue_pop_head (self->stream_stack);
+              g_object_unref (stream);
+              continue;
+            }
+
+          goto finish;
+        }
+
+      /*
+       * If the current token is an include token, we need to resolve the
+       * include path and read tokens from it.
+       */
+      if (tmpl_token_type (*token) == TMPL_TOKEN_INCLUDE)
+        {
+          const gchar *path = tmpl_token_include_get_path (*token);
+          GInputStream *input;
+
+          if (g_hash_table_contains (self->circular, path))
+            {
+              local_error = g_error_new (TMPL_ERROR,
+                                         TMPL_ERROR_CIRCULAR_INCLUDE,
+                                         "A circular include was detected: \"%s\"",
+                                         path);
+              g_clear_pointer (token, tmpl_token_free);
+              goto finish;
+            }
+
+          if (!(input = tmpl_template_locator_locate (self->locator, path, &local_error)))
+            {
+              g_clear_pointer (token, tmpl_token_free);
+              goto finish;
+            }
+
+          g_hash_table_insert (self->circular, g_strdup (path), NULL);
+
+          stream = tmpl_token_input_stream_new (input);
+          g_queue_push_head (self->stream_stack, stream);
+
+          g_clear_pointer (token, tmpl_token_free);
+          g_object_unref (input);
+
+          continue;
+        }
+
+      ret = TRUE;
+      break;
+    }
+
+finish:
+  if ((ret == FALSE) && (local_error != NULL))
+    g_propagate_error (error, local_error);
+
+  g_assert (ret == FALSE || *token != NULL);
+
+  return ret;
+}
diff --git a/contrib/tmpl/tmpl-lexer.h b/contrib/tmpl/tmpl-lexer.h
new file mode 100644
index 0000000..f8ab51b
--- /dev/null
+++ b/contrib/tmpl/tmpl-lexer.h
@@ -0,0 +1,47 @@
+/* tmpl-lexer.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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/>.
+ */
+
+#if !defined (TMPL_INSIDE) && !defined (TMPL_COMPILATION)
+# error "Only <tmpl.h> can be included directly."
+#endif
+
+#ifndef TMPL_LEXER_H
+#define TMPL_LEXER_H
+
+#include <gio/gio.h>
+
+#include "tmpl-token.h"
+#include "tmpl-template-locator.h"
+
+G_BEGIN_DECLS
+
+typedef struct _TmplLexer TmplLexer;
+
+GType      tmpl_lexer_get_type (void);
+TmplLexer *tmpl_lexer_new      (GInputStream         *stream,
+                                TmplTemplateLocator  *locator);
+void       tmpl_lexer_free     (TmplLexer            *self);
+gboolean   tmpl_lexer_next     (TmplLexer            *self,
+                                TmplToken           **token,
+                                GCancellable         *cancellable,
+                                GError              **error);
+
+
+G_END_DECLS
+
+#endif /* TMPL_LEXER_H */
diff --git a/contrib/tmpl/tmpl-node.c b/contrib/tmpl/tmpl-node.c
new file mode 100644
index 0000000..ec13f96
--- /dev/null
+++ b/contrib/tmpl/tmpl-node.c
@@ -0,0 +1,166 @@
+/* tmpl-node.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 "tmpl-expr-node.h"
+#include "tmpl-node.h"
+#include "tmpl-parser.h"
+#include "tmpl-text-node.h"
+
+typedef struct
+{
+  GPtrArray *children;
+} TmplNodePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (TmplNode, tmpl_node, G_TYPE_OBJECT)
+
+static gboolean
+tmpl_node_real_accept (TmplNode      *self,
+                       TmplLexer     *lexer,
+                       GCancellable  *cancellable,
+                       GError       **error)
+{
+  TmplNodePrivate *priv = tmpl_node_get_instance_private (self);
+  TmplToken *token = NULL;
+  TmplNode *child;
+
+  g_assert (TMPL_IS_NODE (self));
+  g_assert (lexer != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  while (TRUE)
+    {
+      if (!tmpl_lexer_next (lexer, &token, cancellable, error))
+        return FALSE;
+
+      if (priv->children == NULL)
+        priv->children = g_ptr_array_new_with_free_func (g_object_unref);
+
+      switch (tmpl_token_type (token))
+        {
+        case TMPL_TOKEN_TEXT:
+          child = tmpl_text_node_new (g_strdup (tmpl_token_get_text (token)));
+          g_ptr_array_add (priv->children, child);
+          break;
+
+        case TMPL_TOKEN_EXPRESSION:
+          {
+            TmplExpr *expr;
+            const gchar *exprstr;
+
+            exprstr = tmpl_token_get_text (token);
+
+            if (!(expr = tmpl_expr_from_string (exprstr, error)))
+              return FALSE;
+
+            child = tmpl_expr_node_new (expr);
+            g_ptr_array_add (priv->children, child);
+          }
+          break;
+
+        case TMPL_TOKEN_INCLUDE:
+          g_assert_not_reached ();
+          break;
+
+        default:
+          g_warning ("Unknown token type 0x%02x", tmpl_token_type (token));
+          break;
+        }
+
+      tmpl_token_free (token);
+    }
+
+  return TRUE;
+}
+
+static void
+tmpl_node_real_visit_children (TmplNode        *self,
+                               TmplNodeVisitor  visitor,
+                               gpointer         user_data)
+{
+  TmplNodePrivate *priv = tmpl_node_get_instance_private (self);
+  gint i;
+
+  g_assert (TMPL_IS_NODE (self));
+  g_assert (visitor != NULL);
+
+  if (priv->children != NULL)
+    {
+      for (i = 0; i < priv->children->len; i++)
+        {
+          TmplNode *child = g_ptr_array_index (priv->children, i);
+
+          visitor (child, user_data);
+        }
+    }
+}
+
+static void
+tmpl_node_finalize (GObject *object)
+{
+  TmplNode *self = (TmplNode *)object;
+  TmplNodePrivate *priv = tmpl_node_get_instance_private (self);
+
+  g_clear_pointer (&priv->children, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (tmpl_node_parent_class)->finalize (object);
+}
+
+static void
+tmpl_node_class_init (TmplNodeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = tmpl_node_finalize;
+
+  klass->accept = tmpl_node_real_accept;
+  klass->visit_children = tmpl_node_real_visit_children;
+}
+
+static void
+tmpl_node_init (TmplNode *self)
+{
+}
+
+TmplNode *
+tmpl_node_new (void)
+{
+  return g_object_new (TMPL_TYPE_NODE, NULL);
+}
+
+gboolean
+tmpl_node_accept (TmplNode      *self,
+                  TmplLexer     *lexer,
+                  GCancellable  *cancellable,
+                  GError       **error)
+{
+  g_return_val_if_fail (TMPL_IS_NODE (self), FALSE);
+  g_return_val_if_fail (lexer != NULL, FALSE);
+
+  return TMPL_NODE_GET_CLASS (self)->accept (self, lexer, cancellable, error);
+}
+
+void
+tmpl_node_visit_children (TmplNode        *self,
+                          TmplNodeVisitor  visitor,
+                          gpointer         user_data)
+{
+  g_return_if_fail (TMPL_IS_NODE (self));
+  g_return_if_fail (visitor != NULL);
+
+  return TMPL_NODE_GET_CLASS (self)->visit_children (self, visitor, user_data);
+}
diff --git a/contrib/tmpl/tmpl-node.h b/contrib/tmpl/tmpl-node.h
new file mode 100644
index 0000000..a7660c7
--- /dev/null
+++ b/contrib/tmpl/tmpl-node.h
@@ -0,0 +1,63 @@
+/* tmpl-node.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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/>.
+ */
+
+#if !defined (TMPL_INSIDE) && !defined (TMPL_COMPILATION)
+# error "Only <tmpl.h> can be included directly."
+#endif
+
+#ifndef TMPL_NODE_H
+#define TMPL_NODE_H
+
+#include <gio/gio.h>
+
+#include "tmpl-lexer.h"
+
+G_BEGIN_DECLS
+
+#define TMPL_TYPE_NODE (tmpl_node_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (TmplNode, tmpl_node, TMPL, NODE, GObject)
+
+typedef void (*TmplNodeVisitor) (TmplNode *self,
+                                 gpointer  user_data);
+
+struct _TmplNodeClass
+{
+  GObjectClass parent_class;
+
+  gboolean (*accept)         (TmplNode        *self,
+                              TmplLexer       *lexer,
+                              GCancellable    *cancellable,
+                              GError         **error);
+  void     (*visit_children) (TmplNode        *self,
+                              TmplNodeVisitor  visitor,
+                              gpointer         user_data);
+};
+
+TmplNode *tmpl_node_new            (void);
+gboolean  tmpl_node_accept         (TmplNode         *self,
+                                    TmplLexer        *lexer,
+                                    GCancellable     *cancellable,
+                                    GError          **error);
+void      tmpl_node_visit_children (TmplNode         *self,
+                                    TmplNodeVisitor   visitor,
+                                    gpointer          user_data);
+
+G_END_DECLS
+
+#endif /* TMPL_NODE_H */
diff --git a/contrib/tmpl/tmpl-parser.c b/contrib/tmpl/tmpl-parser.c
new file mode 100644
index 0000000..4942907
--- /dev/null
+++ b/contrib/tmpl/tmpl-parser.c
@@ -0,0 +1,267 @@
+/* tmpl-parser.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 <glib/gi18n.h>
+
+#include "tmpl-error.h"
+#include "tmpl-lexer.h"
+#include "tmpl-node.h"
+#include "tmpl-parser.h"
+
+struct _TmplParser
+{
+  GObject               parent_instance;
+
+  TmplNode             *root;
+  GInputStream         *stream;
+  TmplTemplateLocator  *locator;
+
+  guint                 has_parsed : 1;
+};
+
+enum {
+  PROP_0,
+  PROP_LOCATOR,
+  PROP_STREAM,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE (TmplParser, tmpl_parser, G_TYPE_OBJECT)
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+tmpl_parser_set_stream (TmplParser   *self,
+                        GInputStream *stream)
+{
+  g_assert (TMPL_IS_PARSER (self));
+  g_assert (!stream || G_IS_INPUT_STREAM (stream));
+
+  if (stream == NULL)
+    {
+      g_warning ("TmplParser created without a stream!");
+      return;
+    }
+
+  g_set_object (&self->stream, stream);
+}
+
+static void
+tmpl_parser_finalize (GObject *object)
+{
+  TmplParser *self = (TmplParser *)object;
+
+  g_clear_object (&self->locator);
+  g_clear_object (&self->stream);
+  g_clear_object (&self->root);
+
+  G_OBJECT_CLASS (tmpl_parser_parent_class)->finalize (object);
+}
+
+static void
+tmpl_parser_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  TmplParser *self = TMPL_PARSER(object);
+
+  switch (prop_id)
+    {
+    case PROP_LOCATOR:
+      g_value_set_object (value, tmpl_parser_get_locator (self));
+      break;
+
+    case PROP_STREAM:
+      g_value_set_object (value, self->stream);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+tmpl_parser_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  TmplParser *self = TMPL_PARSER(object);
+
+  switch (prop_id)
+    {
+    case PROP_LOCATOR:
+      tmpl_parser_set_locator (self, g_value_get_object (value));
+      break;
+
+    case PROP_STREAM:
+      tmpl_parser_set_stream (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+tmpl_parser_class_init (TmplParserClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = tmpl_parser_finalize;
+  object_class->get_property = tmpl_parser_get_property;
+  object_class->set_property = tmpl_parser_set_property;
+
+  properties [PROP_LOCATOR] =
+    g_param_spec_object ("locator",
+                         "Locator",
+                         "The template locator for resolving includes",
+                         TMPL_TYPE_TEMPLATE_LOCATOR,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_STREAM] =
+    g_param_spec_object ("stream",
+                         "Stream",
+                         "The stream to parse",
+                         G_TYPE_INPUT_STREAM,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+tmpl_parser_init (TmplParser *self)
+{
+  self->root = tmpl_node_new ();
+}
+
+TmplParser *
+tmpl_parser_new (GInputStream *stream)
+{
+  g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL);
+
+  return g_object_new (TMPL_TYPE_PARSER,
+                       "stream", stream,
+                       NULL);
+}
+
+/**
+ * tmpl_parser_get_root:
+ * @self: A #TmplNode.
+ *
+ * Gets the root node for the parser.
+ *
+ * See tmpl_parser_visit_children() to apply a visitor to all nodes created
+ * by the parser.
+ *
+ * Returns: (transfer none): An #TmplNode.
+ */
+TmplNode *
+tmpl_parser_get_root (TmplParser *self)
+{
+  g_return_val_if_fail (TMPL_IS_PARSER (self), NULL);
+
+  return self->root;
+}
+
+gboolean
+tmpl_parser_parse (TmplParser    *self,
+                   GCancellable  *cancellable,
+                   GError       **error)
+{
+  TmplLexer *lexer;
+  GError *local_error = NULL;
+
+  g_return_val_if_fail (TMPL_IS_PARSER (self), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  if (self->has_parsed)
+    {
+      g_set_error (error,
+                   TMPL_ERROR,
+                   TMPL_ERROR_INVALID_STATE,
+                   _("%s() may only be called once"),
+                   G_STRFUNC);
+      return FALSE;
+    }
+
+  self->has_parsed = TRUE;
+
+  if (self->stream == NULL)
+    {
+      g_set_error (error,
+                   TMPL_ERROR,
+                   TMPL_ERROR_INVALID_STATE,
+                   _("Parser does not contain an input stream"));
+      return FALSE;
+    }
+
+  lexer = tmpl_lexer_new (self->stream, self->locator);
+  tmpl_node_accept (self->root, lexer, cancellable, &local_error);
+  tmpl_lexer_free (lexer);
+
+  if (local_error != NULL)
+    {
+      g_propagate_error (error, local_error);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+/**
+ * tmpl_parser_get_locator:
+ * @self: an #TmplParser
+ *
+ * Gets the template loader used for resolving includes when parsing template
+ * files.
+ *
+ * Includes are performed using the {{include "path"}} token.  The locator can
+ * be used to restrict where the templates are located from. By default, the
+ * search path is empty, and includes cannot be performed.
+ *
+ * Returns: (transfer none): A #TmplTemplateLocator.
+ */
+TmplTemplateLocator *
+tmpl_parser_get_locator (TmplParser *self)
+{
+  g_return_val_if_fail (TMPL_IS_PARSER (self), NULL);
+
+  return self->locator;
+}
+
+/**
+ * tmpl_parser_set_locator:
+ * @self: A #TmplParser
+ * @locator: A #TmplTemplateLocator
+ *
+ * Sets the template locator used to resolve {{include "path"}} directives.
+ *
+ * See tmpl_parser_get_locator() for more information.
+ */
+void
+tmpl_parser_set_locator (TmplParser          *self,
+                         TmplTemplateLocator *locator)
+{
+  g_return_if_fail (TMPL_IS_PARSER (self));
+  g_return_if_fail (TMPL_IS_TEMPLATE_LOCATOR (locator));
+
+  if (g_set_object (&self->locator, locator))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LOCATOR]);
+}
diff --git a/contrib/tmpl/tmpl-parser.h b/contrib/tmpl/tmpl-parser.h
new file mode 100644
index 0000000..da0c1dc
--- /dev/null
+++ b/contrib/tmpl/tmpl-parser.h
@@ -0,0 +1,48 @@
+/* tmpl-parser.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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/>.
+ */
+
+#if !defined (TMPL_INSIDE) && !defined (TMPL_COMPILATION)
+# error "Only <tmpl.h> can be included directly."
+#endif
+
+#ifndef TMPL_PARSER_H
+#define TMPL_PARSER_H
+
+#include <gio/gio.h>
+
+#include "tmpl-node.h"
+#include "tmpl-template-locator.h"
+
+G_BEGIN_DECLS
+
+#define TMPL_TYPE_PARSER (tmpl_parser_get_type())
+
+G_DECLARE_FINAL_TYPE (TmplParser, tmpl_parser, TMPL, PARSER, GObject)
+
+TmplNode            *tmpl_parser_get_root    (TmplParser           *self);
+TmplParser          *tmpl_parser_new         (GInputStream         *stream);
+TmplTemplateLocator *tmpl_parser_get_locator (TmplParser           *self);
+void                 tmpl_parser_set_locator (TmplParser           *self,
+                                              TmplTemplateLocator  *locator);
+gboolean             tmpl_parser_parse       (TmplParser           *self,
+                                              GCancellable         *cancellable,
+                                              GError              **error);
+
+G_END_DECLS
+
+#endif /* TMPL_PARSER_H */
diff --git a/contrib/tmpl/tmpl-scope.c b/contrib/tmpl/tmpl-scope.c
new file mode 100644
index 0000000..d790b6b
--- /dev/null
+++ b/contrib/tmpl/tmpl-scope.c
@@ -0,0 +1,57 @@
+/* tmpl-scope.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 "tmpl-scope.h"
+
+struct _TmplScope
+{
+  GHashTable *ident_to_value;
+};
+
+TmplScope *
+tmpl_scope_new (void)
+{
+  TmplScope *self;
+
+  self = g_slice_new0 (TmplScope);
+
+  return self;
+}
+
+void
+tmpl_scope_free (TmplScope *self)
+{
+  if (self != NULL)
+    {
+      g_clear_pointer (&self->ident_to_value, g_hash_table_unref);
+      g_slice_free (TmplScope, self);
+    }
+}
+
+const GValue *
+tmpl_scope_lookup (TmplScope   *self,
+                   const gchar *identifier)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+  g_return_val_if_fail (identifier != NULL, NULL);
+
+  if (self->ident_to_value != NULL)
+    return g_hash_table_lookup (self->ident_to_value, identifier);
+
+  return NULL;
+}
diff --git a/contrib/tmpl/tmpl-scope.h b/contrib/tmpl/tmpl-scope.h
new file mode 100644
index 0000000..e13f8dd
--- /dev/null
+++ b/contrib/tmpl/tmpl-scope.h
@@ -0,0 +1,35 @@
+/* tmpl-scope.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 TMPL_SCOPE_H
+#define TMPL_SCOPE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TmplScope TmplScope;
+
+TmplScope    *tmpl_scope_new    (void);
+void          tmpl_scope_free   (TmplScope   *self);
+const GValue *tmpl_scope_lookup (TmplScope   *self,
+                                 const gchar *identifier);
+
+G_END_DECLS
+
+#endif /* TMPL_SCOPE_H */
diff --git a/contrib/tmpl/tmpl-template-locator.c b/contrib/tmpl/tmpl-template-locator.c
new file mode 100644
index 0000000..f31b8f5
--- /dev/null
+++ b/contrib/tmpl/tmpl-template-locator.c
@@ -0,0 +1,197 @@
+/* tmpl-template-locator.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 <glib/gi18n.h>
+
+#include "tmpl-error.h"
+#include "tmpl-template-locator.h"
+
+typedef struct
+{
+  GQueue  *search_path;
+} TmplTemplateLocatorPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (TmplTemplateLocator, tmpl_template_locator, G_TYPE_OBJECT)
+
+static GInputStream *
+tmpl_template_locator_locate_in_path (TmplTemplateLocator *self,
+                                      const gchar         *path_base,
+                                      const gchar         *path)
+{
+  GInputStream *ret = NULL;
+  gchar *full_path;
+
+  g_assert (TMPL_IS_TEMPLATE_LOCATOR (self));
+  g_assert (path_base != NULL);
+  g_assert (path != NULL);
+
+  full_path = g_build_path (path_base, path, NULL);
+
+  if (g_str_has_prefix (full_path, "resource://"))
+    {
+      /*
+       * A mediocre attempt to prevent escapes using ../
+       */
+      if (strstr (full_path, "..") == NULL)
+        ret = g_resources_open_stream (full_path + strlen ("resource://"), 0, NULL);
+    }
+  else
+    {
+      GFile *parent = g_file_new_for_path (path_base);
+      GFile *file = g_file_new_for_path (full_path);
+      gchar *relative;
+
+      /*
+       * If the path tries to escape the search path, using ../../ or
+       * something clever, we will get an invalid path here.
+       */
+      if ((relative = g_file_get_relative_path (parent, file)))
+        {
+          g_free (relative);
+          ret = (GInputStream *)g_file_read (file, NULL, NULL);
+        }
+
+      g_object_unref (parent);
+      g_object_unref (file);
+    }
+
+  g_free (full_path);
+
+  return ret;
+}
+
+static GInputStream *
+tmpl_template_locator_real_locate (TmplTemplateLocator  *self,
+                                   const gchar          *path,
+                                   GError              **error)
+{
+  TmplTemplateLocatorPrivate *priv = tmpl_template_locator_get_instance_private (self);
+  GInputStream *ret = NULL;
+  const GList *iter;
+  const GList *search_path;
+
+  g_assert (TMPL_IS_TEMPLATE_LOCATOR (self));
+  g_assert (path != NULL);
+
+  search_path = priv->search_path->head;
+
+  for (iter = search_path; ret == NULL && iter != NULL; iter = iter->next)
+    {
+      const gchar *path_base = iter->data;
+
+      ret = tmpl_template_locator_locate_in_path (self, path_base, path);
+    }
+
+  if (ret == NULL)
+    {
+      g_set_error (error,
+                   TMPL_ERROR,
+                   TMPL_ERROR_TEMPLATE_NOT_FOUND,
+                   _("Failed to locate template \"%s\""),
+                   path);
+      return NULL;
+    }
+
+  return ret;
+}
+
+static void
+tmpl_template_locator_class_init (TmplTemplateLocatorClass *klass)
+{
+  klass->locate = tmpl_template_locator_real_locate;
+}
+
+static void
+tmpl_template_locator_init (TmplTemplateLocator *self)
+{
+  TmplTemplateLocatorPrivate *priv = tmpl_template_locator_get_instance_private (self);
+
+  priv->search_path = g_queue_new ();
+}
+
+void
+tmpl_template_locator_append_search_path (TmplTemplateLocator *self,
+                                          const gchar         *path)
+{
+  TmplTemplateLocatorPrivate *priv = tmpl_template_locator_get_instance_private (self);
+
+  g_return_if_fail (TMPL_IS_TEMPLATE_LOCATOR (self));
+  g_return_if_fail (path != NULL);
+
+  g_queue_push_tail (priv->search_path, g_strdup (path));
+}
+
+void
+tmpl_template_locator_prepend_search_path (TmplTemplateLocator *self,
+                                           const gchar         *path)
+{
+  TmplTemplateLocatorPrivate *priv = tmpl_template_locator_get_instance_private (self);
+
+  g_return_if_fail (TMPL_IS_TEMPLATE_LOCATOR (self));
+  g_return_if_fail (path != NULL);
+
+  g_queue_push_head (priv->search_path, g_strdup (path));
+}
+
+/**
+ * tmpl_template_locator_get_search_path:
+ * @self: A #TmplTemplateLocator
+ *
+ * Gets the current search path used by the template locator.
+ *
+ * Returns: (transfer full): A %NULL-terminated array of strings.
+ */
+gchar **
+tmpl_template_locator_get_search_path (TmplTemplateLocator *self)
+{
+  TmplTemplateLocatorPrivate *priv = tmpl_template_locator_get_instance_private (self);
+  GPtrArray *ar;
+  const GList *iter;
+
+  g_return_val_if_fail (TMPL_IS_TEMPLATE_LOCATOR (self), NULL);
+
+  ar = g_ptr_array_new ();
+
+  for (iter = priv->search_path->head; iter != NULL; iter = iter->next)
+    {
+      const gchar *path = iter->data;
+
+      g_ptr_array_add (ar, g_strdup (path));
+    }
+
+  g_ptr_array_add (ar, NULL);
+
+  return (gchar **)g_ptr_array_free (ar, FALSE);
+}
+
+TmplTemplateLocator *
+tmpl_template_locator_new (void)
+{
+  return g_object_new (TMPL_TYPE_TEMPLATE_LOCATOR, NULL);
+}
+
+GInputStream *
+tmpl_template_locator_locate (TmplTemplateLocator  *self,
+                              const gchar          *path,
+                              GError              **error)
+{
+  g_return_val_if_fail (TMPL_IS_TEMPLATE_LOCATOR (self), NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+
+  return TMPL_TEMPLATE_LOCATOR_GET_CLASS (self)->locate (self, path, error);
+}
diff --git a/contrib/tmpl/tmpl-template-locator.h b/contrib/tmpl/tmpl-template-locator.h
new file mode 100644
index 0000000..3db60f0
--- /dev/null
+++ b/contrib/tmpl/tmpl-template-locator.h
@@ -0,0 +1,57 @@
+/* tmpl-template-locator.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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/>.
+ */
+
+#if !defined (TMPL_INSIDE) && !defined (TMPL_COMPILATION)
+# error "Only <tmpl.h> can be included directly."
+#endif
+
+#ifndef TMPL_TEMPLATE_LOCATOR_H
+#define TMPL_TEMPLATE_LOCATOR_H
+
+#include <gio/gio.h>
+
+#include "tmpl-template-locator.h"
+
+G_BEGIN_DECLS
+
+#define TMPL_TYPE_TEMPLATE_LOCATOR (tmpl_template_locator_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (TmplTemplateLocator, tmpl_template_locator, TMPL, TEMPLATE_LOCATOR, GObject)
+
+struct _TmplTemplateLocatorClass
+{
+  GObjectClass parent_instance;
+
+  GInputStream *(*locate) (TmplTemplateLocator  *self,
+                           const gchar          *path,
+                           GError              **error);
+};
+
+TmplTemplateLocator  *tmpl_template_locator_new                 (void);
+void                  tmpl_template_locator_append_search_path  (TmplTemplateLocator  *self,
+                                                                 const gchar          *path);
+void                  tmpl_template_locator_prepend_search_path (TmplTemplateLocator  *self,
+                                                                 const gchar          *path);
+GInputStream         *tmpl_template_locator_locate              (TmplTemplateLocator  *self,
+                                                                 const gchar          *path,
+                                                                 GError              **error);
+gchar               **tmpl_template_locator_get_search_path     (TmplTemplateLocator  *self);
+
+G_END_DECLS
+
+#endif /* TMPL_TEMPLATE_LOCATOR_H */
diff --git a/contrib/tmpl/tmpl-template.c b/contrib/tmpl/tmpl-template.c
new file mode 100644
index 0000000..8db1c8e
--- /dev/null
+++ b/contrib/tmpl/tmpl-template.c
@@ -0,0 +1,359 @@
+/* tmpl-template.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 <glib/gi18n.h>
+
+#include "tmpl-error.h"
+#include "tmpl-expr-node.h"
+#include "tmpl-parser.h"
+#include "tmpl-scope.h"
+#include "tmpl-template.h"
+#include "tmpl-text-node.h"
+
+typedef struct
+{
+  TmplParser          *parser;
+  TmplTemplateLocator *locator;
+} TmplTemplatePrivate;
+
+typedef struct
+{
+  TmplTemplate   *self;
+  TmplNode       *root;
+  GString        *output;
+  TmplScope      *scope;
+  GError        **error;
+  gboolean        result;
+} TmplTemplateExpandState;
+
+G_DEFINE_TYPE_WITH_PRIVATE (TmplTemplate, tmpl_template, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_LOCATOR,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+tmpl_template_finalize (GObject *object)
+{
+  TmplTemplate *self = (TmplTemplate *)object;
+  TmplTemplatePrivate *priv = tmpl_template_get_instance_private (self);
+
+  g_clear_object (&priv->parser);
+
+  G_OBJECT_CLASS (tmpl_template_parent_class)->finalize (object);
+}
+
+static void
+tmpl_template_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  TmplTemplate *self = TMPL_TEMPLATE(object);
+
+  switch (prop_id)
+    {
+    case PROP_LOCATOR:
+      g_value_set_object (value, tmpl_template_get_locator (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+tmpl_template_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  TmplTemplate *self = TMPL_TEMPLATE(object);
+
+  switch (prop_id)
+    {
+    case PROP_LOCATOR:
+      tmpl_template_set_locator (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+tmpl_template_class_init (TmplTemplateClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = tmpl_template_finalize;
+  object_class->get_property = tmpl_template_get_property;
+  object_class->set_property = tmpl_template_set_property;
+
+  properties [PROP_LOCATOR] =
+    g_param_spec_object ("locator",
+                         "Locator",
+                         "The locator used for resolving includes",
+                         TMPL_TYPE_TEMPLATE_LOCATOR,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_EXPLICIT_NOTIFY |
+                          G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+tmpl_template_init (TmplTemplate *self)
+{
+}
+
+TmplTemplate *
+tmpl_template_new (TmplTemplateLocator *locator)
+{
+  return g_object_new (TMPL_TYPE_TEMPLATE,
+                       "locator", locator,
+                       NULL);
+}
+
+gboolean
+tmpl_template_parse_file (TmplTemplate  *self,
+                          GFile         *file,
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+  GInputStream *stream;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (TMPL_IS_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (G_IS_FILE (file), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  stream = (GInputStream *)g_file_read (file, cancellable, error);
+
+  if (stream != NULL)
+    {
+      ret = tmpl_template_parse (self, stream, cancellable, error);
+      g_object_unref (stream);
+    }
+
+  return ret;
+}
+
+gboolean
+tmpl_template_parse_path (TmplTemplate  *self,
+                          const gchar   *path,
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+  gboolean ret;
+  GFile *file;
+
+  g_return_val_if_fail (TMPL_IS_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (path != NULL, FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  file = g_file_new_for_path (path);
+  ret = tmpl_template_parse_file (self, file, cancellable, error);
+  g_object_unref (file);
+
+  return ret;
+}
+
+gboolean
+tmpl_template_parse_resource (TmplTemplate  *self,
+                              const gchar   *resource_path,
+                              GCancellable  *cancellable,
+                              GError       **error)
+{
+  gchar *copied = NULL;
+  gboolean ret;
+  GFile *file;
+
+  g_return_val_if_fail (TMPL_IS_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (resource_path != NULL, FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  if (!g_str_has_prefix (resource_path, "resource://"))
+    resource_path = copied = g_strdup_printf ("resource://%s", resource_path);
+
+  file = g_file_new_for_uri (resource_path);
+  ret = tmpl_template_parse_file (self, file, cancellable, error);
+
+  g_object_unref (file);
+  g_free (copied);
+
+  return ret;
+}
+
+gboolean
+tmpl_template_parse (TmplTemplate  *self,
+                     GInputStream  *stream,
+                     GCancellable  *cancellable,
+                     GError       **error)
+{
+  TmplTemplatePrivate *priv = tmpl_template_get_instance_private (self);
+  TmplParser *parser;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (TMPL_IS_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (G_IS_INPUT_STREAM (stream), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  parser = tmpl_parser_new (stream);
+
+  tmpl_parser_set_locator (parser, priv->locator);
+
+  if (tmpl_parser_parse (parser, cancellable, error))
+    {
+      g_set_object (&priv->parser, parser);
+      ret = TRUE;
+    }
+
+  g_object_unref (parser);
+
+  return ret;
+}
+
+static void
+value_into_string (const GValue *value,
+                   GString      *str)
+{
+  GValue transform = G_VALUE_INIT;
+
+  g_value_init (&transform, G_TYPE_STRING);
+
+  if (g_value_transform (value, &transform))
+    {
+      const gchar *tmp;
+
+      if (NULL != (tmp = g_value_get_string (&transform)))
+        g_string_append (str, tmp);
+    }
+
+  g_value_unset (&transform);
+}
+
+static void
+tmpl_template_expand_visitor (TmplNode *node,
+                              gpointer  user_data)
+{
+  TmplTemplateExpandState *state = user_data;
+
+  g_assert (TMPL_IS_NODE (node));
+  g_assert (state != NULL);
+
+  if (TMPL_IS_TEXT_NODE (node))
+    {
+      g_string_append (state->output, tmpl_text_node_get_text (TMPL_TEXT_NODE (node)));
+    }
+  else if (TMPL_IS_EXPR_NODE (node))
+    {
+      GValue return_value = { 0 };
+      TmplExpr *expr;
+
+      expr = tmpl_expr_node_get_expr (TMPL_EXPR_NODE (node));
+
+      /* propagate error? */
+      if (!tmpl_expr_eval (expr, state->scope, &return_value, NULL))
+        return;
+
+      value_into_string (&return_value, state->output);
+      g_value_unset (&return_value);
+    }
+}
+
+gboolean
+tmpl_template_expand (TmplTemplate  *self,
+                      GOutputStream *stream,
+                      TmplScope     *scope,
+                      GCancellable  *cancellable,
+                      GError       **error)
+{
+  TmplTemplatePrivate *priv = tmpl_template_get_instance_private (self);
+  TmplTemplateExpandState state = { 0 };
+
+  g_return_val_if_fail (TMPL_IS_TEMPLATE (self), FALSE);
+  g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  if (priv->parser == NULL)
+    {
+      g_set_error (error,
+                   TMPL_ERROR,
+                   TMPL_ERROR_INVALID_STATE,
+                   _("Must parse template before expanding"));
+      return FALSE;
+    }
+
+  state.root = tmpl_parser_get_root (priv->parser);
+  state.self = self;
+  state.output = g_string_new (NULL);
+  state.result = TRUE;
+  state.error = error;
+  state.scope = scope;
+
+  tmpl_node_visit_children (state.root, tmpl_template_expand_visitor, &state);
+
+  if (state.result != FALSE)
+    state.result = g_output_stream_write_all (stream,
+                                              state.output->str,
+                                              state.output->len,
+                                              NULL,
+                                              cancellable,
+                                              error);
+
+  g_string_free (state.output, TRUE);
+
+  return state.result;
+}
+
+/**
+ * tmpl_template_get_locator:
+ * @template: A #TmplTemplate
+ *
+ * Gets the template locator used when resolving template includes.
+ *
+ * Returns: (transfer none): a #TmplTemplateLocator or %NULL.
+ */
+TmplTemplateLocator *
+tmpl_template_get_locator (TmplTemplate *self)
+{
+  TmplTemplatePrivate *priv = tmpl_template_get_instance_private (self);
+
+  g_return_val_if_fail (TMPL_IS_TEMPLATE (self), NULL);
+
+  return priv->locator;
+}
+
+void
+tmpl_template_set_locator (TmplTemplate        *self,
+                           TmplTemplateLocator *locator)
+{
+  TmplTemplatePrivate *priv = tmpl_template_get_instance_private (self);
+
+  g_return_if_fail (TMPL_IS_TEMPLATE (self));
+  g_return_if_fail (!locator || TMPL_IS_TEMPLATE_LOCATOR (locator));
+
+  if (g_set_object (&priv->locator, locator))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LOCATOR]);
+}
diff --git a/contrib/tmpl/tmpl-template.h b/contrib/tmpl/tmpl-template.h
new file mode 100644
index 0000000..8942f4d
--- /dev/null
+++ b/contrib/tmpl/tmpl-template.h
@@ -0,0 +1,66 @@
+/* tmpl-template.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 TMPL_TEMPLATE_H
+#define TMPL_TEMPLATE_H
+
+#include <gio/gio.h>
+
+#include "tmpl-scope.h"
+#include "tmpl-template-locator.h"
+
+G_BEGIN_DECLS
+
+#define TMPL_TYPE_TEMPLATE (tmpl_template_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (TmplTemplate, tmpl_template, TMPL, TEMPLATE, GObject)
+
+struct _TmplTemplateClass
+{
+  GObjectClass parent_class;
+};
+
+TmplTemplate        *tmpl_template_new            (TmplTemplateLocator  *locator);
+TmplTemplateLocator *tmpl_template_get_locator    (TmplTemplate         *self);
+void                 tmpl_template_set_locator    (TmplTemplate         *self,
+                                                   TmplTemplateLocator  *locator);
+gboolean             tmpl_template_parse_file     (TmplTemplate         *self,
+                                                   GFile                *file,
+                                                   GCancellable         *cancellable,
+                                                   GError              **error);
+gboolean             tmpl_template_parse_resource (TmplTemplate         *self,
+                                                   const gchar          *path,
+                                                   GCancellable         *cancellable,
+                                                   GError              **error);
+gboolean             tmpl_template_parse_path     (TmplTemplate         *self,
+                                                   const gchar          *path,
+                                                   GCancellable         *cancellable,
+                                                   GError              **error);
+gboolean             tmpl_template_parse          (TmplTemplate         *self,
+                                                   GInputStream         *stream,
+                                                   GCancellable         *cancellable,
+                                                   GError              **error);
+gboolean             tmpl_template_expand         (TmplTemplate         *self,
+                                                   GOutputStream        *stream,
+                                                   TmplScope            *scope,
+                                                   GCancellable         *cancellable,
+                                                   GError              **error);
+
+G_END_DECLS
+
+#endif /* TMPL_TEMPLATE_H */
diff --git a/contrib/tmpl/tmpl-text-node.c b/contrib/tmpl/tmpl-text-node.c
new file mode 100644
index 0000000..e55d10c
--- /dev/null
+++ b/contrib/tmpl/tmpl-text-node.c
@@ -0,0 +1,77 @@
+/* tmpl-text-node.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 "tmpl-text-node.h"
+
+struct _TmplTextNode
+{
+  TmplNode  parent_instance;
+  gchar    *text;
+};
+
+G_DEFINE_TYPE (TmplTextNode, tmpl_text_node, TMPL_TYPE_NODE)
+
+static void
+tmpl_text_node_finalize (GObject *object)
+{
+  TmplTextNode *self = (TmplTextNode *)object;
+
+  g_clear_pointer (&self->text, g_free);
+
+  G_OBJECT_CLASS (tmpl_text_node_parent_class)->finalize (object);
+}
+
+static void
+tmpl_text_node_class_init (TmplTextNodeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = tmpl_text_node_finalize;
+}
+
+static void
+tmpl_text_node_init (TmplTextNode *node)
+{
+}
+
+/**
+ * tmpl_text_node_new:
+ * @text: (transfer full): the text for the node
+ *
+ * Creates a new text node.
+ *
+ * Returns: (transfer full): the new node.
+ */
+TmplNode *
+tmpl_text_node_new (gchar *text)
+{
+  TmplTextNode *self;
+
+  self = g_object_new (TMPL_TYPE_TEXT_NODE, NULL);
+  self->text = text;
+
+  return TMPL_NODE (self);
+}
+
+const gchar *
+tmpl_text_node_get_text (TmplTextNode *self)
+{
+  g_return_val_if_fail (TMPL_IS_TEXT_NODE (self), NULL);
+
+  return self->text;
+}
diff --git a/contrib/tmpl/tmpl-text-node.h b/contrib/tmpl/tmpl-text-node.h
new file mode 100644
index 0000000..a481c9e
--- /dev/null
+++ b/contrib/tmpl/tmpl-text-node.h
@@ -0,0 +1,37 @@
+/* tmpl-text-node.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 TMPL_TEXT_NODE_H
+#define TMPL_TEXT_NODE_H
+
+#include "tmpl-node.h"
+
+G_BEGIN_DECLS
+
+#define TMPL_TYPE_TEXT_NODE (tmpl_text_node_get_type())
+
+G_DECLARE_FINAL_TYPE (TmplTextNode, tmpl_text_node, TMPL, TEXT_NODE, TmplNode)
+
+TmplNode     *tmpl_text_node_new      (gchar        *text);
+const gchar  *tmpl_text_node_get_text (TmplTextNode *self);
+void          tmpl_text_node_set_text (TmplTextNode *self,
+                                       const gchar  *text);
+
+G_END_DECLS
+
+#endif /* TMPL_TEXT_NODE_H */
diff --git a/contrib/tmpl/tmpl-token-input-stream.c b/contrib/tmpl/tmpl-token-input-stream.c
new file mode 100644
index 0000000..94d50bc
--- /dev/null
+++ b/contrib/tmpl/tmpl-token-input-stream.c
@@ -0,0 +1,307 @@
+/* tmpl-token-input-stream.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 "tmpl-token-input-stream.h"
+
+struct _TmplTokenInputStream
+{
+  GDataInputStream parent_instance;
+};
+
+G_DEFINE_TYPE (TmplTokenInputStream, tmpl_token_input_stream, G_TYPE_DATA_INPUT_STREAM)
+
+static void
+tmpl_token_input_stream_class_init (TmplTokenInputStreamClass *klass)
+{
+}
+
+static void
+tmpl_token_input_stream_init (TmplTokenInputStream *self)
+{
+}
+
+static gboolean
+tmpl_token_input_stream_read_unichar (TmplTokenInputStream  *self,
+                                      gunichar              *unichar,
+                                      GCancellable          *cancellable,
+                                      GError               **error)
+{
+  GBufferedInputStream *stream = (GBufferedInputStream *)self;
+  gchar str[8] = { 0 };
+  gint c;
+  gint n;
+  gint i;
+
+  g_assert (TMPL_IS_TOKEN_INPUT_STREAM (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (-1 == (c = g_buffered_input_stream_read_byte (stream, cancellable, error)))
+    return FALSE;
+
+  if ((c & 0x80) == 0)
+    n = 1;
+  else if ((c & 0xE0) == 0xC0)
+    n = 2;
+  else if ((c & 0xF0) == 0xE0)
+    n = 3;
+  else if ((c & 0xF8) == 0xF0)
+    n = 4;
+  else if ((c & 0xFC) == 0xF8)
+    n = 5;
+  else if ((c & 0xFE) == 0xFC)
+    n = 6;
+  else
+    n = 0;
+
+  str [0] = c;
+
+  for (i = 1; i < n; i++)
+    {
+      if (-1 == (c = g_buffered_input_stream_read_byte (stream, cancellable, error)))
+        return FALSE;
+
+      str [i] = (gchar)c;
+    }
+
+  *unichar = g_utf8_get_char (str);
+
+  return TRUE;
+}
+
+static gchar *
+tmpl_token_input_stream_read_tag (TmplTokenInputStream  *self,
+                                  gsize                 *length,
+                                  GCancellable          *cancellable,
+                                  GError               **error)
+{
+  GBufferedInputStream *stream = (GBufferedInputStream *)self;
+  GByteArray *ar;
+  GError *local_error = NULL;
+  gboolean in_string = FALSE;
+  guchar byte;
+  gint c;
+
+  g_assert (TMPL_IS_TOKEN_INPUT_STREAM (self));
+  g_assert (length != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  ar = g_byte_array_new ();
+
+  while (TRUE)
+    {
+      if (-1 == (c = g_buffered_input_stream_read_byte (stream, cancellable, &local_error)))
+        goto failure;
+
+      switch (c)
+        {
+        case '\\':
+          if (in_string)
+            {
+              g_byte_array_append (ar, (const guchar *)"\\", 1);
+
+              if (-1 == (c = g_buffered_input_stream_read_byte (stream, cancellable, &local_error)))
+                goto failure;
+            }
+
+          break;
+
+        case '"':
+          in_string = !in_string;
+          break;
+
+        case '}':
+          if (!in_string)
+            {
+              if (-1 == (c = g_buffered_input_stream_read_byte (stream, cancellable, &local_error)))
+                goto failure;
+
+              /* Check if we got }} */
+              if (c == '}')
+                goto finish;
+
+              g_byte_array_append (ar, (const guchar *)"}", 1);
+            }
+
+          break;
+
+        default:
+          break;
+        }
+
+      byte = (guchar)c;
+      g_byte_array_append (ar, (const guchar *)&byte, 1);
+    }
+
+finish:
+  *length = ar->len;
+
+  byte = 0;
+  g_byte_array_append (ar, (const guchar *)&byte, 1);
+
+  return (gchar *)g_byte_array_free (ar, FALSE);
+
+failure:
+  *length = 0;
+
+  g_byte_array_free (ar, TRUE);
+
+  if (local_error)
+    g_propagate_error (error, local_error);
+
+  return FALSE;
+}
+
+/**
+ * tmpl_token_input_stream_read_token:
+ * @self: An #TmplTokenInputStream
+ * @error: (nullable): An optional location for a #GError
+ *
+ * Reads the next token from the underlying stream.
+ *
+ * If there was an error, %NULL is returned and @error is set.
+ *
+ * Returns: (transfer full): A #TmplToken or %NULL.
+ */
+TmplToken *
+tmpl_token_input_stream_read_token (TmplTokenInputStream  *self,
+                                    GCancellable          *cancellable,
+                                    GError               **error)
+{
+  GDataInputStream *stream = (GDataInputStream *)self;
+  GError *local_error = NULL;
+  gunichar ch;
+  gchar *text;
+  gsize len;
+
+  g_return_val_if_fail (TMPL_IS_TOKEN_INPUT_STREAM (self), NULL);
+
+  /*
+   * The syntax of the template language is very simple. All of our symbols
+   * start with {{ and end with }}. We use \ to escape, and you only ever
+   * need to escape the opening of {{ like \{{.
+   *
+   * We scan ahead until a { or \ and take appropriate action based upon
+   * peeking at the next char.
+   *
+   * Once we resolve that, we walk forward past the expression until }}.
+   * To walk past the expression, we need to know when we are in a
+   * string, since }} could theoretically be in there too.
+   */
+
+  text = g_data_input_stream_read_upto (stream, "\\{", -1, &len, cancellable, error);
+
+  /*
+   * Handle end of stream.
+   */
+  if (text == NULL)
+    return NULL;
+
+  /*
+   * Handle successful read up to \ or {.
+   */
+  if (*text != '\0')
+    return tmpl_token_new_text (text);
+
+  g_free (text);
+
+  /*
+   * Peek what type of delimiter we hit.
+   */
+  ch = g_data_input_stream_read_byte (stream, cancellable, &local_error);
+
+  if ((ch == 0) && (local_error != NULL))
+    {
+      g_propagate_error (error, local_error);
+      return NULL;
+    }
+
+  /*
+   * Handle possible escaped \{.
+   */
+  if (ch == '\\')
+    {
+      gchar str[8] = { 0 };
+
+      /*
+       * Get the next char after \.
+       */
+      if (!tmpl_token_input_stream_read_unichar (self, &ch, cancellable, error))
+        return tmpl_token_new_unichar ('\\');
+
+      /*
+       * Handle escaping {.
+       */
+      if (ch == '{')
+        return tmpl_token_new_unichar ('{');
+
+      /*
+       * Nothing escaped, return string as it was read.
+       */
+      g_unichar_to_utf8 (ch, str);
+
+      return tmpl_token_new_text (g_strdup_printf ("\\%s", str));
+    }
+
+  g_assert (ch == '{');
+
+  /*
+   * Look for { following {. If we reached the end of the stream, just
+   * return a token for the final {.
+   */
+  if (!tmpl_token_input_stream_read_unichar (self, &ch, cancellable, error))
+    return tmpl_token_new_unichar ('{');
+
+  /*
+   * If this is not a {{, then just return a string for the pair.
+   */
+  if (ch != '{')
+    {
+      gchar str[8] = { 0 };
+
+      g_unichar_to_utf8 (ch, str);
+
+      return tmpl_token_new_text (g_strdup_printf ("{%s", str));
+    }
+
+  /*
+   * Scan ahead until we find }}.
+   */
+  if (!(text = tmpl_token_input_stream_read_tag (self, &len, cancellable, error)))
+    return NULL;
+
+  return tmpl_token_new_generic (text);
+}
+
+/**
+ * tmpl_token_input_stream_new:
+ * @base_stream: the stream to read from
+ *
+ * Creates a #TmplTokenInputStream using @base_stream for the raw
+ * text stream.
+ *
+ * Returns: (transfer full): An #TmplTokenInputStream.
+ */
+TmplTokenInputStream *
+tmpl_token_input_stream_new (GInputStream *base_stream)
+{
+  g_return_val_if_fail (G_IS_INPUT_STREAM (base_stream), NULL);
+
+  return g_object_new (TMPL_TYPE_TOKEN_INPUT_STREAM,
+                       "base-stream", base_stream,
+                       NULL);
+}
diff --git a/contrib/tmpl/tmpl-token-input-stream.h b/contrib/tmpl/tmpl-token-input-stream.h
new file mode 100644
index 0000000..e951ba6
--- /dev/null
+++ b/contrib/tmpl/tmpl-token-input-stream.h
@@ -0,0 +1,43 @@
+/* tmpl-token-input-stream.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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/>.
+ */
+
+#if !defined (TMPL_INSIDE) && !defined (TMPL_COMPILATION)
+# error "Only <tmpl.h> can be included directly."
+#endif
+
+#ifndef TMPL_TOKEN_INPUT_STREAM_H
+#define TMPL_TOKEN_INPUT_STREAM_H
+
+#include <gio/gio.h>
+
+#include "tmpl-token.h"
+
+G_BEGIN_DECLS
+
+#define TMPL_TYPE_TOKEN_INPUT_STREAM (tmpl_token_input_stream_get_type())
+
+G_DECLARE_FINAL_TYPE (TmplTokenInputStream, tmpl_token_input_stream, TMPL, TOKEN_INPUT_STREAM, 
GDataInputStream)
+
+TmplTokenInputStream *tmpl_token_input_stream_new        (GInputStream          *base_stream);
+TmplToken            *tmpl_token_input_stream_read_token (TmplTokenInputStream  *self,
+                                                          GCancellable          *cancellable,
+                                                          GError               **error);
+
+G_END_DECLS
+
+#endif /* TMPL_TOKEN_INPUT_STREAM_H */
diff --git a/contrib/tmpl/tmpl-token.c b/contrib/tmpl/tmpl-token.c
new file mode 100644
index 0000000..fcaf6c7
--- /dev/null
+++ b/contrib/tmpl/tmpl-token.c
@@ -0,0 +1,147 @@
+/* tmpl-token.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tmpl-token.h"
+
+struct _TmplToken
+{
+  TmplTokenType type;
+  gchar *text;
+};
+
+static TmplToken *
+tmpl_token_new (void)
+{
+  TmplToken *self;
+
+  self = g_slice_new0 (TmplToken);
+
+  return self;
+}
+
+void
+tmpl_token_free (TmplToken *self)
+{
+  if (self != NULL)
+    {
+      g_free (self->text);
+      g_slice_free (TmplToken, self);
+    }
+}
+
+/**
+ * tmpl_token_new_text:
+ * @text: (transfer full): The text for the token.
+ *
+ * Creates a new #TmplToken containing @text.
+ * This is a text literal type.
+ *
+ * Returns: (transfer full): A newly allocated #TmplToken.
+ */
+TmplToken *
+tmpl_token_new_text (gchar *text)
+{
+  TmplToken *self;
+
+  self = tmpl_token_new ();
+  self->type = TMPL_TOKEN_TEXT;
+  self->text = text;
+
+  return self;
+}
+
+TmplToken *
+tmpl_token_new_unichar (gunichar ch)
+{
+  gchar utf8[8];
+  gint len;
+
+  len = g_unichar_to_utf8 (ch, utf8);
+  utf8 [len] = '\0';
+
+  return tmpl_token_new_text (g_strdup (utf8));
+}
+
+TmplTokenType
+tmpl_token_type (TmplToken *self)
+{
+  g_return_val_if_fail (self != NULL, 0);
+
+  return self->type;
+}
+
+gchar *
+tmpl_token_include_get_path (TmplToken *self)
+{
+  char *path = NULL;
+
+  g_return_val_if_fail (self != NULL, NULL);
+  g_return_val_if_fail (self->type == TMPL_TOKEN_INCLUDE, NULL);
+
+  if (1 == sscanf (self->text, "include \"%m[^\"]", &path))
+    {
+      gchar *tmp = g_strdup (path);
+      free (path);
+      return tmp;
+    }
+
+  return NULL;
+}
+
+TmplToken *
+tmpl_token_new_generic (gchar *text)
+{
+  TmplToken *self;
+
+  g_return_val_if_fail (text != NULL, NULL);
+
+  self = g_slice_new0 (TmplToken);
+  self->text = g_strstrip (text);
+
+  if (g_str_has_prefix (self->text, "if "))
+    self->type = TMPL_TOKEN_IF;
+  else if (g_str_has_prefix (self->text, "else if "))
+    self->type = TMPL_TOKEN_ELSE_IF;
+  else if (g_str_has_prefix (self->text, "else"))
+    self->type = TMPL_TOKEN_ELSE;
+  else if (g_str_has_prefix (self->text, "end"))
+    self->type = TMPL_TOKEN_END;
+  else if (g_str_has_prefix (self->text, "for "))
+    self->type = TMPL_TOKEN_FOR;
+  else if (g_str_has_prefix (self->text, "include "))
+    self->type = TMPL_TOKEN_INCLUDE;
+  else
+    self->type = TMPL_TOKEN_EXPRESSION;
+
+  return self;
+}
+
+const gchar *
+tmpl_token_get_text (TmplToken *self)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+
+  return self->text;
+}
diff --git a/contrib/tmpl/tmpl-token.h b/contrib/tmpl/tmpl-token.h
new file mode 100644
index 0000000..6bdbfd1
--- /dev/null
+++ b/contrib/tmpl/tmpl-token.h
@@ -0,0 +1,54 @@
+/* tmpl-token.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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/>.
+ */
+
+#if !defined (TMPL_INSIDE) && !defined (TMPL_COMPILATION)
+# error "Only <tmpl.h> can be included directly."
+#endif
+
+#ifndef TMPL_TOKEN_H
+#define TMPL_TOKEN_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TmplToken TmplToken;
+
+typedef enum
+{
+  TMPL_TOKEN_TEXT,
+  TMPL_TOKEN_IF,
+  TMPL_TOKEN_ELSE_IF,
+  TMPL_TOKEN_ELSE,
+  TMPL_TOKEN_END,
+  TMPL_TOKEN_FOR,
+  TMPL_TOKEN_EXPRESSION,
+  TMPL_TOKEN_INCLUDE,
+} TmplTokenType;
+
+TmplToken     *tmpl_token_new_generic      (gchar     *str);
+TmplToken     *tmpl_token_new_unichar      (gunichar   ch);
+TmplToken     *tmpl_token_new_text         (gchar     *text);
+const gchar   *tmpl_token_get_text         (TmplToken *self);
+TmplTokenType  tmpl_token_type             (TmplToken *self);
+void           tmpl_token_free             (TmplToken *self);
+gchar         *tmpl_token_include_get_path (TmplToken *self);
+
+G_END_DECLS
+
+#endif /* TMPL_TOKEN_H */
diff --git a/contrib/tmpl/tmpl.c b/contrib/tmpl/tmpl.c
new file mode 100644
index 0000000..2c1d688
--- /dev/null
+++ b/contrib/tmpl/tmpl.c
@@ -0,0 +1,74 @@
+/* tmpl.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 <stdlib.h>
+#include <stdio.h>
+#include <tmpl.h>
+
+#include <gio/gunixoutputstream.h>
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+  TmplTemplateLocator *locator = NULL;
+  GOutputStream *stream = NULL;
+  TmplTemplate *tmpl = NULL;
+  TmplScope *scope = NULL;
+  GFile *file = NULL;
+  GError *error = NULL;
+  gint ret = EXIT_FAILURE;
+
+  if (argc != 2)
+    {
+      g_printerr ("usage: %s TEMPLATE\n", argv [0]);
+      return EXIT_FAILURE;
+    }
+
+  locator = tmpl_template_locator_new ();
+  tmpl_template_locator_prepend_search_path (locator, ".");
+  tmpl = tmpl_template_new (locator);
+  file = g_file_new_for_commandline_arg (argv [1]);
+  scope = tmpl_scope_new ();
+
+  if (!tmpl_template_parse_file (tmpl, file, NULL, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      g_clear_error (&error);
+      goto cleanup;
+    }
+
+  stream = g_unix_output_stream_new (STDOUT_FILENO, FALSE);
+
+  if (!tmpl_template_expand (tmpl, stream, scope, NULL, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      g_clear_error (&error);
+      goto cleanup;
+    }
+
+  ret = EXIT_SUCCESS;
+
+cleanup:
+  g_clear_object (&stream);
+  g_clear_object (&locator);
+  g_clear_object (&tmpl);
+  g_clear_object (&file);
+
+  return ret;
+}
diff --git a/contrib/tmpl/tmpl.h b/contrib/tmpl/tmpl.h
new file mode 100644
index 0000000..411d7de
--- /dev/null
+++ b/contrib/tmpl/tmpl.h
@@ -0,0 +1,35 @@
+/* tmpl.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat 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 2.1 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 TMPL_H
+#define TMPL_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define TMPL_INSIDE
+# include "tmpl-error.h"
+# include "tmpl-scope.h"
+# include "tmpl-template.h"
+# include "tmpl-template-locator.h"
+#undef TMPL_INSIDE
+
+G_END_DECLS
+
+#endif /* TMPL_H */



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