From e330f8553dd2777900cd73f0cbc9e3fdc51f99d9 Mon Sep 17 00:00:00 2001 From: Peter Keresztes Schmidt Date: Sun, 4 Apr 2021 00:30:18 +0200 Subject: [PATCH] utils: cleanup Split and Join --- src/zm_libvlc_camera.cpp | 2 +- src/zm_logger.cpp | 2 +- src/zm_rtsp.cpp | 42 +++++++-------- src/zm_rtsp_auth.cpp | 6 +-- src/zm_sdp.cpp | 28 +++++----- src/zm_user.cpp | 2 +- src/zm_utils.cpp | 109 ++++++++++++++------------------------- src/zm_utils.h | 12 ++--- tests/zm_utils.cpp | 66 +++++++++++------------- 9 files changed, 116 insertions(+), 153 deletions(-) diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index af4bbdbf4..a1a552fd5 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -213,7 +213,7 @@ void LibvlcCamera::Terminate() { int LibvlcCamera::PrimeCapture() { Debug(1, "Priming capture from %s, libvlc version %s", mPath.c_str(), (*libvlc_get_version_f)()); - StringVector opVect = split(Options(), ","); + StringVector opVect = Split(Options(), ","); // Set transport method as specified by method field, rtpUni is default if ( Method() == "rtpMulti" ) diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index d425764ae..25ff59a7d 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -166,7 +166,7 @@ void Logger::initialise(const std::string &id, const Options &options) { tempSyslogLevel = atoi(envPtr); if ( config.log_debug ) { - StringVector targets = split(config.log_debug_target, "|"); + StringVector targets = Split(config.log_debug_target, "|"); for ( unsigned int i = 0; i < targets.size(); i++ ) { const std::string &target = targets[i]; if ( target == mId || target == "_"+mId || target == "_"+mIdRoot || target == "" ) { diff --git a/src/zm_rtsp.cpp b/src/zm_rtsp.cpp index 6d42db22b..3492e2318 100644 --- a/src/zm_rtsp.cpp +++ b/src/zm_rtsp.cpp @@ -34,7 +34,7 @@ RtspThread::PortSet RtspThread::smAssignedPorts; bool RtspThread::sendCommand(std::string message) { if ( mNeedAuth ) { - StringVector parts = split(message, " "); + StringVector parts = Split(message, " "); if ( parts.size() > 1 ) message += mAuthenticator->getAuthHeader(parts[0], parts[1]); } @@ -172,7 +172,7 @@ RtspThread::RtspThread( mHttpSession = stringtf("%d", rand()); mNeedAuth = false; - StringVector parts = split(auth, ":"); + StringVector parts = Split(auth, ":"); Debug(2, "# of auth parts %d", parts.size()); if ( parts.size() > 1 ) mAuthenticator = new zm::Authenticator(parts[0], parts[1]); @@ -320,7 +320,7 @@ void RtspThread::Run() { } // end if failed response maybe due to auth char publicLine[256] = ""; - StringVector lines = split(response, "\r\n"); + StringVector lines = Split(response, "\r\n"); for ( size_t i = 0; i < lines.size(); i++ ) sscanf(lines[i].c_str(), "Public: %[^\r\n]\r\n", publicLine); @@ -352,7 +352,7 @@ void RtspThread::Run() { std::string DescHeader = response.substr(0, sdpStart); Debug(1, "Processing DESCRIBE response header '%s'", DescHeader.c_str()); - lines = split(DescHeader, "\r\n"); + lines = Split(DescHeader, "\r\n"); for ( size_t i = 0; i < lines.size(); i++ ) { // If the device sends us a url value for Content-Base in the response header, we should use that instead if ( ( lines[i].size() > 13 ) && ( lines[i].substr( 0, 13 ) == "Content-Base:" ) ) { @@ -453,14 +453,14 @@ void RtspThread::Run() { if ( !recvResponse(response) ) return; - lines = split(response, "\r\n"); + lines = Split(response, "\r\n"); std::string session; int timeout = 0; char transport[256] = ""; for ( size_t i = 0; i < lines.size(); i++ ) { if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) ) { - StringVector sessionLine = split(lines[i].substr(9), ";"); + StringVector sessionLine = Split(lines[i].substr(9), ";"); session = TrimSpaces(sessionLine[0]); if ( sessionLine.size() == 2 ) sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout); @@ -483,33 +483,33 @@ void RtspThread::Run() { int remoteChannels[2] = { 0, 0 }; std::string distribution = ""; unsigned long ssrc = 0; - StringVector parts = split( transport, ";" ); + StringVector parts = Split(transport, ";"); for ( size_t i = 0; i < parts.size(); i++ ) { if ( parts[i] == "unicast" || parts[i] == "multicast" ) distribution = parts[i]; else if (StartsWith(parts[i], "server_port=") ) { method = "RTP/UNICAST"; - StringVector subparts = split( parts[i], "=" ); - StringVector ports = split( subparts[1], "-" ); + StringVector subparts = Split(parts[i], "="); + StringVector ports = Split(subparts[1], "-"); remotePorts[0] = strtol( ports[0].c_str(), nullptr, 10 ); remotePorts[1] = strtol( ports[1].c_str(), nullptr, 10 ); } else if (StartsWith(parts[i], "interleaved=") ) { method = "RTP/RTSP"; - StringVector subparts = split( parts[i], "=" ); - StringVector channels = split( subparts[1], "-" ); + StringVector subparts = Split(parts[i], "="); + StringVector channels = Split(subparts[1], "-"); remoteChannels[0] = strtol( channels[0].c_str(), nullptr, 10 ); remoteChannels[1] = strtol( channels[1].c_str(), nullptr, 10 ); } else if (StartsWith(parts[i], "port=") ) { method = "RTP/MULTICAST"; - StringVector subparts = split( parts[i], "=" ); - StringVector ports = split( subparts[1], "-" ); + StringVector subparts = Split(parts[i], "="); + StringVector ports = Split(subparts[1], "-"); localPorts[0] = strtol( ports[0].c_str(), nullptr, 10 ); localPorts[1] = strtol( ports[1].c_str(), nullptr, 10 ); } else if (StartsWith(parts[i], "destination=") ) { - StringVector subparts = split( parts[i], "=" ); + StringVector subparts = Split(parts[i], "="); localHost = subparts[1]; } else if (StartsWith(parts[i], "ssrc=") ) { - StringVector subparts = split( parts[i], "=" ); + StringVector subparts = Split(parts[i], "="); ssrc = strtoll( subparts[1].c_str(), nullptr, 16 ); } } @@ -528,14 +528,14 @@ void RtspThread::Run() { if ( !recvResponse(response) ) return; - lines = split(response, "\r\n"); + lines = Split(response, "\r\n"); std::string rtpInfo; for ( size_t i = 0; i < lines.size(); i++ ) { if ( ( lines[i].size() > 9 ) && ( lines[i].substr(0, 9) == "RTP-Info:" ) ) rtpInfo = TrimSpaces(lines[i].substr(9)); // Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) && ( timeout == 0 ) ) { - StringVector sessionLine = split(lines[i].substr(9), ";"); + StringVector sessionLine = Split(lines[i].substr(9), ";"); if ( sessionLine.size() == 2 ) sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout); if ( timeout > 0 ) @@ -551,18 +551,18 @@ void RtspThread::Run() { } else { Debug( 2, "Got RTP Info %s", rtpInfo.c_str() ); // More than one stream can be included in the RTP Info - streams = split( rtpInfo.c_str(), "," ); + streams = Split(rtpInfo.c_str(), ","); for ( size_t i = 0; i < streams.size(); i++ ) { // We want the stream that matches the trackUrl we are using if ( streams[i].find(controlUrl.c_str()) != std::string::npos ) { // Parse the sequence and rtptime values - parts = split( streams[i].c_str(), ";" ); + parts = Split(streams[i].c_str(), ";"); for ( size_t j = 0; j < parts.size(); j++ ) { if (StartsWith(parts[j], "seq=") ) { - StringVector subparts = split( parts[j], "=" ); + StringVector subparts = Split(parts[j], "="); seq = strtol( subparts[1].c_str(), nullptr, 10 ); } else if (StartsWith(parts[j], "rtptime=") ) { - StringVector subparts = split( parts[j], "=" ); + StringVector subparts = Split(parts[j], "="); rtpTime = strtol( subparts[1].c_str(), nullptr, 10 ); } } diff --git a/src/zm_rtsp_auth.cpp b/src/zm_rtsp_auth.cpp index 5e6dcc655..d947fc75e 100644 --- a/src/zm_rtsp_auth.cpp +++ b/src/zm_rtsp_auth.cpp @@ -68,10 +68,10 @@ void Authenticator::authHandleHeader(std::string headerData) { else if ( strncasecmp(headerData.c_str(), digest_match, digest_match_len) == 0) { fAuthMethod = AUTH_DIGEST; Debug(2, "Set authMethod to Digest"); - StringVector subparts = split(headerData.substr(digest_match_len, headerData.length() - digest_match_len), ","); + StringVector subparts = Split(headerData.substr(digest_match_len, headerData.length() - digest_match_len), ","); // subparts are key="value" for ( size_t i = 0; i < subparts.size(); i++ ) { - StringVector kvPair = split(TrimSpaces(subparts[i]), "="); + StringVector kvPair = Split(TrimSpaces(subparts[i]), "="); std::string key = TrimSpaces(kvPair[0]); if ( key == "realm" ) { fRealm = Trim(kvPair[1], "\""); @@ -194,7 +194,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin void Authenticator::checkAuthResponse(std::string &response) { std::string authLine; - StringVector lines = split(response, "\r\n"); + StringVector lines = Split(response, "\r\n"); const char* authenticate_match = "WWW-Authenticate:"; size_t authenticate_match_len = strlen(authenticate_match); diff --git a/src/zm_sdp.cpp b/src/zm_sdp.cpp index d8bea2802..8c691085f 100644 --- a/src/zm_sdp.cpp +++ b/src/zm_sdp.cpp @@ -107,7 +107,7 @@ SessionDescriptor::ConnInfo::ConnInfo( const std::string &connInfo ) : mTtl( 16 ), mNoAddresses( 0 ) { - StringVector tokens = split(connInfo, " "); + StringVector tokens = Split(connInfo, " "); if ( tokens.size() < 3 ) throw Exception( "Unable to parse SDP connection info from '"+connInfo+"'" ); mNetworkType = tokens[0]; @@ -116,7 +116,7 @@ SessionDescriptor::ConnInfo::ConnInfo( const std::string &connInfo ) : mAddressType = tokens[1]; if ( mAddressType != "IP4" && mAddressType != "IP6" ) throw Exception( "Invalid SDP address type '"+mAddressType+"' in connection info '"+connInfo+"'" ); - StringVector addressTokens = split( tokens[2], "/" ); + StringVector addressTokens = Split(tokens[2], "/"); if ( addressTokens.size() < 1 ) throw Exception( "Invalid SDP address '"+tokens[2]+"' in connection info '"+connInfo+"'" ); mAddress = addressTokens[0]; @@ -129,7 +129,7 @@ SessionDescriptor::ConnInfo::ConnInfo( const std::string &connInfo ) : SessionDescriptor::BandInfo::BandInfo( const std::string &bandInfo ) : mValue( 0 ) { - StringVector tokens = split( bandInfo, ":" ); + StringVector tokens = Split(bandInfo, ":"); if ( tokens.size() < 2 ) throw Exception( "Unable to parse SDP bandwidth info from '"+bandInfo+"'" ); mType = tokens[0]; @@ -165,7 +165,7 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string { MediaDescriptor *currMedia = nullptr; - StringVector lines = split( sdp, "\r\n" ); + StringVector lines = Split(sdp, "\r\n"); for ( StringVector::const_iterator iter = lines.begin(); iter != lines.end(); ++iter ) { std::string line = *iter; if ( line.empty() ) @@ -208,7 +208,7 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string case 'a' : { mAttributes.push_back( line ); - StringVector tokens = split( line, ":", 2 ); + StringVector tokens = Split(line, ":", 2); std::string attrName = tokens[0]; if ( currMedia ) { if ( attrName == "control" ) { @@ -220,14 +220,14 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string // a=rtpmap:96 MP4V-ES/90000 if ( tokens.size() < 2 ) throw Exception( "Unable to parse SDP rtpmap attribute '"+line+"' for media '"+currMedia->getType()+"'" ); - StringVector attrTokens = split( tokens[1], " " ); + StringVector attrTokens = Split(tokens[1], " "); int payloadType = atoi(attrTokens[0].c_str()); if ( payloadType != currMedia->getPayloadType() ) throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) ); std::string payloadDesc = attrTokens[1]; //currMedia->setPayloadType( payloadType ); if ( attrTokens.size() > 1 ) { - StringVector payloadTokens = split( attrTokens[1], "/" ); + StringVector payloadTokens = Split(attrTokens[1], "/"); std::string payloadDesc = payloadTokens[0]; int payloadClock = atoi(payloadTokens[1].c_str()); currMedia->setPayloadDesc( payloadDesc ); @@ -237,13 +237,13 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string // a=framesize:96 320-240 if ( tokens.size() < 2 ) throw Exception("Unable to parse SDP framesize attribute '"+line+"' for media '"+currMedia->getType()+"'"); - StringVector attrTokens = split(tokens[1], " "); + StringVector attrTokens = Split(tokens[1], " "); int payloadType = atoi(attrTokens[0].c_str()); if ( payloadType != currMedia->getPayloadType() ) throw Exception( stringtf("Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str())); //currMedia->setPayloadType( payloadType ); - StringVector sizeTokens = split(attrTokens[1], "-"); + StringVector sizeTokens = Split(attrTokens[1], "-"); int width = atoi(sizeTokens[0].c_str()); int height = atoi(sizeTokens[1].c_str()); currMedia->setFrameSize(width, height); @@ -257,16 +257,16 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string // a=fmtp:96 profile-level-id=247; config=000001B0F7000001B509000001000000012008D48D8803250F042D14440F if ( tokens.size() < 2 ) throw Exception("Unable to parse SDP fmtp attribute '"+line+"' for media '"+currMedia->getType()+"'"); - StringVector attrTokens = split(tokens[1], " ", 2); + StringVector attrTokens = Split(tokens[1], " ", 2); int payloadType = atoi(attrTokens[0].c_str()); if ( payloadType != currMedia->getPayloadType() ) throw Exception(stringtf("Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str())); //currMedia->setPayloadType( payloadType ); if ( attrTokens.size() > 1 ) { - StringVector attr2Tokens = split( attrTokens[1], "; " ); + StringVector attr2Tokens = Split(attrTokens[1], "; "); for ( unsigned int i = 0; i < attr2Tokens.size(); i++ ) { - StringVector attr3Tokens = split( attr2Tokens[i], "=" ); + StringVector attr3Tokens = Split(attr2Tokens[i], "="); //Info( "Name = %s, Value = %s", attr3Tokens[0].c_str(), attr3Tokens[1].c_str() ); if ( attr3Tokens[0] == "profile-level-id" ) { } else if ( attr3Tokens[0] == "config" ) { @@ -294,13 +294,13 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string } case 'm' : { - StringVector tokens = split(line, " "); + StringVector tokens = Split(line, " "); if ( tokens.size() < 4 ) throw Exception("Can't parse SDP media description '"+line+"'"); std::string mediaType = tokens[0]; if ( mediaType != "audio" && mediaType != "video" && mediaType != "application" ) throw Exception("Unsupported media type '"+mediaType+"' in SDP media attribute '"+line+"'"); - StringVector portTokens = split(tokens[1], "/"); + StringVector portTokens = Split(tokens[1], "/"); int mediaPort = atoi(portTokens[0].c_str()); int mediaNumPorts = 1; if ( portTokens.size() > 1 ) diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 13cf7c1ac..3eeef8e03 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -54,7 +54,7 @@ User::User(const MYSQL_ROW &dbrow) { system = (Permission)atoi(dbrow[index++]); char *monitor_ids_str = dbrow[index++]; if ( monitor_ids_str && *monitor_ids_str ) { - StringVector ids = split(monitor_ids_str, ","); + StringVector ids = Split(monitor_ids_str, ","); for ( StringVector::iterator i = ids.begin(); i < ids.end(); ++i ) { monitor_ids.push_back(atoi((*i).c_str())); } diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index 6c076fdb1..4c5ece6e2 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -61,53 +61,62 @@ std::string ReplaceAll(std::string str, const std::string &old_value, const std: return str; } -std::vector split(const std::string &s, char delim) { - std::vector elems; - std::stringstream ss(s); - std::string item; - while(std::getline(ss, item, delim)) { - elems.push_back(TrimSpaces(item)); +StringVector Split(const std::string &str, char delim) { + std::vector tokens; + + size_t start = 0; + for (size_t end = str.find(delim); end != std::string::npos; end = str.find(delim, start)) { + tokens.push_back(str.substr(start, end - start)); + start = end + 1; } - return elems; + + tokens.push_back(str.substr(start)); + + return tokens; } -StringVector split(const std::string &string, const std::string &chars, int limit) { - StringVector stringVector; - std::string tempString = string; - std::string::size_type startIndex = 0; - std::string::size_type endIndex = 0; +StringVector Split(const std::string &str, const std::string &delim, size_t limit) { + StringVector tokens; + size_t start = 0; - //Info( "Looking for '%s' in '%s', limit %d", chars.c_str(), string.c_str(), limit ); do { - // Find delimiters - endIndex = string.find_first_of( chars, startIndex ); - //Info( "Got endIndex at %d", endIndex ); - if ( endIndex > 0 ) { - //Info( "Adding '%s'", string.substr( startIndex, endIndex-startIndex ).c_str() ); - stringVector.push_back( string.substr( startIndex, endIndex-startIndex ) ); + size_t end = str.find_first_of(delim, start); + if (end > 0) { + tokens.push_back(str.substr(start, end - start)); } - if ( endIndex == std::string::npos ) + if (end == std::string::npos) { break; + } // Find non-delimiters - startIndex = tempString.find_first_not_of( chars, endIndex ); - if ( limit && (stringVector.size() == (unsigned int)(limit-1)) ) { - stringVector.push_back( string.substr( startIndex ) ); + start = str.find_first_not_of(delim, end); + if (limit && (tokens.size() == limit - 1)) { + tokens.push_back(str.substr(start)); break; } - //Info( "Got new startIndex at %d", startIndex ); - } while ( startIndex != std::string::npos ); - //Info( "Finished with %d strings", stringVector.size() ); + } while (start != std::string::npos); - return stringVector; + return tokens; } -const std::string join(const StringVector &v, const char * delim=",") { +std::pair PairSplit(const std::string &str, char delim) { + if (str.empty()) + return std::make_pair("", ""); + + size_t pos = str.find(delim); + + if (pos == std::string::npos) + return std::make_pair("", ""); + + return std::make_pair(str.substr(0, pos), str.substr(pos + 1, std::string::npos)); +} + +std::string Join(const StringVector &values, const std::string &delim) { std::stringstream ss; - for (size_t i = 0; i < v.size(); ++i) { + for (size_t i = 0; i < values.size(); ++i) { if ( i != 0 ) ss << delim; - ss << v[i]; + ss << values[i]; } return ss.str(); } @@ -159,46 +168,6 @@ const std::string base64Encode(const std::string &inString) { return outString; } -int split(const char* string, const char delim, std::vector& items) { - if ( string == nullptr ) - return -1; - - if ( string[0] == 0 ) - return -2; - - std::string str(string); - - while ( true ) { - size_t pos = str.find(delim); - items.push_back(str.substr(0, pos)); - str.erase(0, pos+1); - - if ( pos == std::string::npos ) - break; - } - - return items.size(); -} - -int pairsplit(const char* string, const char delim, std::string& name, std::string& value) { - if ( string == nullptr ) - return -1; - - if ( string[0] == 0 ) - return -2; - - std::string str(string); - size_t pos = str.find(delim); - - if ( pos == std::string::npos || pos == 0 || pos >= str.length() ) - return -3; - - name = str.substr(0, pos); - value = str.substr(pos+1, std::string::npos); - - return 0; -} - /* Detect special hardware features, such as SIMD instruction sets */ void hwcaps_detect() { neonversion = 0; diff --git a/src/zm_utils.h b/src/zm_utils.h index a0b63d6c3..2aa33d546 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -35,6 +35,12 @@ std::string Trim(const std::string &str, const std::string &char_set); inline std::string TrimSpaces(const std::string &str) { return Trim(str, " \t"); } std::string ReplaceAll(std::string str, const std::string& old_value, const std::string& new_value); +StringVector Split(const std::string &str, char delim); +StringVector Split(const std::string &str, const std::string &delim, size_t limit = 0); +std::pair PairSplit(const std::string &str, char delim); + +std::string Join(const StringVector &values, const std::string &delim = ","); + inline bool StartsWith(const std::string &haystack, const std::string &needle) { return (haystack.substr(0, needle.length()) == needle); } @@ -50,15 +56,9 @@ std::string stringtf(const std::string &format, Args... args) { return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside } -StringVector split( const std::string &string, const std::string &chars, int limit=0 ); -const std::string join( const StringVector &, const char * ); - const std::string base64Encode( const std::string &inString ); void string_toupper(std::string& str); -int split(const char* string, const char delim, std::vector& items); -int pairsplit(const char* string, const char delim, std::string& name, std::string& value); - void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes); void timespec_diff(struct timespec *start, struct timespec *end, struct timespec *diff); diff --git a/tests/zm_utils.cpp b/tests/zm_utils.cpp index ee6e44017..8b021ef35 100644 --- a/tests/zm_utils.cpp +++ b/tests/zm_utils.cpp @@ -73,77 +73,71 @@ TEST_CASE("StartsWith") { REQUIRE(StartsWith(" test=abc", "test") == false); } -TEST_CASE("split (char delimiter)") { - std::vector items; - int res; +TEST_CASE("Split (char delimiter)") { + std::vector items = Split("", ' '); + REQUIRE(items == std::vector{""}); - res = split(nullptr, ' ', items); - REQUIRE(res == -1); - REQUIRE(items.size() == 0); - - res = split("", ' ', items); - REQUIRE(res == -2); - REQUIRE(items.size() == 0); - - res = split("abc def ghi", ' ', items); - REQUIRE(res == 3); + items = Split("abc def ghi", ' '); REQUIRE(items == std::vector{"abc", "def", "ghi"}); + + items = Split("abc,def,,ghi", ','); + REQUIRE(items == std::vector{"abc", "def", "", "ghi"}); } -TEST_CASE("split (string delimiter)") { +TEST_CASE("Split (string delimiter)") { std::vector items; - items = split("", ""); + items = Split("", ""); REQUIRE(items == std::vector{""}); - items = split("", " "); + items = Split("", " "); REQUIRE(items == std::vector{""}); - items = split("", " \t"); + items = Split("", " \t"); REQUIRE(items == std::vector{""}); - items = split("", " \t"); + items = Split("", " \t"); REQUIRE(items == std::vector{""}); - items = split(" ", " "); + items = Split(" ", " "); REQUIRE(items.size() == 0); - items = split(" ", " "); + items = Split(" ", " "); REQUIRE(items.size() == 0); - items = split(" ", " \t"); + items = Split(" ", " \t"); REQUIRE(items.size() == 0); - items = split("a b", ""); + items = Split("a b", ""); REQUIRE(items == std::vector{"a b"}); - items = split("a b", " "); + items = Split("a b", " "); REQUIRE(items == std::vector{"a", "b"}); - items = split("a \tb", " \t"); + items = Split("a \tb", " \t"); REQUIRE(items == std::vector{"a", "b"}); - items = split(" a \tb ", " \t"); + items = Split(" a \tb ", " \t"); REQUIRE(items == std::vector{"a", "b"}); - items = split(" a=b ", "="); + items = Split(" a=b ", "="); REQUIRE(items == std::vector{" a", "b "}); - items = split(" a=b ", " ="); + items = Split(" a=b ", " ="); REQUIRE(items == std::vector{"a", "b"}); - items = split("a b c", " ", 2); + items = Split("a b c", " ", 2); REQUIRE(items == std::vector{"a", "b c"}); } -TEST_CASE("join") { - REQUIRE(join({}, "") == ""); - REQUIRE(join({}, " ") == ""); - REQUIRE(join({""}, "") == ""); - REQUIRE(join({"a"}, "") == "a"); - REQUIRE(join({"a"}, ",") == "a"); - REQUIRE(join({"a", "b"}, ",") == "a,b"); - REQUIRE(join({"a", "b"}, "") == "ab"); +TEST_CASE("Join") { + REQUIRE(Join({}, "") == ""); + REQUIRE(Join({}, " ") == ""); + REQUIRE(Join({""}, "") == ""); + REQUIRE(Join({"a"}, "") == "a"); + REQUIRE(Join({"a"}, ",") == "a"); + REQUIRE(Join({"a", "b"}, ",") == "a,b"); + REQUIRE(Join({"a", "b"}, "") == "ab"); } TEST_CASE("base64Encode") {