[gnome-builder] libide-projects: revamp template creation
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] libide-projects: revamp template creation
- Date: Tue, 12 Jul 2022 06:39:10 +0000 (UTC)
commit 0695ff538dcc7fb24d94641f7cce7143dd48caf3
Author: Christian Hergert <chergert redhat com>
Date: Mon Jul 11 18:00:49 2022 -0700
libide-projects: revamp template creation
This moves a bunch of template creation machinery into the libide-projects
library so that it is not duplicated in multiple template plugins.
src/libide/projects/ide-project-template.c | 357 ++++++--
src/libide/projects/ide-project-template.h | 86 +-
src/libide/projects/ide-template-base.c | 10 +-
src/libide/projects/ide-template-base.h | 19 +-
src/libide/projects/ide-template-input.c | 1311 ++++++++++++++++++++++++++++
src/libide/projects/ide-template-input.h | 116 +++
src/libide/projects/ide-template-locator.c | 185 ++++
src/libide/projects/ide-template-locator.h | 51 ++
src/libide/projects/libide-projects.h | 2 +
src/libide/projects/meson.build | 13 +-
10 files changed, 2021 insertions(+), 129 deletions(-)
---
diff --git a/src/libide/projects/ide-project-template.c b/src/libide/projects/ide-project-template.c
index ac95b5e8c..3b94240b4 100644
--- a/src/libide/projects/ide-project-template.c
+++ b/src/libide/projects/ide-project-template.c
@@ -24,53 +24,258 @@
#include "ide-project-template.h"
-G_DEFINE_INTERFACE (IdeProjectTemplate, ide_project_template, G_TYPE_OBJECT)
+typedef struct
+{
+ char *id;
+ char *name;
+ char *description;
+ char **languages;
+ int priority;
+} IdeProjectTemplatePrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (IdeProjectTemplate, ide_project_template, IDE_TYPE_TEMPLATE_BASE)
+
+enum {
+ PROP_0,
+ PROP_DESCRIPTION,
+ PROP_ID,
+ PROP_NAME,
+ PROP_LANGUAGES,
+ PROP_PRIORITY,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static gboolean
+ide_project_template_real_validate_name (IdeProjectTemplate *self,
+ const char *name)
+{
+ g_assert (IDE_IS_PROJECT_TEMPLATE (self));
+
+ if (name == NULL)
+ return FALSE;
+
+ if (g_unichar_isdigit (g_utf8_get_char (name)))
+ return FALSE;
+
+ for (const char *c = name; *c; c = g_utf8_next_char (c))
+ {
+ gunichar ch = g_utf8_get_char (c);
+
+ if (g_unichar_isspace (ch))
+ return FALSE;
+
+ if (ch == '/')
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+ide_project_template_real_validate_app_id (IdeProjectTemplate *self,
+ const char *app_id)
+{
+ guint n_dots = 0;
+
+ g_assert (IDE_IS_PROJECT_TEMPLATE (self));
+
+ /* Rely on defaults if empty */
+ if (ide_str_empty0 (app_id))
+ return TRUE;
+
+ if (!g_application_id_is_valid (app_id))
+ return FALSE;
+
+ /* Flatpak's require at least 3 parts to be valid, which is more than
+ * what g_application_id_is_valid() will require. Additionally, you
+ * cannot have "-" in Flatpak app ids.
+ */
+ for (const char *c = app_id; *c; c = g_utf8_next_char (c))
+ {
+ switch (*c)
+ {
+ case '-':
+ return FALSE;
+
+ case '.':
+ n_dots++;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return n_dots >= 2;
+}
static void
-ide_project_template_default_init (IdeProjectTemplateInterface *iface)
+ide_project_template_dispose (GObject *object)
{
+ IdeProjectTemplate *self = (IdeProjectTemplate *)object;
+ IdeProjectTemplatePrivate *priv = ide_project_template_get_instance_private (self);
+
+ g_clear_pointer (&priv->id, g_free);
+ g_clear_pointer (&priv->name, g_free);
+ g_clear_pointer (&priv->description, g_free);
+ g_clear_pointer (&priv->languages, g_strfreev);
+
+ G_OBJECT_CLASS (ide_project_template_parent_class)->dispose (object);
}
-gchar *
-ide_project_template_get_id (IdeProjectTemplate *self)
+static void
+ide_project_template_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
{
- g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+ IdeProjectTemplate *self = IDE_PROJECT_TEMPLATE (object);
+
+ switch (prop_id)
+ {
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, ide_project_template_get_description (self));
+ break;
+
+ case PROP_ID:
+ g_value_set_string (value, ide_project_template_get_id (self));
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, ide_project_template_get_name (self));
+ break;
- return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_id (self);
+ case PROP_LANGUAGES:
+ g_value_set_boxed (value, ide_project_template_get_languages (self));
+ break;
+
+ case PROP_PRIORITY:
+ g_value_set_int (value, ide_project_template_get_priority (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
}
-gchar *
-ide_project_template_get_name (IdeProjectTemplate *self)
+static void
+ide_project_template_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeProjectTemplate *self = IDE_PROJECT_TEMPLATE (object);
+ IdeProjectTemplatePrivate *priv = ide_project_template_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_DESCRIPTION:
+ priv->description = g_value_dup_string (value);
+ break;
+
+ case PROP_ID:
+ priv->id = g_value_dup_string (value);
+ break;
+
+ case PROP_NAME:
+ priv->name = g_value_dup_string (value);
+ break;
+
+ case PROP_LANGUAGES:
+ priv->languages = g_value_dup_boxed (value);
+ break;
+
+ case PROP_PRIORITY:
+ priv->priority = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_project_template_class_init (IdeProjectTemplateClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = ide_project_template_dispose;
+ object_class->get_property = ide_project_template_get_property;
+ object_class->set_property = ide_project_template_set_property;
+
+ klass->validate_name = ide_project_template_real_validate_name;
+ klass->validate_app_id = ide_project_template_real_validate_app_id;
+
+ properties [PROP_ID] =
+ g_param_spec_string ("id", NULL, NULL, NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_NAME] =
+ g_param_spec_string ("name", NULL, NULL, NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DESCRIPTION] =
+ g_param_spec_string ("description", NULL, NULL, NULL,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LANGUAGES] =
+ g_param_spec_boxed ("languages", NULL, NULL,
+ G_TYPE_STRV,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PRIORITY] =
+ g_param_spec_int ("priority", NULL, NULL,
+ G_MININT, G_MAXINT, 0,
+ (G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_project_template_init (IdeProjectTemplate *self)
{
+}
+
+const char *
+ide_project_template_get_id (IdeProjectTemplate *self)
+{
+ IdeProjectTemplatePrivate *priv = ide_project_template_get_instance_private (self);
+
g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
- return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_name (self);
+ return priv->id;
}
-gchar *
-ide_project_template_get_description (IdeProjectTemplate *self)
+const char *
+ide_project_template_get_name (IdeProjectTemplate *self)
{
+ IdeProjectTemplatePrivate *priv = ide_project_template_get_instance_private (self);
+
g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
- return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_description (self);
+ return priv->name;
}
-/**
- * ide_project_template_get_widget:
- * @self: An #IdeProjectTemplate
- *
- * Get's the configuration widget for the template if there is one.
- *
- * Returns: (transfer none): a #GtkWidget.
- *
- * Since: 3.32
- */
-GtkWidget *
-ide_project_template_get_widget (IdeProjectTemplate *self)
+const char *
+ide_project_template_get_description (IdeProjectTemplate *self)
{
+ IdeProjectTemplatePrivate *priv = ide_project_template_get_instance_private (self);
+
g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
- return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_widget (self);
+ return priv->description;
}
/**
@@ -80,31 +285,33 @@ ide_project_template_get_widget (IdeProjectTemplate *self)
* Gets the list of languages that this template can support when generating
* the project.
*
- * Returns: (transfer full): A newly allocated, NULL terminated list of
- * supported languages.
- *
- * Since: 3.32
+ * Returns: (transfer none) (nullable): an array of language names
*/
-gchar **
+const char * const *
ide_project_template_get_languages (IdeProjectTemplate *self)
{
+ IdeProjectTemplatePrivate *priv = ide_project_template_get_instance_private (self);
+
g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
- return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_languages (self);
+ return (const char * const *)priv->languages;
}
-gchar *
-ide_project_template_get_icon_name (IdeProjectTemplate *self)
+int
+ide_project_template_get_priority (IdeProjectTemplate *self)
{
- g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+ IdeProjectTemplatePrivate *priv = ide_project_template_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), 0);
- return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_icon_name (self);
+ return priv->priority;
}
/**
* ide_project_template_expand_async:
* @self: an #IdeProjectTemplate
- * @params: (element-type utf8 GLib.Variant): A hashtable of template parameters.
+ * @input: the template input
+ * @scope: scope for the template
* @cancellable: (nullable): a #GCancellable or %NULL.
* @callback: the callback for the asynchronous operation.
* @user_data: user data for @callback.
@@ -115,22 +322,21 @@ ide_project_template_get_icon_name (IdeProjectTemplate *self)
* expanding files based on the contents of @params.
*
* It is expected that this method is only called once on an #IdeProjectTemplate.
- *
- * Since: 3.32
*/
void
ide_project_template_expand_async (IdeProjectTemplate *self,
- GHashTable *params,
+ IdeTemplateInput *input,
+ TmplScope *scope,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (IDE_IS_PROJECT_TEMPLATE (self));
- g_return_if_fail (params != NULL);
- g_return_if_fail (g_hash_table_contains (params, "name"));
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (input));
+ g_return_if_fail (scope != NULL);
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
- IDE_PROJECT_TEMPLATE_GET_IFACE (self)->expand_async (self, params, cancellable, callback, user_data);
+ IDE_PROJECT_TEMPLATE_GET_CLASS (self)->expand_async (self, input, scope, cancellable, callback, user_data);
}
gboolean
@@ -141,48 +347,49 @@ ide_project_template_expand_finish (IdeProjectTemplate *self,
g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), FALSE);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
- return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->expand_finish (self, result, error);
-}
-
-/**
- * ide_project_template_get_priority:
- * @self: a #IdeProjectTemplate
- *
- * Gets the priority of the template. This can be used to sort the templates
- * in the "new project" view.
- *
- * Returns: the priority of the template
- *
- * Since: 3.32
- */
-gint
-ide_project_template_get_priority (IdeProjectTemplate *self)
-{
- g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), 0);
-
- if (IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_priority)
- return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_priority (self);
-
- return 0;
+ return IDE_PROJECT_TEMPLATE_GET_CLASS (self)->expand_finish (self, result, error);
}
-gint
+int
ide_project_template_compare (IdeProjectTemplate *a,
IdeProjectTemplate *b)
{
- gint ret;
+ const char *a_name;
+ const char *b_name;
+ int prio_a;
+ int prio_b;
g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (a), 0);
g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (b), 0);
- ret = ide_project_template_get_priority (a) - ide_project_template_get_priority (b);
+ prio_a = ide_project_template_get_priority (a);
+ prio_b = ide_project_template_get_priority (b);
- if (ret == 0)
- {
- g_autofree gchar *a_name = ide_project_template_get_name (a);
- g_autofree gchar *b_name = ide_project_template_get_name (b);
- ret = g_utf8_collate (a_name, b_name);
- }
+ if (prio_a < prio_b)
+ return -1;
+ else if (prio_a > prio_b)
+ return 1;
+
+ a_name = ide_project_template_get_name (a);
+ b_name = ide_project_template_get_name (b);
+
+ return g_utf8_collate (a_name, b_name);
+}
+
+gboolean
+ide_project_template_validate_name (IdeProjectTemplate *self,
+ const char *name)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), FALSE);
+
+ return IDE_PROJECT_TEMPLATE_GET_CLASS (self)->validate_name (self, name);
+}
+
+gboolean
+ide_project_template_validate_app_id (IdeProjectTemplate *self,
+ const char *app_id)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), FALSE);
- return ret;
+ return IDE_PROJECT_TEMPLATE_GET_CLASS (self)->validate_app_id (self, app_id);
}
diff --git a/src/libide/projects/ide-project-template.h b/src/libide/projects/ide-project-template.h
index 23af6fcff..a1f8d596a 100644
--- a/src/libide/projects/ide-project-template.h
+++ b/src/libide/projects/ide-project-template.h
@@ -24,63 +24,69 @@
# error "Only <libide-projects.h> can be included directly."
#endif
-#include <libide-core.h>
#include <gtk/gtk.h>
+#include <tmpl-glib.h>
+
+#include <libide-core.h>
+
+#include "ide-template-base.h"
+#include "ide-template-input.h"
G_BEGIN_DECLS
#define IDE_TYPE_PROJECT_TEMPLATE (ide_project_template_get_type())
-IDE_AVAILABLE_IN_3_32
-G_DECLARE_INTERFACE (IdeProjectTemplate, ide_project_template, IDE, PROJECT_TEMPLATE, GObject)
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_DERIVABLE_TYPE (IdeProjectTemplate, ide_project_template, IDE, PROJECT_TEMPLATE, IdeTemplateBase)
-struct _IdeProjectTemplateInterface
+struct _IdeProjectTemplateClass
{
- GTypeInterface parent;
+ IdeTemplateBaseClass parent_instance;
- gchar *(*get_id) (IdeProjectTemplate *self);
- gchar *(*get_name) (IdeProjectTemplate *self);
- gchar *(*get_description) (IdeProjectTemplate *self);
- GtkWidget *(*get_widget) (IdeProjectTemplate *self);
- gchar **(*get_languages) (IdeProjectTemplate *self);
- gchar *(*get_icon_name) (IdeProjectTemplate *self);
+ gboolean (*validate_name) (IdeProjectTemplate *self,
+ const char *name);
+ gboolean (*validate_app_id) (IdeProjectTemplate *self,
+ const char *app_id);
void (*expand_async) (IdeProjectTemplate *self,
- GHashTable *params,
+ IdeTemplateInput *input,
+ TmplScope *scope,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean (*expand_finish) (IdeProjectTemplate *self,
GAsyncResult *result,
GError **error);
- gint (*get_priority) (IdeProjectTemplate *self);
};
-IDE_AVAILABLE_IN_3_32
-gchar *ide_project_template_get_id (IdeProjectTemplate *self);
-IDE_AVAILABLE_IN_3_32
-gint ide_project_template_get_priority (IdeProjectTemplate *self);
-IDE_AVAILABLE_IN_3_32
-gchar *ide_project_template_get_name (IdeProjectTemplate *self);
-IDE_AVAILABLE_IN_3_32
-gchar *ide_project_template_get_description (IdeProjectTemplate *self);
-IDE_AVAILABLE_IN_3_32
-GtkWidget *ide_project_template_get_widget (IdeProjectTemplate *self);
-IDE_AVAILABLE_IN_3_32
-gchar **ide_project_template_get_languages (IdeProjectTemplate *self);
-IDE_AVAILABLE_IN_3_32
-gchar *ide_project_template_get_icon_name (IdeProjectTemplate *self);
-IDE_AVAILABLE_IN_3_32
-void ide_project_template_expand_async (IdeProjectTemplate *self,
- GHashTable *params,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-IDE_AVAILABLE_IN_3_32
-gboolean ide_project_template_expand_finish (IdeProjectTemplate *self,
- GAsyncResult *result,
- GError **error);
-IDE_AVAILABLE_IN_3_32
-gint ide_project_template_compare (IdeProjectTemplate *a,
- IdeProjectTemplate *b);
+IDE_AVAILABLE_IN_ALL
+const char *ide_project_template_get_id (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_ALL
+int ide_project_template_get_priority (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_ALL
+const char *ide_project_template_get_name (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_ALL
+const char *ide_project_template_get_description (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_ALL
+const char * const *ide_project_template_get_languages (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_ALL
+void ide_project_template_expand_async (IdeProjectTemplate *self,
+ IdeTemplateInput *input,
+ TmplScope *scope,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_project_template_expand_finish (IdeProjectTemplate *self,
+ GAsyncResult *result,
+ GError **error);
+IDE_AVAILABLE_IN_ALL
+int ide_project_template_compare (IdeProjectTemplate *a,
+ IdeProjectTemplate *b);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_project_template_validate_name (IdeProjectTemplate *self,
+ const char *name);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_project_template_validate_app_id (IdeProjectTemplate *self,
+ const char *app_id);
G_END_DECLS
diff --git a/src/libide/projects/ide-template-base.c b/src/libide/projects/ide-template-base.c
index 81879f3da..284ad28df 100644
--- a/src/libide/projects/ide-template-base.c
+++ b/src/libide/projects/ide-template-base.c
@@ -28,6 +28,7 @@
#include <string.h>
#include "ide-template-base.h"
+#include "ide-template-locator.h"
#define TIMEOUT_INTERVAL_MSEC 17
#define TIMEOUT_DURATION_MSEC 2
@@ -134,8 +135,6 @@ ide_template_base_mkdirs_finish (IdeTemplateBase *self,
* Fetches the #TmplTemplateLocator used for resolving templates.
*
* Returns: (transfer none) (nullable): a #TmplTemplateLocator or %NULL.
- *
- * Since: 3.32
*/
TmplTemplateLocator *
ide_template_base_get_locator (IdeTemplateBase *self)
@@ -246,8 +245,6 @@ ide_template_base_class_init (IdeTemplateBaseClass *klass)
* that should be used to resolve template includes. If %NULL, templates
* will not be allowed to include other templates.
* directive.
- *
- * Since: 3.32
*/
properties [PROP_LOCATOR] =
g_param_spec_object ("locator",
@@ -264,6 +261,8 @@ ide_template_base_init (IdeTemplateBase *self)
{
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
+ priv->locator = TMPL_TEMPLATE_LOCATOR (ide_template_locator_new ());
+
priv->files = g_array_new (FALSE, TRUE, sizeof (FileExpansion));
g_array_set_clear_func (priv->files, clear_file_expansion);
}
@@ -294,6 +293,9 @@ ide_template_base_parse_worker (IdeTask *task,
if (!tmpl_template_parse_file (template, fexp->file, cancellable, &error))
{
+ g_debug ("Failed to parse template: %s: %s",
+ g_file_peek_path (fexp->file),
+ error->message);
ide_task_return_error (task, g_steal_pointer (&error));
return;
}
diff --git a/src/libide/projects/ide-template-base.h b/src/libide/projects/ide-template-base.h
index 678e93eb3..cd38d80b9 100644
--- a/src/libide/projects/ide-template-base.h
+++ b/src/libide/projects/ide-template-base.h
@@ -24,14 +24,15 @@
# error "Only <libide-projects.h> can be included directly."
#endif
-#include <libide-core.h>
#include <tmpl-glib.h>
+#include <libide-core.h>
+
G_BEGIN_DECLS
#define IDE_TYPE_TEMPLATE_BASE (ide_template_base_get_type())
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
G_DECLARE_DERIVABLE_TYPE (IdeTemplateBase, ide_template_base, IDE, TEMPLATE_BASE, GObject)
struct _IdeTemplateBaseClass
@@ -39,33 +40,33 @@ struct _IdeTemplateBaseClass
GObjectClass parent_class;
};
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
TmplTemplateLocator *ide_template_base_get_locator (IdeTemplateBase *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
void ide_template_base_set_locator (IdeTemplateBase *self,
TmplTemplateLocator *locator);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
void ide_template_base_add_resource (IdeTemplateBase *self,
const gchar *resource_path,
GFile *destination,
TmplScope *scope,
gint mode);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
void ide_template_base_add_path (IdeTemplateBase *self,
const gchar *path,
GFile *destination,
TmplScope *scope,
gint mode);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
void ide_template_base_expand_all_async (IdeTemplateBase *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
gboolean ide_template_base_expand_all_finish (IdeTemplateBase *self,
GAsyncResult *result,
GError **error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
void ide_template_base_reset (IdeTemplateBase *self);
G_END_DECLS
diff --git a/src/libide/projects/ide-template-input.c b/src/libide/projects/ide-template-input.c
new file mode 100644
index 000000000..f20de3ca7
--- /dev/null
+++ b/src/libide/projects/ide-template-input.c
@@ -0,0 +1,1311 @@
+/* ide-template-input.c
+ *
+ * Copyright 2022 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-template-input"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include <libide-threading.h>
+#include <libide-vcs.h>
+
+#include "ide-projects-global.h"
+#include "ide-project-template.h"
+#include "ide-template-input.h"
+#include "ide-template-locator.h"
+#include "ide-template-provider.h"
+
+#define DEFAULT_USE_VERSION_CONTROL TRUE
+#define DEFAULT_PROJECT_VERSION "0.1.0"
+#define DEFAULT_LANGUAGE "C"
+#define DEFAULT_LICECNSE_NAME "GPL-3.0-or-later"
+#define DEFAULT_VCS_MODULE_NAME "git"
+
+struct _IdeTemplateInput
+{
+ GObject parent_instance;
+
+ GListStore *templates;
+ GtkStringList *languages;
+ GtkStringList *licenses;
+ GtkFilterListModel *filtered_templates;
+ GtkCustomFilter *template_filter;
+
+ GFile *directory;
+
+ char *app_id;
+ char *author;
+ char *language;
+ char *license_name;
+ char *name;
+ char *project_version;
+ char *template;
+
+ guint use_version_control : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_APP_ID,
+ PROP_AUTHOR,
+ PROP_DIRECTORY,
+ PROP_LANGUAGE,
+ PROP_LANGUAGES_MODEL,
+ PROP_LICENSE_NAME,
+ PROP_LICENSES_MODEL,
+ PROP_NAME,
+ PROP_PROJECT_VERSION,
+ PROP_TEMPLATE,
+ PROP_TEMPLATE_NAME,
+ PROP_TEMPLATES_MODEL,
+ PROP_USE_VERSION_CONTROL,
+ N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (IdeTemplateInput, ide_template_input, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+static const struct {
+ const char *spdx;
+ const char *short_path;
+ const char *full_path;
+} licenses[] = {
+ { "AGPL-3.0-or-later", "agpl_3_short", "agpl_3_full" },
+ { "Apache-2.0", "apache_2_short", "apache_2_full" },
+ { "GPL-2.0-or-later", "gpl_2_short", "gpl_2_full" },
+ { "GPL-3.0-or-later", "gpl_3_short", "gpl_3_full" },
+ { "LGPL-2.1-or-later", "lgpl_2_1_short", "lgpl_2_1_full" },
+ { "LGPL-3.0-or-later", "lgpl_3_full", "lgpl_3_short" },
+ { "MIT", "mit_x11_short", "mit_x11_full" },
+ { "No License", NULL, NULL },
+};
+
+static const char *
+get_template_name (IdeTemplateInput *self)
+{
+ guint n_items;
+
+ g_assert (IDE_IS_TEMPLATE_INPUT (self));
+
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->templates));
+
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autoptr(IdeProjectTemplate) template = g_list_model_get_item (G_LIST_MODEL (self->templates), i);
+ const char *id = ide_project_template_get_id (template);
+
+ if (g_strcmp0 (id, self->template) == 0)
+ return ide_project_template_get_name (template);
+ }
+
+ return NULL;
+}
+
+static int
+sort_by_priority (gconstpointer aptr,
+ gconstpointer bptr)
+{
+ IdeProjectTemplate *a = *(IdeProjectTemplate **)aptr;
+ IdeProjectTemplate *b = *(IdeProjectTemplate **)bptr;
+
+ return ide_project_template_compare (a, b);
+}
+
+static int
+sort_strings (const char * const *a,
+ const char * const *b)
+{
+ return g_strcmp0 (*a, *b);
+}
+
+static void
+ide_template_input_set_templates (IdeTemplateInput *self,
+ GPtrArray *templates)
+{
+ g_autoptr(GHashTable) seen_languages = NULL;
+ g_autofree char **sorted_langs = NULL;
+ guint len;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_TEMPLATE_INPUT (self));
+ g_assert (templates != NULL);
+
+ seen_languages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ g_ptr_array_sort (templates, sort_by_priority);
+
+ for (guint i = 0; i < templates->len; i++)
+ {
+ IdeProjectTemplate *template = g_ptr_array_index (templates, i);
+ const char * const *langs = ide_project_template_get_languages (template);
+
+ g_list_store_append (self->templates, template);
+
+ if (langs == NULL)
+ continue;
+
+ for (guint j = 0; langs[j]; j++)
+ {
+ if (!g_hash_table_contains (seen_languages, langs[j]))
+ g_hash_table_insert (seen_languages, g_strdup (langs[j]), NULL);
+ }
+ }
+
+ if (templates->len > 0)
+ {
+ const char *id = ide_project_template_get_id (g_ptr_array_index (templates, 0));
+ ide_template_input_set_template (self, id);
+ }
+
+ sorted_langs = (char **)g_hash_table_get_keys_as_array (seen_languages, &len);
+ g_qsort_with_data (sorted_langs,
+ len,
+ sizeof (char *),
+ (GCompareDataFunc) sort_strings,
+ NULL);
+
+ gtk_string_list_splice (self->languages, 0, 0,
+ (const char * const *)sorted_langs);
+
+ IDE_EXIT;
+}
+
+static gboolean
+template_filter_func (gpointer item,
+ gpointer user_data)
+{
+ IdeProjectTemplate *template = item;
+ const char *language = user_data;
+ const char * const *languages;
+
+ g_assert (IDE_IS_PROJECT_TEMPLATE (template));
+ g_assert (language != NULL);
+
+ if ((languages = ide_project_template_get_languages (template)))
+ return g_strv_contains (languages, language);
+
+ return FALSE;
+}
+
+static void
+foreach_template_provider_cb (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeTemplateProvider *provider = (IdeTemplateProvider *)exten;
+ GPtrArray *templates = user_data;
+ GList *list;
+
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_TEMPLATE_PROVIDER (provider));
+
+ list = ide_template_provider_get_project_templates (provider);
+ for (GList *iter = list; iter; iter = iter->next)
+ g_ptr_array_add (templates, g_steal_pointer (&iter->data));
+ g_list_free (list);
+}
+
+static void
+ide_template_input_constructed (GObject *object)
+{
+ IdeTemplateInput *self = (IdeTemplateInput *)object;
+ g_autoptr(PeasExtensionSet) set = NULL;
+ g_autoptr(GPtrArray) templates = NULL;
+
+ G_OBJECT_CLASS (ide_template_input_parent_class)->constructed (object);
+
+ templates = g_ptr_array_new_with_free_func (g_object_unref);
+ set = peas_extension_set_new (peas_engine_get_default (),
+ IDE_TYPE_TEMPLATE_PROVIDER,
+ NULL);
+ peas_extension_set_foreach (set, foreach_template_provider_cb, templates);
+
+ ide_template_input_set_templates (self, templates);
+}
+
+static void
+ide_template_input_dispose (GObject *object)
+{
+ IdeTemplateInput *self = (IdeTemplateInput *)object;
+
+ g_clear_object (&self->template_filter);
+ g_clear_object (&self->filtered_templates);
+ g_clear_object (&self->directory);
+ g_clear_object (&self->templates);
+ g_clear_object (&self->languages);
+ g_clear_object (&self->licenses);
+
+ g_clear_pointer (&self->author, g_free);
+ g_clear_pointer (&self->language, g_free);
+ g_clear_pointer (&self->name, g_free);
+ g_clear_pointer (&self->app_id, g_free);
+ g_clear_pointer (&self->project_version, g_free);
+ g_clear_pointer (&self->license_name, g_free);
+
+ G_OBJECT_CLASS (ide_template_input_parent_class)->dispose (object);
+}
+
+static void
+ide_template_input_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTemplateInput *self = IDE_TEMPLATE_INPUT (object);
+
+ switch (prop_id)
+ {
+ case PROP_AUTHOR:
+ g_value_set_string (value, self->author);
+ break;
+
+ case PROP_DIRECTORY:
+ g_value_set_object (value, self->directory);
+ break;
+
+ case PROP_LANGUAGE:
+ g_value_set_string (value, self->language);
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, self->name);
+ break;
+
+ case PROP_APP_ID:
+ g_value_set_string (value, self->app_id);
+ break;
+
+ case PROP_PROJECT_VERSION:
+ g_value_set_string (value, self->project_version);
+ break;
+
+ case PROP_LICENSE_NAME:
+ g_value_set_string (value, self->license_name);
+ break;
+
+ case PROP_TEMPLATE:
+ g_value_set_string (value, self->template);
+ break;
+
+ case PROP_TEMPLATE_NAME:
+ g_value_set_string (value, get_template_name (self));
+ break;
+
+ case PROP_TEMPLATES_MODEL:
+ g_value_set_object (value, self->filtered_templates);
+ break;
+
+ case PROP_LANGUAGES_MODEL:
+ g_value_set_object (value, self->languages);
+ break;
+
+ case PROP_LICENSES_MODEL:
+ g_value_set_object (value, self->licenses);
+ break;
+
+ case PROP_USE_VERSION_CONTROL:
+ g_value_set_boolean (value, self->use_version_control);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_template_input_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTemplateInput *self = IDE_TEMPLATE_INPUT (object);
+
+ switch (prop_id)
+ {
+ case PROP_AUTHOR:
+ ide_template_input_set_author (self, g_value_get_string (value));
+ break;
+
+ case PROP_DIRECTORY:
+ ide_template_input_set_directory (self, g_value_get_object (value));
+ break;
+
+ case PROP_LANGUAGE:
+ ide_template_input_set_language (self, g_value_get_string (value));
+ break;
+
+ case PROP_NAME:
+ ide_template_input_set_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_APP_ID:
+ ide_template_input_set_app_id (self, g_value_get_string (value));
+ break;
+
+ case PROP_PROJECT_VERSION:
+ ide_template_input_set_project_version (self, g_value_get_string (value));
+ break;
+
+ case PROP_LICENSE_NAME:
+ ide_template_input_set_license_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_TEMPLATE:
+ ide_template_input_set_template (self, g_value_get_string (value));
+ break;
+
+ case PROP_USE_VERSION_CONTROL:
+ ide_template_input_set_use_version_control (self, g_value_get_boolean (value));
+ break;
+
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_template_input_class_init (IdeTemplateInputClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = ide_template_input_constructed;
+ object_class->dispose = ide_template_input_dispose;
+ object_class->get_property = ide_template_input_get_property;
+ object_class->set_property = ide_template_input_set_property;
+
+ properties [PROP_AUTHOR] =
+ g_param_spec_string ("author", NULL, NULL, NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DIRECTORY] =
+ g_param_spec_object ("directory", NULL, NULL, G_TYPE_FILE,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LANGUAGE] =
+ g_param_spec_string ("language", NULL, NULL, DEFAULT_LANGUAGE,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_NAME] =
+ g_param_spec_string ("name", NULL, NULL, "",
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_APP_ID] =
+ g_param_spec_string ("app-id", NULL, NULL, "",
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PROJECT_VERSION] =
+ g_param_spec_string ("project-version", NULL, NULL, DEFAULT_PROJECT_VERSION,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LICENSE_NAME] =
+ g_param_spec_string ("license-name", NULL, NULL, DEFAULT_LICECNSE_NAME,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TEMPLATE] =
+ g_param_spec_string ("template", NULL, NULL, NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TEMPLATE_NAME] =
+ g_param_spec_string ("template-name", NULL, NULL, NULL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TEMPLATES_MODEL] =
+ g_param_spec_object ("templates-model", NULL, NULL, G_TYPE_LIST_MODEL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LANGUAGES_MODEL] =
+ g_param_spec_object ("languages-model", NULL, NULL, G_TYPE_LIST_MODEL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LICENSES_MODEL] =
+ g_param_spec_object ("licenses-model", NULL, NULL, G_TYPE_LIST_MODEL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_USE_VERSION_CONTROL] =
+ g_param_spec_boolean ("use-version-control", NULL, NULL, DEFAULT_USE_VERSION_CONTROL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_template_input_init (IdeTemplateInput *self)
+{
+ self->name = g_strdup ("");
+ self->directory = g_file_new_for_path (ide_get_projects_dir ());
+ self->author = g_strdup (g_get_real_name ());
+ self->app_id = g_strdup ("");
+ self->language = g_strdup (DEFAULT_LANGUAGE);
+ self->license_name = g_strdup (DEFAULT_LICECNSE_NAME);
+ self->project_version = g_strdup (DEFAULT_PROJECT_VERSION);
+ self->use_version_control = DEFAULT_USE_VERSION_CONTROL;
+ self->templates = g_list_store_new (IDE_TYPE_PROJECT_TEMPLATE);
+ self->languages = gtk_string_list_new (NULL);
+ self->licenses = gtk_string_list_new (NULL);
+
+ for (guint i = 0; i < G_N_ELEMENTS (licenses); i++)
+ gtk_string_list_append (self->licenses, licenses[i].spdx);
+
+ self->template_filter = gtk_custom_filter_new (template_filter_func,
+ g_strdup (self->language),
+ g_free);
+ self->filtered_templates = g_object_new (GTK_TYPE_FILTER_LIST_MODEL,
+ "filter", self->template_filter,
+ "model", self->templates,
+ NULL);
+}
+
+const char *
+ide_template_input_get_author (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+ return self->author;
+}
+
+/**
+ * ide_template_input_get_directory:
+ * @self: a #IdeTemplateInput
+ *
+ * Gets the directory to use to contain the new project directory.
+ *
+ * Returns: (transfer none) (not nullable): a #GFile for the directory
+ * to use when generating the template.
+ */
+GFile *
+ide_template_input_get_directory (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+ return self->directory;
+}
+
+const char *
+ide_template_input_get_language (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+ return self->language;
+}
+
+const char *
+ide_template_input_get_name (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+ return self->name;
+}
+
+const char *
+ide_template_input_get_app_id (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+ return self->app_id;
+}
+
+const char *
+ide_template_input_get_project_version (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+ return self->project_version;
+}
+
+const char *
+ide_template_input_get_license_name (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+ return self->license_name;
+}
+
+const char *
+ide_template_input_get_template (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+ return self->template;
+}
+
+gboolean
+ide_template_input_get_use_version_control (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), FALSE);
+ return self->use_version_control;
+}
+
+void
+ide_template_input_set_author (IdeTemplateInput *self,
+ const char *author)
+{
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+
+ if (g_strcmp0 (author, self->author) != 0)
+ {
+ g_free (self->author);
+ self->author = g_strdup (author);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_AUTHOR]);
+ }
+}
+
+void
+ide_template_input_set_directory (IdeTemplateInput *self,
+ GFile *directory)
+{
+ g_autoptr(GFile) fallback = NULL;
+
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+ g_return_if_fail (!directory || G_IS_FILE (directory));
+
+ if (directory == NULL)
+ directory = fallback = g_file_new_for_path (ide_get_projects_dir ());
+
+ g_assert (G_IS_FILE (directory));
+ g_assert (G_IS_FILE (self->directory));
+
+ if (g_file_equal (self->directory, directory))
+ return;
+
+ g_set_object (&self->directory, directory);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIRECTORY]);
+}
+
+static void
+auto_select_template (IdeTemplateInput *self)
+{
+ const char *first_id = NULL;
+ GListModel *model;
+ guint n_items;
+
+ g_assert (IDE_IS_TEMPLATE_INPUT (self));
+
+ model = G_LIST_MODEL (self->filtered_templates);
+ n_items = g_list_model_get_n_items (model);
+
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autoptr(IdeProjectTemplate) template = g_list_model_get_item (model, i);
+ const char *id = ide_project_template_get_id (template);
+
+ if (ide_str_equal0 (id, self->template))
+ return;
+
+ if (first_id == NULL)
+ first_id = id;
+ }
+
+ if (first_id != NULL)
+ ide_template_input_set_template (self, first_id);
+}
+
+void
+ide_template_input_set_language (IdeTemplateInput *self,
+ const char *language)
+{
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+
+ if (g_strcmp0 (language, self->language) != 0)
+ {
+ g_free (self->language);
+ self->language = g_strdup (language);
+
+ gtk_custom_filter_set_filter_func (self->template_filter,
+ template_filter_func,
+ g_strdup (language),
+ g_free);
+ auto_select_template (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LANGUAGE]);
+ }
+}
+
+void
+ide_template_input_set_name (IdeTemplateInput *self,
+ const char *name)
+{
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+
+ if (g_strcmp0 (name, self->name) != 0)
+ {
+ g_free (self->name);
+ self->name = g_strdup (name);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+ }
+}
+
+void
+ide_template_input_set_app_id (IdeTemplateInput *self,
+ const char *app_id)
+{
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+
+ if (g_strcmp0 (app_id, self->app_id) != 0)
+ {
+ g_free (self->app_id);
+ self->app_id = g_strdup (app_id);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_APP_ID]);
+ }
+}
+
+void
+ide_template_input_set_project_version (IdeTemplateInput *self,
+ const char *project_version)
+{
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+
+ if (g_strcmp0 (project_version, self->project_version) != 0)
+ {
+ g_free (self->project_version);
+ self->project_version = g_strdup (project_version);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROJECT_VERSION]);
+ }
+}
+
+void
+ide_template_input_set_license_name (IdeTemplateInput *self,
+ const char *license_name)
+{
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+
+ if (g_strcmp0 (license_name, self->license_name) != 0)
+ {
+ g_free (self->license_name);
+ self->license_name = g_strdup (license_name);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LICENSE_NAME]);
+ }
+}
+
+void
+ide_template_input_set_template (IdeTemplateInput *self,
+ const char *template)
+{
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+
+ if (g_strcmp0 (template, self->template) != 0)
+ {
+ g_free (self->template);
+ self->template = g_strdup (template);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TEMPLATE]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TEMPLATE_NAME]);
+ }
+}
+
+void
+ide_template_input_set_use_version_control (IdeTemplateInput *self,
+ gboolean use_version_control)
+{
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+
+ use_version_control = !!use_version_control;
+
+ if (use_version_control != self->use_version_control)
+ {
+ self->use_version_control = use_version_control;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_USE_VERSION_CONTROL]);
+ }
+}
+
+static void
+scope_take_string (TmplScope *scope,
+ const char *name,
+ char *value)
+{
+ tmpl_scope_set_string (scope, name, value);
+ g_free (value);
+}
+
+static char *
+capitalize (const gchar *input)
+{
+ gunichar c;
+ GString *str;
+
+ if (input == NULL)
+ return NULL;
+
+ if (*input == 0)
+ return g_strdup ("");
+
+ c = g_utf8_get_char (input);
+ if (g_unichar_isupper (c))
+ return g_strdup (input);
+
+ str = g_string_new (NULL);
+ input = g_utf8_next_char (input);
+ g_string_append_unichar (str, g_unichar_toupper (c));
+ if (*input)
+ g_string_append (str, input);
+
+ return g_string_free (str, FALSE);
+}
+
+static char *
+camelize (const char *input)
+{
+ gboolean next_is_upper = TRUE;
+ gboolean skip = FALSE;
+ GString *str;
+
+ if (input == NULL)
+ return NULL;
+
+ if (!strchr (input, '_') && !strchr (input, ' ') && !strchr (input, '-'))
+ return capitalize (input);
+
+ str = g_string_new (NULL);
+
+ for (; *input; input = g_utf8_next_char (input))
+ {
+ gunichar c = g_utf8_get_char (input);
+
+ switch (c)
+ {
+ case '_':
+ case '-':
+ case ' ':
+ next_is_upper = TRUE;
+ skip = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (skip)
+ {
+ skip = FALSE;
+ continue;
+ }
+
+ if (next_is_upper)
+ {
+ c = g_unichar_toupper (c);
+ next_is_upper = FALSE;
+ }
+ else
+ c = g_unichar_tolower (c);
+
+ g_string_append_unichar (str, c);
+ }
+
+ if (g_str_has_suffix (str->str, "Private"))
+ g_string_truncate (str, str->len - strlen ("Private"));
+
+ return g_string_free (str, FALSE);
+}
+
+static char *
+functify (const gchar *input)
+{
+ gunichar last = 0;
+ GString *str;
+
+ if (input == NULL)
+ return NULL;
+
+ str = g_string_new (NULL);
+
+ for (; *input; input = g_utf8_next_char (input))
+ {
+ gunichar c = g_utf8_get_char (input);
+ gunichar n = g_utf8_get_char (g_utf8_next_char (input));
+
+ if (last)
+ {
+ if ((g_unichar_islower (last) && g_unichar_isupper (c)) ||
+ (g_unichar_isupper (c) && g_unichar_islower (n)))
+ g_string_append_c (str, '_');
+ }
+
+ if ((c == ' ') || (c == '-'))
+ c = '_';
+
+ g_string_append_unichar (str, g_unichar_tolower (c));
+
+ last = c;
+ }
+
+ if (g_str_has_suffix (str->str, "_private") ||
+ g_str_has_suffix (str->str, "_PRIVATE"))
+ g_string_truncate (str, str->len - strlen ("_private"));
+
+ return g_string_free (str, FALSE);
+}
+
+static char *
+build_app_path (const char *app_id)
+{
+ GString *str = g_string_new ("/");
+
+ for (const char *c = app_id; *c; c = g_utf8_next_char (c))
+ {
+ if (*c == '.')
+ g_string_append_c (str, '/');
+ else
+ g_string_append_unichar (str, g_utf8_get_char (c));
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static char *
+get_short_license (IdeTemplateInput *self)
+{
+ g_assert (IDE_IS_TEMPLATE_INPUT (self));
+
+ for (guint i = 0; i < G_N_ELEMENTS (licenses); i++)
+ {
+ if (g_strcmp0 (licenses[i].spdx, self->license_name) == 0)
+ {
+ g_autofree char *resource_path = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+ const guint8 *data;
+ gsize len;
+
+ if (licenses[i].short_path == NULL)
+ break;
+
+ resource_path = g_strdup_printf ("/org/gnome/libide-projects/licenses/%s",
+ licenses[i].short_path);
+ bytes = g_resources_lookup_data (resource_path, 0, NULL);
+
+ if (bytes == NULL)
+ break;
+
+ data = g_bytes_get_data (bytes, &len);
+ return (char *)g_memdup2 (data, len);
+ }
+ }
+
+ return g_strdup ("");
+}
+
+static char *
+get_spdx_id (IdeTemplateInput *self)
+{
+ g_assert (IDE_IS_TEMPLATE_INPUT (self));
+
+ for (guint i = 0; i < G_N_ELEMENTS (licenses); i++)
+ {
+ if (g_strcmp0 (licenses[i].spdx, self->license_name) == 0)
+ {
+ if (licenses[i].short_path != NULL)
+ return g_strdup (licenses[i].spdx);
+ break;
+ }
+ }
+
+ return g_strdup ("LicenseRef-proprietary");
+}
+
+static TmplScope *
+ide_template_input_to_scope (IdeTemplateInput *self)
+{
+ g_autoptr(TmplScope) scope = NULL;
+ g_autoptr(GDateTime) now = NULL;
+ g_autofree char *name_lower = NULL;
+ g_autofree char *prefix_ = NULL;
+ g_autofree char *prefix = NULL;
+ g_autofree char *Prefix = NULL;
+ g_autofree char *PreFix = NULL;
+ const char *app_id;
+
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+
+ now = g_date_time_new_now_local ();
+ scope = tmpl_scope_new ();
+
+ app_id = !ide_str_empty0 (self->app_id) ? self->app_id : "org.gnome.Example";
+ tmpl_scope_set_string (scope, "appid", app_id);
+ scope_take_string (scope, "appid_path", build_app_path (app_id));
+
+ tmpl_scope_set_string (scope, "template", self->template);
+ tmpl_scope_set_string (scope, "author", self->author);
+ tmpl_scope_set_string (scope, "project_version", self->project_version);
+ scope_take_string (scope, "language", g_utf8_strdown (self->language, -1));
+ tmpl_scope_set_boolean (scope, "versioning", self->use_version_control);
+ scope_take_string (scope, "project_path", g_file_get_path (self->directory));
+
+ /* Name variants for use as classes, functions, etc */
+ name_lower = g_utf8_strdown (self->name ? self->name : "example", -1);
+ tmpl_scope_set_string (scope, "name", name_lower);
+ scope_take_string (scope, "name_", functify (name_lower));
+ scope_take_string (scope, "NAME", g_strdelimit (g_utf8_strup (name_lower, -1), "-", '_'));
+ scope_take_string (scope, "year", g_date_time_format (now, "%Y"));
+ scope_take_string (scope, "YEAR", g_date_time_format (now, "%Y"));
+ scope_take_string (scope, "Title", capitalize (self->name));
+
+ if (g_str_has_suffix (name_lower, "_glib"))
+ prefix = g_strndup (name_lower, strlen (name_lower) - 5);
+ else
+ prefix = g_strdup (name_lower);
+ Prefix = capitalize (prefix);
+ PreFix = camelize (prefix);
+ prefix_ = g_strdelimit (g_utf8_strdown (prefix, -1), "-", '_');
+
+ /* Various prefixes for use as namespaces, etc */
+ tmpl_scope_set_string (scope, "prefix", prefix);
+ tmpl_scope_set_string (scope, "prefix_", prefix_);
+ scope_take_string (scope, "PREFIX", g_strdelimit (g_utf8_strup (prefix, -1), "-", '_'));
+ tmpl_scope_set_string (scope, "Prefix", Prefix);
+ tmpl_scope_set_string (scope, "PreFix", PreFix);
+ scope_take_string (scope, "spaces", g_strnfill (strlen (prefix_), ' '));
+ scope_take_string (scope, "Spaces", g_strnfill (strlen (PreFix), ' '));
+
+ scope_take_string (scope, "project_license", get_spdx_id (self));
+
+ return g_steal_pointer (&scope);
+}
+
+/**
+ * ide_template_input_get_license_path:
+ * @self: a #IdeTemplateInput
+ *
+ * Gets a path to a #GResource containing the full license text.
+ *
+ * Returns: (transfer full) (nullable): a resource path or %NULL
+ */
+char *
+ide_template_input_get_license_path (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+
+ for (guint i = 0; i < G_N_ELEMENTS (licenses); i++)
+ {
+ if (g_strcmp0 (licenses[i].spdx, self->license_name) == 0)
+ {
+ if (licenses[i].full_path == NULL)
+ return NULL;
+
+ return g_strdup_printf ("/org/gnome/libide-projects/licenses/%s",
+ licenses[i].full_path);
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * ide_template_input_get_templates_model:
+ * @self: a #IdeTemplateInput
+ *
+ * Returns: (transfer none): A #GListModel
+ */
+GListModel *
+ide_template_input_get_templates_model (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+
+ return G_LIST_MODEL (self->filtered_templates);
+}
+
+/**
+ * ide_template_input_get_languages_model:
+ * @self: a #IdeTemplateInput
+ *
+ * Returns: (transfer none): A #GListModel
+ */
+GListModel *
+ide_template_input_get_languages_model (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+
+ return G_LIST_MODEL (self->languages);
+}
+
+/**
+ * ide_template_input_get_licenses_model:
+ * @self: a #IdeTemplateInput
+ *
+ * Returns: (transfer none): A #GListModel
+ */
+GListModel *
+ide_template_input_get_licenses_model (IdeTemplateInput *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), NULL);
+
+ return G_LIST_MODEL (self->licenses);
+}
+
+static IdeProjectTemplate *
+find_template (IdeTemplateInput *self,
+ const char *template_id)
+{
+ GListModel *model;
+ guint n_items;
+
+ g_assert (IDE_IS_TEMPLATE_INPUT (self));
+
+ if (template_id == NULL)
+ return NULL;
+
+ model = G_LIST_MODEL (self->templates);
+ n_items = g_list_model_get_n_items (model);
+
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autoptr(IdeProjectTemplate) template = g_list_model_get_item (model, i);
+ const char *id = ide_project_template_get_id (template);
+
+ if (ide_str_equal0 (template_id, id))
+ return g_steal_pointer (&template);
+ }
+
+ return NULL;
+}
+
+IdeTemplateInputValidation
+ide_template_input_validate (IdeTemplateInput *self)
+{
+ IdeTemplateInputValidation flags = 0;
+ IdeProjectTemplate *template;
+ g_autoptr(GFile) dest = NULL;
+ const char * const *languages;
+
+ g_return_val_if_fail (IDE_IS_TEMPLATE_INPUT (self), 0);
+
+ if (!(template = find_template (self, self->template)))
+ flags |= IDE_TEMPLATE_INPUT_INVAL_TEMPLATE;
+
+ if (template && !ide_project_template_validate_app_id (template, self->app_id))
+ flags |= IDE_TEMPLATE_INPUT_INVAL_APP_ID;
+
+ if (ide_str_empty0 (self->name))
+ flags |= IDE_TEMPLATE_INPUT_INVAL_NAME;
+ else if (template && !ide_project_template_validate_name (template, self->name))
+ flags |= IDE_TEMPLATE_INPUT_INVAL_NAME;
+
+ if (self->directory == NULL ||
+ self->name == NULL ||
+ !(dest = g_file_get_child (self->directory, self->name)) ||
+ g_file_query_exists (dest, NULL))
+ flags |= IDE_TEMPLATE_INPUT_INVAL_LOCATION;
+
+ if (template != NULL && /* ignore if template is not set*/
+ (self->language == NULL ||
+ !(languages = ide_project_template_get_languages (template)) ||
+ !g_strv_contains (languages, self->language)))
+ flags |= IDE_TEMPLATE_INPUT_INVAL_LANGUAGE;
+
+ return flags;
+}
+
+static void
+ide_template_input_initialize_vcs_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeVcsInitializer *initializer = (IdeVcsInitializer *)object;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+ GFile *directory;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_VCS_INITIALIZER (initializer));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_TASK (task));
+
+ directory = ide_task_get_task_data (task);
+ g_assert (G_IS_FILE (directory));
+
+ if (!ide_vcs_initializer_initialize_finish (initializer, result, &error))
+ ide_task_return_error (task, g_steal_pointer (&error));
+ else
+ ide_task_return_pointer (task, g_object_ref (directory), g_object_unref);
+
+ ide_object_destroy (IDE_OBJECT (initializer));
+
+ IDE_EXIT;
+}
+
+static void
+ide_template_input_expand_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(IdeVcsInitializer) initializer = NULL;
+ IdeProjectTemplate *template = (IdeProjectTemplate *)object;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+ IdeTemplateInput *self;
+ PeasPluginInfo *plugin_info;
+ GCancellable *cancellable;
+ IdeContext *context = NULL;
+ PeasEngine *engine;
+ GFile *directory;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_PROJECT_TEMPLATE (template));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_TASK (task));
+
+ self = ide_task_get_source_object (task);
+ directory = ide_task_get_task_data (task);
+ cancellable = ide_task_get_cancellable (task);
+ context = g_object_get_data (G_OBJECT (task), "CONTEXT");
+
+ g_assert (IDE_IS_TEMPLATE_INPUT (self));
+ g_assert (G_IS_FILE (directory));
+ g_assert (G_IS_CANCELLABLE (cancellable));
+ g_assert (IDE_IS_CONTEXT (context));
+
+ if (!ide_project_template_expand_finish (template, result, &error))
+ {
+ ide_task_return_error (task, g_steal_pointer (&error));
+ IDE_EXIT;
+ }
+
+ if (self->use_version_control == FALSE)
+ {
+ ide_task_return_pointer (task, g_object_ref (directory), g_object_unref);
+ IDE_EXIT;
+ }
+
+ engine = peas_engine_get_default ();
+
+ if (!(plugin_info = peas_engine_get_plugin_info (engine, DEFAULT_VCS_MODULE_NAME)))
+ {
+ /* Just continue without creating the VCS backend. Not like this can really
+ * hit in production use anyway.
+ */
+ ide_task_return_pointer (task, g_object_ref (directory), g_object_unref);
+ IDE_EXIT;
+ }
+
+ initializer = (IdeVcsInitializer *)
+ peas_engine_create_extension (engine,
+ plugin_info,
+ IDE_TYPE_VCS_INITIALIZER,
+ "parent", context,
+ NULL);
+
+ if (initializer == NULL)
+ {
+ ide_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Failed to create initializer for %s version control",
+ DEFAULT_VCS_MODULE_NAME);
+ IDE_EXIT;
+ }
+
+ ide_vcs_initializer_initialize_async (initializer,
+ directory,
+ cancellable,
+ ide_template_input_initialize_vcs_cb,
+ g_steal_pointer (&task));
+
+ IDE_EXIT;
+}
+
+void
+ide_template_input_expand_async (IdeTemplateInput *self,
+ IdeContext *context,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeProjectTemplate) template = NULL;
+ g_autoptr(TmplScope) scope = NULL;
+ g_autoptr(IdeTask) task = NULL;
+ TmplTemplateLocator *locator;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_MAIN_THREAD ());
+ g_return_if_fail (IDE_IS_TEMPLATE_INPUT (self));
+ g_return_if_fail (IDE_IS_CONTEXT (context));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, ide_template_input_expand_async);
+ ide_task_set_task_data (task,
+ g_file_get_child (self->directory, self->name),
+ g_object_unref);
+ g_object_set_data_full (G_OBJECT (task),
+ "CONTEXT",
+ g_object_ref (context),
+ g_object_unref);
+
+ if (ide_template_input_validate (self) != IDE_TEMPLATE_INPUT_VALID)
+ {
+ ide_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_INVAL,
+ "Template input is not valid");
+ IDE_EXIT;
+ }
+
+ if (!(template = find_template (self, self->template)))
+ {
+ ide_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_INVAL,
+ "Failed to locate template");
+ IDE_EXIT;
+ }
+
+ if (!(scope = ide_template_input_to_scope (self)))
+ {
+ ide_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_INVAL,
+ "Failed to create scope for template");
+ IDE_EXIT;
+ }
+
+ if ((locator = ide_template_base_get_locator (IDE_TEMPLATE_BASE (template))) &&
+ IDE_IS_TEMPLATE_LOCATOR (locator))
+ {
+ g_autofree char *license_text = get_short_license (self);
+ ide_template_locator_set_license_text (IDE_TEMPLATE_LOCATOR (locator), license_text);
+ }
+
+ ide_project_template_expand_async (template,
+ self,
+ scope,
+ cancellable,
+ ide_template_input_expand_cb,
+ g_steal_pointer (&task));
+
+ IDE_EXIT;
+}
+
+/**
+ * ide_template_input_expand_finish:
+ *
+ * Returns: (transfer full): a #GFile or %NULL and @error is set.
+ */
+GFile *
+ide_template_input_expand_finish (IdeTemplateInput *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ GFile *ret;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_TEMPLATE_INPUT (self));
+ g_assert (IDE_IS_TASK (result));
+
+ ret = ide_task_propagate_pointer (IDE_TASK (result), error);
+
+ IDE_RETURN (ret);
+}
diff --git a/src/libide/projects/ide-template-input.h b/src/libide/projects/ide-template-input.h
new file mode 100644
index 000000000..ff0182fd1
--- /dev/null
+++ b/src/libide/projects/ide-template-input.h
@@ -0,0 +1,116 @@
+/* ide-template-input.h
+ *
+ * Copyright 2022 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
+
+#include <tmpl-glib.h>
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TEMPLATE_INPUT (ide_template_input_get_type())
+
+typedef enum
+{
+ IDE_TEMPLATE_INPUT_VALID = 0,
+ IDE_TEMPLATE_INPUT_INVAL_NAME = 1 << 0,
+ IDE_TEMPLATE_INPUT_INVAL_APP_ID = 1 << 1,
+ IDE_TEMPLATE_INPUT_INVAL_LOCATION = 1 << 2,
+ IDE_TEMPLATE_INPUT_INVAL_LANGUAGE = 1 << 3,
+ IDE_TEMPLATE_INPUT_INVAL_TEMPLATE = 1 << 4,
+} IdeTemplateInputValidation;
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeTemplateInput, ide_template_input, IDE, TEMPLATE_INPUT, GObject)
+
+IDE_AVAILABLE_IN_ALL
+IdeTemplateInput *ide_template_input_new (void);
+IDE_AVAILABLE_IN_ALL
+const char *ide_template_input_get_author (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_set_author (IdeTemplateInput *self,
+ const char *author);
+IDE_AVAILABLE_IN_ALL
+GFile *ide_template_input_get_directory (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_set_directory (IdeTemplateInput *self,
+ GFile *directory);
+IDE_AVAILABLE_IN_ALL
+const char *ide_template_input_get_language (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_set_language (IdeTemplateInput *self,
+ const char *language);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_template_input_get_use_version_control (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_set_use_version_control (IdeTemplateInput *self,
+ gboolean
use_version_control);
+IDE_AVAILABLE_IN_ALL
+const char *ide_template_input_get_name (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_set_name (IdeTemplateInput *self,
+ const char *name);
+IDE_AVAILABLE_IN_ALL
+const char *ide_template_input_get_app_id (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_set_app_id (IdeTemplateInput *self,
+ const char *app_id);
+IDE_AVAILABLE_IN_ALL
+const char *ide_template_input_get_project_version (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_set_project_version (IdeTemplateInput *self,
+ const char
*project_version);
+IDE_AVAILABLE_IN_ALL
+const char *ide_template_input_get_license_name (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_set_license_name (IdeTemplateInput *self,
+ const char *license_name);
+IDE_AVAILABLE_IN_ALL
+const char *ide_template_input_get_template (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_set_template (IdeTemplateInput *self,
+ const char *template);
+IDE_AVAILABLE_IN_ALL
+GListModel *ide_template_input_get_templates_model (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+GListModel *ide_template_input_get_languages_model (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+GListModel *ide_template_input_get_licenses_model (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+IdeTemplateInputValidation ide_template_input_validate (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+char *ide_template_input_get_license_path (IdeTemplateInput *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_input_expand_async (IdeTemplateInput *self,
+ IdeContext *context,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_ALL
+GFile *ide_template_input_expand_finish (IdeTemplateInput *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/src/libide/projects/ide-template-locator.c b/src/libide/projects/ide-template-locator.c
new file mode 100644
index 000000000..6763c56d8
--- /dev/null
+++ b/src/libide/projects/ide-template-locator.c
@@ -0,0 +1,185 @@
+/* ide-template-locator.c
+ *
+ * Copyright 2022 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-template-locator"
+
+#include "config.h"
+
+#include <gtksourceview/gtksource.h>
+
+#include <libide-code.h>
+
+#include "ide-template-locator.h"
+
+typedef struct
+{
+ char *license_text;
+} IdeTemplateLocatorPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeTemplateLocator, ide_template_locator, TMPL_TYPE_TEMPLATE_LOCATOR)
+
+enum {
+ PROP_0,
+ PROP_LICENSE_TEXT,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static GInputStream *
+ide_template_locator_locate (TmplTemplateLocator *locator,
+ const char *path,
+ GError **error)
+{
+ IdeTemplateLocator *self = (IdeTemplateLocator *)locator;
+ IdeTemplateLocatorPrivate *priv = ide_template_locator_get_instance_private (self);
+
+ g_assert (IDE_IS_TEMPLATE_LOCATOR (self));
+ g_assert (path != NULL);
+
+ if (g_str_has_prefix (path, "license."))
+ {
+ GtkSourceLanguageManager *manager = gtk_source_language_manager_get_default ();
+ GtkSourceLanguage *language = gtk_source_language_manager_guess_language (manager, path, NULL);
+
+ if (priv->license_text != NULL && language != NULL)
+ {
+ g_autofree char *header = ide_language_format_header (language, priv->license_text);
+ gsize len = strlen (header);
+
+ return g_memory_input_stream_new_from_data (g_steal_pointer (&header), len, g_free);
+ }
+
+ /* We don't want to fail here just because we didn't have any
+ * license text to expand into a header.
+ */
+ return g_memory_input_stream_new ();
+ }
+
+ return TMPL_TEMPLATE_LOCATOR_CLASS (ide_template_locator_parent_class)->locate (locator, path, error);
+}
+
+static void
+ide_template_locator_dispose (GObject *object)
+{
+ IdeTemplateLocator *self = (IdeTemplateLocator *)object;
+ IdeTemplateLocatorPrivate *priv = ide_template_locator_get_instance_private (self);
+
+ g_clear_pointer (&priv->license_text, g_free);
+
+ G_OBJECT_CLASS (ide_template_locator_parent_class)->dispose (object);
+}
+
+static void
+ide_template_locator_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTemplateLocator *self = IDE_TEMPLATE_LOCATOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_LICENSE_TEXT:
+ g_value_set_string (value, ide_template_locator_get_license_text (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_template_locator_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTemplateLocator *self = IDE_TEMPLATE_LOCATOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_LICENSE_TEXT:
+ ide_template_locator_set_license_text (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_template_locator_class_init (IdeTemplateLocatorClass *klass)
+{
+ TmplTemplateLocatorClass *locator_class = TMPL_TEMPLATE_LOCATOR_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = ide_template_locator_dispose;
+ object_class->get_property = ide_template_locator_get_property;
+ object_class->set_property = ide_template_locator_set_property;
+
+ locator_class->locate = ide_template_locator_locate;
+
+ properties [PROP_LICENSE_TEXT] =
+ g_param_spec_string ("license-text",
+ "License Text",
+ "The text of the license to include in headers",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_template_locator_init (IdeTemplateLocator *self)
+{
+}
+
+const char *
+ide_template_locator_get_license_text (IdeTemplateLocator *self)
+{
+ IdeTemplateLocatorPrivate *priv = ide_template_locator_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_TEMPLATE_LOCATOR (self), NULL);
+
+ return priv->license_text;
+}
+
+void
+ide_template_locator_set_license_text (IdeTemplateLocator *self,
+ const char *license_text)
+{
+ IdeTemplateLocatorPrivate *priv = ide_template_locator_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_TEMPLATE_LOCATOR (self));
+
+ if (g_strcmp0 (license_text, priv->license_text) != 0)
+ {
+ g_free (priv->license_text);
+ priv->license_text = g_strdup (license_text);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LICENSE_TEXT]);
+ }
+}
+
+IdeTemplateLocator *
+ide_template_locator_new (void)
+{
+ return g_object_new (IDE_TYPE_TEMPLATE_LOCATOR, NULL);
+}
diff --git a/src/libide/projects/ide-template-locator.h b/src/libide/projects/ide-template-locator.h
new file mode 100644
index 000000000..5394a10fa
--- /dev/null
+++ b/src/libide/projects/ide-template-locator.h
@@ -0,0 +1,51 @@
+/* ide-template-locator.h
+ *
+ * Copyright 2022 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
+
+#include <tmpl-glib.h>
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TEMPLATE_LOCATOR (ide_template_locator_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_DERIVABLE_TYPE (IdeTemplateLocator, ide_template_locator, IDE, TEMPLATE_LOCATOR,
TmplTemplateLocator)
+
+struct _IdeTemplateLocatorClass
+{
+ TmplTemplateLocatorClass parent_class;
+};
+
+IDE_AVAILABLE_IN_ALL
+IdeTemplateLocator *ide_template_locator_new (void);
+IDE_AVAILABLE_IN_ALL
+const char *ide_template_locator_get_license_text (IdeTemplateLocator *self);
+IDE_AVAILABLE_IN_ALL
+void ide_template_locator_set_license_text (IdeTemplateLocator *self,
+ const char *license_text);
+
+G_END_DECLS
diff --git a/src/libide/projects/libide-projects.h b/src/libide/projects/libide-projects.h
index 62e1cfc75..d025be15c 100644
--- a/src/libide/projects/libide-projects.h
+++ b/src/libide/projects/libide-projects.h
@@ -36,6 +36,8 @@
#include "ide-recent-projects.h"
#include "ide-similar-file-locator.h"
#include "ide-template-base.h"
+#include "ide-template-input.h"
+#include "ide-template-locator.h"
#include "ide-template-provider.h"
#undef IDE_PROJECTS_INSIDE
diff --git a/src/libide/projects/meson.build b/src/libide/projects/meson.build
index 2106e9264..afd47f716 100644
--- a/src/libide/projects/meson.build
+++ b/src/libide/projects/meson.build
@@ -17,6 +17,8 @@ libide_projects_public_headers = [
'ide-recent-projects.h',
'ide-similar-file-locator.h',
'ide-template-base.h',
+ 'ide-template-input.h',
+ 'ide-template-locator.h',
'ide-template-provider.h',
'libide-projects.h',
]
@@ -46,10 +48,18 @@ libide_projects_public_sources = [
'ide-recent-projects.c',
'ide-similar-file-locator.c',
'ide-template-base.c',
+ 'ide-template-input.c',
+ 'ide-template-locator.c',
'ide-template-provider.c',
]
-libide_projects_sources = libide_projects_public_sources + libide_projects_private_sources
+libide_projects_resources = gnome.compile_resources(
+ 'ide-projects-resources',
+ 'libide-projects.gresource.xml',
+ c_name: 'ide_projects',
+)
+
+libide_projects_sources = libide_projects_public_sources + libide_projects_private_sources +
libide_projects_resources
#
# Dependencies
@@ -65,6 +75,7 @@ libide_projects_deps = [
libide_core_dep,
libide_io_dep,
libide_threading_dep,
+ libide_tree_dep,
libide_vcs_dep,
]
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]