grim/gobquery

Initial revision basic query validation done
draft default tip
2021-10-20, Gary Kramlich
b3f3d1bce7d3
Parents
Children
Initial revision basic query validation done
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore Wed Oct 20 04:33:12 2021 -0500
@@ -0,0 +1,3 @@
+syntax: regexp
+^build(-.+)?/
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gobquery/gobquery-query.c Wed Oct 20 04:33:12 2021 -0500
@@ -0,0 +1,95 @@
+#include <gobquery-query.h>
+
+static gboolean
+gobquery_query_validate(gchar **query, GType type, GError **error) {
+ GObjectClass *obj_class = g_type_class_ref(type);
+ gint i = 0;
+
+ /* Walk through each item in the query, splitting it on the first :, and
+ * then look to see if a property exists for the type with that name.
+ */
+ for(i = 0; query[i] != NULL; i++) {
+ gchar **parts = g_strsplit(query[i], ":", 2);
+
+ if(!g_object_class_find_property(obj_class, parts[0])) {
+ g_set_error(error, GOBQUERY_DOMAIN, 0,
+ "%s has no property named %s",
+ g_type_name(type), parts[0]);
+
+ g_strfreev(parts);
+
+ return FALSE;
+ }
+
+ g_strfreev(parts);
+ }
+
+ g_type_class_unref(obj_class);
+
+ return TRUE;
+}
+
+static gboolean
+gobquery_query_matches(gchar **query, GObject *object) {
+ return FALSE;
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+gboolean
+gobquery_query_valid(const gchar *query, GType type, GError **error) {
+ gchar **parts = NULL;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail(query != NULL, FALSE);
+ g_return_val_if_fail(g_type_is_a(type, G_TYPE_OBJECT), FALSE);
+
+ if(!g_shell_parse_argv(query, NULL, &parts, error)) {
+ return FALSE;
+ }
+
+ ret = gobquery_query_validate(parts, type, error);
+
+ g_strfreev(parts);
+
+ return ret;
+}
+
+GListStore *
+gobquery_query_list(GListModel *model, const gchar *query, GError **error) {
+ GListStore *store = NULL;
+ GType type = G_TYPE_NONE;
+ gchar **parts = NULL;
+ guint i = 0, items = 0;
+
+ g_return_val_if_fail(G_IS_LIST_MODEL(model), NULL);
+ g_return_val_if_fail(query != NULL, NULL);
+
+ type = g_list_model_get_item_type(model);
+
+ if(!g_shell_parse_argv(query, NULL, &parts, error)) {
+ return NULL;
+ }
+
+ if(!gobquery_query_validate(parts, type, error)) {
+ g_strfreev(parts);
+
+ return NULL;
+ }
+
+ store = g_list_store_new(type);
+
+ items = g_list_model_get_n_items(model);
+ for(i = 0; i < items; i++) {
+ GObject *object = g_list_model_get_item(model, i);
+
+ if(gobquery_query_matches(parts, object)) {
+ g_list_store_insert(store, 0, object);
+ }
+ }
+
+ g_strfreev(parts);
+
+ return store;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gobquery/gobquery-query.h Wed Oct 20 04:33:12 2021 -0500
@@ -0,0 +1,42 @@
+#ifndef GOBQUERY_QUERY_H
+#define GOBQUERY_QUERY_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GOBQUERY_DOMAIN (g_quark_from_static_string("gobquery"))
+
+/**
+ * gobquery_query_valid:
+ * @query: The query string.
+ * @type: The GType that's being queried.
+ * @error: A return address from an error.
+ *
+ * Validates that @query is valid for @type. This is useful for showing
+ * validation in an entry before querying a large list. If the list is small,
+ * it might be easier to just call gobquery_query_list().
+ *
+ * Returns: %TRUE if @query is valid for @type, otherwise %FALSE with @error
+ * set.
+ */
+gboolean gobquery_query_valid(const gchar *query, GType type, GError **error);
+
+/**
+ * gobquery_query_list:
+ * @model: The list model to query.
+ * @query: The query string.
+ * @error: A return address for an error.
+ *
+ * Run @query against @model and return a new GListModel with the results.
+ *
+ * Returns: (transfer full): The results or NULL on error with @error set.
+ */
+GListStore *gobquery_query_list(GListModel *model, const gchar *query, GError **error);
+
+G_END_DECLS
+
+#endif /* GOBQUERY_QUERY_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gobquery/meson.build Wed Oct 20 04:33:12 2021 -0500
@@ -0,0 +1,19 @@
+gobquery_sources = [
+ 'gobquery-query.c',
+ 'gobquery-query.h',
+]
+
+libgobquery = library(
+ 'gobquery',
+ gobquery_sources,
+ dependencies : [GIO, GLIB, GOBJECT],
+)
+
+libgobquery_dep = declare_dependency(
+ sources : gobquery_sources,
+ include_directories : include_directories('.'),
+ link_with : libgobquery,
+ dependencies : [GIO, GLIB, GOBJECT],
+)
+
+subdir('tests')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gobquery/tests/meson.build Wed Oct 20 04:33:12 2021 -0500
@@ -0,0 +1,7 @@
+e = executable(
+ 'test-gobquery-query',
+ 'test-gobquery-query.c',
+ dependencies : [libgobquery_dep, GLIB, GIO, GOBJECT]
+)
+
+test('test-gobquery-query', e)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gobquery/tests/test-gobquery-query.c Wed Oct 20 04:33:12 2021 -0500
@@ -0,0 +1,218 @@
+#include <glib.h>
+
+#include <gobquery-query.h>
+
+/******************************************************************************
+ * Test Objects
+ *****************************************************************************/
+enum {
+ PROP_0,
+ PROP_ENABLED,
+ PROP_NAME,
+ N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+#define TEST_GOBQUERY_TYPE_OBJECT (test_gobquery_object_get_type())
+G_DECLARE_FINAL_TYPE(TestGOBQueryObject, test_gobquery_object, TEST_GOBQUERY,
+ OBJECT, GObject)
+
+struct _TestGOBQueryObject {
+ GObject parent;
+
+ gboolean enabled;
+ gchar *name;
+};
+
+G_DEFINE_TYPE(TestGOBQueryObject, test_gobquery_object, G_TYPE_OBJECT)
+
+static void
+test_gobquery_object_get_property(GObject *obj, guint param_id, GValue *value,
+ GParamSpec *pspec)
+{
+ TestGOBQueryObject *object = TEST_GOBQUERY_OBJECT(obj);
+
+ switch(param_id) {
+ case PROP_ENABLED:
+ g_value_set_boolean(value, object->enabled);
+ break;
+ case PROP_NAME:
+ g_value_set_string(value, object->name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+test_gobquery_object_set_property(GObject *obj, guint param_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ TestGOBQueryObject *object = TEST_GOBQUERY_OBJECT(obj);
+
+ switch(param_id) {
+ case PROP_ENABLED:
+ object->enabled = g_value_get_boolean(value);
+ break;
+ case PROP_NAME:
+ object->name = g_value_dup_string(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+test_gobquery_object_init(TestGOBQueryObject *object) {
+}
+
+static void
+test_gobquery_object_finalize(GObject *obj) {
+ TestGOBQueryObject *object = TEST_GOBQUERY_OBJECT(obj);
+
+ g_clear_pointer(&object->name, g_free);
+
+ G_OBJECT_CLASS(test_gobquery_object_parent_class)->finalize(obj);
+}
+
+static void
+test_gobquery_object_class_init(TestGOBQueryObjectClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+ obj_class->finalize = test_gobquery_object_finalize;
+ obj_class->get_property = test_gobquery_object_get_property;
+ obj_class->set_property = test_gobquery_object_set_property;
+
+ properties[PROP_ENABLED] = g_param_spec_boolean(
+ "enabled", "enabled", "whether this object is enabled",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_NAME] = g_param_spec_string(
+ "name", "name", "the name of this object",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_gobquery_query_valid_null(void) {
+ if(g_test_subprocess()) {
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ /* This test should trigger a g_return_val_if_fail which will return
+ * False, not set the error, but will output a warning on stderr.
+ */
+ ret = gobquery_query_valid(NULL, TEST_GOBQUERY_TYPE_OBJECT, &error);
+ g_assert_no_error(error);
+ g_clear_error(&error);
+
+ g_assert_false(ret);
+ }
+
+ g_test_trap_subprocess(NULL, 0, 0);
+ g_test_trap_assert_stderr("*CRITICAL*query != NULL*");
+}
+
+static void
+test_gobquery_query_valid_empty(void) {
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ ret = gobquery_query_valid("", TEST_GOBQUERY_TYPE_OBJECT, &error);
+
+ g_assert_error(error, G_SHELL_ERROR, 1);
+ g_clear_error(&error);
+
+ g_assert_false(ret);
+}
+
+static void
+test_gobquery_query_valid_single(void) {
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ ret = gobquery_query_valid("enabled:true",
+ TEST_GOBQUERY_TYPE_OBJECT,
+ &error);
+
+ g_assert_no_error(error);
+ g_clear_error(&error);
+
+ g_assert_true(ret);
+}
+
+static void
+test_gobquery_query_valid_multiple(void) {
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ ret = gobquery_query_valid("enabled:true name:foo",
+ TEST_GOBQUERY_TYPE_OBJECT,
+ &error);
+
+ g_assert_no_error(error);
+ g_clear_error(&error);
+
+ g_assert_true(ret);
+}
+
+static void
+test_gobquery_query_valid_quoted(void) {
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ ret = gobquery_query_valid("name:\"foo bar\"",
+ TEST_GOBQUERY_TYPE_OBJECT,
+ &error);
+
+ g_assert_no_error(error);
+ g_clear_error(&error);
+
+ g_assert_true(ret);
+}
+
+static void
+test_gobquery_query_valid_unknown(void) {
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ ret = gobquery_query_valid("foo:bar",
+ TEST_GOBQUERY_TYPE_OBJECT,
+ &error);
+
+ g_assert_error(error, GOBQUERY_DOMAIN, 0);
+ g_clear_error(&error);
+
+ g_assert_false(ret);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar *argv[]) {
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/gobquery/query-valid/null",
+ test_gobquery_query_valid_null);
+ g_test_add_func("/gobquery/query-valid/empty",
+ test_gobquery_query_valid_empty);
+ g_test_add_func("/gobquery/query-valid/single",
+ test_gobquery_query_valid_single);
+ g_test_add_func("/gobquery/query-valid/multiple",
+ test_gobquery_query_valid_multiple);
+ g_test_add_func("/gobquery/query-valid/quoted",
+ test_gobquery_query_valid_quoted);
+ g_test_add_func("/gobquery/query-valid/unknown",
+ test_gobquery_query_valid_unknown);
+
+ return g_test_run();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/meson.build Wed Oct 20 04:33:12 2021 -0500
@@ -0,0 +1,33 @@
+project(
+ 'gobquery',
+ 'c',
+ license : 'LGPL-2.0-or-later',
+ version : '0.1.0-dev',
+ meson_version : '>=0.56.0',
+ default_options : ['c_std=c99']
+)
+
+parts = meson.project_version().split('-')
+if parts.length() > 1
+ extra = parts[1]
+else
+ extra = ''
+endif
+
+parts = parts[0].split('.')
+GOBQUERY_MAJOR_VERSION = parts[0]
+
+version_conf = configuration_data()
+version_conf.set('GOBQUERY_MAJOR_VERSION', GOBQUERY_MAJOR_VERSION)
+version_conf.set('GOBQUERY_MINOR_VERSION', parts[1])
+version_conf.set('GOBQUERY_MICRO_VERSION', parts[2])
+version_conf.set('GOBQUERY_EXTRA_VERSION', extra)
+version_conf.set('GOBQUERY_VERSION', meson.project_version())
+
+gnome = import('gnome')
+
+GLIB = dependency('glib-2.0', version: '>=2.64.0')
+GIO = dependency('gio-2.0', version: '>=2.64.0')
+GOBJECT = dependency('gobject-2.0', version: '>=2.64.0')
+
+subdir('gobquery')