[API Break] Fix pathTools (and a bit stringTools).

Api changes :
 - removeLastPathElement do not takes extra arguments
   `removePreSeparator` and `removePostSeparator`.
   This is not needed as path do not need special tailing separator.
 - Only one function `split`. Arguments can be implicitly convert to
   string. No need for overloading functions to explicitly cast them.
 - `split` function takes another argument `trimEmpty`. If true, empty
   element are removed.

Path manipulation now almost pass trough a vector<string> to store each
path's part.

Most of the complex works is now made in the normalizeParts function.
This commit is contained in:
Matthieu Gautier 2019-09-18 15:43:35 +02:00
parent 49c0c5ff47
commit 598dd3c175
11 changed files with 344 additions and 116 deletions

View File

@ -25,9 +25,7 @@
bool isRelativePath(const std::string& path); bool isRelativePath(const std::string& path);
std::string computeAbsolutePath(const std::string& path, const std::string& relativePath); std::string computeAbsolutePath(const std::string& path, const std::string& relativePath);
std::string computeRelativePath(const std::string& path, const std::string& absolutePath); std::string computeRelativePath(const std::string& path, const std::string& absolutePath);
std::string removeLastPathElement(const std::string& path, std::string removeLastPathElement(const std::string& path);
const bool removePreSeparator = false,
const bool removePostSeparator = false);
std::string appendToDirectory(const std::string& directoryPath, const std::string& filename); std::string appendToDirectory(const std::string& directoryPath, const std::string& filename);
unsigned int getFileSize(const std::string& path); unsigned int getFileSize(const std::string& path);

View File

@ -43,10 +43,7 @@ void loadICUExternalTables();
std::string urlEncode(const std::string& value, bool encodeReserved = false); std::string urlEncode(const std::string& value, bool encodeReserved = false);
std::string urlDecode(const std::string& value, bool component = false); std::string urlDecode(const std::string& value, bool component = false);
std::vector<std::string> split(const std::string&, const std::string&); std::vector<std::string> split(const std::string&, const std::string&, bool trimEmpty = true);
std::vector<std::string> split(const char*, const char*);
std::vector<std::string> split(const std::string&, const char*);
std::vector<std::string> split(const char*, const std::string&);
std::string join(const std::vector<std::string>& list, const std::string& sep); std::string join(const std::vector<std::string>& list, const std::string& sep);
std::string ucAll(const std::string& word); std::string ucAll(const std::string& word);

View File

