Skip to content

Commit

Permalink
filterx: implement flatten_dict()
Browse files Browse the repository at this point in the history
Signed-off-by: Attila Szakacs <attila.szakacs@axoflow.com>
  • Loading branch information
alltilla committed Jul 22, 2024
1 parent de19587 commit 97fb599
Showing 1 changed file with 232 additions and 2 deletions.
234 changes: 232 additions & 2 deletions lib/filterx/func-flatten-dict.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,253 @@
*/

#include "filterx/func-flatten-dict.h"
#include "filterx/object-dict-interface.h"
#include "filterx/object-primitive.h"
#include "filterx/object-string.h"
#include "filterx/filterx-eval.h"
#include "scratch-buffers.h"

#define FILTERX_FUNC_FLATTEN_DICT_USAGE "Usage: unset_empties(object, separator=\".\")"

typedef struct FilterXFunctionFlattenDict_
{
FilterXFunction super;
FilterXExpr *object_expr;
gchar *separator;
} FilterXFunctionFlattenDict;

typedef struct FilterXFunctionFlattenDictKV_
{
FilterXObject *key;
FilterXObject *value;
} FilterXFunctionFlattenDictKV;

static FilterXFunctionFlattenDictKV *
_kv_new(FilterXObject *key, FilterXObject *value)
{
FilterXFunctionFlattenDictKV *self = g_new0(FilterXFunctionFlattenDictKV, 1);
self->key = filterx_object_ref(key);
self->value = filterx_object_ref(value);
return self;
}

static void
_kv_free(FilterXFunctionFlattenDictKV *self)
{
filterx_object_unref(self->key);
filterx_object_unref(self->value);
g_free(self);
}

static gboolean
_collect_modifications_from_elem(FilterXObject *key, FilterXObject *value, gpointer user_data)
{
FilterXFunctionFlattenDict *self = ((gpointer *) user_data)[0];
GList **flattened_kvs = ((gpointer *) user_data)[1];
GList **top_level_dict_keys = ((gpointer *) user_data)[2];
GString *key_buffer = ((gpointer *) user_data)[3];
gboolean is_top_level = (gboolean) GPOINTER_TO_INT(((gpointer *) user_data)[4]);

if (filterx_object_is_type(value, &FILTERX_TYPE_NAME(dict)))
{
if (is_top_level)
*top_level_dict_keys = g_list_append(*top_level_dict_keys, filterx_object_ref(key));

gssize orig_len = key_buffer->len;
if (!filterx_object_repr_append(key, key_buffer))
{
filterx_eval_push_error("failed to call repr() on key", self->object_expr, key);
return FALSE;
}
g_string_append(key_buffer, self->separator);

gpointer inner_user_data[] = { self, flattened_kvs, NULL, key_buffer, GINT_TO_POINTER(FALSE)};
gboolean result = filterx_dict_iter(value, _collect_modifications_from_elem, inner_user_data);

g_string_truncate(key_buffer, orig_len);
return result;
}

if (is_top_level)
{
/* Top level leaf. This KV does not need flattening, we can keep it. */
return TRUE;
}

/* Not top level leaf. This KV needs flattening. */
gssize orig_len = key_buffer->len;
if (!filterx_object_repr_append(key, key_buffer))
{
filterx_eval_push_error("failed to call repr() on key", self->object_expr, key);
return FALSE;
}

FilterXObject *flat_key = filterx_string_new(key_buffer->str, (gssize) MIN(key_buffer->len, G_MAXSSIZE));
*flattened_kvs = g_list_append(*flattened_kvs, _kv_new(flat_key, value));
filterx_object_unref(flat_key);

g_string_truncate(key_buffer, orig_len);
return TRUE;
}

/* Also removes KVs that need to be moved. */
static gboolean
_collect_dict_modifications(FilterXFunctionFlattenDict *self, FilterXObject *dict,
GList **flattened_kvs, GList **top_level_dict_keys)
{
GString *key_buffer = scratch_buffers_alloc();
gpointer user_data[] = { self, flattened_kvs, top_level_dict_keys, key_buffer, GINT_TO_POINTER(TRUE)};
return filterx_dict_iter(dict, _collect_modifications_from_elem, user_data);
}

static gboolean
_remove_keys(FilterXFunctionFlattenDict *self, FilterXObject *dict, GList *keys)
{
for (GList *elem = keys; elem; elem = elem->next)
{
FilterXObject *key = (FilterXObject *) elem->data;

if (!filterx_object_unset_key(dict, key))
return FALSE;
}

return TRUE;
}

