# HG changeset patch
# Node ID d822d3ada92b5b853c699c52568edd7bdc10ac68
# Parent  d9905faa5d3f833eaa9e921794a9e72839d787f3
diff --git a/ChangeLog b/ChangeLog
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,4 @@
-0.3.1: ????-??-??
+0.4.0: ????-??-??
   * Nothing yet, be the first!
 
 0.3.0: 2024-07-06
diff --git a/ibis/ibisclient.c b/ibis/ibisclient.c
--- a/ibis/ibisclient.c
+++ b/ibis/ibisclient.c
@@ -195,22 +195,20 @@
 
 		g_clear_error(&error);
 	} else if(IBIS_IS_MESSAGE(message)) {
-		char *upper_command = NULL;
+		GQuark quark = 0;
 		const char *command = NULL;
 		gboolean handled = FALSE;
 
-		command = ibis_message_get_command(message);
-		upper_command = g_utf8_strup(command, -1);
+		/* We use the quark as we know it's been normalized. */
+		quark = ibis_message_get_command_quark(message);
+		command = g_quark_to_string(quark);
 
 		if(!client->connected && ibis_str_equal(command, IBIS_RPL_WELCOME)) {
 			ibis_client_set_connected(client, TRUE);
 		}
 
-		g_signal_emit(client, signals[SIG_MESSAGE],
-		              g_quark_from_string(upper_command), upper_command,
-		              message, &handled);
-
-		g_clear_pointer(&upper_command, g_free);
+		g_signal_emit(client, signals[SIG_MESSAGE], quark, command, message,
+		              &handled);
 
 		if(!handled) {
 			g_warning("unhandled message: '%s'", line);
diff --git a/ibis/ibismessage.h b/ibis/ibismessage.h
--- a/ibis/ibismessage.h
+++ b/ibis/ibismessage.h
@@ -79,6 +79,22 @@
 const char *ibis_message_get_command(IbisMessage *message);
 
 /**
+ * ibis_message_get_command_quark:
+ * @message: The instance.
+ *
+ * Gets the #GQuark for @message.
+ *
+ * This value is used when emitting signals like [signal@Client::message] and
+ * isn't typically needed outside of Ibis itself.
+ *
+ * Returns: The quark.
+ *
+ * Since: 0.4
+ */
+IBIS_AVAILABLE_IN_0_4
+GQuark ibis_message_get_command_quark(IbisMessage *message);
+
+/**
  * ibis_message_get_ctcp:
  * @message: The instance.
  *
diff --git a/ibis/ibismessage.c b/ibis/ibismessage.c
--- a/ibis/ibismessage.c
+++ b/ibis/ibismessage.c
@@ -30,6 +30,7 @@
 enum {
 	PROP_0,
 	PROP_COMMAND,
+	PROP_COMMAND_QUARK,
 	PROP_SOURCE,
 	PROP_PARAMS,
 	PROP_RAW_MESSAGE,
@@ -46,6 +47,8 @@
 	char *raw_message;
 
 	char *command;
+	GQuark command_quark;
+
 	GStrv params;
 	char *source;
 	IbisTags *tags;
@@ -153,11 +156,26 @@
  *****************************************************************************/
 static void
 ibis_message_set_command(IbisMessage *message, const char *command) {
+	char *upper_command = NULL;
+
 	g_return_if_fail(IBIS_IS_MESSAGE(message));
 
-	if(g_set_str(&message->command, command)) {
-		g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_COMMAND]);
+	if(command != NULL) {
+		upper_command = g_utf8_strup(command, -1);
 	}
+
+	if(g_set_str(&message->command, upper_command)) {
+		GObject *obj = G_OBJECT(message);
+
+		message->command_quark = g_quark_from_string(upper_command);
+
+		g_object_freeze_notify(obj);
+		g_object_notify_by_pspec(obj, properties[PROP_COMMAND]);
+		g_object_notify_by_pspec(obj, properties[PROP_COMMAND_QUARK]);
+		g_object_thaw_notify(obj);
+	}
+
+	g_clear_pointer(&upper_command, g_free);
 }
 
 static void
@@ -262,6 +280,9 @@
 	case PROP_COMMAND:
 		g_value_set_string(value, ibis_message_get_command(message));
 		break;
+	case PROP_COMMAND_QUARK:
+		g_value_set_uint(value, ibis_message_get_command_quark(message));
+		break;
 	case PROP_PARAMS:
 		g_value_set_boxed(value, ibis_message_get_params(message));
 		break;
@@ -339,6 +360,9 @@
 	 *
 	 * This could be something like JOIN or a server reply numeric like 005.
 	 *
+	 * As of version 0.4, this will now be automatically noramlized to
+	 * uppercase to help with comparison.
+	 *
 	 * Since: 0.1
 	 */
 	properties[PROP_COMMAND] = g_param_spec_string(
@@ -347,6 +371,21 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
 	/**
+	 * IbisMessage:command-quark:
+	 *
+	 * The #GQuark for the command of this message.
+	 *
+	 * This is typically only used for emitting signals like
+	 * [signal@Client::message].
+	 *
+	 * Since: 0.4
+	 */
+	properties[PROP_COMMAND_QUARK] = g_param_spec_string(
+		"command-quark", NULL, NULL,
+		NULL,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * IbisMessage:params:
 	 *
 	 * The parameters of the message.
@@ -453,6 +492,13 @@
 	return message->command;
 }
 
+GQuark
+ibis_message_get_command_quark(IbisMessage *message) {
+	g_return_val_if_fail(IBIS_IS_MESSAGE(message), 0);
+
+	return message->command_quark;
+}
+
 const char *
 ibis_message_get_source(IbisMessage *message) {
 	g_return_val_if_fail(IBIS_IS_MESSAGE(message), NULL);
diff --git a/ibis/ibisversion.h b/ibis/ibisversion.h
--- a/ibis/ibisversion.h
+++ b/ibis/ibisversion.h
@@ -197,6 +197,30 @@
 #define IBIS_AVAILABLE_TYPE_IN_0_3
 #endif
 
+/**
+ * IBIS_VERSION_0_4:
+ *
+ * A macro that evaluates to the 0.4 version of ibis, in a format that can be
+ * used by the C pre-processor.
+ *
+ * Since: 0.4
+ */
+#define IBIS_VERSION_0_4 (G_ENCODE_VERSION(0, 4))
+
+#if IBIS_VERSION_MAX_ALLOWED < IBIS_VERSION_0_4
+#define IBIS_AVAILABLE_IN_0_4 IBIS_UNAVAILABLE(0, 4)
+#define IBIS_AVAILABLE_STATIC_INLINE_IN_0_4 IBIS_UNAVAILABLE_STATIC_INLINE(0, 4)
+#define IBIS_AVAILABLE_MACRO_IN_0_4 IBIS_UNAVAILABLE_MACRO(0, 4)
+#define IBIS_AVAILABLE_ENUMERATOR_IN_0_4 IBIS_UNAVAILABLE_ENUMERATOR(0, 4)
+#define IBIS_AVAILABLE_TYPE_IN_0_4 IBIS_UNAVAILABLE_TYPE(0, 4)
+#else
+#define IBIS_AVAILABLE_IN_0_4 _IBIS_EXTERN
+#define IBIS_AVAILABLE_STATIC_INLINE_IN_0_4
+#define IBIS_AVAILABLE_MACRO_IN_0_4
+#define IBIS_AVAILABLE_ENUMERATOR_IN_0_4
+#define IBIS_AVAILABLE_TYPE_IN_0_4
+#endif
+
 G_BEGIN_DECLS
 
 /**
diff --git a/ibis/tests/test_message.c b/ibis/tests/test_message.c
--- a/ibis/tests/test_message.c
+++ b/ibis/tests/test_message.c
@@ -87,7 +87,7 @@
 		"error", &error2,
 		NULL);
 
-	g_assert_cmpstr(command, ==, "command");
+	g_assert_cmpstr(command, ==, "COMMAND");
 	g_clear_pointer(&command, g_free);
 
 	g_assert_cmpstrv(params2, params1);
@@ -130,6 +130,24 @@
 }
 
 static void
+test_ibis_message_command_quark(void) {
+	IbisMessage *message = NULL;
+	GQuark privmsg = g_quark_from_static_string(IBIS_MSG_PRIVMSG);
+	GQuark mixed_case = g_quark_from_static_string("PrIvMsg");
+	GQuark notice = g_quark_from_static_string(IBIS_MSG_NOTICE);
+	GQuark quark = 0;
+
+	message = ibis_message_new(IBIS_MSG_PRIVMSG);
+	quark = ibis_message_get_command_quark(message);
+
+	g_assert_cmpuint(quark, ==, privmsg);
+	g_assert_cmpuint(quark, !=, mixed_case);
+	g_assert_cmpuint(quark, !=, notice);
+
+	g_assert_finalize_object(message);
+}
+
+static void
 test_ibis_message_set_ctcp_privmsg(void) {
 	IbisMessage *message = NULL;
 
@@ -301,7 +319,7 @@
 	message = ibis_message_new("command");
 
 	serialized = ibis_message_serialize_to_string(message);
-	g_assert_cmpstr(serialized, ==, "command\r\n");
+	g_assert_cmpstr(serialized, ==, "COMMAND\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -457,7 +475,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, "foo bar baz asdf\r\n");
+	g_assert_cmpstr(serialized, ==, "FOO bar baz asdf\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -505,7 +523,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, ":coolguy foo bar baz asdf\r\n");
+	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz asdf\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -521,7 +539,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, "foo bar baz :asdf quux\r\n");
+	g_assert_cmpstr(serialized, ==, "FOO bar baz :asdf quux\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -537,7 +555,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, "foo bar baz :\r\n");
+	g_assert_cmpstr(serialized, ==, "FOO bar baz :\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -553,7 +571,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, "foo bar baz ::asdf\r\n");
+	g_assert_cmpstr(serialized, ==, "FOO bar baz ::asdf\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -570,7 +588,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, ":coolguy foo bar baz :asdf quux\r\n");
+	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz :asdf quux\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -588,7 +606,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, ":coolguy foo bar baz :  asdf quux \r\n");
+	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz :  asdf quux \r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -622,7 +640,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, ":coolguy foo bar baz :\r\n");
+	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz :\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -640,7 +658,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, ":coolguy foo bar baz :  \r\n");
+	g_assert_cmpstr(serialized, ==, ":coolguy FOO bar baz :  \r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -657,7 +675,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, ":coolguy foo b\tar baz\r\n");
+	g_assert_cmpstr(serialized, ==, ":coolguy FOO b\tar baz\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -679,7 +697,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, "@asd :coolguy foo bar baz :  \r\n");
+	g_assert_cmpstr(serialized, ==, "@asd :coolguy FOO bar baz :  \r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -702,7 +720,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, "@asd :coolguy foo bar baz :  \r\n");
+	g_assert_cmpstr(serialized, ==, "@asd :coolguy FOO bar baz :  \r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -724,7 +742,7 @@
 
 	serialized = ibis_message_serialize_to_string(message);
 
-	g_assert_cmpstr(serialized, ==, "@a=b\\\\and\\nk;d=gh\\:764 foo\r\n");
+	g_assert_cmpstr(serialized, ==, "@a=b\\\\and\\nk;d=gh\\:764 FOO\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -748,7 +766,7 @@
 	serialized = ibis_message_serialize_to_string(message);
 
 	g_assert_cmpstr(serialized, ==,
-	                "@a=b\\\\and\\nk;d=gh\\:764 foo par1 par2\r\n");
+	                "@a=b\\\\and\\nk;d=gh\\:764 FOO par1 par2\r\n");
 
 	g_clear_pointer(&serialized, g_free);
 	g_assert_finalize_object(message);
@@ -787,6 +805,8 @@
 	g_test_add_func("/ibis/message/new", test_ibis_message_new);
 	g_test_add_func("/ibis/message/properties", test_ibis_message_properties);
 	g_test_add_func("/ibis/message/take-error", test_ibis_message_take_error);
+	g_test_add_func("/ibis/message/command-quark",
+	                test_ibis_message_command_quark);
 
 	g_test_add_func("/ibis/message/set_ctcp/privmsg",
 	                test_ibis_message_set_ctcp_privmsg);
diff --git a/ibis/tests/test_parser.c b/ibis/tests/test_parser.c
--- a/ibis/tests/test_parser.c
+++ b/ibis/tests/test_parser.c
@@ -44,6 +44,7 @@
 	GError *error = NULL;
 	GStrv params = NULL;
 	const char *command = NULL;
+	char *upper_command = NULL;
 	const char *raw_message = NULL;
 	const char *source = NULL;
 	guint n_params = 0;
@@ -55,8 +56,12 @@
 	raw_message = ibis_message_get_raw_message(message);
 	g_assert_cmpstr(raw_message, ==, raw);
 
+	if(d->command != NULL) {
+		upper_command = g_utf8_strup(d->command, -1);
+	}
 	command = ibis_message_get_command(message);
-	g_assert_cmpstr(command, ==, d->command);
+	g_assert_cmpstr(command, ==, upper_command);
+	g_clear_pointer(&upper_command, g_free);
 
 	source = ibis_message_get_source(message);
 	g_assert_cmpstr(source, ==, d->source);
diff --git a/meson.build b/meson.build
--- a/meson.build
+++ b/meson.build
@@ -2,7 +2,7 @@
   default_options : ['c_std=c17', 'warning_level=2'],
   license : 'LGPL-2.1-or-later',
   meson_version : '>=1.0.0',
-  version : '0.3.1-dev')
+  version : '0.4.0-dev')
 
 toplevel_inc = include_directories('.')
 
