From d070f32ed6e566c40c5eaee208cd004cf45fe780 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Thu, 26 Jan 2017 16:48:30 -0500 Subject: [PATCH] Adding cmake, tests --- .gitignore | 1 + .gitmodules | 3 + CMakeLists.txt | 23 ++++ ProgramOP.hpp | 174 ----------------------------- examples/CMakeLists.txt | 2 + try.cpp => examples/try.cpp | 0 try2.cpp => examples/try1.cpp | 0 CLI.hpp => include/CLI.hpp | 4 +- Program.hpp => include/Program.hpp | 0 tests/CLItest.cpp | 44 ++++++++ tests/CMakeLists.txt | 13 +++ tests/SmallTest.cpp | 43 +++++++ tests/googletest | 1 + try1.cpp | 32 ------ 14 files changed, 132 insertions(+), 208 deletions(-) create mode 100644 .gitmodules create mode 100644 CMakeLists.txt delete mode 100644 ProgramOP.hpp create mode 100644 examples/CMakeLists.txt rename try.cpp => examples/try.cpp (100%) rename try2.cpp => examples/try1.cpp (100%) rename CLI.hpp => include/CLI.hpp (99%) rename Program.hpp => include/Program.hpp (100%) create mode 100644 tests/CLItest.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/SmallTest.cpp create mode 160000 tests/googletest delete mode 100644 try1.cpp diff --git a/.gitignore b/.gitignore index d708fd8a..77b23213 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ a.out* +/build* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e23a605b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/googletest"] + path = tests/googletest + url = git@github.com:google/googletest.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..886e0e8c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 2.8 FATAL_ERROR) + +project(CLI11 CXX) + +# C++11 without GNU extensions +# Requires CMAKE 3.1+ for Mac +if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 3.1) + add_compile_options(-std=c++11) +else() + cmake_policy(VERSION 3.1) # Needed for Mac + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() +# be moderately paranoid with flags +add_compile_options(-pedantic -Wall -Wextra) + +include_directories(include) +set(headers "${PROJECT_SOURCE_DIR}/include/CLI.hpp") +enable_testing() + +add_subdirectory(tests) +add_subdirectory(examples) diff --git a/ProgramOP.hpp b/ProgramOP.hpp deleted file mode 100644 index e7178a04..00000000 --- a/ProgramOP.hpp +++ /dev/null @@ -1,174 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "optionparser.h" - -// This is unreachable outside this file; you should not use Combiner directly -namespace { - -struct Combiner { - int positional; - bool required; - bool defaulted; - - /// Can be or-ed together - Combiner operator | (Combiner b) const { - Combiner self; - self.positional = positional + b.positional; - self.required = required || b.required; - self.defaulted = defaulted || b.defaulted; - return self; - } - - /// Call to give the number of arguments expected on cli - Combiner operator() (int n) const { - return Combiner{n, required, defaulted}; - } -}; -} - -/// Creates a command line program, with very few defaults. -/** To use, create a new Program() instance with argc, argv, and a help description. The templated -* add_option methods make it easy to prepare options. Remember to call `.start` before starting your -* program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ -class Program { -public: - static constexpr Combiner REQUIRED{0,true,false}; - static constexpr Combiner DEFAULT{0,false,true}; - static constexpr Combiner POSITIONAL{1,false,false}; - -protected: - std::vector usage; - std::vector)>> convert; /// Number is loc+2 - std::unordered_set required; - std::vector counts; - std::vector random_name_store; - - int argc; - char **argv; - - /// Parses the command line (internal function) - void parse() { - usage.push_back(option::Descriptor{0, 0, nullptr, nullptr, nullptr, nullptr}); - - option::Stats stats(usage.data(), argc, argv); - std::vector options(stats.options_max); - std::vector buffer(stats.buffer_max); - option::Parser parse(usage.data(), argc, argv, options.data(), buffer.data()); - - if(parse.error()) { - std::cerr << "ERROR. See usage:" << std::endl; - option::printUsage(std::cerr, usage.data()); - exit(1); - } - - - if(options[1]){ - option::printUsage(std::cerr, usage.data()); - exit(0); - } - - bool found_unk = false; - for (option::Option* opt = options[0]; opt; opt = opt->next()) { - std::cout << "Unknown option: " << opt->name << "\n"; - found_unk = true; - } - if(found_unk) - exit(2); - - for(int i=2; i opt_list; - for(option::Option* opt = options[i]; opt; opt = opt->next()) - opt_list.emplace_back(opt->arg ? opt->arg : ""); - convert.at(i-2)(opt_list); - } - } - } - -public: - - /// Create a new program. Pass in the same arguments as main(), along with a help string. - Program(int argc, char** argv, std::string description) - : argc(argc), argv(argv) { - random_name_store.emplace_back(description); - usage.push_back(option::Descriptor{0, 0, "", "", option::Arg::None, random_name_store.back().c_str()}); - usage.push_back(option::Descriptor{1, 0, "h", "help", option::Arg::None, "Display usage and exit."}); - } - - - /// Add an option, will automatically understand the type for common types. - /** To use, create a variable with the expected type, and pass it in after the name. - * After start is called, you can use count to see if the value was passed, and - * the value will be initialized properly. - * - * Program::REQUIRED, Program::DEFAULT, and Program::POSITIONAL are options, and can be `|` - * together. The positional options take an optional number of arguments. - * - * For example, - * - * std::string filename - * program.add_option("filename", filename, "description of filename"); - */ - template - void add_option( - std::string name, ///< The name, long,short - T &value, ///< The value - std::string description, ///< Description string - Combiner options ///< The options (REQUIRED, DEFAULT, POSITIONAL) - ) { - - int curr_num = convert.size(); - - if(options.required) - required.emplace(curr_num); - - random_name_store.emplace_back(name); - random_name_store.emplace_back(description); - - usage.push_back(option::Descriptor{(unsigned int) convert.size()+2, 0, "", random_name_store.at(random_name_store.size()-2).c_str(), option::Arg::Optional, random_name_store.back().c_str()}); - add_option_internal(value); - - if(options.positional!=0) - std::cout << "positional args not yet supported" << std::endl; - - - - } - - /// Adds a flag style option - void add_flag(std::string name, std::string description, int& flag) { - counts.emplace_back(0); - random_name_store.emplace_back(name); - random_name_store.emplace_back(description); - usage.push_back(option::Descriptor{(unsigned int) convert.size()+2, 0, "", random_name_store.at(random_name_store.size()-2).c_str(), option::Arg::None, random_name_store.back().c_str()}); - convert.push_back([&flag](std::vector v){flag = v.size(); return true;}); - } - - void add_option_internal(int &val) { - convert.push_back([&val](std::vector v){val = std::stoi(v.at(0)); return v.size()==1;}); - } - - void add_option_internal(std::string &val) { - convert.push_back([&val](std::vector v){val = v.at(0); return v.size()==1;}); - } - /// This must be called after the options are in but before the rest of the program. - /** Calls the Boost boost::program_options initialization, causing the program to exit - * if -h or an invalid option is passed. */ - void start() { - parse(); - } - - /// Counts the number of times the given option was passed. - int count(std::string name) const { - return 0; - } - - -}; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..6ddc1fed --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(try try.cpp ${headers}) +add_executable(try1 try1.cpp ${headers}) diff --git a/try.cpp b/examples/try.cpp similarity index 100% rename from try.cpp rename to examples/try.cpp diff --git a/try2.cpp b/examples/try1.cpp similarity index 100% rename from try2.cpp rename to examples/try1.cpp diff --git a/CLI.hpp b/include/CLI.hpp similarity index 99% rename from CLI.hpp rename to include/CLI.hpp index a22029d6..e89f244b 100644 --- a/CLI.hpp +++ b/include/CLI.hpp @@ -16,7 +16,7 @@ // This is unreachable outside this file; you should not use Combiner directly namespace { -void logit(std::string output) { +void logit(std::string) { //std::cout << "\033[1;31m" << output << "\033[0m" << std::endl; } @@ -422,7 +422,7 @@ public: std::string name, ///< The name, short,long std::string discription="" ///< Discription string ) { - CLI::callback_t fun = [](CLI::results_t res){ + CLI::callback_t fun = [](CLI::results_t){ return true; }; diff --git a/Program.hpp b/include/Program.hpp similarity index 100% rename from Program.hpp rename to include/Program.hpp diff --git a/tests/CLItest.cpp b/tests/CLItest.cpp new file mode 100644 index 00000000..86209eea --- /dev/null +++ b/tests/CLItest.cpp @@ -0,0 +1,44 @@ + +#include "CLI.hpp" +#include "gtest/gtest.h" + + +typedef std::vector input_t; + +TEST(Basic, Empty) { + + { + CLI::App app; + input_t simpleput; + app.parse(simpleput); + } + { + CLI::App app; + input_t spare = {"spare"}; + EXPECT_THROW(app.parse(spare), CLI::ExtraPositionalsError); + } + { + CLI::App app; + input_t simpleput; + app.parse(simpleput); + } +} + +struct TApp : public ::testing::Test { + CLI::App app{"My Test Program"}; + input_t args; + + void run() { + input_t newargs = args; + std::reverse(std::begin(newargs), std::end(newargs)); + app.parse(newargs); + } + +}; + +TEST_F(TApp, AFewArgs) { + app.add_flag("c,count"); + args = {"-c"}; + run(); + EXPECT_EQ(1, app.count("count")); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..bf8494b7 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8 FATAL_ERROR) + +add_subdirectory(googletest) + +include_directories(${gtest_SOURCE_DIR}/include) + +add_executable(CLITest CLITest.cpp ${headers}) +target_link_libraries(CLITest gtest gtest_main) +add_test(CLITest CLITest) + +add_executable(SmallTest SmallTest.cpp ${headers}) +target_link_libraries(SmallTest gtest gtest_main) +add_test(SmallTest SmallTest) diff --git a/tests/SmallTest.cpp b/tests/SmallTest.cpp new file mode 100644 index 00000000..22e41727 --- /dev/null +++ b/tests/SmallTest.cpp @@ -0,0 +1,43 @@ +#include "CLI.hpp" +#include "gtest/gtest.h" + + +TEST(Split, GoodStrings) { + std::vector test_strings = {"a,boo", ",coo", "d,", "Q,this-is", "s", "single"}; + + std::string s, l; + + std::tie(s, l) = CLI::split("a,boo"); + EXPECT_EQ("a", s); + EXPECT_EQ("boo", l); + + std::tie(s, l) = CLI::split(",coo"); + EXPECT_EQ("", s); + EXPECT_EQ("coo", l); + + std::tie(s, l) = CLI::split("d,"); + EXPECT_EQ("d", s); + EXPECT_EQ("", l); + + std::tie(s, l) = CLI::split("Q,this-is"); + EXPECT_EQ("Q", s); + EXPECT_EQ("this-is", l); + + std::tie(s, l) = CLI::split("s"); + EXPECT_EQ("s", s); + EXPECT_EQ("", l); + + std::tie(s, l) = CLI::split("single"); + EXPECT_EQ("", s); + EXPECT_EQ("single", l); + } + +TEST(Split, BadStrings) { + std::vector test_fails= {"a,,boo", "a,b,c", "ssd,sfd", "-a", "", ",", "one two"}; + + for(std::string name : test_fails) { + EXPECT_THROW(CLI::split(name), CLI::BadNameString); + } + + +} diff --git a/tests/googletest b/tests/googletest new file mode 160000 index 00000000..ec44c6c1 --- /dev/null +++ b/tests/googletest @@ -0,0 +1 @@ +Subproject commit ec44c6c1675c25b9827aacd08c02433cccde7780 diff --git a/try1.cpp b/try1.cpp deleted file mode 100644 index 10c1ee5e..00000000 --- a/try1.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "CLI.hpp" - - -int main (int argc, char** argv) { - - std::vector test_strings = {"a,boo", ",coo", "d,", "Q,this-is", "s", "single"}; - - for(std::string name : test_strings) { - std::string one; - std::string two; - - std::tie(one, two) = CLI::split(name); - std::cout << one << ", " << two << std::endl; - } - - std::vector test_fails= {"a,,boo", "a,b,c", "ssd,sfd", "-a", "", ",", "one two"}; - - for(std::string name : test_fails) { - std::string one; - std::string two; - - try { - std::tie(one, two) = CLI::split(name); - std::cout << "Failed to catch: " << name << std::endl; - return 1; - } catch (const CLI::BadNameString &e) { - std::cout << "Hooray! Caught: " << name << std::endl; - } - } - - -}