# HG changeset patch
# Node ID b24e4726c32c
# Parent  02886594438d
diff --git a/ibis/ibisclient.c b/ibis/ibisclient.c
--- a/ibis/ibisclient.c
+++ b/ibis/ibisclient.c
@@ -144,7 +144,7 @@
 		return;
 	}
 
-	parsed = ibis_parser_parse(client->parser, line, &error, client);
+	parsed = ibis_parser_parse(client->parser, line, &error);
 	if(!parsed) {
 		g_warning("failed to handle '%s': %s", line,
 		          error != NULL ? error->message : "unknown error");
diff --git a/ibis/ibismessage.h b/ibis/ibismessage.h
--- a/ibis/ibismessage.h
+++ b/ibis/ibismessage.h
@@ -203,6 +203,43 @@
 IBIS_AVAILABLE_IN_0_1
 char *ibis_message_serialize_to_string(IbisMessage *message);
 
+/**
+ * ibis_message_get_error:
+ * @message: The instance.
+ *
+ * Gets the error from @message.
+ *
+ * Returns: (nullable) (transfer none): The error.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+GError *ibis_message_get_error(IbisMessage *message);
+
+/**
+ * ibis_message_set_error:
+ * @message: The instance.
+ * @error: (nullable) (transfer none): The error.
+ *
+ * Sets the error of @message to @error.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+void ibis_message_set_error(IbisMessage *message, GError *error);
+
+/**
+ * ibis_message_take_error:
+ * @message: The instance.
+ * @error: (nullable) (transfer full): The error.
+ *
+ * Sets the error of @message to @error taking ownership of @error.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+void ibis_message_take_error(IbisMessage *message, GError *error);
+
 G_END_DECLS
 
 #endif /* IBIS_MESSAGE_H */
diff --git a/ibis/ibismessage.c b/ibis/ibismessage.c
--- a/ibis/ibismessage.c
+++ b/ibis/ibismessage.c
@@ -32,6 +32,7 @@
 	PROP_SOURCE,
 	PROP_PARAMS,
 	PROP_TAGS,
+	PROP_ERROR,
 	N_PROPERTIES,
 };
 static GParamSpec *properties[N_PROPERTIES] = {NULL, };
@@ -43,6 +44,8 @@
 	GStrv params;
 	char *source;
 	IbisTags *tags;
+
+	GError *error;
 };
 
 /******************************************************************************
@@ -140,6 +143,7 @@
 	g_clear_pointer(&message->params, g_strfreev);
 	g_clear_pointer(&message->source, g_free);
 	g_clear_object(&message->tags);
+	g_clear_error(&message->error);
 
 	G_OBJECT_CLASS(ibis_message_parent_class)->finalize(obj);
 }
@@ -154,6 +158,9 @@
 	case PROP_COMMAND:
 		g_value_set_string(value, ibis_message_get_command(message));
 		break;
+	case PROP_ERROR:
+		g_value_set_boxed(value, ibis_message_get_error(message));
+		break;
 	case PROP_PARAMS:
 		g_value_set_boxed(value, ibis_message_get_params(message));
 		break;
@@ -179,6 +186,9 @@
 	case PROP_COMMAND:
 		ibis_message_set_command(message, g_value_get_string(value));
 		break;
+	case PROP_ERROR:
+		ibis_message_set_error(message, g_value_get_boxed(value));\
+		break;
 	case PROP_PARAMS:
 		ibis_message_set_paramsv(message, g_value_get_boxed(value));
 		break;
@@ -222,6 +232,19 @@
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
 	/**
+	 * IbisMessage:error:
+	 *
+	 * An error for the message.
+	 *
+	 * Since: 0.1
+	 */
+	properties[PROP_ERROR] = g_param_spec_boxed(
+		"error", "error",
+		"The error for the message.",
+		G_TYPE_ERROR,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * IbisMessage:params:
 	 *
 	 * The parameters of the message.
@@ -396,3 +419,32 @@
 
 	return g_string_free_and_steal(ibis_message_serialize_internal(message));
 }
