From 27ddf0105247d69829a86edba40afe3bb3558e53 Mon Sep 17 00:00:00 2001 From: Jessica James Date: Tue, 21 Jan 2020 19:47:06 -0600 Subject: [PATCH] Added `app_parameters` class and tests --- src/common/CMakeLists.txt | 9 +- src/common/app_parameters.cpp | 136 ++++++++++++++++ src/include/app_parameters.hpp | 44 ++++++ src/test/CMakeLists.txt | 2 +- src/test/app_parameters.cpp | 277 +++++++++++++++++++++++++++++++++ 5 files changed, 464 insertions(+), 4 deletions(-) create mode 100644 src/common/app_parameters.cpp create mode 100644 src/include/app_parameters.hpp create mode 100644 src/test/app_parameters.cpp diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 0e474fc..f31812e 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,8 +1,6 @@ -cmake_minimum_required(VERSION 3.8) - # Setup source files set(SOURCE_FILES - timer/timer.cpp timer/timer_manager.cpp thread_pool.cpp timer/timer_context.cpp timer/cancel_token.cpp timer/synchronized_timer.cpp object.cpp parser/parser.cpp parser/parser_manager.cpp config.cpp serialize.cpp parsers/json.cpp unicode.cpp) + timer/timer.cpp timer/timer_manager.cpp thread_pool.cpp timer/timer_context.cpp timer/cancel_token.cpp timer/synchronized_timer.cpp object.cpp parser/parser.cpp parser/parser_manager.cpp config.cpp serialize.cpp parsers/json.cpp unicode.cpp app_parameters.cpp) # Setup library build target add_library(jessilib ${SOURCE_FILES}) @@ -13,3 +11,8 @@ target_include_directories(jessilib PRIVATE ../include/impl/asio/include) # Setup additionally needed libs target_link_libraries(jessilib ${JESSILIB_ADDITOINAL_LIBS}) + +# Setup fmt::fmt; include directories are included as SYSTEM to suppress warnings (which would be treated as errors) +get_target_property(fmt_include_dirs fmt::fmt INTERFACE_INCLUDE_DIRECTORIES) +target_include_directories(jessilib SYSTEM PUBLIC ${fmt_include_dirs}) +target_link_libraries(jessilib fmt::fmt) diff --git a/src/common/app_parameters.cpp b/src/common/app_parameters.cpp new file mode 100644 index 0000000..ea77955 --- /dev/null +++ b/src/common/app_parameters.cpp @@ -0,0 +1,136 @@ +/** + * Copyright (C) 2019 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "app_parameters.hpp" + +namespace jessilib { + +app_parameters::app_parameters(int in_argc, char** in_argv) + : app_parameters{ in_argc, const_cast(in_argv) } { + // Empty ctor body +} + +app_parameters::app_parameters(int in_argc, const char** in_argv) { + // Sanity safety check; should never happen + if (in_argc <= 0 || in_argv == nullptr) { + return; + } + + // Populate path + m_path = in_argv[0]; + + // Process args + std::string_view key{ nullptr }; + std::string value; + auto flush_value = [&key, &value, this]() { + // This is the start of a key; flush what we were previously processing + if (!key.empty()) { + if (value.empty()) { + m_switches.emplace_back(key); + } + else { + m_values.emplace(key, std::move(value)); + value.clear(); + } + } + }; + + for (int index = 1; index < in_argc; ++index) { + const char* arg = in_argv[index]; + if (arg != nullptr && *arg != '\0') { + // Check if this is a key or value + if (*arg == '-') { + // Flush pending value (if any) + flush_value(); + + // Strip any leading '-' or '--' and set key + key = arg + 1; + if (key[0] == '-') { + key.remove_prefix(1); + } + + // Parse key for any value denominator ('=') + size_t key_end = key.find('='); + if (key_end != std::string_view::npos) { + // arg contains start of a value + value = key.substr(key_end + 1); + key = key.substr(0, key_end); + } + } + else { + // This is part of a value; add it + if (!value.empty()) { + value += ' '; + } + + value += arg; + } + } + // else // empty string encountered + + // Push arg + m_args.emplace_back(arg); + } + + // Flush any pending switch/value + flush_value(); +} + +std::string_view app_parameters::path() const { + return m_path; +} + +const std::vector& app_parameters::arguments() const { + return m_args; +} + +const std::vector& app_parameters::switches() const { + return m_switches; +} + +std::unordered_set app_parameters::switches_set() const { + return { m_switches.begin(), m_switches.end() }; +} + +const std::unordered_map& app_parameters::values() const { + return m_values; +} + +object app_parameters::as_object() const { + // Null check + if (m_path.empty() + && m_args.empty()) { + // app_parameters is null; return a null object + return object{}; + } + + // Transform m_values into appropriate map type; TODO: add helper to object for this + std::map values_map; + for (auto& value : m_values) { + values_map.emplace(value.first, value.second); + } + + return std::map{ + { "Path", m_path }, + { "Args", m_args }, + { "Switches", m_switches }, + { "Values", values_map } + }; +} + +} // namespace jessilib diff --git a/src/include/app_parameters.hpp b/src/include/app_parameters.hpp new file mode 100644 index 0000000..f1925f0 --- /dev/null +++ b/src/include/app_parameters.hpp @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2019 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "object.hpp" + +namespace jessilib { + +class app_parameters { +public: + app_parameters(int in_argc, char** in_argv); + app_parameters(int in_argc, const char** in_argv); + + std::string_view path() const; + const std::vector& arguments() const; + const std::vector& switches() const; + std::unordered_set switches_set() const; + const std::unordered_map& values() const; + jessilib::object as_object() const; + + operator jessilib::object() const { return as_object(); } + +private: + std::string_view m_path; + std::vector m_args; + std::vector m_switches; + std::unordered_map m_values; +}; + +} // namespace jessilib diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 4b83aee..77710c2 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,6 +1,6 @@ # Setup source files set(SOURCE_FILES - timer.cpp thread_pool.cpp util.cpp object.cpp parser.cpp config.cpp parsers/json.cpp unicode.cpp) + timer.cpp thread_pool.cpp util.cpp object.cpp parser.cpp config.cpp parsers/json.cpp unicode.cpp app_parameters.cpp) # Setup gtest add_subdirectory(googletest/googletest) diff --git a/src/test/app_parameters.cpp b/src/test/app_parameters.cpp new file mode 100644 index 0000000..70deb61 --- /dev/null +++ b/src/test/app_parameters.cpp @@ -0,0 +1,277 @@ +/** + * Copyright (C) 2020 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "test.hpp" +#include "app_parameters.hpp" + +using namespace jessilib; +using namespace std::literals; + +class ArgWrapper { +public: + template + ArgWrapper(Args... in_args) + : ArgWrapper{ std::vector{ in_args... } } { + // Empty ctor body + } + + ArgWrapper(std::vector in_args) + : m_args{ in_args }, + m_argv{ new const char*[in_args.size()] } { + // Populate m_argv + for (size_t index = 0; index != m_args.size(); ++index) { + m_argv[index] = m_args[index].c_str(); + } + } + + const char** argv() const { + return m_argv.get(); + } + + int argc() const { + return m_args.size(); + } + +private: + std::vector m_args; + std::unique_ptr m_argv; +}; + +TEST(AppParametersTest, null) { + app_parameters parameters{ 0, static_cast(nullptr) }; + app_parameters parameters2{ 1234, static_cast(nullptr) }; + + EXPECT_TRUE(parameters.path().empty()); + EXPECT_TRUE(parameters.arguments().empty()); + EXPECT_TRUE(parameters.switches().empty()); + EXPECT_TRUE(parameters.switches_set().empty()); + EXPECT_TRUE(parameters.values().empty()); + EXPECT_TRUE(parameters.as_object().null()); + + EXPECT_TRUE(parameters2.path().empty()); + EXPECT_TRUE(parameters2.arguments().empty()); + EXPECT_TRUE(parameters2.switches().empty()); + EXPECT_TRUE(parameters2.switches_set().empty()); + EXPECT_TRUE(parameters2.values().empty()); + EXPECT_TRUE(parameters2.as_object().null()); +} + +TEST(AppParametersTest, path_only) { + ArgWrapper args{ "/path/to/exe" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_EQ(parameters.path(), "/path/to/exe"); + EXPECT_TRUE(parameters.arguments().empty()); + EXPECT_TRUE(parameters.switches().empty()); + EXPECT_TRUE(parameters.switches_set().empty()); + EXPECT_TRUE(parameters.values().empty()); + + auto obj = parameters.as_object(); + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); +} + +TEST(AppParametersTest, single_switch) { + ArgWrapper args{ "/path/to/exe", "-switch" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 1U); + EXPECT_EQ(parameters.switches().size(), 1U); + EXPECT_EQ(parameters.switches_set().size(), 1U); + EXPECT_TRUE(parameters.values().empty()); + + auto obj = parameters.as_object(); + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], object{ std::vector{ "-switch" } }); + EXPECT_EQ(obj["Switches"], object{ std::vector{ "switch" } }); +} + +TEST(AppParametersTest, double_switch) { + ArgWrapper args{ "/path/to/exe", "-switch1", "--switch2" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 2U); + EXPECT_EQ(parameters.switches().size(), 2U); + EXPECT_EQ(parameters.switches_set().size(), 2U); + EXPECT_TRUE(parameters.values().empty()); + + auto obj = parameters.as_object(); + std::vector expected_args{ "-switch1", "--switch2" }; + std::vector expected_switches{ "switch1", "switch2" }; + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], expected_args); + EXPECT_EQ(obj["Switches"], expected_switches); +} + +TEST(AppParametersTest, duplicate_switch) { + ArgWrapper args{ "/path/to/exe", "-switch", "--switch" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 2U); + EXPECT_EQ(parameters.switches().size(), 2U); + EXPECT_EQ(parameters.switches_set().size(), 1U); + EXPECT_TRUE(parameters.values().empty()); + + auto obj = parameters.as_object(); + std::vector expected_args{ "-switch", "--switch" }; + std::vector expected_switches{ "switch", "switch" }; + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], expected_args); + EXPECT_EQ(obj["Switches"], expected_switches); +} + +TEST(AppParametersTest, single_value) { + ArgWrapper args{ "/path/to/exe", "-key", "value" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 2U); + EXPECT_EQ(parameters.switches().size(), 0U); + EXPECT_EQ(parameters.switches_set().size(), 0U); + EXPECT_EQ(parameters.values().size(), 1U); + + auto obj = parameters.as_object(); + std::vector expected_args{ "-key", "value" }; + std::map expected_values{ { "key", "value" } }; + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], expected_args); + EXPECT_EQ(obj["Values"], expected_values); +} + +TEST(AppParametersTest, single_value_eq) { + ArgWrapper args{ "/path/to/exe", "-key=value" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 1U); + EXPECT_EQ(parameters.switches().size(), 0U); + EXPECT_EQ(parameters.switches_set().size(), 0U); + EXPECT_EQ(parameters.values().size(), 1U); + + auto obj = parameters.as_object(); + std::vector expected_args{ "-key=value" }; + std::map expected_values{ { "key", "value" } }; + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], expected_args); + EXPECT_EQ(obj["Values"], expected_values); +} + +TEST(AppParametersTest, multiword_value) { + ArgWrapper args{ "/path/to/exe", "-key", "valuePart1", "valuePart2" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 3U); + EXPECT_EQ(parameters.switches().size(), 0U); + EXPECT_EQ(parameters.switches_set().size(), 0U); + EXPECT_EQ(parameters.values().size(), 1U); + + auto obj = parameters.as_object(); + std::vector expected_args{ "-key", "valuePart1", "valuePart2" }; + std::map expected_values{ { "key", "valuePart1 valuePart2" } }; + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], expected_args); + EXPECT_EQ(obj["Values"], expected_values); +} + +TEST(AppParametersTest, multiword_value_eq) { + ArgWrapper args{ "/path/to/exe", "-key=valuePart1", "valuePart2" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 2U); + EXPECT_EQ(parameters.switches().size(), 0U); + EXPECT_EQ(parameters.switches_set().size(), 0U); + EXPECT_EQ(parameters.values().size(), 1U); + + auto obj = parameters.as_object(); + std::vector expected_args{ "-key=valuePart1", "valuePart2" }; + std::map expected_values{ { "key", "valuePart1 valuePart2" } }; + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], expected_args); + EXPECT_EQ(obj["Values"], expected_values); +} + +TEST(AppParametersTest, double_value) { + ArgWrapper args{ "/path/to/exe", "-key", "value", "--key2", "value2" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 4U); + EXPECT_EQ(parameters.switches().size(), 0U); + EXPECT_EQ(parameters.switches_set().size(), 0U); + EXPECT_EQ(parameters.values().size(), 2U); + + auto obj = parameters.as_object(); + std::vector expected_args{ "-key", "value", "--key2", "value2" }; + std::map expected_values{ { "key", "value" }, { "key2", "value2" } }; + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], expected_args); + EXPECT_EQ(obj["Values"], expected_values); +} + +TEST(AppParametersTest, double_value_eq) { + ArgWrapper args{ "/path/to/exe", "-key=value", "--key2=value2" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 2U); + EXPECT_EQ(parameters.switches().size(), 0U); + EXPECT_EQ(parameters.switches_set().size(), 0U); + EXPECT_EQ(parameters.values().size(), 2U); + + auto obj = parameters.as_object(); + std::vector expected_args{ "-key=value", "--key2=value2" }; + std::map expected_values{ { "key", "value" }, { "key2", "value2" } }; + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], expected_args); + EXPECT_EQ(obj["Values"], expected_values); +} + +TEST(AppParametersTest, switch_and_value) { + ArgWrapper args{ "/path/to/exe", "--switch", "-key", "value" }; + app_parameters parameters{ args.argc(), args.argv() }; + + EXPECT_FALSE(parameters.path().empty()); + EXPECT_EQ(parameters.arguments().size(), 3U); + EXPECT_EQ(parameters.switches().size(), 1U); + EXPECT_EQ(parameters.switches_set().size(), 1U); + EXPECT_EQ(parameters.values().size(), 1U); + + auto obj = parameters.as_object(); + std::vector expected_args{ "--switch", "-key", "value" }; + std::vector expected_switches{ "switch" }; + std::map expected_values{ { "key", "value" } }; + EXPECT_FALSE(obj.null()); + EXPECT_EQ(obj["Path"], "/path/to/exe"); + EXPECT_EQ(obj["Args"], expected_args); + EXPECT_EQ(obj["Switches"], expected_switches); + EXPECT_EQ(obj["Values"], expected_values); +}