[gnome-builder/wip/chergert/templates] wip on templating ideas
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/templates] wip on templating ideas
- Date: Wed, 6 Jan 2016 10:27:30 +0000 (UTC)
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]