Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding size instead of expected + unchangable #92

Merged
merged 4 commits into from
Apr 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ Other, non-user facing changes:
* Informational printout now added to CTest [#95]
* Better single file generation [#95]
* Added support for GTest on MSVC 2017 (but not in C++17 mode, will need next version of GTest)
* Types now have a specific size, separate from the expected number - cleaner and more powerful internally [#92]

[#64]: https://github.com/CLIUtils/CLI11/issues/64
[#90]: https://github.com/CLIUtils/CLI11/issues/90
[#92]: https://github.com/CLIUtils/CLI11/issues/92
[#95]: https://github.com/CLIUtils/CLI11/pull/95


Expand Down
48 changes: 32 additions & 16 deletions cmake/AddGoogletest.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,39 @@
# gives output on failed tests without having to set an environment variable.
#
#
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

include(DownloadProject)
download_project(PROJ googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
UPDATE_DISCONNECTED 1
QUIET
)
if(CMAKE_VERSION VERSION_LESS 3.11)
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
include(DownloadProject)
download_project(PROJ googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
UPDATE_DISCONNECTED 1
QUIET
)

# CMake warning suppression will not be needed in version 1.9
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "")
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_SOURCE_DIR} EXCLUDE_FROM_ALL)
unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS)
else()
include(FetchContent)
FetchContent_Declare(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0)
FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)
FetchContent_Populate(googletest)
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "")
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS)
endif()
endif()

set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# CMake warning suppression will not be needed in version 1.9
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "")
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_SOURCE_DIR} EXCLUDE_FROM_ALL)
unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS)

if (CMAKE_CONFIGURATION_TYPES)
if(CMAKE_CONFIGURATION_TYPES)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}
--force-new-ctest-process --output-on-failure
--build-config "$<CONFIGURATION>")
Expand Down Expand Up @@ -54,16 +69,17 @@ macro(add_gtest TESTNAME)
gtest_add_tests(TARGET ${TESTNAME}
TEST_PREFIX "${TESTNAME}."
TEST_LIST TmpTestList)
set_tests_properties(${TmpTestList} PROPERTIES FOLDER "Tests")
else()
gtest_discover_tests(${TESTNAME}
TEST_PREFIX "${TESTNAME}."
)
PROPERTIES FOLDER "Tests")

endif()
else()
add_test(${TESTNAME} ${TESTNAME})
set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests")
endif()
set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests")

endmacro()