+
+GError *
+ibis_message_get_error(IbisMessage *message) {
+	g_return_val_if_fail(IBIS_IS_MESSAGE(message), NULL);
+
+	return message->error;
+}
+
+void
+ibis_message_set_error(IbisMessage *message, GError *error) {
+	g_return_if_fail(IBIS_IS_MESSAGE(message));
+
+	g_clear_error(&message->error);
+	if(error != NULL) {
+		message->error = g_error_copy(error);
+	}
+
+	g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_ERROR]);
+}
+
+void
+ibis_message_take_error(IbisMessage *message, GError *error) {
+	g_return_if_fail(IBIS_IS_MESSAGE(message));
+
+	g_clear_error(&message->error);
+	message->error = error;
+
+	g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_ERROR]);
+}
diff --git a/ibis/ibisparser.h b/ibis/ibisparser.h
--- a/ibis/ibisparser.h
+++ b/ibis/ibisparser.h
@@ -31,21 +31,6 @@
 
 G_BEGIN_DECLS
 
-/**
- * IbisMessageHandler:
- * @message: The [class@Message] to handle.
- * @error: (nullable): A return address for a [type@GLib.Error].
- * @data: The user data that was passed to [method@Parser.parse].
- *
- * Defines the type of a function that will be called to handle @message.
- *
- * Returns: %TRUE if the command was handled properly, otherwise %FALSE and
- *          @error may be set.
- *
- * Since: 0.1
- */
-typedef gboolean (*IbisMessageHandler)(IbisMessage *message, GError **error, gpointer data);
-
 #define IBIS_TYPE_PARSER (ibis_parser_get_type())
 
 /**
@@ -72,60 +57,22 @@
 IbisParser *ibis_parser_new(void);
 
 /**
- * ibis_parser_set_fallback_handler: (skip):
- * @parser: The instance.
- * @handler: (scope forever): A [callback@Ibis.MessageHandler].
- *
- * Sets @handler to be called for any messages that @parser doesn't know how to
- * handle.
- *
- * Since: 0.1
- */
-IBIS_AVAILABLE_IN_0_1
-void ibis_parser_set_fallback_handler(IbisParser *parser, IbisMessageHandler handler);
-
-/**
  * ibis_parser_parse:
  * @parser: The instance.
  * @buffer: The buffer to parse.
  * @error: Return address for a #GError, or %NULL.
- * @data: (nullable): Optional data to pass to the handler.
  *
  * Parses @buffer with @parser.
  *
+ * If a message is found, [signal@Parser::message] will be emitted with the
+ * name of the command as the detail of the signal.
+ *
  * Returns: %TRUE if the buffer was parsed correctly or %FALSE with @error set.
  *
  * Since: 0.1
  */
 IBIS_AVAILABLE_IN_0_1
-gboolean ibis_parser_parse(IbisParser *parser, const gchar *buffer, GError **error, gpointer data);
-
-/**
- * ibis_parser_add_handler:
- * @parser: The instance.
- * @command: The command string.
- * @handler: (scope forever): The handler to call.
- *
- * Calls @handler every time @parser finds the command named @command.
- *
- * Since: 0.1
- */
-IBIS_AVAILABLE_IN_0_1
-void ibis_parser_add_handler(IbisParser *parser, const char *command, IbisMessageHandler handler);
-
-/**
- * ibis_parser_add_handlers:
- * @parser: The instance.
- * @handler: (scope forever): The handler to call when the command is received.
- * @...: A %NULL terminated list of string command names.
- *
- * Like [method@Parser.add_handler] but allows you to add multiple commands at
- * once that share a handler.
- *
- * Since: 0.1
- */
-IBIS_AVAILABLE_IN_0_1
-void ibis_parser_add_handlers(IbisParser *parser, IbisMessageHandler handler, ...) G_GNUC_NULL_TERMINATED;
+gboolean ibis_parser_parse(IbisParser *parser, const gchar *buffer, GError **error);
 
 G_END_DECLS
 
diff --git a/ibis/ibisparser.c b/ibis/ibisparser.c
--- a/ibis/ibisparser.c
+++ b/ibis/ibisparser.c
@@ -27,13 +27,16 @@
 #include "ibisstring.h"
 #include "ibistags.h"
 
+enum {
+	SIG_MESSAGE,
+	N_SIGNALS,
+};
+static guint signals[N_SIGNALS] = {0, };
+
 struct _IbisParser {
 	GObject parent;
 
 	GRegex *regex_message;
-
-	IbisMessageHandler fallback_handler;
-	GHashTable *handlers;
 };
 
 G_DEFINE_FINAL_TYPE(IbisParser, ibis_parser, G_TYPE_OBJECT)
