From e69bef3ce389967b8239648e4b9eaec42217bc95 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 3 Dec 2014 21:39:45 +0100 Subject: [PATCH] util/SplitString: new utility class Replaces GLib's g_strsplit(). --- Makefile.am | 2 + configure.ac | 10 ++-- src/output/plugins/AoOutputPlugin.cxx | 12 ++--- src/output/plugins/JackOutputPlugin.cxx | 30 ++++-------- src/util/SplitString.cxx | 59 ++++++++++++++++++++++ src/util/SplitString.hxx | 39 +++++++++++++++ test/SplitStringTest.hxx | 65 +++++++++++++++++++++++++ test/test_util.cxx | 2 + 8 files changed, 185 insertions(+), 34 deletions(-) create mode 100644 src/util/SplitString.cxx create mode 100644 src/util/SplitString.hxx create mode 100644 test/SplitStringTest.hxx diff --git a/Makefile.am b/Makefile.am index a79c18172..4aa1061e5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -375,6 +375,7 @@ libutil_a_SOURCES = \ src/util/NumberParser.hxx \ src/util/StringUtil.cxx src/util/StringUtil.hxx \ src/util/DivideString.cxx src/util/DivideString.hxx \ + src/util/SplitString.cxx src/util/SplitString.hxx \ src/util/FormatString.cxx src/util/FormatString.hxx \ src/util/Tokenizer.cxx src/util/Tokenizer.hxx \ src/util/TextFile.hxx \ @@ -2002,6 +2003,7 @@ endif test_test_util_SOURCES = \ test/DivideStringTest.hxx \ + test/SplitStringTest.hxx \ test/UriUtilTest.hxx \ test/TestCircularBuffer.hxx \ test/test_util.cxx diff --git a/configure.ac b/configure.ac index c45e32dcb..1c4ad43f4 100644 --- a/configure.ac +++ b/configure.ac @@ -1237,9 +1237,8 @@ fi AM_CONDITIONAL(ENABLE_HTTPD_OUTPUT, test x$enable_httpd_output = xyes) dnl ----------------------------------- JACK ---------------------------------- -MPD_ENABLE_AUTO_PKG_DEPENDS(jack, JACK, [jack >= 0.100], - [JACK output plugin], [libjack not found], [], - [enable_glib], [Cannot use --enable-jack with --disable-glib]) +MPD_ENABLE_AUTO_PKG(jack, JACK, [jack >= 0.100], + [JACK output plugin], [libjack not found]) if test x$enable_jack = xyes; then # check whether jack_set_info_function() is available @@ -1252,9 +1251,8 @@ if test x$enable_jack = xyes; then fi dnl ---------------------------------- libao ---------------------------------- -MPD_ENABLE_AUTO_PKG_DEPENDS(ao, AO, [ao], - [libao output plugin], [libao not found], [], - [enable_glib], [Cannot use --enable-ao with --disable-glib]) +MPD_ENABLE_AUTO_PKG(ao, AO, [ao], + [libao output plugin], [libao not found]) dnl ---------------------------------- OpenAL --------------------------------- AC_SUBST(OPENAL_CFLAGS,"") diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx index 6faa30b1b..689e7de7c 100644 --- a/src/output/plugins/AoOutputPlugin.cxx +++ b/src/output/plugins/AoOutputPlugin.cxx @@ -21,12 +21,12 @@ #include "AoOutputPlugin.hxx" #include "../OutputAPI.hxx" #include "util/DivideString.hxx" +#include "util/SplitString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" #include -#include #include @@ -127,22 +127,18 @@ AoOutput::Configure(const config_param ¶m, Error &error) value = param.GetBlockValue("options", nullptr); if (value != nullptr) { - gchar **_options = g_strsplit(value, ";", 0); - - for (unsigned i = 0; _options[i] != nullptr; ++i) { - const DivideString ss(_options[i], '='); + for (const auto &i : SplitString(value, ';')) { + const DivideString ss(i.c_str(), '='); if (!ss.IsDefined()) { error.Format(ao_output_domain, "problems parsing options \"%s\"", - _options[i]); + i.c_str()); return false; } ao_append_option(&options, ss.GetFirst(), ss.GetSecond()); } - - g_strfreev(_options); } return true; diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx index 5651de68a..777db11bf 100644 --- a/src/output/plugins/JackOutputPlugin.cxx +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -21,13 +21,13 @@ #include "JackOutputPlugin.hxx" #include "../OutputAPI.hxx" #include "config/ConfigError.hxx" +#include "util/SplitString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" #include -#include #include #include #include @@ -56,10 +56,10 @@ struct JackOutput { /* configuration */ - char *source_ports[MAX_PORTS]; + std::string source_ports[MAX_PORTS]; unsigned num_source_ports; - char *destination_ports[MAX_PORTS]; + std::string destination_ports[MAX_PORTS]; unsigned num_destination_ports; size_t ringbuffer_size; @@ -261,13 +261,13 @@ mpd_jack_connect(JackOutput *jd, Error &error) for (unsigned i = 0; i < jd->num_source_ports; ++i) { jd->ports[i] = jack_port_register(jd->client, - jd->source_ports[i], + jd->source_ports[i].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if (jd->ports[i] == nullptr) { error.Format(jack_output_domain, "Cannot register output port \"%s\"", - jd->source_ports[i]); + jd->source_ports[i].c_str()); mpd_jack_disconnect(jd); return false; } @@ -283,23 +283,19 @@ mpd_jack_test_default_device(void) } static unsigned -parse_port_list(const char *source, char **dest, Error &error) +parse_port_list(const char *source, std::string dest[], Error &error) { - char **list = g_strsplit(source, ",", 0); unsigned n = 0; - - for (n = 0; list[n] != nullptr; ++n) { + for (auto &&i : SplitString(source, ',')) { if (n >= MAX_PORTS) { error.Set(config_domain, "too many port names"); return 0; } - dest[n] = list[n]; + dest[n++] = std::move(i); } - g_free(list); - if (n == 0) { error.Format(config_domain, "at least one port name expected"); @@ -392,12 +388,6 @@ mpd_jack_finish(AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; - for (unsigned i = 0; i < jd->num_source_ports; ++i) - g_free(jd->source_ports[i]); - - for (unsigned i = 0; i < jd->num_destination_ports; ++i) - g_free(jd->destination_ports[i]); - delete jd; } @@ -505,8 +495,8 @@ mpd_jack_start(JackOutput *jd, Error &error) /* use the configured output ports */ num_destination_ports = jd->num_destination_ports; - memcpy(destination_ports, jd->destination_ports, - num_destination_ports * sizeof(*destination_ports)); + for (unsigned i = 0; i < num_destination_ports; ++i) + destination_ports[i] = jd->destination_ports[i].c_str(); jports = nullptr; } diff --git a/src/util/SplitString.cxx b/src/util/SplitString.cxx new file mode 100644 index 000000000..0bb1e5165 --- /dev/null +++ b/src/util/SplitString.cxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "SplitString.hxx" +#include "StringUtil.hxx" + +#include + +std::forward_list +SplitString(const char *s, char separator, bool strip) +{ + if (strip) + s = StripLeft(s); + + std::forward_list list; + if (*s == 0) + return list; + + auto i = list.before_begin(); + + while (true) { + const char *next = strchr(s, separator); + if (next == nullptr) + break; + + const char *end = next++; + if (strip) + end = StripRight(s, end); + + i = list.emplace_after(i, s, end); + + s = next; + if (strip) + s = StripLeft(s); + } + + const char *end = s + strlen(s); + if (strip) + end = StripRight(s, end); + + list.emplace_after(i, s, end); + return list; +} diff --git a/src/util/SplitString.hxx b/src/util/SplitString.hxx new file mode 100644 index 000000000..bc95cff81 --- /dev/null +++ b/src/util/SplitString.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SPLIT_STRING_HXX +#define MPD_SPLIT_STRING_HXX + +#include +#include + +/** + * Split a string at a certain separator character into sub strings + * and returns a list of these. + * + * Two consecutive separator characters result in an empty string in + * the list. + * + * An empty input string, as a special case, results in an empty list + * (and not a list with an empty string). + */ +std::forward_list +SplitString(const char *s, char separator, bool strip=true); + +#endif diff --git a/test/SplitStringTest.hxx b/test/SplitStringTest.hxx new file mode 100644 index 000000000..87ed385ea --- /dev/null +++ b/test/SplitStringTest.hxx @@ -0,0 +1,65 @@ +/* + * Unit tests for src/util/ + */ + +#include "check.h" +#include "util/SplitString.hxx" +#include "util/Macros.hxx" + +#include +#include + +#include + +class SplitStringTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SplitStringTest); + CPPUNIT_TEST(TestBasic); + CPPUNIT_TEST(TestStrip); + CPPUNIT_TEST(TestNoStrip); + CPPUNIT_TEST(TestEmpty); + CPPUNIT_TEST_SUITE_END(); + +public: + void TestBasic() { + constexpr char input[] = "foo.bar"; + const char *const output[] = { "foo", "bar" }; + size_t i = 0; + for (auto p : SplitString(input, '.')) { + CPPUNIT_ASSERT(i < ARRAY_SIZE(output)); + CPPUNIT_ASSERT(p == output[i]); + ++i; + } + + CPPUNIT_ASSERT_EQUAL(ARRAY_SIZE(output), i); + } + + void TestStrip() { + constexpr char input[] = " foo\t.\r\nbar\r\n2"; + const char *const output[] = { "foo", "bar\r\n2" }; + size_t i = 0; + for (auto p : SplitString(input, '.')) { + CPPUNIT_ASSERT(i < ARRAY_SIZE(output)); + CPPUNIT_ASSERT(p == output[i]); + ++i; + } + + CPPUNIT_ASSERT_EQUAL(ARRAY_SIZE(output), i); + } + + void TestNoStrip() { + constexpr char input[] = " foo\t.\r\nbar\r\n2"; + const char *const output[] = { " foo\t", "\r\nbar\r\n2" }; + size_t i = 0; + for (auto p : SplitString(input, '.', false)) { + CPPUNIT_ASSERT(i < ARRAY_SIZE(output)); + CPPUNIT_ASSERT(p == output[i]); + ++i; + } + + CPPUNIT_ASSERT_EQUAL(ARRAY_SIZE(output), i); + } + + void TestEmpty() { + CPPUNIT_ASSERT(SplitString("", '.').empty()); + } +}; diff --git a/test/test_util.cxx b/test/test_util.cxx index e9b49c4da..c2d73d7d9 100644 --- a/test/test_util.cxx +++ b/test/test_util.cxx @@ -4,6 +4,7 @@ #include "config.h" #include "DivideStringTest.hxx" +#include "SplitStringTest.hxx" #include "UriUtilTest.hxx" #include "TestCircularBuffer.hxx" @@ -15,6 +16,7 @@ #include CPPUNIT_TEST_SUITE_REGISTRATION(DivideStringTest); +CPPUNIT_TEST_SUITE_REGISTRATION(SplitStringTest); CPPUNIT_TEST_SUITE_REGISTRATION(UriUtilTest); CPPUNIT_TEST_SUITE_REGISTRATION(TestCircularBuffer);