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); +}