@@ -108,19 +111,6 @@
 }
 
 /******************************************************************************
- * Handlers
- *****************************************************************************/
-static gboolean
-ibis_fallback_handler(IbisMessage *message, GError **error,
-                      G_GNUC_UNUSED gpointer data)
-{
-	g_set_error(error, IBIS_DOMAIN, 0, "no handler for command %s",
-	            ibis_message_get_command(message));
-
-	return FALSE;
-}
-
-/******************************************************************************
  * GObject Implementation
  *****************************************************************************/
 static void
@@ -129,8 +119,6 @@
 
 	g_clear_pointer(&parser->regex_message, g_regex_unref);
 
-	g_hash_table_destroy(parser->handlers);
-
 	G_OBJECT_CLASS(ibis_parser_parent_class)->finalize(obj);
 }
 
@@ -143,10 +131,6 @@
 	                                     "(?<coda> +:(?<trailing>.*)?)?",
 	                                     0, 0, NULL);
 	g_assert(parser->regex_message != NULL);
-
-	parser->fallback_handler = ibis_fallback_handler;
-	parser->handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-	                                         NULL);
 }
 
 static void
@@ -154,6 +138,35 @@
 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
 
 	obj_class->finalize = ibis_parser_finalize;
+
+	/**
+	 * IbisParser::message:
+	 * @parser: The instance.
+	 * @command: The command.
+	 * @message: The message.
+	 *
+	 * Emitted when the parser has parsed a new message.
+	 *
+	 * This signal supports *details* based on the upper case version of the
+	 * command from the message. For example, `PRIVMSG`, `NOTICE`, etc.
+	 *
+	 * Returns: %TRUE to stop other handlers from being invoked, otherwise
+	 *          %FALSE to propagate further.
+	 *
+	 * Since: 0.1
+	 */
+	signals[SIG_MESSAGE] = g_signal_new_class_handler(
+		"message",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
+		NULL,
+		g_signal_accumulator_true_handled,
+		NULL,
+		NULL,
+		G_TYPE_BOOLEAN,
+		2,
+		G_TYPE_STRING,
+		IBIS_TYPE_MESSAGE);
 }
 
 /******************************************************************************
@@ -164,34 +177,22 @@
 	return g_object_new(IBIS_TYPE_PARSER, NULL);
 }
 
-void
-ibis_parser_set_fallback_handler(IbisParser *parser,
-                                 IbisMessageHandler handler)
-{
-	g_return_if_fail(IBIS_IS_PARSER(parser));
-
-	parser->fallback_handler = handler;
-}
-
 gboolean
-ibis_parser_parse(IbisParser *parser, const char *buffer, GError **error,
-                  gpointer data)
-{
+ibis_parser_parse(IbisParser *parser, const char *buffer, GError **error) {
 	IbisMessage *message = NULL;
-	IbisMessageHandler handler = NULL;
 	IbisTags *tags = NULL;
 	GError *local_error = NULL;
 	GMatchInfo *info = NULL;
 	GStrv params = NULL;
-	gpointer func = NULL;
 	char *coda = NULL;
 	char *command = NULL;
 	char *middle = NULL;
 	char *source = NULL;
 	char *tags_string = NULL;
 	char *trailing = NULL;
+	gboolean handled = FALSE;
 	gboolean matches = FALSE;
-	gboolean result = FALSE;
+	gboolean result = TRUE;
 
 	g_return_val_if_fail(IBIS_IS_PARSER(parser), FALSE);
 	g_return_val_if_fail(buffer != NULL, FALSE);
@@ -209,23 +210,6 @@
 
 	/* Extract the command from the buffer, so we can find the handler. */
 	command = g_match_info_fetch_named(info, "command");
-	func = g_hash_table_lookup(parser->handlers, command);
-	if(func == NULL) {
-		if(parser->fallback_handler == NULL) {
-			g_set_error(error, IBIS_DOMAIN, 0,
-			            "no handler found for command %s and no default "
-			            "handler set.", command);
-
-			g_free(command);
-			g_match_info_unref(info);
-
-			return FALSE;
-		}
-
-		handler = parser->fallback_handler;
-	} else {
-		handler = (IbisMessageHandler)func;
-	}
 
 	/* If we made it this far, we have our handler, so lets get the rest of the
 	 * parameters and call the handler.
@@ -269,14 +253,28 @@
 		ibis_message_set_paramsv(message, params);
 	}
 
+	/* Emit the message signal. */
+	g_signal_emit(parser, signals[SIG_MESSAGE], g_quark_from_string(command),
+	              command, message, &handled);
+	if(!handled) {
+		g_warning("unhandled command '%s'", command);
+	}
+
+	/* Clean up everything */
 	g_free(command);
 	g_free(middle);
 	g_free(coda);
 	g_free(trailing);
 	g_strfreev(params);
 