static gboolean
_add_kvs(FilterXFunctionFlattenDict *self, FilterXObject *dict, GList *kvs)
{
for (GList *elem = kvs; elem; elem = elem->next)
{
FilterXFunctionFlattenDictKV *kv = (FilterXFunctionFlattenDictKV *) elem->data;

/*
* We set the same value as it was before,
* no need to clone, as the old value is not used at this point.
*/
FilterXObject *value = filterx_object_ref(kv->value);
if (!filterx_object_set_subscript(dict, kv->key, &value))
{
filterx_eval_push_error("failed to set dict value", self->object_expr, kv->key);
filterx_object_unref(value);
return FALSE;
}
}

return TRUE;
}

static gboolean
_flatten(FilterXFunctionFlattenDict *self, FilterXObject *dict)
{
GList *flattened_kvs = NULL, *top_level_dict_keys = NULL;
gboolean result = FALSE;

if (!_collect_dict_modifications(self, dict, &flattened_kvs, &top_level_dict_keys))
goto exit;

if (!_remove_keys(self, dict, top_level_dict_keys))
goto exit;

if (!_add_kvs(self, dict, flattened_kvs))
goto exit;

result = TRUE;

exit:
g_list_free_full(flattened_kvs, (GDestroyNotify) _kv_free);
g_list_free_full(top_level_dict_keys, (GDestroyNotify) filterx_object_unref);
return result;
}

static FilterXObject *
_eval(FilterXExpr *s)
{
// TODO: implement
return NULL;
FilterXFunctionFlattenDict *self = (FilterXFunctionFlattenDict *) s;

FilterXObject *dict = filterx_expr_eval_typed(self->object_expr);
if (!dict)
return NULL;

gboolean result = FALSE;

if (!filterx_object_is_type(dict, &FILTERX_TYPE_NAME(dict)))
{
filterx_eval_push_error_info("object must be a dict", self->object_expr,
g_strdup_printf("got %s instead", dict->type->name), TRUE);
goto exit;
}

result = _flatten(self, dict);

exit:
filterx_object_unref(dict);
return result ? filterx_boolean_new(TRUE) : NULL;
}

static void
_free(FilterXExpr *s)
{
FilterXFunctionFlattenDict *self = (FilterXFunctionFlattenDict *) s;

filterx_expr_unref(self->object_expr);
g_free(self->separator);
filterx_function_free_method(&self->super);
}

static FilterXExpr *
_extract_object_arg(FilterXFunctionArgs *args, GError **error)
{
FilterXExpr *object_expr = filterx_function_args_get_expr(args, 0);
if (!object_expr)
{
g_set_error(error, FILTERX_FUNCTION_ERROR, FILTERX_FUNCTION_ERROR_CTOR_FAIL,
"argument must be set: object. " FILTERX_FUNC_FLATTEN_DICT_USAGE);
return NULL;
}

return object_expr;
}

static gchar *
_extract_separator_arg(FilterXFunctionArgs *args, GError **error)
{
gboolean exists;
const gchar *value = filterx_function_args_get_named_literal_string(args, "separator", NULL, &exists);
if (!exists)
return g_strdup(".");

if (!value)
{
g_set_error(error, FILTERX_FUNCTION_ERROR, FILTERX_FUNCTION_ERROR_CTOR_FAIL,
"separator argument must be string literal. " FILTERX_FUNC_FLATTEN_DICT_USAGE);
return NULL;
}

return g_strdup(value);
}

static gboolean
_extract_args(FilterXFunctionFlattenDict *self, FilterXFunctionArgs *args, GError **error)
{
if (filterx_function_args_len(args) != 1)
{
g_set_error(error, FILTERX_FUNCTION_ERROR, FILTERX_FUNCTION_ERROR_CTOR_FAIL,
"invalid number of arguments. " FILTERX_FUNC_FLATTEN_DICT_USAGE);
return FALSE;
}

self->object_expr = _extract_object_arg(args, error);
if (!self->object_expr)
return FALSE;

self->separator = _extract_separator_arg(args, error);
if (!self->separator)
return FALSE;

return TRUE;
}

FilterXFunction *
filterx_function_flatten_dict_new(const gchar *function_name, FilterXFunctionArgs *args, GError **error)
{
Expand All @@ -51,6 +278,9 @@ filterx_function_flatten_dict_new(const gchar *function_name, FilterXFunctionArg
self->super.super.eval = _eval;
self->super.super.free_fn = _free;

if (!_extract_args(self, args, error))
goto error;

filterx_function_args_free(args);
return &self->super;

Expand Down

0 comments on commit 97fb599

Please sign in to comment.