mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-30 12:43:52 +00:00
Adding cmake, tests
This commit is contained in:
parent
8ab3c8e7c8
commit
d070f32ed6
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
a.out*
|
a.out*
|
||||||
|
/build*
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "tests/googletest"]
|
||||||
|
path = tests/googletest
|
||||||
|
url = git@github.com:google/googletest.git
|
23
CMakeLists.txt
Normal file
23
CMakeLists.txt
Normal file
@ -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)
|
174
ProgramOP.hpp
174
ProgramOP.hpp
@ -1,174 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <functional>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#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<option::Descriptor> usage;
|
|
||||||
std::vector<std::function<bool(std::vector<std::string>)>> convert; /// Number is loc+2
|
|
||||||
std::unordered_set<int> required;
|
|
||||||
std::vector<int> counts;
|
|
||||||
std::vector<std::string> 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<option::Option> options(stats.options_max);
|
|
||||||
std::vector<option::Option> 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<convert.size()+2; i++) {
|
|
||||||
counts.emplace_back(options[i].count());
|
|
||||||
if(options[i]) {
|
|
||||||
std::vector<std::string> 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<typename T>
|
|
||||||
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<std::string> v){flag = v.size(); return true;});
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_option_internal(int &val) {
|
|
||||||
convert.push_back([&val](std::vector<std::string> v){val = std::stoi(v.at(0)); return v.size()==1;});
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_option_internal(std::string &val) {
|
|
||||||
convert.push_back([&val](std::vector<std::string> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
2
examples/CMakeLists.txt
Normal file
2
examples/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
add_executable(try try.cpp ${headers})
|
||||||
|
add_executable(try1 try1.cpp ${headers})
|
@ -16,7 +16,7 @@
|
|||||||
// This is unreachable outside this file; you should not use Combiner directly
|
// This is unreachable outside this file; you should not use Combiner directly
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
void logit(std::string output) {
|
void logit(std::string) {
|
||||||
//std::cout << "\033[1;31m" << output << "\033[0m" << std::endl;
|
//std::cout << "\033[1;31m" << output << "\033[0m" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,7 +422,7 @@ public:
|
|||||||
std::string name, ///< The name, short,long
|
std::string name, ///< The name, short,long
|
||||||
std::string discription="" ///< Discription string
|
std::string discription="" ///< Discription string
|
||||||
) {
|
) {
|
||||||
CLI::callback_t fun = [](CLI::results_t res){
|
CLI::callback_t fun = [](CLI::results_t){
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
44
tests/CLItest.cpp
Normal file
44
tests/CLItest.cpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
#include "CLI.hpp"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef std::vector<std::string> 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"));
|
||||||
|
}
|
13
tests/CMakeLists.txt
Normal file
13
tests/CMakeLists.txt
Normal file
@ -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)
|
43
tests/SmallTest.cpp
Normal file
43
tests/SmallTest.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "CLI.hpp"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Split, GoodStrings) {
|
||||||
|
std::vector<std::string> 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<std::string> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
1
tests/googletest
Submodule
1
tests/googletest
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit ec44c6c1675c25b9827aacd08c02433cccde7780
|
32
try1.cpp
32
try1.cpp
@ -1,32 +0,0 @@
|
|||||||
#include "CLI.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
int main (int argc, char** argv) {
|
|
||||||
|
|
||||||
std::vector<std::string> 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<std::string> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user