-	/* Call the handler. */
-	result = handler(message, error, data);
+	local_error = ibis_message_get_error(message);
+	if(local_error != NULL) {
+		result = FALSE;
+
+		if(error != NULL && *error == NULL) {
+			*error = g_error_copy(local_error);
+		}
+	}
 
 	/* Cleanup the left overs. */
 	g_match_info_unref(info);
@@ -284,32 +282,3 @@
 
 	return result;
 }
-
-void
-ibis_parser_add_handler(IbisParser *parser, const char *command,
-                        IbisMessageHandler handler)
-{
-	g_return_if_fail(IBIS_IS_PARSER(parser));
-	g_return_if_fail(command != NULL);
-	g_return_if_fail(handler != NULL);
-
-	g_hash_table_insert(parser->handlers, g_strdup(command), handler);
-}
-
-void
-ibis_parser_add_handlers(IbisParser *parser, IbisMessageHandler handler, ...) {
-	va_list vargs;
-	const char *command = NULL;
-
-	g_return_if_fail(IBIS_IS_PARSER(parser));
-	g_return_if_fail(handler != NULL);
-
-	va_start(vargs, handler);
-
-	while((command = va_arg(vargs, const char *)) != NULL) {
-		ibis_parser_add_handler(parser, command, handler);
-	}
-
-	va_end(vargs);
-}
-
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
@@ -20,6 +20,8 @@
 
 #include <ibis.h>
 
+#define TEST_IBIS_MESSAGE_ERROR (g_quark_from_static_string("test-ibis-message"))
+
 /******************************************************************************
  * Tests
  *****************************************************************************/
@@ -39,6 +41,8 @@
 	IbisMessage *message = NULL;
 	IbisTags *tags1 = NULL;
 	IbisTags *tags2 = NULL;
+	GError *error1 = NULL;
+	GError *error2 = NULL;
 	GStrv params1 = NULL;
 	GStrv params2 = NULL;
 	GStrvBuilder *builder = NULL;
@@ -54,43 +58,69 @@
 	params1 = g_strv_builder_end(builder);
 	g_strv_builder_unref(builder);
 
+	error1 = g_error_new(TEST_IBIS_MESSAGE_ERROR, 0, "weeeee");
+
 	message = g_object_new(
 		IBIS_TYPE_MESSAGE,
-		"tags", tags1,
+		"command", "command",
+		"error", error1,
+		"params", params1,
 		"source", "source",
-		"command", "command",
-		"params", params1,
+		"tags", tags1,
 		NULL);
 
+	g_clear_error(&error1);
+
 	g_assert_true(IBIS_IS_MESSAGE(message));
 
 	g_object_get(
 		message,
-		"tags", &tags2,
+		"command", &command,
+		"error", &error2,
+		"params", &params2,
 		"source", &source,
-		"command", &command,
-		"params", &params2,
+		"tags", &tags2,
 		NULL);
 
+	g_assert_cmpstr(command, ==, "command");
+	g_clear_pointer(&command, g_free);
+
+	g_assert_error(error2, TEST_IBIS_MESSAGE_ERROR, 0);
+	g_clear_error(&error2);
+
+	g_assert_cmpstrv(params2, params1);
+	g_clear_pointer(&params2, g_strfreev);
+
+	g_assert_cmpstr(source, ==, "source");
+	g_clear_pointer(&source, g_free);
+
 	g_assert_nonnull(tags2);
 	g_assert_true(ibis_tags_equal(tags1, tags2));
 	g_clear_object(&tags2);
 
-	g_assert_cmpstr(source, ==, "source");
-	g_clear_pointer(&source, g_free);
-
-	g_assert_cmpstr(command, ==, "command");
-	g_clear_pointer(&command, g_free);
-
-	g_assert_cmpstrv(params2, params1);
-	g_clear_pointer(&params2, g_strfreev);
-
 	g_clear_object(&tags1);
 	g_clear_pointer(&params1, g_strfreev);
 	g_assert_finalize_object(message);
 }
 
 static void
