Create the PurpleTags object for handling tags
Testing Done:
Ran the unit tests.
Bugs closed: PIDGIN-17664
Reviewed at https://reviews.imfreedom.org/r/1777/
--- a/libpurple/meson.build Thu Sep 22 23:26:05 2022 -0500
+++ b/libpurple/meson.build Fri Sep 23 00:13:38 2022 -0500
@@ -83,6 +83,7 @@
'purplesqlitehistoryadapter.c',
'purplewhiteboardmanager.c',
@@ -185,6 +186,7 @@
'purplesqlitehistoryadapter.h',
'purplewhiteboardmanager.h',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purpletags.c Fri Sep 23 00:13:38 2022 -0500
@@ -0,0 +1,177 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 2 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 <https://www.gnu.org/licenses/>. +G_DEFINE_TYPE(PurpleTags, purple_tags, G_TYPE_OBJECT) +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +purple_tags_dispose(GObject *obj) { + PurpleTags *tags = PURPLE_TAGS(obj); + if(tags->tags != NULL) { + g_list_free_full(tags->tags, g_free); + G_OBJECT_CLASS(purple_tags_parent_class)->dispose(obj); +purple_tags_init(PurpleTags *tags) { +purple_tags_class_init(PurpleTagsClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->dispose = purple_tags_dispose; +/****************************************************************************** + *****************************************************************************/ + return g_object_new(PURPLE_TYPE_TAGS, NULL); +purple_tags_lookup(PurpleTags *tags, const gchar *name, gboolean *found) { + g_return_val_if_fail(PURPLE_IS_TAGS(tags), FALSE); + g_return_val_if_fail(name != NULL, FALSE); + /* Assume we're going to find the tag, if we don't we set found to false + * before we return. This sounds silly, but it saves some additional logic + name_len = strlen(name); + for(GList *l = tags->tags; l != NULL; l = l->next) { + const gchar *tag = l->data; + if(g_str_has_prefix(tag, name)) { + const gchar *value = tag + name_len; + } else if(*value == ':') { + /* We didn't find the tag, so set found to false if necessary. */ +purple_tags_get(PurpleTags *tags, const gchar *name) { + g_return_val_if_fail(PURPLE_IS_TAGS(tags), NULL); + g_return_val_if_fail(name != NULL, NULL); + return purple_tags_lookup(tags, name, NULL); +purple_tags_add(PurpleTags *tags, const gchar *tag) { + g_return_if_fail(PURPLE_IS_TAGS(tags)); + g_return_if_fail(tag != NULL); + tags->tags = g_list_append(tags->tags, g_strdup(tag)); +purple_tags_remove(PurpleTags *tags, const gchar *tag) { + g_return_val_if_fail(PURPLE_IS_TAGS(tags), FALSE); + g_return_val_if_fail(tag != NULL, FALSE); + for(GList *l = tags->tags; l != NULL; l = l->next) { + if(purple_strequal(etag, tag)) { + tags->tags = g_list_delete_link(tags->tags, l); +purple_tags_get_count(PurpleTags *tags) { + g_return_val_if_fail(PURPLE_IS_TAGS(tags), 0); + return g_list_length(tags->tags); +purple_tags_get_all(PurpleTags *tags) { + g_return_val_if_fail(PURPLE_IS_TAGS(tags), NULL); +purple_tags_to_string(PurpleTags *tags, const gchar *separator) { + g_return_val_if_fail(PURPLE_IS_TAGS(tags), NULL); + value = g_string_new(""); + for(GList *l = tags->tags; l != NULL; l = l->next) { + const gchar *tag = l->data; + g_string_append(value, tag); + if(separator != NULL && l->next != NULL) { + g_string_append(value, separator); + return g_string_free(value, FALSE); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purpletags.h Fri Sep 23 00:13:38 2022 -0500
@@ -0,0 +1,157 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 2 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 <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION) +# error "only <purple.h> may be included directly" +#define PURPLE_TYPE_TAGS (purple_tags_get_type()) +G_DECLARE_FINAL_TYPE(PurpleTags, purple_tags, PURPLE, TAGS, GObject) + * Tags is an object that can be used to keep track of arbitrary tags on + * objects. Tags are simple strings that use the first ':' to delimit a value. + * For example: `foo` is a tag with just a name and no value, but `foo:bar` is + * a tag with a name and value. Please note this distinction when the API calls + * for a name versus a tag which would be the name and the value. + * Creates a new tags object. + * Returns: (transfer full): The new tags object. +PurpleTags *purple_tags_new(void); + * @name: The name of the tag to check if it exists. + * @found: (out) (nullable): A return address for a boolean on whether the tag + * Gets the value of @name if it exists in @tags. + * If @found is non %NULL, it will be set to %TRUE if @name was found. + * Returns: The value of the first tag matching @name. If there is no value, +const gchar *purple_tags_lookup(PurpleTags *tags, const gchar *name, gboolean *found); + * @name: The name of the tag to get. + * Gets the first tag that matches @name. + * Returns: The value of the first tag matching @name. If there is no value, +const gchar *purple_tags_get(PurpleTags *tags, const gchar *name); +void purple_tags_add(PurpleTags *tags, const gchar *tag); + * Removes the first occurrence of @tag from @tags. Note that this is the tag + * name and value not just the name. + * Returns: %TRUE if @tag was found and removed, otherwise %FALSE. +gboolean purple_tags_remove(PurpleTags *tags, const gchar *tag); + * purple_tags_get_count: + * Gets the number of tags in @tags. + * Returns: The number of tags that @tags is keeping track of. +guint purple_tags_get_count(PurpleTags *tags); + * Gets a list of all the tags. + * Returns: (transfer none) (element-type utf8): The list of all the tags. +GList *purple_tags_get_all(PurpleTags *tags); + * purple_tags_to_string: + * @separator: (nullable): A string to separate the items. + * Creates a @separator delimited string containing each item in @tags. + * Returns: (transfer full): The string representation. +gchar *purple_tags_to_string(PurpleTags *tags, const gchar *separator); +#endif /* PURPLE_TAGS_H */ --- a/libpurple/tests/meson.build Thu Sep 22 23:26:05 2022 -0500
+++ b/libpurple/tests/meson.build Fri Sep 23 00:13:38 2022 -0500
@@ -16,6 +16,7 @@
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_tags.c Fri Sep 23 00:13:38 2022 -0500
@@ -0,0 +1,285 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * 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 2 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 <https://www.gnu.org/licenses/>. +/****************************************************************************** + *****************************************************************************/ +test_purple_tags_lookup_exists(void) { + PurpleTags *tags = purple_tags_new(); + gboolean found = FALSE; + const gchar *value = NULL; + purple_tags_add(tags, "foo"); + value = purple_tags_lookup(tags, "foo", &found); + purple_tags_add(tags, "bar:baz"); + value = purple_tags_lookup(tags, "bar", &found); + g_assert_cmpstr(value, ==, "baz"); + /* make sure that a name of pur doesn't match a tag of purple */ + purple_tags_add(tags, "purple"); + value = purple_tags_lookup(tags, "pur", &found); +test_purple_tags_lookup_non_existent(void) { + PurpleTags *tags = purple_tags_new(); + gboolean found = FALSE; + value = purple_tags_lookup(tags, "foo", &found); +test_purple_tags_add_remove_bare(void) { + PurpleTags *tags = purple_tags_new(); + purple_tags_add(tags, "tag1"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 1); + purple_tags_remove(tags, "tag1"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 0); +test_purple_tags_add_duplicate_bare(void) { + PurpleTags *tags = purple_tags_new(); + purple_tags_add(tags, "tag1"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 1); + purple_tags_add(tags, "tag1"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 2); +test_purple_tags_remove_non_existent_bare(void) { + PurpleTags *tags = purple_tags_new(); + purple_tags_remove(tags, "tag1"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 0); +test_purple_tags_add_remove_with_value(void) { + PurpleTags *tags = purple_tags_new(); + purple_tags_add(tags, "tag1:purple"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 1); + purple_tags_remove(tags, "tag1:purple"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 0); +test_purple_tags_add_duplicate_with_value(void) { + PurpleTags *tags = purple_tags_new(); + purple_tags_add(tags, "tag1:purple"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 1); + purple_tags_add(tags, "tag1:purple"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 2); +test_purple_tags_remove_non_existent_with_value(void) { + PurpleTags *tags = purple_tags_new(); + purple_tags_remove(tags, "tag1:purple"); + g_assert_cmpuint(purple_tags_get_count(tags), ==, 0); +test_purple_tags_get_single(void) { + PurpleTags *tags = purple_tags_new(); + const gchar *value = NULL; + purple_tags_add(tags, "tag1:purple"); + value = purple_tags_get(tags, "tag1"); + g_assert_cmpstr(value, ==, "purple"); +test_purple_tags_get_multiple(void) { + PurpleTags *tags = purple_tags_new(); + const gchar *value = NULL; + purple_tags_add(tags, "tag1:purple"); + purple_tags_add(tags, "tag1:pink"); + value = purple_tags_get(tags, "tag1"); + g_assert_cmpstr(value, ==, "purple"); +test_purple_tags_get_all(void) { + PurpleTags *tags = purple_tags_new(); + GList *all_tags = NULL; + const gchar *values[] = {"foo", "bar", "baz", "qux", "quux", NULL}; + for(i = 0; values[i] != NULL; i++) { + purple_tags_add(tags, values[i]); + all_tags = purple_tags_get_all(tags); + for(GList *l = all_tags; l != NULL; l = l->next) { + const gchar *value = l->data; + g_assert_cmpint(i, <, len); + g_assert_cmpstr(value, ==, values[i]); + g_assert_cmpint(i, ==, len); +test_purple_tags_to_string_single(void) { + PurpleTags *tags = purple_tags_new(); + purple_tags_add(tags, "foo"); + value = purple_tags_to_string(tags, NULL); + g_assert_cmpstr(value, ==, "foo"); +test_purple_tags_to_string_multiple_with_separator(void) { + PurpleTags *tags = purple_tags_new(); + purple_tags_add(tags, "foo"); + purple_tags_add(tags, "bar"); + purple_tags_add(tags, "baz"); + value = purple_tags_to_string(tags, ", "); + g_assert_cmpstr(value, ==, "foo, bar, baz"); +test_purple_tags_to_string_multiple_with_null_separator(void) { + PurpleTags *tags = purple_tags_new(); + purple_tags_add(tags, "foo"); + purple_tags_add(tags, "bar"); + purple_tags_add(tags, "baz"); + value = purple_tags_to_string(tags, NULL); + g_assert_cmpstr(value, ==, "foobarbaz"); +/****************************************************************************** + *****************************************************************************/ +main(gint argc, gchar **argv) { + g_test_init(&argc, &argv, NULL); + g_test_add_func("/tags/lookup-exists", test_purple_tags_lookup_exists); + g_test_add_func("/tags/lookup-non-existent", + test_purple_tags_lookup_non_existent); + g_test_add_func("/tags/add-remove-bare", + test_purple_tags_add_remove_bare); + g_test_add_func("/tags/add-duplicate-bare", + test_purple_tags_add_duplicate_bare); + g_test_add_func("/tags/remove-non-existent-bare", + test_purple_tags_remove_non_existent_bare); + g_test_add_func("/tags/add-remove-with-value", + test_purple_tags_add_remove_with_value); + g_test_add_func("/tags/add-duplicate-with-value", + test_purple_tags_add_duplicate_with_value); + g_test_add_func("/tags/remove-non-existent-with-value", + test_purple_tags_remove_non_existent_with_value); + g_test_add_func("/tags/get-single", test_purple_tags_get_single); + g_test_add_func("/tags/get-multiple", test_purple_tags_get_multiple); + g_test_add_func("/tags/get-all", test_purple_tags_get_all); + g_test_add_func("/tags/to-string-single", + test_purple_tags_to_string_single); + g_test_add_func("/tags/to-string-multiple-with-separator", + test_purple_tags_to_string_multiple_with_separator); + g_test_add_func("/tags/to-string-multiple-with-null-separator", + test_purple_tags_to_string_multiple_with_null_separator);