Expand Down
22 changes: 11 additions & 11 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ class App {
std::string value;

// Non-flags
if(opt->get_expected() != 0) {
if(opt->get_type_size() != 0) {

// If the option was found on command line
if(opt->count() > 0)
Expand Down Expand Up @@ -1069,7 +1069,7 @@ class App {
/// Currently checks to see if multiple positionals exist with -1 args
void _validate() const {
auto count = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
return opt->get_expected() == -1 && opt->get_positional();
return opt->get_items_expected() < 0 && opt->get_positional();
});
if(count > 1)
throw InvalidError(name_);
Expand Down Expand Up @@ -1189,8 +1189,8 @@ class App {
// Required or partially filled
if(opt->get_required() || opt->count() != 0) {
// Make sure enough -N arguments parsed (+N is already handled in parsing function)
if(opt->get_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_expected()))
throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_expected());
if(opt->get_items_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_items_expected()))
throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_items_expected());

// Required but empty
if(opt->get_required() && opt->count() == 0)
Expand Down Expand Up @@ -1260,7 +1260,7 @@ class App {

if(op->results_.empty()) {
// Flag parsing
if(op->get_expected() == 0) {
if(op->get_type_size() == 0) {
if(current.inputs.size() == 1) {
std::string val = current.inputs.at(0);
val = detail::to_lower(val);
Expand Down Expand Up @@ -1320,9 +1320,9 @@ class App {
size_t _count_remaining_positionals(bool required = false) const {
size_t retval = 0;
for(const Option_p &opt : options_)
if(opt->get_positional() && (!required || opt->get_required()) && opt->get_expected() > 0 &&
static_cast<int>(opt->count()) < opt->get_expected())
retval = static_cast<size_t>(opt->get_expected()) - opt->count();
if(opt->get_positional() && (!required || opt->get_required()) && opt->get_items_expected() > 0 &&
static_cast<int>(opt->count()) < opt->get_items_expected())
retval = static_cast<size_t>(opt->get_items_expected()) - opt->count();

return retval;
}
Expand All @@ -1334,7 +1334,7 @@ class App {
for(const Option_p &opt : options_) {
// Eat options, one by one, until done
if(opt->get_positional() &&
(static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) {
(static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) {

opt->add_result(positional);
parse_order_.push_back(opt.get());
Expand Down Expand Up @@ -1421,7 +1421,7 @@ class App {
// Get a reference to the pointer to make syntax bearable
Option_p &op = *op_ptr;

int num = op->get_expected();
int num = op->get_items_expected();

// Make sure we always eat the minimum for unlimited vectors
int collected = 0;
Expand Down Expand Up @@ -1459,7 +1459,7 @@ class App {

// If there are any unlimited positionals, those also take priority
if(std::any_of(std::begin(options_), std::end(options_), [](const Option_p &opt) {
return opt->get_positional() && opt->get_expected() < 0;
return opt->get_positional() && opt->get_items_expected() < 0;
}))
break;
}
Expand Down
5 changes: 4 additions & 1 deletion include/CLI/Error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ class IncorrectConstruction : public ConstructionError {
static IncorrectConstruction Set0Opt(std::string name) {
return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead");
}
static IncorrectConstruction SetFlag(std::string name) {
return IncorrectConstruction(name + ": Cannot set an expected number for flags");
}
static IncorrectConstruction ChangeNotVector(std::string name) {
return IncorrectConstruction(name + ": You can only change the expected arguments for vectors");
}
Expand All @@ -102,7 +105,7 @@ class IncorrectConstruction : public ConstructionError {
return IncorrectConstruction("Option " + name + " is not defined");
}
static IncorrectConstruction MultiOptionPolicy(std::string name) {
return IncorrectConstruction(name + ": multi_option_policy only works for flags and single value options");
return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options");
}
};

Expand Down
92 changes: 70 additions & 22 deletions include/CLI/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,13 @@ class Option : public OptionBase<Option> {
/// @name Configuration
///@{

/// The number of expected values, 0 for flag, -1 for unlimited vector
int expected_{1};
/// The number of arguments that make up one option. -1=unlimited (vector-like), 0=flag, 1=normal option,
/// 2=complex/pair, etc. Set only when the option is created; this is intrinsic to the type. Eventually, -2 may mean
/// vector of pairs.
int type_size_{1};

/// A private setting to allow args to not be able to accept incorrect expected values
bool changeable_{false};
/// The number of expected values, type_size_ must be < 0. Ignored for flag. N < 0 means at least -N values.
int expected_{1};

/// A list of validators to run on each value parsed
std::vector<std::function<std::string(std::string &)>> validators_;
Expand Down Expand Up @@ -244,14 +246,25 @@ class Option : public OptionBase<Option> {
/// @name Setting options
///@{

/// Set the number of expected arguments (Flags bypass this)
/// Set the number of expected arguments (Flags don't use this)
Option *expected(int value) {
if(expected_ == value)
return this;
// Break if this is a flag
if(type_size_ == 0)
throw IncorrectConstruction::SetFlag(single_name());

// Setting 0 is not allowed
else if(value == 0)
throw IncorrectConstruction::Set0Opt(single_name());
else if(!changeable_)

// No change is okay, quit now
else if(expected_ == value)
return this;

// Type must be a vector
else if(type_size_ >= 0)
throw IncorrectConstruction::ChangeNotVector(single_name());

// TODO: Can support multioption for non-1 values (except for join)
else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw)
throw IncorrectConstruction::AfterMultiOpt(single_name());

Expand Down Expand Up @@ -368,9 +381,10 @@ class Option : public OptionBase<Option> {
return this;
}

/// Take the last argument if given multiple times
/// Take the last argument if given multiple times (or another policy)
Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
if(get_expected() != 0 && get_expected() != 1)

if(get_items_expected() < 0)
throw IncorrectConstruction::MultiOptionPolicy(single_name());
multi_option_policy_ = value;
return this;
Expand All @@ -381,8 +395,32 @@ class Option : public OptionBase<Option> {
///@{

/// The number of arguments the option expects
int get_type_size() const { return type_size_; }

/// The number of times the option expects to be included
int get_expected() const { return expected_; }

/// \breif The total number of expected values (including the type)
/// This is positive if exactly this number is expected, and negitive for at least N values
///
/// v = fabs(size_type*expected)
/// !MultiOptionPolicy::Throw
/// | Expected < 0 | Expected == 0 | Expected > 0
/// Size < 0 | -v | 0 | -v
/// Size == 0 | 0 | 0 | 0
/// Size > 0 | -v | 0 | -v // Expected must be 1
///
/// MultiOptionPolicy::Throw
/// | Expected < 0 | Expected == 0 | Expected > 0
/// Size < 0 | -v | 0 | v
/// Size == 0 | 0 | 0 | 0
/// Size > 0 | v | 0 | v // Expected must be 1
///
int get_items_expected() const {
return std::abs(type_size_ * expected_) *
((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1));
}

/// True if this has a default value
int get_default() const { return default_; }

Expand Down Expand Up @@ -428,7 +466,7 @@ class Option : public OptionBase<Option> {
return out;
}

/// The most discriptive name available
/// The most descriptive name available
std::string single_name() const {
if(!lnames_.empty())
return std::string("--") + lnames_[0];
Expand Down Expand Up @@ -456,7 +494,7 @@ class Option : public OptionBase<Option> {
std::string help_aftername() const {
std::stringstream out;

if(get_expected() != 0) {
if(get_type_size() != 0) {
if(!typeval_.empty())
out << " " << typeval_;
if(!defaultval_.empty())
Expand Down Expand Up @@ -500,20 +538,29 @@ class Option : public OptionBase<Option> {

bool local_result;

// Num items expected or length of vector, always at least 1
// Only valid for a trimming policy
int trim_size = std::min(std::max(std::abs(get_items_expected()), 1), static_cast<int>(results_.size()));

// Operation depends on the policy setting
if(multi_option_policy_ == MultiOptionPolicy::TakeLast) {
results_t partial_result = {results_.back()};
// Allow multi-option sizes (including 0)
results_t partial_result{results_.end() - trim_size, results_.end()};
local_result = !callback_(partial_result);

} else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) {
results_t partial_result = {results_.at(0)};
results_t partial_result{results_.begin(), results_.begin() + trim_size};
local_result = !callback_(partial_result);

} else if(multi_option_policy_ == MultiOptionPolicy::Join) {
results_t partial_result = {detail::join(results_, "\n")};
local_result = !callback_(partial_result);

} else {
if((expected_ > 0 && results_.size() != static_cast<size_t>(expected_)) ||
(expected_ < 0 && results_.size() < static_cast<size_t>(-expected_)))
throw ArgumentMismatch(single_name(), expected_, results_.size());
// For now, vector of non size 1 types are not supported but possibility included here
if((get_items_expected() > 0 && results_.size() != static_cast<size_t>(get_items_expected())) ||
(get_items_expected() < 0 && results_.size() < static_cast<size_t>(-get_items_expected())))
throw ArgumentMismatch(single_name(), get_items_expected(), results_.size());
else
local_result = !callback_(results_);
}
Expand Down Expand Up @@ -595,13 +642,14 @@ class Option : public OptionBase<Option> {
/// @name Custom options
///@{

/// Set a custom option, typestring, expected; locks changeable unless expected is -1
void set_custom_option(std::string typeval, int expected = 1) {
/// Set a custom option, typestring, type_size
void set_custom_option(std::string typeval, int type_size = 1) {
typeval_ = typeval;
expected_ = expected;
if(expected == 0)
type_size_ = type_size;
if(type_size_ == 0)
required_ = false;
changeable_ = expected < 0;
if(type_size < 0)
expected_ = -1;
}

/// Set the default value string representation
Expand Down
Loading