+test_ibis_message_take_error(void) {
+	IbisMessage *message = NULL;
+	GError *error1 = NULL;
+	GError *error2 = NULL;
+
+	error1 = g_error_new(TEST_IBIS_MESSAGE_ERROR, 0, "weeeee");
+
+	message = ibis_message_new("TEST");
+	ibis_message_take_error(message, error1);
+
+	error2 = ibis_message_get_error(message);
+	g_assert_error(error2, TEST_IBIS_MESSAGE_ERROR, 0);
+
+	g_assert_finalize_object(message);
+}
+
+static void
 test_ibis_message_set_params_variadic(void) {
 	IbisMessage *message = NULL;
 	GStrv params = NULL;
@@ -564,6 +594,8 @@
 
 	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/set-params-variadic",
 	                test_ibis_message_set_params_variadic);
 
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
@@ -34,17 +34,20 @@
  * Handlers
  *****************************************************************************/
 static gboolean
-test_ibis_test_handler(IbisMessage *message,
-                       G_GNUC_UNUSED GError **error,
-                       gpointer data)
+test_ibis_parser_message_cb(IbisParser *parser, const char *command,
+                            IbisMessage *message, gpointer data)
 {
 	TestIbisParserData *d = data;
 	GStrv params = NULL;
-	const char *command = NULL;
+	const char *msg_command = NULL;
 	const char *source = NULL;
 	guint n_params = 0;
 
-	command = ibis_message_get_command(message);
+	g_assert_true(IBIS_IS_PARSER(parser));
+
+	msg_command = ibis_message_get_command(message);
+	g_assert_cmpstr(msg_command, ==, command);
+
 	params = ibis_message_get_params(message);
 	source = ibis_message_get_source(message);
 
@@ -77,18 +80,25 @@
 	/* Cleanup everything the caller allocated. */
 	g_clear_object(&d->tags);
 
-	/* Return the return value the caller asked for. */
+	/* Return the TRUE because we handled the message. */
 	return TRUE;
 }
 
 static gboolean
-test_ibis_test_handler_error(G_GNUC_UNUSED IbisMessage *message,
-                             GError **error,
-                             G_GNUC_UNUSED gpointer data)
+test_ibis_parser_message_error_cb(IbisParser *parser,
+                                  G_GNUC_UNUSED const char *command,
+                                  IbisMessage *message,
+                                  G_GNUC_UNUSED gpointer data)
 {
-	g_set_error(error, TEST_IBIS_PARSER_DOMAIN, 0, "test error");
+	GError *error = NULL;
+
+	g_assert_true(IBIS_IS_PARSER(parser));
+	g_assert_true(IBIS_IS_MESSAGE(message));
 
-	return FALSE;
+	error = g_error_new(TEST_IBIS_PARSER_DOMAIN, 0, "test error");
+	ibis_message_take_error(message, error);
+
+	return TRUE;
 }
 
 /******************************************************************************
@@ -100,9 +110,10 @@
 	GError *error = NULL;
 	gboolean result = FALSE;
 
-	ibis_parser_set_fallback_handler(parser, test_ibis_test_handler);
+	g_signal_connect(parser, "message",
+	                 G_CALLBACK(test_ibis_parser_message_cb), d);
 
-	result = ibis_parser_parse(parser, source, &error, d);
+	result = ibis_parser_parse(parser, source, &error);
 
 	g_assert_no_error(error);
 	g_assert_true(result);
@@ -119,9 +130,9 @@
 	GError *error = NULL;
 	gboolean result = FALSE;
 
-	ibis_parser_set_fallback_handler(parser, test_ibis_test_handler_error);
-
-	result = ibis_parser_parse(parser, "COMMAND", &error, NULL);
+	g_signal_connect(parser, "message",
+	                 G_CALLBACK(test_ibis_parser_message_error_cb), NULL);
+	result = ibis_parser_parse(parser, "COMMAND", &error);
 	g_assert_error(error, TEST_IBIS_PARSER_DOMAIN, 0);
 	g_clear_error(&error);
 
@@ -688,9 +699,6 @@
 main(gint argc, gchar *argv[]) {
 	g_test_init(&argc, &argv, NULL);
 
-	/* Make sure an error in the handler is propagated back up to the calling
-	 * function.
-	 */
 	g_test_add_func("/ibis/parser/propagates-errors",
 	                test_ibis_parser_propagates_errors);
 