@ -49,7 +49,7 @@ Aria2::Aria2():
m_secret = "token:"+m_secret; m_secret = "token:"+m_secret;
std::string aria2cmd = appendToDirectory( std::string aria2cmd = appendToDirectory(
removeLastPathElement(getExecutablePath(true), true, true), removeLastPathElement(getExecutablePath(true)),
ARIA2_CMD); ARIA2_CMD);
if (fileExists(aria2cmd)) { if (fileExists(aria2cmd)) {
// A local aria2c exe exists (packaged with kiwix-desktop), use it. // A local aria2c exe exists (packaged with kiwix-desktop), use it.

View File

@ -36,7 +36,7 @@ void KiwixServe::run()
std::vector<const char*> callCmd; std::vector<const char*> callCmd;
std::string kiwixServeCmd = appendToDirectory( std::string kiwixServeCmd = appendToDirectory(
removeLastPathElement(getExecutablePath(true), true, true), removeLastPathElement(getExecutablePath(true)),
KIWIXSERVE_CMD); KIWIXSERVE_CMD);
if (fileExists(kiwixServeCmd)) { if (fileExists(kiwixServeCmd)) {
// A local kiwix-serve exe exists (packaged with kiwix-desktop), use it. // A local kiwix-serve exe exists (packaged with kiwix-desktop), use it.

View File

@ -115,7 +115,7 @@ unsigned int Library::getBookCount(const bool localBooks,
bool Library::writeToFile(const std::string& path) bool Library::writeToFile(const std::string& path)
{ {
auto baseDir = removeLastPathElement(path, true, false); auto baseDir = removeLastPathElement(path);
LibXMLDumper dumper(this); LibXMLDumper dumper(this);
dumper.setBaseDir(baseDir); dumper.setBaseDir(baseDir);
return writeTextFile(path, dumper.dumpLibXMLContent(getBooksIds())); return writeTextFile(path, dumper.dumpLibXMLContent(getBooksIds()));

View File

@ -59,7 +59,7 @@ bool Manager::parseXmlDom(const pugi::xml_document& doc,
book.setReadOnly(readOnly); book.setReadOnly(readOnly);
book.updateFromXml(bookNode, book.updateFromXml(bookNode,
removeLastPathElement(libraryPath, true, false)); removeLastPathElement(libraryPath));
/* Update the book properties with the new importer */ /* Update the book properties with the new importer */
if (libraryVersion.empty() if (libraryVersion.empty()
@ -177,7 +177,7 @@ std::string Manager::addBookFromPathAndGetId(const std::string& pathToOpen,
if (pathToSave != pathToOpen) { if (pathToSave != pathToOpen) {
book.setPath(isRelativePath(pathToSave) book.setPath(isRelativePath(pathToSave)
? computeAbsolutePath( ? computeAbsolutePath(
removeLastPathElement(writableLibraryPath, true, false), removeLastPathElement(writableLibraryPath),
pathToSave) pathToSave)
: pathToSave); : pathToSave);
} }

View File

@ -39,11 +39,12 @@
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
#include <algorithm>
#ifdef _WIN32 #ifdef _WIN32
const std::string SEPARATOR("\\"); #define SEPARATOR "\\"
#else #else
const std::string SEPARATOR("/"); #define SEPARATOR "/"
#include <unistd.h> #include <unistd.h>
#endif #endif
@ -62,11 +63,63 @@ bool isRelativePath(const std::string& path)
#endif #endif
} }
std::vector<std::string> normalizeParts(std::vector<std::string> parts, bool absolute)
{
std::vector<std::string> ret;
#ifdef _WIN32
//Special case if we have a drive directory not at first.
//Starts from there.
auto it = find_if(parts.rbegin(), parts.rend(),
[](const std::string& p) ->bool
{ return p.length() == 2 && p[1] == ':'; });
if (it != parts.rend()) {
parts.erase(parts.begin(), it.base()-1);
}
#endif
for (auto& part: parts) {
if (part == "..") {
if (absolute) {
// We try to remove as far as possible.
if (ret.size() > 1) {
ret.pop_back();
}
} else {
// We remove only if we can remove it.
// Else we add it.
if (!ret.empty() && ret.back() != "..") {
ret.pop_back();
} else {
ret.push_back("..");
}
}
continue;
}
if (part == "") {
#ifndef _WIN32
if (ret.empty()) {
ret.push_back("");
}
#endif
continue;
}
if (part == ".") {
continue;
}
ret.push_back(part);
}
#ifndef _WIN32
if (absolute && ret.size() == 1 && ret.back() == "") {
ret.push_back("");
}
#endif
return ret;
}
std::string computeRelativePath(const std::string& path, const std::string& absolutePath) std::string computeRelativePath(const std::string& path, const std::string& absolutePath)
{ {
std::vector<std::string> pathParts = kiwix::split(path, SEPARATOR); auto pathParts = normalizeParts(kiwix::split(path, SEPARATOR, false), false);
std::vector<std::string> absolutePathParts auto absolutePathParts = kiwix::split(absolutePath, SEPARATOR, false);
= kiwix::split(absolutePath, SEPARATOR);
unsigned int commonCount = 0; unsigned int commonCount = 0;
while (commonCount < pathParts.size() while (commonCount < pathParts.size()
@ -75,108 +128,63 @@ std::string computeRelativePath(const std::string& path, const std::string& abso
commonCount++; commonCount++;
} }
std::string relativePath; std::vector<std::string> relativeParts;
#ifdef _WIN32
/* On Windows you have a token more because the root is represented
by a letter */
if (commonCount == 0) {
relativePath = ".." + SEPARATOR;
}
#endif
for (unsigned int i = commonCount; i < pathParts.size(); i++) { for (unsigned int i = commonCount; i < pathParts.size(); i++) {
relativePath += ".." + SEPARATOR; relativeParts.push_back("..");
} }
for (unsigned int i = commonCount; i < absolutePathParts.size(); i++) { for (unsigned int i = commonCount; i < absolutePathParts.size(); i++) {
relativePath += absolutePathParts[i]; relativeParts.push_back(absolutePathParts[i]);
relativePath += i + 1 < absolutePathParts.size() ? SEPARATOR : "";
} }
return relativePath; return kiwix::join(normalizeParts(relativeParts, false), SEPARATOR);
} }
#ifdef _WIN32
# define STRTOK strtok_s
#else
# define STRTOK strtok_r
#endif
/* Warning: the relative path must be with slashes */
std::string computeAbsolutePath(const std::string& path, const std::string& relativePath) std::string computeAbsolutePath(const std::string& path, const std::string& relativePath)
{ {
std::string absolutePath; std::string absolutePath = path;
if (path.empty()) { if (path.empty()) {
char* path = NULL; char* cpath;
size_t size = 0;
#ifdef _WIN32 #ifdef _WIN32
path = _getcwd(path, size); cpath = _getcwd(NULL, 0);
#else #else
path = getcwd(path, size); cpath = getcwd(NULL, 0);
#endif #endif
absolutePath = cpath;
absolutePath = std::string(path) + SEPARATOR; free(cpath);
} else {
absolutePath = path.substr(path.length() - 1, 1) == SEPARATOR
? path
: path + SEPARATOR;
} }
#if _WIN32 auto absoluteParts = normalizeParts(kiwix::split(absolutePath, SEPARATOR, false), true);
char* cRelativePath = _strdup(relativePath.c_str()); auto relativeParts = kiwix::split(relativePath, SEPARATOR, false);
#else
char* cRelativePath = strdup(relativePath.c_str());
#endif
char* saveptr = nullptr;
char* token = STRTOK(cRelativePath, "/", &saveptr);
while (token != NULL) { absoluteParts.insert(absoluteParts.end(), relativeParts.begin(), relativeParts.end());
if (std::string(token) == "..") { return kiwix::join(normalizeParts(absoluteParts, true), SEPARATOR);
absolutePath = removeLastPathElement(absolutePath, true, false);
token = STRTOK(NULL, "/", &saveptr);
} else if (strcmp(token, ".") && strcmp(token, "")) {
absolutePath += std::string(token);
token = STRTOK(NULL, "/", &saveptr);
if (token != NULL) {
absolutePath += SEPARATOR;
}
} else {
token = STRTOK(NULL, "/", &saveptr);
}
}
free(cRelativePath);
return absolutePath;
} }
std::string removeLastPathElement(const std::string& path, std::string removeLastPathElement(const std::string& path)
const bool removePreSeparator,
const bool removePostSeparator)
{ {
std::string newPath = path; auto parts = normalizeParts(kiwix::split(path, SEPARATOR, false), false);
size_t offset = newPath.find_last_of(SEPARATOR); if (!parts.empty()) {
if (removePreSeparator && parts.pop_back();
#ifndef _WIN32
offset != newPath.find_first_of(SEPARATOR) &&
#endif
offset == newPath.length() - 1) {
newPath = newPath.substr(0, offset);
offset = newPath.find_last_of(SEPARATOR);
} }
newPath = removePostSeparator ? newPath.substr(0, offset) return kiwix::join(parts, SEPARATOR);
: newPath.substr(0, offset + 1);
return newPath;
} }
std::string appendToDirectory(const std::string& directoryPath, const std::string& filename) std::string appendToDirectory(const std::string& directoryPath, const std::string& filename)
{ {
std::string newPath = directoryPath + SEPARATOR + filename; std::string newPath = directoryPath;
if (!directoryPath.empty() && directoryPath.back() != SEPARATOR[0]) {
newPath += SEPARATOR;
}
newPath += filename;
return newPath; return newPath;
} }
std::string getLastPathElement(const std::string& path) std::string getLastPathElement(const std::string& path)
{ {
return path.substr(path.find_last_of(SEPARATOR) + 1); auto parts = normalizeParts(kiwix::split(path, SEPARATOR), false);
if (parts.empty()) {
return "";
}
return parts.back();
} }
unsigned int getFileSize(const std::string& path) unsigned int getFileSize(const std::string& path)

View File

@ -267,37 +267,28 @@ std::string kiwix::urlDecode(const std::string& value, bool component)
/* Split string in a token array */ /* Split string in a token array */
std::vector<std::string> kiwix::split(const std::string& str, std::vector<std::string> kiwix::split(const std::string& str,
const std::string& delims = " *-") const std::string& delims,
bool trimEmpty)
{ {
std::string::size_type lastPos = str.find_first_not_of(delims, 0); std::string::size_type lastPos = 0;
std::string::size_type pos = str.find_first_of(delims, lastPos); std::string::size_type pos = 0;
std::vector<std::string> tokens; std::vector<std::string> tokens;
while( (pos = str.find_first_of(delims, lastPos)) < str.length() )
while (std::string::npos != pos || std::string::npos != lastPos) { {
tokens.push_back(str.substr(lastPos, pos - lastPos)); auto token = str.substr(lastPos, pos - lastPos);
lastPos = str.find_first_not_of(delims, pos); if (!trimEmpty || !token.empty()) {
pos = str.find_first_of(delims, lastPos); tokens.push_back(token);
}
lastPos = pos + 1;
} }
auto token = str.substr(lastPos);
if (!trimEmpty || !token.empty()) {
tokens.push_back(token);
}
return tokens; return tokens;
} }
std::vector<std::string> kiwix::split(const char* lhs, const char* rhs)
{
const std::string m1(lhs), m2(rhs);
return split(m1, m2);
}
std::vector<std::string> kiwix::split(const char* lhs, const std::string& rhs)
{
return split(lhs, rhs.c_str());
}
std::vector<std::string> kiwix::split(const std::string& lhs, const char* rhs)
{
return split(lhs.c_str(), rhs);
}
std::string kiwix::join(const std::vector<std::string>& list, const std::string& sep) std::string kiwix::join(const std::vector<std::string>& list, const std::string& sep)
{ {
std::stringstream ss; std::stringstream ss;

View File

@ -5,7 +5,8 @@ tests = [
'library', 'library',
'regex', 'regex',
'tagParsing', 'tagParsing',
'stringTools' 'stringTools',
'pathTools'
] ]

216
test/pathTools.cpp Normal file
View File

@ -0,0 +1,216 @@
/*
* Copyright (C) 2019 Matthieu Gautier
*
* 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
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
* NON-INFRINGEMENT. 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 St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gtest/gtest.h"
#include <string>
#include <vector>
#include "../include/tools/pathTools.h"
#ifdef _WIN32
# define S "\\"
# define AS "c:"
#else
# define S "/"
# define AS ""
#endif
#define P2(a, b) a S b
#define P3(a, b, c) P2(P2(a, b), c)
#define P4(a, b, c, d) P2(P3(a, b, c), d)
#define P5(a, b, c, d, e) P2(P4(a, b, c, d), e)
#define P6(a, b, c, d, e, f) P2(P5(a, b, c ,d, e), f)
#define A1(a) P2(AS,a)
#define A2(a, b) A1(P2(a, b))
#define A3(a, b, c) A1(P3(a, b, c))
#define A4(a, b, c, d) A1(P4(a, b, c, d))
#define A5(a, b, c, d, e) A1(P5(a, b, c, d, e))
std::vector<std::string> normalizeParts(std::vector<std::string> parts, bool absolute);
namespace
{
#define V std::vector<std::string>
TEST(pathTools, normalizePartsAbsolute)
{
#define N(...) normalizeParts(__VA_ARGS__, true)
ASSERT_EQ(N({}), V({}));
#ifdef _WIN32
ASSERT_EQ(N({"c:"}), V({"c:"}));
#else
ASSERT_EQ(N({""}), V({"", ""}));
#endif
ASSERT_EQ(N({AS, "a"}), V({AS, "a"}));
ASSERT_EQ(N({AS, "a", "b"}), V({AS, "a", "b"}));
ASSERT_EQ(N({AS, "a", "b", ".."}), V({AS, "a"}));
#ifdef _WIN32
ASSERT_EQ(N({AS, "a", "b", "..", ".."}), V({AS}));
#else
ASSERT_EQ(N({AS, "a", "b", "..", ".."}), V({AS, ""}));
#endif
ASSERT_EQ(N({AS, "a", "b", "..", "..", "..", "foo"}), V({AS, "foo"}));
ASSERT_EQ(N({AS, "..", "..", "c", "d", ".", "..", "foo"}), V({AS, "c", "foo"}));
ASSERT_EQ(N({AS, "a", "b", ".", "c", "d", "..", "foo"}), V({AS, "a", "b", "c", "foo"}));
#ifdef _WIN32
ASSERT_EQ(N({"c:", "a", "b", ".", "c", "d:", "..", "foo"}), V({"d:", "foo"}));
#endif
#undef N
}
TEST(pathTools, normalizePartsRelative)
{
#define N(...) normalizeParts(__VA_ARGS__, false)
ASSERT_EQ(N({}), V({}));
ASSERT_EQ(N({""}), V({""}));
ASSERT_EQ(N({"a"}), V({"a"}));
ASSERT_EQ(N({"a", "b"}), V({"a", "b"}));
ASSERT_EQ(N({"a", "b", ".."}), V({"a"}));
ASSERT_EQ(N({"a", "b", "..", ".."}), V({}));
ASSERT_EQ(N({"a", "b", "..", "..", "..", "foo"}), V({"..", "foo"}));
ASSERT_EQ(N({"..", "..", "c", "d", ".", "..", "foo"}), V({"..", "..", "c", "foo"}));
ASSERT_EQ(N({"a", "b", ".", "c", "d", "..", "foo"}), V({"a", "b", "c", "foo"}));
#undef N
}
TEST(pathTools, isRelativePath)
{
ASSERT_TRUE(isRelativePath("foo"));
ASSERT_TRUE(isRelativePath(P2("foo","bar")));
ASSERT_TRUE(isRelativePath(P3(".","foo","bar")));
ASSERT_TRUE(isRelativePath(P2("..","foo")));
ASSERT_TRUE(isRelativePath(P4("foo","","bar","")));
ASSERT_FALSE(isRelativePath(A1("foo")));
ASSERT_FALSE(isRelativePath(A2("foo", "bar")));
}
TEST(pathTools, computeAbsolutePath)
{
ASSERT_EQ(computeAbsolutePath(A2("a","b"), "foo"),
A3("a","b","foo"));
ASSERT_EQ(computeAbsolutePath(A3("a","b",""), "foo"),
A3("a","b","foo"));
ASSERT_EQ(computeAbsolutePath(A2("a","b"), P2(".","foo")),
A3("a","b","foo"));
ASSERT_EQ(computeAbsolutePath(A2("a","b"), P2("..","foo")),
A2("a","foo"));
ASSERT_EQ(computeAbsolutePath(A3("a","b",""), P2("..","foo")),
A2("a","foo"));
ASSERT_EQ(computeAbsolutePath(A5("a","b","c","d","e"), P2("..","foo")),
A5("a","b","c","d","foo"));
ASSERT_EQ(computeAbsolutePath(A5("a","b","c","d","e"), P5("..","..","..","g","foo")),
A4("a","b","g","foo"));
}
TEST(pathTools, computeRelativePath)
{
ASSERT_EQ(computeRelativePath(A2("a","b"), A3("a","b","foo")),
"foo");
ASSERT_EQ(computeRelativePath(A3("a","b",""), A3("a","b","foo")),
"foo");
ASSERT_EQ(computeRelativePath(A2("a","b"), A2("a","foo")),
P2("..","foo"));
ASSERT_EQ(computeRelativePath(A3("a","b",""), A2("a","foo")),
P2("..","foo"));
ASSERT_EQ(computeRelativePath(A5("a","b","c","d","e"), A5("a","b","c","d","foo")),
P2("..","foo"));
ASSERT_EQ(computeRelativePath(A5("a","b","c","d","e"), A4("a","b","g","foo")),
P5("..","..","..","g","foo"));
}
TEST(pathTools, removeLastPathElement)
{
ASSERT_EQ(removeLastPathElement(P3("a","b","c")),
P2("a","b"));
ASSERT_EQ(removeLastPathElement(A3("a","b","c")),
A2("a","b"));
ASSERT_EQ(removeLastPathElement(P4("a","b","c","")),
P2("a","b"));
ASSERT_EQ(removeLastPathElement(A4("a","b","c","")),
A2("a","b"));
}
TEST(pathTools, appendToDirectory)
{
ASSERT_EQ(appendToDirectory(P3("a","b","c"), "foo.xml"),
P4("a","b","c","foo.xml"));
ASSERT_EQ(appendToDirectory(P4("a","b","c",""), "foo.xml"),
P4("a","b","c","foo.xml"));
ASSERT_EQ(appendToDirectory(P3("a","b","c"), P2("d","foo.xml")),
P5("a","b","c","d","foo.xml"));
ASSERT_EQ(appendToDirectory(P4("a","b","c",""), P2("d","foo.xml")),
P5("a","b","c","d","foo.xml"));
ASSERT_EQ(appendToDirectory(P3("a","b","c"), P2(".","foo.xml")),
P5("a","b","c",".","foo.xml"));
ASSERT_EQ(appendToDirectory(P4("a","b","c",""), P2(".","foo.xml")),
P5("a","b","c",".","foo.xml"));
}
TEST(pathTools, goUp)
{
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), ".."),
A2("a", "b"));
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), P2("..","..")),
A1("a"));
#ifdef _WIN32
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), P3("..","..","..")),
"c:");
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), P4("..","..","..","..")),
"c:");
#else
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), P3("..","..","..")),
"/");
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), P4("..","..","..","..")),
"/");
#endif
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), P2("..", "foo")),
A3("a", "b","foo"));
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), P3("..","..","foo")),
A2("a","foo"));
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), P4("..","..","..","foo")),
A1("foo"));
ASSERT_EQ(computeAbsolutePath(A3("a","b","c"), P5("..","..","..","..","foo")),
A1("foo"));
}
#ifdef _WIN32
TEST(pathTools, dirChange)
{
std::string p1("c:\\a\\b\\c");
std::string p2("d:\\d\\e\\foo.xml");
std::string relative_path = computeRelativePath(p1, p2);
ASSERT_EQ(relative_path, "d:\\d\\e\\foo.xml");
std::string abs_path = computeAbsolutePath(p1, relative_path);
ASSERT_EQ(abs_path, p2);
ASSERT_EQ(computeAbsolutePath(p1, "..\\..\\..\\..\\..\\d:\\d\\e\\foo.xml"), p2);
}
#endif
};
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -23,6 +23,7 @@
namespace kiwix { namespace kiwix {
std::string join(const std::vector<std::string>& list, const std::string& sep); std::string join(const std::vector<std::string>& list, const std::string& sep);
std::vector<std::string> split(const std::string& base, const std::string& sep, bool trimEmpty);
}; };
using namespace kiwix; using namespace kiwix;
@ -36,6 +37,22 @@ TEST(stringTools, join)
ASSERT_EQ(join(list, ";"), "a;b;c"); ASSERT_EQ(join(list, ";"), "a;b;c");
} }
TEST(stringTools, split)
{
std::vector<std::string> list1 = { "a", "b", "c" };
ASSERT_EQ(split("a;b;c", ";", false), list1);
ASSERT_EQ(split("a;b;c", ";", true), list1);
std::vector<std::string> list2 = { "", "a", "b", "c" };
ASSERT_EQ(split(";a;b;c", ";", false), list2);
ASSERT_EQ(split(";a;b;c", ";", true), list1);
std::vector<std::string> list3 = { "", "a", "b", "c", ""};
ASSERT_EQ(split(";a;b;c;", ";", false), list3);
ASSERT_EQ(split(";a;b;c;", ";", true), list1);
std::vector<std::string> list4 = { "", "a", "b", "", "c", ""};
ASSERT_EQ(split(";a;b;;c;", ";", false), list4);
ASSERT_EQ(split(";a;b;;c;", ";", true), list1);
}
}; };
int main(int argc, char** argv) int main(int argc, char** argv)
{ {