diff options
author | Ted Gould <ted@canonical.com> | 2009-11-03 20:23:41 -0600 |
---|---|---|
committer | Ted Gould <ted@canonical.com> | 2009-11-03 20:23:41 -0600 |
commit | 541f18ceb56e3db6d342ae3dbac51e527592614e (patch) | |
tree | 41ddb409dfbc7b4b905d9f57d6e7c8403c8220fa | |
parent | b5f41d1c0ca30f1cf7379e754d906681ccfbe9e1 (diff) | |
parent | c09917d4792ccbd9fbd773874ae6e75e73c32a6e (diff) | |
download | libayatana-indicator-541f18ceb56e3db6d342ae3dbac51e527592614e.tar.gz libayatana-indicator-541f18ceb56e3db6d342ae3dbac51e527592614e.tar.bz2 libayatana-indicator-541f18ceb56e3db6d342ae3dbac51e527592614e.zip |
Merging in an interface for loading modules cleanly.
-rw-r--r-- | .bzrignore | 14 | ||||
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | configure.ac | 15 | ||||
-rw-r--r-- | libindicator/Makefile.am | 17 | ||||
-rw-r--r-- | libindicator/indicator-object.c | 290 | ||||
-rw-r--r-- | libindicator/indicator-object.h | 61 | ||||
-rw-r--r-- | tests/Makefile.am | 92 | ||||
-rw-r--r-- | tests/dummy-indicator-blank.c | 6 | ||||
-rw-r--r-- | tests/dummy-indicator-null.c | 23 | ||||
-rw-r--r-- | tests/dummy-indicator-simple.c | 28 | ||||
-rw-r--r-- | tests/test-loader.c | 109 | ||||
-rw-r--r-- | tools/Makefile.am | 1 |
12 files changed, 658 insertions, 2 deletions
@@ -103,3 +103,17 @@ src-sus/indicator-applet-sus data/GNOME_IndicatorAppletSUS.server data/GNOME_IndicatorAppletSUS.server.in src-sus/indicator-applet-no-sus +libindicator/libindicator.la +libindicator/libindicator_la-indicator-object. +libindicator/libindicator_la-indicator-object.lo +tests/loader-check-results.xml +tests/loader-check-results.html +tests/test-loader +tests/libdummy-indicator-null.la +tests/libdummy_indicator_null_la-dummy-indicator-null.lo +tests/libdummy-indicator-simple.la +tests/libdummy_indicator_simple_la-dummy-indicator-simple.lo +tests/libdummy-indicator-blank.la +tests/libdummy_indicator_blank_la-dummy-indicator-blank.lo +libindicator-[0-9].[0-9].[0-9].tar.gz +libindicator-[0-9].[0-9].[0-9].tar.gz.asc diff --git a/Makefile.am b/Makefile.am index ae85ded..8894e24 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,8 @@ SUBDIRS = \ - libindicator + libindicator \ + tests \ + tools DISTCLEANFILES = \ libindicator-*.tar.gz diff --git a/configure.ac b/configure.ac index 090e281..d2e18fc 100644 --- a/configure.ac +++ b/configure.ac @@ -21,6 +21,19 @@ AC_CONFIG_MACRO_DIR([m4]) m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) ############################## +# Dependencies +############################## + +GTK_REQUIRED_VERSION=2.18 +DBUS_REQUIRED_VERSION=0.76 + +PKG_CHECK_MODULES(LIBINDICATOR, gtk+-2.0 >= $GTK_REQUIRED_VERSION + dbus-glib-1 >= $DBUS_REQUIRED_VERSION) + +AC_SUBST(LIBINDICATOR_CFLAGS) +AC_SUBST(LIBINDICATOR_LIBS) + +############################## # Custom Junk ############################## @@ -73,6 +86,8 @@ AC_OUTPUT([ Makefile libindicator/Makefile libindicator/indicator.pc +tests/Makefile +tools/Makefile ]) ########################### diff --git a/libindicator/Makefile.am b/libindicator/Makefile.am index be68721..db45f3c 100644 --- a/libindicator/Makefile.am +++ b/libindicator/Makefile.am @@ -4,11 +4,26 @@ EXTRA_DIST = \ libindicatorincludedir=$(includedir)/libindicator-0.1/libindicator indicator_headers = \ - indicator.h + indicator.h \ + indicator-object.h libindicatorinclude_HEADERS = \ $(indicator_headers) +lib_LTLIBRARIES = \ + libindicator.la + +libindicator_la_SOURCES = \ + $(indicator_headers) \ + indicator-object.c + +libindicator_la_CFLAGS = \ + $(LIBINDICATOR_CFLAGS) \ + -Wall -Werror + +libindicator_la_LIBADD = \ + $(LIBINDICATOR_LIBS) + pkgconfig_DATA = indicator.pc pkgconfigdir = $(libdir)/pkgconfig diff --git a/libindicator/indicator-object.c b/libindicator/indicator-object.c new file mode 100644 index 0000000..ce07ad6 --- /dev/null +++ b/libindicator/indicator-object.c @@ -0,0 +1,290 @@ +/* +An object to represent loadable indicator modules to make loading +them easy and objectified. + +Copyright 2009 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 3.0 as published by the Free Software Foundation. + +This library 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 version 3.0 for more details. + +You should have received a copy of the GNU General Public +License along with this library. If not, see +<http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "indicator.h" +#include "indicator-object.h" + +/** + IndicatorObjectPrivate: + @label: The label representing this indicator or #NULL if none. + @icon: The icon representing this indicator or #NULL if none. + @menu: The menu representing this indicator or #NULL if none. + + Structure to define the memory for the private area + of the object instance. +*/ +typedef struct _IndicatorObjectPrivate IndicatorObjectPrivate; +struct _IndicatorObjectPrivate { + GtkLabel * label; + GtkImage * icon; + GtkMenu * menu; +}; + +#define INDICATOR_OBJECT_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), INDICATOR_OBJECT_TYPE, IndicatorObjectPrivate)) + +static void indicator_object_class_init (IndicatorObjectClass *klass); +static void indicator_object_init (IndicatorObject *self); +static void indicator_object_dispose (GObject *object); +static void indicator_object_finalize (GObject *object); + +G_DEFINE_TYPE (IndicatorObject, indicator_object, G_TYPE_OBJECT); + +/* Setup the class and put the functions into the + class structure */ +static void +indicator_object_class_init (IndicatorObjectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (IndicatorObjectPrivate)); + + object_class->dispose = indicator_object_dispose; + object_class->finalize = indicator_object_finalize; + + return; +} + +/* Initialize an instance */ +static void +indicator_object_init (IndicatorObject *self) +{ + IndicatorObjectPrivate * priv = INDICATOR_OBJECT_GET_PRIVATE(self); + + priv->label = NULL; + priv->icon = NULL; + priv->menu = NULL; + + return; +} + +/* Unref the objects that we're holding on to. */ +static void +indicator_object_dispose (GObject *object) +{ + IndicatorObjectPrivate * priv = INDICATOR_OBJECT_GET_PRIVATE(object); + + if (priv->label != NULL) { + g_object_unref(priv->label); + priv->label = NULL; + } + + if (priv->icon != NULL) { + g_object_unref(priv->icon); + priv->icon = NULL; + } + + if (priv->menu != NULL) { + g_object_unref(priv->menu); + priv->menu = NULL; + } + + G_OBJECT_CLASS (indicator_object_parent_class)->dispose (object); + return; +} + +/* Free memory */ +static void +indicator_object_finalize (GObject *object) +{ + + G_OBJECT_CLASS (indicator_object_parent_class)->finalize (object); + return; +} + +/** + indicator_object_new_from_file: + @file: Filename containing a loadable module + + This function builds an #IndicatorObject using the symbols + that are found in @file. The module is loaded and the + references are all kept by the object. To unload the + module the object must be destroyed. + + Return value: A valid #IndicatorObject or #NULL if error. +*/ +IndicatorObject * +indicator_object_new_from_file (const gchar * file) +{ + /* Check to make sure the name exists and that the + file itself exists */ + if (file == NULL) { + g_warning("Invalid filename."); + return NULL; + } + + if (!g_file_test(file, G_FILE_TEST_EXISTS)) { + g_warning("File '%s' does not exist.", file); + return NULL; + } + + /* Grab the g_module reference, pull it in but let's + keep the symbols local to avoid conflicts. */ + GModule * module = g_module_open(file, + G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); + if(module == NULL) { + g_warning("Unable to load module: %s", file); + return NULL; + } + + /* Look for the version function, error if not found. */ + get_version_t lget_version = NULL; + if (!g_module_symbol(module, INDICATOR_GET_VERSION_S, (gpointer *)(&lget_version))) { + g_warning("Unable to get the symbol for getting the version."); + return NULL; + } + + /* Check the version with the macro and make sure we're + all talking the same language. */ + if (!INDICATOR_VERSION_CHECK(lget_version())) { + g_warning("Indicator using API version '%s' we're expecting '%s'", lget_version(), INDICATOR_VERSION); + return NULL; + } + + /* A this point we allocate the object, any code beyond + here needs to deallocate it if we're returning in an + error'd state. */ + GObject * object = g_object_new(INDICATOR_OBJECT_TYPE, NULL); + IndicatorObjectPrivate * priv = INDICATOR_OBJECT_GET_PRIVATE(object); + + /* The function for grabbing a label from the module + execute it, and make sure everything is a-okay */ + get_label_t lget_label = NULL; + if (!g_module_symbol(module, INDICATOR_GET_LABEL_S, (gpointer *)(&lget_label))) { + g_warning("Unable to get '" INDICATOR_GET_LABEL_S "' symbol from module: %s", file); + goto unrefandout; + } + if (lget_label == NULL) { + g_warning("Symbol '" INDICATOR_GET_LABEL_S "' is (null) in module: %s", file); + goto unrefandout; + } + priv->label = lget_label(); + if (priv->label) { + g_object_ref(G_OBJECT(priv->label)); + } + + /* The function for grabbing an icon from the module + execute it, and make sure everything is a-okay */ + get_icon_t lget_icon = NULL; + if (!g_module_symbol(module, INDICATOR_GET_ICON_S, (gpointer *)(&lget_icon))) { + g_warning("Unable to get '" INDICATOR_GET_ICON_S "' symbol from module: %s", file); + goto unrefandout; + } + if (lget_icon == NULL) { + g_warning("Symbol '" INDICATOR_GET_ICON_S "' is (null) in module: %s", file); + goto unrefandout; + } + priv->icon = lget_icon(); + if (priv->icon) { + g_object_ref(G_OBJECT(priv->icon)); + } + + /* The function for grabbing a menu from the module + execute it, and make sure everything is a-okay */ + get_menu_t lget_menu = NULL; + if (!g_module_symbol(module, INDICATOR_GET_MENU_S, (gpointer *)(&lget_menu))) { + g_warning("Unable to get '" INDICATOR_GET_MENU_S "' symbol from module: %s", file); + goto unrefandout; + } + if (lget_menu == NULL) { + g_warning("Symbol '" INDICATOR_GET_MENU_S "' is (null) in module: %s", file); + goto unrefandout; + } + priv->menu = lget_menu(); + if (priv->menu) { + g_object_ref(G_OBJECT(priv->menu)); + } + + if (priv->label == NULL && priv->icon == NULL) { + /* This is the case where there is nothing to display, + kinda odd that we'd have a module with nothing. */ + g_warning("No label or icon. Odd."); + goto unrefandout; + } + + return INDICATOR_OBJECT(object); + + /* Error, let's drop the object and return NULL. Sad when + this happens. */ +unrefandout: + g_object_unref(object); + return NULL; +} + +/** + indicator_object_get_label: + @io: An #IndicatorObject. + + A function to get the label for a particular object. This + function does not increase the refcount. That's your job + if you want to do it. + + Return value: A #GtkLabel or #NULL if unavailable. +*/ +GtkLabel * +indicator_object_get_label (IndicatorObject * io) +{ + g_return_val_if_fail(IS_INDICATOR_OBJECT(io), NULL); + IndicatorObjectPrivate * priv = INDICATOR_OBJECT_GET_PRIVATE(io); + return priv->label; +} + +/** + indicator_object_get_icon: + @io: An #IndicatorObject. + + A function to get the icon for a particular object. This + function does not increase the refcount. That's your job + if you want to do it. + + Return value: A #GtkImage or #NULL if unavailable. +*/ +GtkImage * +indicator_object_get_icon (IndicatorObject * io) +{ + g_return_val_if_fail(IS_INDICATOR_OBJECT(io), NULL); + IndicatorObjectPrivate * priv = INDICATOR_OBJECT_GET_PRIVATE(io); + return priv->icon; +} + +/** + indicator_object_get_menu: + @io: An #IndicatorObject. + + A function to get the menu for a particular object. This + function does not increase the refcount. That's your job + if you want to do it. + + Return value: A #GtkMenu or #NULL if unavailable. +*/ +GtkMenu * +indicator_object_get_menu (IndicatorObject * io) +{ + g_return_val_if_fail(IS_INDICATOR_OBJECT(io), NULL); + IndicatorObjectPrivate * priv = INDICATOR_OBJECT_GET_PRIVATE(io); + return priv->menu; +} diff --git a/libindicator/indicator-object.h b/libindicator/indicator-object.h new file mode 100644 index 0000000..fa6373d --- /dev/null +++ b/libindicator/indicator-object.h @@ -0,0 +1,61 @@ +/* +An object to represent loadable indicator modules to make loading +them easy and objectified. + +Copyright 2009 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 3.0 as published by the Free Software Foundation. + +This library 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 version 3.0 for more details. + +You should have received a copy of the GNU General Public +License along with this library. If not, see +<http://www.gnu.org/licenses/>. +*/ + +#ifndef __INDICATOR_OBJECT_H__ +#define __INDICATOR_OBJECT_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define INDICATOR_OBJECT_TYPE (indicator_object_get_type ()) +#define INDICATOR_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_OBJECT_TYPE, IndicatorObject)) +#define INDICATOR_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_OBJECT_TYPE, IndicatorObjectClass)) +#define IS_INDICATOR_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_OBJECT_TYPE)) +#define IS_INDICATOR_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_OBJECT_TYPE)) +#define INDICATOR_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_OBJECT_TYPE, IndicatorObjectClass)) + +typedef struct _IndicatorObject IndicatorObject; +typedef struct _IndicatorObjectClass IndicatorObjectClass; + +struct _IndicatorObjectClass { + GObjectClass parent_class; + +}; + +struct _IndicatorObject { + GObject parent; + +}; + +GType indicator_object_get_type (void); +IndicatorObject * indicator_object_new_from_file (const gchar * file); + +GtkLabel * indicator_object_get_label (IndicatorObject * io); +GtkImage * indicator_object_get_icon (IndicatorObject * io); +GtkMenu * indicator_object_get_menu (IndicatorObject * io); + +G_END_DECLS + +#endif diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..8121136 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,92 @@ + +check_PROGRAMS = \ + test-loader + +lib_LTLIBRARIES = \ + libdummy-indicator-blank.la \ + libdummy-indicator-null.la \ + libdummy-indicator-simple.la + +############################# +# Test Loader +############################# + +test_loader_SOURCES = \ + test-loader.c + +test_loader_CFLAGS = \ + -Wall -Werror \ + $(LIBINDICATOR_CFLAGS) -I$(top_srcdir) \ + -DBUILD_DIR="\"$(builddir)\"" + +test_loader_LDADD = \ + $(LIBINDICATOR_LIBS) $(top_builddir)/libindicator/.libs/libindicator.a + +############################# +# Dummy Indicator Blank +############################# + +libdummy_indicator_blank_la_SOURCES = \ + dummy-indicator-blank.c + +libdummy_indicator_blank_la_CFLAGS = \ + -Wall -Werror \ + $(LIBINDICATOR_CFLAGS) -I$(top_srcdir) + +libdummy_indicator_blank_la_LIBADD = \ + $(LIBINDICATOR_LIBS) + +libdummy_indicator_blank_la_LDFLAGS = \ + -module \ + -avoid-version + +############################# +# Dummy Indicator NULL +############################# + +libdummy_indicator_null_la_SOURCES = \ + dummy-indicator-null.c + +libdummy_indicator_null_la_CFLAGS = \ + -Wall -Werror \ + $(LIBINDICATOR_CFLAGS) -I$(top_srcdir) + +libdummy_indicator_null_la_LIBADD = \ + $(LIBINDICATOR_LIBS) + +libdummy_indicator_null_la_LDFLAGS = \ + -module \ + -avoid-version + +############################# +# Dummy Indicator Simple +############################# + +libdummy_indicator_simple_la_SOURCES = \ + dummy-indicator-simple.c + +libdummy_indicator_simple_la_CFLAGS = \ + -Wall -Werror \ + $(LIBINDICATOR_CFLAGS) -I$(top_srcdir) + +libdummy_indicator_simple_la_LIBADD = \ + $(LIBINDICATOR_LIBS) + +libdummy_indicator_simple_la_LDFLAGS = \ + -module \ + -avoid-version + +############################# +# Test stuff +############################# + +XML_REPORT = loader-check-results.xml +HTML_REPORT = loader-check-results.html + +loader-tester: test-loader libdummy-indicator-null.la libdummy-indicator-simple.la + @gtester -k --verbose -o=$(XML_REPORT) ./test-loader + +check-local: loader-tester + +DISTCLEANFILES = $(XML_REPORT) $(HTML_REPORT) + diff --git a/tests/dummy-indicator-blank.c b/tests/dummy-indicator-blank.c new file mode 100644 index 0000000..41249f2 --- /dev/null +++ b/tests/dummy-indicator-blank.c @@ -0,0 +1,6 @@ + +#include "libindicator/indicator.h" + +INDICATOR_SET_VERSION +INDICATOR_SET_NAME("dummy-indicator-null") + diff --git a/tests/dummy-indicator-null.c b/tests/dummy-indicator-null.c new file mode 100644 index 0000000..ff99d71 --- /dev/null +++ b/tests/dummy-indicator-null.c @@ -0,0 +1,23 @@ + +#include "libindicator/indicator.h" + +INDICATOR_SET_VERSION +INDICATOR_SET_NAME("dummy-indicator-null") + +GtkLabel * +get_label (void) +{ + return NULL; +} + +GtkImage * +get_icon (void) +{ + return NULL; +} + +GtkMenu * +get_menu (void) +{ + return NULL; +} diff --git a/tests/dummy-indicator-simple.c b/tests/dummy-indicator-simple.c new file mode 100644 index 0000000..cee4eac --- /dev/null +++ b/tests/dummy-indicator-simple.c @@ -0,0 +1,28 @@ + +#include "libindicator/indicator.h" + +INDICATOR_SET_VERSION +INDICATOR_SET_NAME("dummy-indicator-simple") + +GtkLabel * +get_label (void) +{ + return GTK_LABEL(gtk_label_new("Simple Item")); +} + +GtkImage * +get_icon (void) +{ + return GTK_IMAGE(gtk_image_new()); +} + +GtkMenu * +get_menu (void) +{ + GtkMenu * main_menu = GTK_MENU(gtk_menu_new()); + GtkWidget * loading_item = gtk_menu_item_new_with_label("Loading..."); + gtk_menu_shell_append(GTK_MENU_SHELL(main_menu), loading_item); + gtk_widget_show(GTK_WIDGET(loading_item)); + + return main_menu; +} diff --git a/tests/test-loader.c b/tests/test-loader.c new file mode 100644 index 0000000..4b2b096 --- /dev/null +++ b/tests/test-loader.c @@ -0,0 +1,109 @@ +#include <gtk/gtk.h> +#include "libindicator/indicator-object.h" + +void destroy_cb (gpointer data, GObject * object); + +void +test_loader_filename_dummy_simple_accessors (void) +{ + IndicatorObject * object = indicator_object_new_from_file(BUILD_DIR "/.libs/libdummy-indicator-simple.so"); + g_assert(object != NULL); + + g_assert(indicator_object_get_label(object) != NULL); + g_assert(indicator_object_get_menu(object) != NULL); + g_assert(indicator_object_get_icon(object) != NULL); + + g_object_unref(object); + + return; +} + +void +test_loader_filename_dummy_simple (void) +{ + IndicatorObject * object = indicator_object_new_from_file(BUILD_DIR "/.libs/libdummy-indicator-simple.so"); + g_assert(object != NULL); + + gboolean unreffed = FALSE; + g_object_weak_ref(G_OBJECT(object), destroy_cb, &unreffed); + + g_object_unref(object); + g_assert(unreffed == TRUE); + + return; +} + +void +test_loader_filename_dummy_blank (void) +{ + IndicatorObject * object = indicator_object_new_from_file(BUILD_DIR "/.libs/libdummy-indicator-blank.so"); + g_assert(object == NULL); + return; +} + +void +test_loader_filename_dummy_null (void) +{ + IndicatorObject * object = indicator_object_new_from_file(BUILD_DIR "/.libs/libdummy-indicator-null.so"); + g_assert(object == NULL); + return; +} + +void +test_loader_filename_bad (void) +{ + IndicatorObject * object = indicator_object_new_from_file("/this/file/should/not/exist.so"); + g_assert(object == NULL); + return; +} + +void +destroy_cb (gpointer data, GObject * object) +{ + gboolean * bob = (gboolean *)data; + *bob = TRUE; + return; +} + +void +test_loader_refunref (void) +{ + GObject * object = g_object_new(INDICATOR_OBJECT_TYPE, NULL); + + gboolean unreffed = FALSE; + g_object_weak_ref(object, destroy_cb, &unreffed); + + g_object_unref(object); + + g_assert(unreffed == TRUE); + + return; +} + +void +test_loader_creation_deletion_suite (void) +{ + g_test_add_func ("/libindicator/loader/ref_and_unref", test_loader_refunref); + g_test_add_func ("/libindicator/loader/filename_bad", test_loader_filename_bad); + g_test_add_func ("/libindicator/loader/dummy/null_load", test_loader_filename_dummy_null); + g_test_add_func ("/libindicator/loader/dummy/blank_load", test_loader_filename_dummy_null); + g_test_add_func ("/libindicator/loader/dummy/simple_load", test_loader_filename_dummy_simple); + g_test_add_func ("/libindicator/loader/dummy/simple_accessors", test_loader_filename_dummy_simple_accessors); + + return; +} + + +int +main (int argc, char ** argv) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + gtk_init(&argc, &argv); + + test_loader_creation_deletion_suite(); + + g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL); + + return g_test_run(); +} diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..9de44fc --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1 @@ +#Something |