diff --git a/Sources/Plasma/NucleusLib/pnUtils/CMakeLists.txt b/Sources/Plasma/NucleusLib/pnUtils/CMakeLists.txt
index 03610c9d..349c2e40 100644
--- a/Sources/Plasma/NucleusLib/pnUtils/CMakeLists.txt
+++ b/Sources/Plasma/NucleusLib/pnUtils/CMakeLists.txt
@@ -15,6 +15,8 @@ set(pnUtils_HEADERS
pnUtSort.h
pnUtStr.h
pnUtTime.h
+
+ plCmdParser.h
)
set(pnUtils_SOURCES
@@ -24,9 +26,13 @@ set(pnUtils_SOURCES
pnUtList.cpp
pnUtStr.cpp
pnUtTime.cpp
+
+ plCmdParser.cpp
)
add_library(pnUtils STATIC ${pnUtils_HEADERS} ${pnUtils_SOURCES})
+target_link_libraries(pnUtils CoreLib)
+
source_group("Header Files" FILES ${pnUtils_HEADERS})
source_group("Source Files" FILES ${pnUtils_SOURCES})
diff --git a/Sources/Plasma/NucleusLib/pnUtils/plCmdParser.cpp b/Sources/Plasma/NucleusLib/pnUtils/plCmdParser.cpp
new file mode 100644
index 00000000..375d9e10
--- /dev/null
+++ b/Sources/Plasma/NucleusLib/pnUtils/plCmdParser.cpp
@@ -0,0 +1,617 @@
+/*==LICENSE==*
+
+CyanWorlds.com Engine - MMOG client, server and tools
+Copyright (C) 2011 Cyan Worlds, Inc.
+
+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 3 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, see .
+
+Additional permissions under GNU GPL version 3 section 7
+
+If you modify this Program, or any covered work, by linking or
+combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
+NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
+JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
+(or a modified version of those libraries),
+containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
+PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
+JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
+licensors of this Program grant you additional
+permission to convey the resulting work. Corresponding Source for a
+non-source form of such a combination shall include the source code for
+the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
+work.
+
+You can contact Cyan Worlds, Inc. by email legal@cyan.com
+ or by snail mail at:
+ Cyan Worlds, Inc.
+ 14617 N Newport Hwy
+ Mead, WA 99021
+
+*==LICENSE==*/
+
+#include "plCmdParser.h"
+
+#include
+
+#define WHITESPACE " \"\t\r\n\x1A"
+#define FLAGS "-/"
+#define SEPARATORS "=:"
+#define TOGGLES "+-"
+#define ALL WHITESPACE FLAGS SEPARATORS TOGGLES
+
+#define hsCheckFlagBits(f,c,m) ((f & m)==c)
+
+
+struct plCmdArgData
+{
+ plCmdArgDef def;
+
+ union {
+ bool boolVal;
+ float floatVal;
+ int32_t intVal;
+ const char* strVal;
+ uint32_t uintVal;
+ } val;
+
+ plString buffer;
+ size_t nameChars;
+ bool isSpecified;
+};
+
+struct plCmdTokenState {
+ size_t fPendingIndex;
+ size_t fUnflaggedIndex;
+};
+
+
+class plCmdParserImpl
+{
+protected:
+ plString fProgramName;
+ std::vector fArgArray;
+ std::vector fLookupArray;
+ std::vector fUnflaggedArray;
+ uint32_t fRequiredCount;
+ CmdError fError;
+
+ void SetDefaultValue(plCmdArgData& arg);
+ bool ProcessValue(plCmdTokenState* state, size_t index, const plString& str);
+ bool TokenizeFlags(plCmdTokenState* state, const plString& str);
+ bool LookupFlagged(plString& name, size_t* lastIndex, bool force=false) const;
+
+public:
+ plCmdParserImpl(const plCmdArgDef* defs, size_t defCount);
+
+ bool Tokenize(plCmdTokenState* state, std::vector& strs);
+ const plCmdArgData* FindArgByName(const plString& name) const;
+ const plCmdArgData* FindArgById(size_t id) const;
+ bool CheckAllRequiredArguments(plCmdTokenState* state);
+
+ const plString GetProgramName() const { return fProgramName; }
+
+ CmdError GetError() const { return fError; }
+};
+
+
+plCmdParserImpl::plCmdParserImpl(const plCmdArgDef* defs, size_t defCount)
+{
+ size_t loop;
+ fError = kCmdErrorSuccess;
+
+ // Save the argument definitions
+ size_t maxId = 0;
+ size_t unflaggedCount = 0;
+
+ fArgArray.resize(defCount);
+
+ for (loop = 0; loop < defCount; ++loop) {
+ plCmdArgDef def = defs[loop];
+
+ // Check whether this argument is flagged
+ bool flagged = hsCheckFlagBits(def.flags,
+ kCmdArgFlagged,
+ kCmdArgMask);
+
+ // Disallow names on unflagged arguments
+ ASSERT(flagged || !def.name.IsEmpty());
+
+ // Store the argument data
+ plCmdArgData& arg = fArgArray[loop];
+ arg.def = def;
+ arg.buffer = "";
+ arg.nameChars = def.name.GetSize();
+ arg.isSpecified = false;
+
+ SetDefaultValue(arg);
+ maxId = std::max(maxId, def.id);
+
+ // Track the number of unflagged arguments
+ if (!flagged) {
+ ++unflaggedCount;
+ }
+ }
+
+
+ // Build the id lookup table
+ size_t idTableSize = std::min(maxId + 1, defCount * 2);
+ fLookupArray.resize(idTableSize);
+
+ for (loop = 0; loop < defCount; ++loop) {
+ if (defs[loop].id < idTableSize) {
+ fLookupArray[defs[loop].id] = loop;
+ }
+ }
+
+
+ // Build the unflagged array
+ size_t unflaggedIndex = 0;
+ fUnflaggedArray.resize(unflaggedCount);
+
+ for (loop = 0; loop < defCount; ++loop) {
+ bool req = hsCheckFlagBits(defs[loop].flags,
+ kCmdArgRequired,
+ kCmdArgMask);
+
+ if (req) {
+ fUnflaggedArray[unflaggedIndex++] = loop;
+ }
+ }
+
+ fRequiredCount = unflaggedIndex;
+
+ for (loop = 0; loop < defCount; ++loop) {
+ bool flagged = hsCheckFlagBits(defs[loop].flags,
+ kCmdArgFlagged,
+ kCmdArgMask);
+
+ bool req = hsCheckFlagBits(defs[loop].flags,
+ kCmdArgRequired,
+ kCmdArgMask);
+
+ if (!flagged && !req) {
+ fUnflaggedArray[unflaggedIndex++] = loop;
+ }
+ }
+}
+
+
+void plCmdParserImpl::SetDefaultValue(plCmdArgData& arg)
+{
+ uint32_t argType = arg.def.flags & kCmdTypeMask;
+
+ switch (argType) {
+ case kCmdTypeBool:
+ arg.val.boolVal = !hsCheckFlagBits(arg.def.flags,
+ kCmdBoolSet,
+ kCmdBoolMask);
+ break;
+
+ case kCmdTypeInt:
+ arg.val.intVal = 0;
+ break;
+
+ case kCmdTypeUint:
+ arg.val.uintVal = 0;
+ break;
+
+ case kCmdTypeFloat:
+ arg.val.floatVal = 0.0f;
+ break;
+
+ case kCmdTypeString:
+ arg.val.strVal = "";
+ break;
+
+ DEFAULT_FATAL(argType);
+ }
+}
+
+
+bool plCmdParserImpl::Tokenize(plCmdTokenState* state, std::vector& strs)
+{
+ bool result = true;
+
+ for (auto it = strs.begin(); result && it != strs.end(); ++it) {
+ if (fProgramName.IsEmpty()) {
+ fProgramName = *it;
+ continue;
+ }
+
+ // If the previous argument is awaiting a value, then use this token
+ // as the value
+ if (state->fPendingIndex != size_t(-1)) {
+ result = ProcessValue(state, state->fPendingIndex, *it);
+ state->fPendingIndex = size_t(-1);
+ continue;
+ }
+
+ // Identify and process flagged parameters
+ if ((*it).REMatch("[" FLAGS "].+") && TokenizeFlags(state, *it)) {
+ continue;
+ }
+
+ // Process unflagged parameters
+ if (state->fUnflaggedIndex < fUnflaggedArray.size()) {
+ result = ProcessValue(state, fUnflaggedArray[state->fUnflaggedIndex++], *it);
+ continue;
+ }
+
+ // Process invalid parameters
+ if (!fError) {
+ fError = kCmdErrorTooManyArgs;
+ }
+ result = false;
+ break;
+ }
+
+ return result;
+}
+
+
+bool plCmdParserImpl::ProcessValue(plCmdTokenState* state, size_t index, const plString& str)
+{
+ plCmdArgData& arg = fArgArray[index];
+ arg.isSpecified = true;
+ uint32_t argType = arg.def.flags & kCmdTypeMask;
+
+ switch (argType) {
+ case kCmdTypeBool:
+ if (str.CompareI("true") == 0)
+ arg.val.boolVal = true;
+ else if (str.CompareI("false") == 0)
+ arg.val.boolVal = false;
+ else if (str.IsEmpty())
+ arg.val.boolVal = hsCheckFlagBits(arg.def.flags,
+ kCmdBoolSet,
+ kCmdBoolMask);
+ else
+ fError = kCmdErrorInvalidValue;
+ break;
+
+ case kCmdTypeFloat:
+ arg.val.floatVal = str.ToFloat();
+ break;
+
+ case kCmdTypeInt:
+ arg.val.intVal = str.ToInt();
+ break;
+
+ case kCmdTypeString:
+ arg.buffer = str;
+ arg.val.strVal = arg.buffer.c_str();
+ break;
+
+ case kCmdTypeUint:
+ arg.val.uintVal = str.ToUInt(10);
+ break;
+
+ DEFAULT_FATAL(argType);
+
+ }
+ return true;
+}
+
+bool plCmdParserImpl::TokenizeFlags(plCmdTokenState* state, const plString& str)
+{
+ bool result = true;
+ std::vector tokens = str.Tokenize(ALL);
+
+ for (auto it = tokens.begin(); result && it != tokens.end(); ++it) {
+ size_t lastIndex = size_t(-1);
+ plString buffer = *it;
+
+ if (buffer.IsEmpty()) {
+ continue;
+ }
+
+ while (result) {
+ // Lookup the argument name
+ result = LookupFlagged(buffer, &lastIndex);
+ if (!result) {
+ fError = kCmdErrorInvalidArg;
+ result = false;
+ }
+
+ break;
+ }
+
+ if (!result) {
+ break;
+ }
+
+ // Check for an argument value provided using a separator
+ if (str.REMatch(".+[" SEPARATORS "].+") && !(*(++it)).IsEmpty()) {
+ result = ProcessValue(state, lastIndex, *it);
+ break;
+ }
+
+ bool isBool = hsCheckFlagBits(fArgArray[lastIndex].def.flags,
+ kCmdTypeBool,
+ kCmdTypeMask);
+
+ // Process values for boolean arguments
+ if (isBool) {
+ result = ProcessValue(state, lastIndex, plString::Null);
+ continue;
+ }
+
+ // Process values for non-boolean arguments
+ else {
+ // Check for an argument value immediately following the name
+ if (!buffer.IsEmpty()) {
+ result = ProcessValue(state, lastIndex, buffer);
+ break;
+ }
+
+ // Check for an argument value in the next token
+ else {
+ state->fPendingIndex = lastIndex;
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+bool plCmdParserImpl::LookupFlagged(plString& name, size_t* lastIndex, bool force) const
+{
+ size_t argCount = fArgArray.size();
+ size_t chars = name.GetSize();
+ size_t bestIndex = size_t(-1);
+ size_t bestChars = 0;
+
+
+ size_t prevChars = 0;
+ if (*lastIndex != size_t(-1)) {
+ prevChars = fArgArray[*lastIndex].def.name.GetSize();
+ }
+
+ for (; prevChars != size_t(-1) && !bestChars; --prevChars) {
+ // Find this argument in the list
+ for (size_t index = 0; index < argCount; ++index) {
+ const plCmdArgData& arg = fArgArray[index];
+
+ // Ignore non-flagged arguments
+ bool flagged = hsCheckFlagBits(arg.def.flags,
+ kCmdArgFlagged,
+ kCmdArgMask);
+ if (!flagged && !force)
+ continue;
+
+ // Ignore this arg if it wouldn't beat the previous best match
+ if (arg.def.name.GetSize() < bestChars + prevChars)
+ continue;
+
+ // Ignore this argument if it doesn't match the prefix
+ bool caseSensitive = hsCheckBits(arg.def.flags,
+ kCmdCaseSensitive);
+
+ if (prevChars) {
+ const plCmdArgData& prev = fArgArray[*lastIndex];
+
+ if (prevChars >= arg.def.name.GetSize())
+ continue;
+
+ if (caseSensitive &&
+ arg.def.name.CompareN(prev.def.name, prevChars))
+ continue;
+
+ if (!caseSensitive &&
+ arg.def.name.CompareNI(prev.def.name, prevChars))
+ continue;
+ }
+
+ // Ignore this argument if it doesn't match the suffix
+ plString suffix = arg.def.name.Substr(prevChars);
+ if (caseSensitive && suffix.CompareN(name, std::min(name.GetSize(), suffix.GetSize())))
+ continue;
+
+ if (!caseSensitive && suffix.CompareNI(name, std::min(name.GetSize(), suffix.GetSize())))
+ continue;
+
+ // Track the best match
+ bestIndex = index;
+ bestChars = arg.def.name.GetSize() - prevChars;
+ if (bestChars == chars)
+ break;
+ }
+ }
+
+ // Return the result
+ name = name.Substr(bestChars);
+ *lastIndex = bestIndex;
+ return bestChars != 0;
+}
+
+const plCmdArgData* plCmdParserImpl::FindArgByName(const plString& name) const
+{
+ // Search for an argument with this name
+ size_t index = size_t(-1);
+ plString arg = name;
+ if (!LookupFlagged(arg, &index, true)) {
+ return nullptr;
+ }
+
+ // Return the argument data
+ return &fArgArray[index];
+}
+
+const plCmdArgData* plCmdParserImpl::FindArgById(size_t id) const
+{
+ // Search for the argument with this id
+ size_t index;
+ if (id < fLookupArray.size()) {
+ index = fLookupArray[id];
+ } else {
+ for (index = 0; index < fArgArray.size(); ++index) {
+ if (fArgArray[index].def.id == id) {
+ break;
+ }
+ }
+ }
+
+ // Verify that we found the correct argument
+ if ((index >= fArgArray.size()) || (fArgArray[index].def.id != id)) {
+ return nullptr;
+ }
+
+ // Return the argument data
+ return &fArgArray[index];
+}
+
+bool plCmdParserImpl::CheckAllRequiredArguments(plCmdTokenState* state)
+{
+ bool result = (state->fUnflaggedIndex >= fRequiredCount);
+
+ if (!result) {
+ fError = kCmdErrorTooFewArgs;
+ }
+
+ return result;
+}
+
+
+
+plCmdParser::plCmdParser(const plCmdArgDef* defs, size_t defCount)
+{
+ Initialize(defs, defCount);
+}
+
+plCmdParser::~plCmdParser()
+{
+ delete fParser;
+}
+
+void plCmdParser::Initialize(const plCmdArgDef* defs, size_t defCount)
+{
+ fParser = new plCmdParserImpl(defs, defCount);
+}
+
+bool plCmdParser::Parse(const plString& cmdLine)
+{
+ // Process the command line
+ plCmdTokenState state = {
+ size_t(-1), // pending index
+ 0 // unflagged index
+ };
+
+ std::vector tokens = cmdLine.Tokenize(WHITESPACE);
+
+ bool result = fParser->Tokenize(&state, tokens);
+
+ if (result) {
+ result = fParser->CheckAllRequiredArguments(&state);
+ }
+
+ return result;
+}
+
+bool plCmdParser::Parse(std::vector& argv)
+{
+ // Process the command line
+ plCmdTokenState state = {
+ size_t(-1), // pending index
+ 0 // unflagged index
+ };
+
+ bool result = fParser->Tokenize(&state, argv);
+
+ if (result) {
+ result = fParser->CheckAllRequiredArguments(&state);
+ }
+
+ return result;
+}
+
+
+const plString plCmdParser::GetProgramName() const
+{
+ return fParser->GetProgramName();
+}
+
+
+bool plCmdParser::GetBool(size_t id) const
+{
+ return fParser->FindArgById(id)->val.boolVal;
+}
+
+bool plCmdParser::GetBool(const plString& name) const
+{
+ return fParser->FindArgByName(name)->val.boolVal;
+}
+
+float plCmdParser::GetFloat(size_t id) const
+{
+ return fParser->FindArgById(id)->val.floatVal;
+}
+
+float plCmdParser::GetFloat(const plString& name) const
+{
+ return fParser->FindArgByName(name)->val.floatVal;
+}
+
+int32_t plCmdParser::GetInt(size_t id) const
+{
+ return fParser->FindArgById(id)->val.intVal;
+}
+
+int32_t plCmdParser::GetInt(const plString& name) const
+{
+ return fParser->FindArgByName(name)->val.intVal;
+}
+
+const plString plCmdParser::GetString(size_t id) const
+{
+ return fParser->FindArgById(id)->val.strVal;
+}
+
+const plString plCmdParser::GetString(const plString& name) const
+{
+ return fParser->FindArgByName(name)->val.strVal;
+}
+
+uint32_t plCmdParser::GetUint(size_t id) const
+{
+ return fParser->FindArgById(id)->val.uintVal;
+}
+
+uint32_t plCmdParser::GetUint(const plString& name) const
+{
+ return fParser->FindArgByName(name)->val.uintVal;
+}
+
+bool plCmdParser::IsSpecified(size_t id) const
+{
+ if (const plCmdArgData* data = fParser->FindArgById(id)) {
+ return data->isSpecified;
+ }
+
+ return false;
+}
+
+bool plCmdParser::IsSpecified(const plString& name) const
+{
+ if (const plCmdArgData* data = fParser->FindArgByName(name)) {
+ return data->isSpecified;
+ }
+
+ return false;
+}
+
+CmdError plCmdParser::GetError() const
+{
+ return fParser->GetError();
+}
diff --git a/Sources/Plasma/NucleusLib/pnUtils/plCmdParser.h b/Sources/Plasma/NucleusLib/pnUtils/plCmdParser.h
new file mode 100644
index 00000000..8c84a584
--- /dev/null
+++ b/Sources/Plasma/NucleusLib/pnUtils/plCmdParser.h
@@ -0,0 +1,136 @@
+/*==LICENSE==*
+
+CyanWorlds.com Engine - MMOG client, server and tools
+Copyright (C) 2011 Cyan Worlds, Inc.
+
+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 3 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, see .
+
+Additional permissions under GNU GPL version 3 section 7
+
+If you modify this Program, or any covered work, by linking or
+combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
+NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
+JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
+(or a modified version of those libraries),
+containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
+PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
+JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
+licensors of this Program grant you additional
+permission to convey the resulting work. Corresponding Source for a
+non-source form of such a combination shall include the source code for
+the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
+work.
+
+You can contact Cyan Worlds, Inc. by email legal@cyan.com
+ or by snail mail at:
+ Cyan Worlds, Inc.
+ 14617 N Newport Hwy
+ Mead, WA 99021
+
+*==LICENSE==*/
+
+#ifndef _plCmdParser_h_
+#define _plCmdParser_h_
+
+#include "plString.h"
+
+enum CmdArg
+{
+ kCmdArgFlagged = 0x0, // default
+ kCmdArgOptional = 0x1,
+ kCmdArgRequired = 0x2,
+ kCmdArgMask = 0xF
+};
+
+enum CmdType
+{
+ kCmdTypeBool = 0x0 << 4, // default
+ kCmdTypeInt = 0x1 << 4,
+ kCmdTypeUint = 0x2 << 4,
+ kCmdTypeFloat = 0x3 << 4,
+ kCmdTypeString = 0x4 << 4,
+ kCmdTypeMask = 0xF << 4
+};
+
+enum CmdBool
+{
+ kCmdBoolSet = 0x0 << 8, // default
+ kCmdBoolUnset = 0x1 << 8,
+ kCmdBoolMask = 0xF << 8
+};
+
+enum CmdCase
+{
+ kCmdCaseSensitive = 0x1 << 28
+};
+
+// Error codes
+enum CmdError
+{
+ kCmdErrorSuccess,
+ kCmdErrorInvalidArg,
+ kCmdErrorInvalidValue,
+ kCmdErrorTooFewArgs,
+ kCmdErrorTooManyArgs,
+ kNumCmdErrors
+};
+
+
+struct plCmdArgDef
+{
+ uint32_t flags;
+ plString name; // must be compile-time constant
+ size_t id;
+};
+
+
+class plCmdParser
+{
+protected:
+ class plCmdParserImpl* fParser;
+
+ plCmdParser() : fParser(nullptr) { }
+ void Initialize(const plCmdArgDef* defs, size_t defCount);
+
+public:
+ plCmdParser(const plCmdArgDef* defs, size_t defCount);
+ virtual ~plCmdParser();
+
+ bool Parse(const plString& cmdLine);
+ bool Parse(std::vector& argv);
+
+ const plString GetProgramName() const;
+
+ bool GetBool(size_t id) const;
+ bool GetBool(const plString& name) const;
+
+ float GetFloat(size_t id) const;
+ float GetFloat(const plString& name) const;
+
+ int32_t GetInt(size_t id) const;
+ int32_t GetInt(const plString& name) const;
+
+ const plString GetString(size_t id) const;
+ const plString GetString(const plString& name) const;
+
+ uint32_t GetUint(size_t id) const;
+ uint32_t GetUint(const plString& name) const;
+
+ bool IsSpecified(size_t id) const;
+ bool IsSpecified(const plString& name) const;
+
+ CmdError GetError() const;
+};
+
+#endif //_plCmdParser_h_
diff --git a/Sources/Tests/NucleusTests/CMakeLists.txt b/Sources/Tests/NucleusTests/CMakeLists.txt
index 56df15ee..6abd34a5 100644
--- a/Sources/Tests/NucleusTests/CMakeLists.txt
+++ b/Sources/Tests/NucleusTests/CMakeLists.txt
@@ -1 +1,2 @@
add_subdirectory(pnEncryptionTest)
+add_subdirectory(pnUtilsTest)
diff --git a/Sources/Tests/NucleusTests/pnUtilsTest/CMakeLists.txt b/Sources/Tests/NucleusTests/pnUtilsTest/CMakeLists.txt
new file mode 100644
index 00000000..f96fc670
--- /dev/null
+++ b/Sources/Tests/NucleusTests/pnUtilsTest/CMakeLists.txt
@@ -0,0 +1,16 @@
+include_directories(${GTEST_INCLUDE_DIR})
+include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
+include_directories(../../../Plasma/CoreLib)
+include_directories(../../../Plasma/NucleusLib)
+
+set(pnUtilsTest_SOURCES
+ test_plCmdParser.cpp
+ )
+
+add_executable(test_pnUtils ${pnUtilsTest_SOURCES})
+target_link_libraries(test_pnUtils gtest gtest_main)
+target_link_libraries(test_pnUtils pnUtils)
+
+add_test(NAME test_pnUtils COMMAND test_pnUtils)
+add_dependencies(check test_pnUtils)
+
diff --git a/Sources/Tests/NucleusTests/pnUtilsTest/test_plCmdParser.cpp b/Sources/Tests/NucleusTests/pnUtilsTest/test_plCmdParser.cpp
new file mode 100644
index 00000000..85d28cf9
--- /dev/null
+++ b/Sources/Tests/NucleusTests/pnUtilsTest/test_plCmdParser.cpp
@@ -0,0 +1,547 @@
+/*==LICENSE==*
+
+CyanWorlds.com Engine - MMOG client, server and tools
+Copyright (C) 2011 Cyan Worlds, Inc.
+
+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 3 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, see .
+
+Additional permissions under GNU GPL version 3 section 7
+
+If you modify this Program, or any covered work, by linking or
+combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
+NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
+JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
+(or a modified version of those libraries),
+containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
+PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
+JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
+licensors of this Program grant you additional
+permission to convey the resulting work. Corresponding Source for a
+non-source form of such a combination shall include the source code for
+the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
+work.
+
+You can contact Cyan Worlds, Inc. by email legal@cyan.com
+ or by snail mail at:
+ Cyan Worlds, Inc.
+ 14617 N Newport Hwy
+ Mead, WA 99021
+
+*==LICENSE==*/
+
+#include
+#include
+#include "pnUtils/plCmdParser.h"
+
+TEST(plCmdParser, basic_parsing)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdArgRequired | kCmdTypeString, "path", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ bool success = parser.Parse("plCmdParser ~/.plasma/config.dat");
+
+ plString prog = parser.GetProgramName();
+ plString path = parser.GetString(0);
+
+ EXPECT_EQ(success, true);
+ EXPECT_STREQ(prog.c_str(), "plCmdParser");
+ EXPECT_STREQ(path.c_str(), "~/.plasma/config.dat");
+ EXPECT_EQ(parser.GetError(), kCmdErrorSuccess);
+}
+
+TEST(plCmdParser, argv_parsing)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdArgRequired | kCmdTypeString, "path", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+
+ const char* args[] = {"plCmdParser", "~/.plasma/config.dat"};
+ int argc = 2;
+
+ std::vector tokens(args, args+argc);
+
+ bool success = parser.Parse(tokens);
+
+ plString prog = parser.GetProgramName();
+ plString path = parser.GetString(0);
+
+ EXPECT_EQ(success, true);
+ EXPECT_STREQ(prog.c_str(), "plCmdParser");
+ EXPECT_STREQ(path.c_str(), "~/.plasma/config.dat");
+ EXPECT_EQ(parser.GetError(), kCmdErrorSuccess);
+}
+
+TEST(plCmdParser, argv_preserving_spaces)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdArgRequired | kCmdTypeString, "path", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+
+ const char* args[] = {"plCmdParser", "~/.plasma/Uru Live/config.dat"};
+ int argc = 2;
+
+ std::vector tokens(args, args+argc);
+
+ bool success = parser.Parse(tokens);
+
+ plString prog = parser.GetProgramName();
+ plString path = parser.GetString(0);
+
+ EXPECT_EQ(success, true);
+ EXPECT_STREQ(prog.c_str(), "plCmdParser");
+ EXPECT_STREQ(path.c_str(), "~/.plasma/Uru Live/config.dat");
+ EXPECT_EQ(parser.GetError(), kCmdErrorSuccess);
+}
+
+TEST(plCmdParser, wchar_argv_parsing)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdArgRequired | kCmdTypeString, "path", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+
+ const wchar_t* args[] = {L"plCmdParser", L"~/.plasma/config.dat"};
+ int argc = 2;
+
+ std::vector tokens(argc);
+ for (int i = 0; i < argc; i++) {
+ tokens.push_back(plString::FromWchar(args[i]));
+ }
+
+ bool success = parser.Parse(tokens);
+
+ plString prog = parser.GetProgramName();
+ plString path = parser.GetString(0);
+
+ EXPECT_EQ(success, true);
+ EXPECT_STREQ(prog.c_str(), "plCmdParser");
+ EXPECT_STREQ(path.c_str(), "~/.plasma/config.dat");
+ EXPECT_EQ(parser.GetError(), kCmdErrorSuccess);
+}
+
+
+TEST(plCmdParser, flagged_int)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --size 5");
+
+ int32_t size = parser.GetInt(0);
+
+ EXPECT_EQ(size, 5);
+}
+
+TEST(plCmdParser, flagged_int_short)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser -s 5");
+
+ int32_t size = parser.GetInt(0);
+
+ EXPECT_EQ(size, 5);
+}
+
+TEST(plCmdParser, flagged_int_assign)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --size=5");
+
+ int32_t size = parser.GetInt(0);
+
+ EXPECT_EQ(size, 5);
+}
+
+TEST(plCmdParser, flagged_int_slash)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser /size -5");
+
+ int32_t size = parser.GetInt(0);
+
+ EXPECT_EQ(size, -5);
+}
+
+TEST(plCmdParser, flagged_int_bystring)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --size 5");
+
+ int32_t size = parser.GetInt("size");
+
+ EXPECT_EQ(size, 5);
+}
+
+
+TEST(plCmdParser, flagged_uint)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeUint, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --size 5");
+
+ uint32_t size = parser.GetUint(0);
+
+ EXPECT_EQ(size, 5);
+}
+
+TEST(plCmdParser, flagged_uint_bystring)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeUint, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --size 5");
+
+ uint32_t size = parser.GetUint("size");
+
+ EXPECT_EQ(size, 5);
+}
+
+
+TEST(plCmdParser, flagged_float)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeFloat, "volume", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --volume 0.5");
+
+ float vol = parser.GetFloat(0);
+
+ EXPECT_EQ(vol, 0.5);
+}
+
+TEST(plCmdParser, flagged_float_bystring)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeFloat, "volume", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --volume 0.5");
+
+ float vol = parser.GetFloat("volume");
+
+ EXPECT_EQ(vol, 0.5);
+}
+
+
+TEST(plCmdParser, flagged_string)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeString, "path", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --path foo");
+
+ plString path = parser.GetString(0);
+
+ EXPECT_STREQ(path.c_str(), "foo");
+}
+
+TEST(plCmdParser, flagged_string_bystring)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeString, "path", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --path foo");
+
+ plString path = parser.GetString("path");
+
+ EXPECT_STREQ(path.c_str(), "foo");
+}
+
+
+TEST(plCmdParser, flagged_bool_default)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeBool, "verbose", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --verbose");
+
+ bool verbose = parser.GetBool(0);
+
+ EXPECT_EQ(verbose, true);
+}
+
+TEST(plCmdParser, flagged_bool_true)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeBool, "verbose", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --verbose=TRUE");
+
+ bool verbose = parser.GetBool(0);
+
+ EXPECT_EQ(verbose, true);
+}
+
+TEST(plCmdParser, flagged_bool_false)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeBool, "verbose", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --verbose=FALSE");
+
+ bool verbose = parser.GetBool(0);
+
+ EXPECT_EQ(verbose, false);
+}
+
+TEST(plCmdParser, flagged_bool_invalid)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeBool, "verbose", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --verbose=foo");
+
+ bool verbose = parser.GetBool(0);
+
+ EXPECT_EQ(verbose, false);
+ EXPECT_EQ(parser.GetError(), kCmdErrorInvalidValue);
+}
+
+TEST(plCmdParser, flagged_bool_bystring)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeBool, "verbose", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --verbose");
+
+ bool verbose = parser.GetBool("verbose");
+
+ EXPECT_EQ(verbose, true);
+}
+
+
+TEST(plCmdParser, optional_unspecified)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt | kCmdArgOptional, "speed", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser");
+
+ bool specified = parser.IsSpecified(0);
+ int32_t speed = parser.GetInt(0);
+
+ EXPECT_EQ(specified, false);
+ EXPECT_EQ(speed, 0);
+}
+
+TEST(plCmdParser, optional_specified)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt | kCmdArgOptional, "speed", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser 1");
+
+ bool specified = parser.IsSpecified(0);
+ int32_t speed = parser.GetInt(0);
+
+ EXPECT_EQ(specified, true);
+ EXPECT_EQ(speed, 1);
+}
+
+TEST(plCmdParser, optional_specified_bystring)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt | kCmdArgOptional, "speed", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser 1");
+
+ bool specified = parser.IsSpecified("speed");
+ int32_t speed = parser.GetInt("speed");
+
+ EXPECT_EQ(specified, true);
+ EXPECT_EQ(speed, 1);
+}
+
+
+TEST(plCmdParser, specified_invalid)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt | kCmdArgOptional, "speed", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser 1");
+
+ bool specified = parser.IsSpecified(1);
+
+ EXPECT_EQ(specified, false);
+}
+
+TEST(plCmdParser, specified_invalid_bystring)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt | kCmdArgOptional, "speed", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser 1");
+
+ bool specified = parser.IsSpecified("path");
+
+ EXPECT_EQ(specified, false);
+}
+
+
+TEST(plCmdParser, flagged_weird_id)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt, "size", 10}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --size 5");
+
+ int32_t size = parser.GetInt(10);
+
+ EXPECT_EQ(size, 5);
+}
+
+
+TEST(plCmdParser, fake_flag)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ bool success = parser.Parse("plCmdParser --speed 5");
+
+ EXPECT_EQ(success, false);
+ EXPECT_EQ(parser.GetError(), kCmdErrorInvalidArg);
+}
+
+
+TEST(plCmdParser, too_many_args)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ bool success = parser.Parse("plCmdParser --size 10 foo");
+
+ EXPECT_EQ(success, false);
+ EXPECT_EQ(parser.GetError(), kCmdErrorTooManyArgs);
+}
+
+
+TEST(plCmdParser, missing_required)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdArgRequired | kCmdTypeString, "path", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ bool success = parser.Parse("plCmdParser");
+
+ EXPECT_EQ(success, false);
+ EXPECT_EQ(parser.GetError(), kCmdErrorTooFewArgs);
+}
+
+
+TEST(plCmdParser, combined_assign)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt, "size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser --size5");
+
+ int32_t size = parser.GetInt(0);
+
+ EXPECT_EQ(size, 5);
+}
+
+
+TEST(plCmdParser, case_sensitive_nomatch)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt | kCmdCaseSensitive, "Size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser -s 5");
+
+ bool specified = parser.IsSpecified(0);
+
+ EXPECT_EQ(specified, false);
+}
+
+TEST(plCmdParser, case_sensitive_match)
+{
+ const plCmdArgDef cmds[] = {
+ { kCmdTypeInt | kCmdCaseSensitive, "Size", 0}
+ };
+
+ plCmdParser parser(cmds, arrsize(cmds));
+ parser.Parse("plCmdParser -S 5");
+
+ bool specified = parser.IsSpecified(0);
+
+ EXPECT_EQ(specified, true);
+}