-
Notifications
You must be signed in to change notification settings - Fork 354
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
[WIP] Generalized flag values #229
Conversation
@henryiii This what I have been tinkering with, Not sure you would want any or all of it. My target is making a transition from boost::program options in a complex layered setup a little easier. I also saw you were playing with some map stuff, which is related to some things I was tinkering with related to Transformer. I suspect you probably don't want both but they may be able to be merged. Right now the PR has a number of merges from the recent set PR so that is why there is so many commits. I might be able to peel off some of them to make a few other PRs of specific components. |
Codecov Report
@@ Coverage Diff @@
## master #229 +/- ##
==========================================
- Coverage 100% 98.15% -1.85%
==========================================
Files 12 12
Lines 2057 2228 +171
==========================================
+ Hits 2057 2187 +130
- Misses 0 41 +41
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #229 +/- ##
==========================================
- Coverage 100% 98.15% -1.85%
==========================================
Files 12 12
Lines 2057 2228 +171
==========================================
+ Hits 2057 2187 +130
- Misses 0 41 +41
Continue to review full report at Codecov.
|
Needs a rebase or something equivalent. Sounds exciting. Wonder how it compares to #228. Edit: yes, as you mentioned in your comment. |
* move toward generalized default flag values * collapse the add_flag functions with a common element, add some additional results output functions * clear up some additional shadow warnings, fix some of the templates, add a few more tests * add Transform object * add checkedTransform validator * add digit_args examples
1f26a61
to
545999d
Compare
It would be nice to at least split into two PRs, one for transformer and checkedTransformer validators. I think I'd like to compare with the IsMember validator. The other parts sound useful and could be evaluated and merged better on their own. |
throw IncorrectConstruction::PositionalFlag(flag_name); | ||
private: | ||
/// internal function for adding a flag | ||
Option *add_flag_internal(std::string &flag_name, CLI::callback_t &fun, std::string &flag_description) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should start with an underscore, internal functions all start with underscore. (Python style)
detail::sum_flag_vector(res, flag_count); | ||
try { | ||
detail::sum_flag_vector(res, flag_count); | ||
} catch(const std::invalid_argument &) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I've seen invalid_argument
, nice.
opt->check(IsMember{options}); | ||
return opt; | ||
} | ||
|
||
/// Add set of options (No default, set can be changed afterwords - do not destroy the set) DEPRECATED | ||
/// Add set of options (No default, set can be changed afterwards - do not destroy the set) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DEPRECATED, please :)
op = get_option("--" + item.name); | ||
} catch(const OptionNotFound &) { | ||
Option *op = get_option_no_throw("--" + item.name); | ||
if(op == nullptr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I like avoiding exceptions as control flow if possible.
@@ -69,14 +69,10 @@ class Config { | |||
/// Convert a configuration into an app | |||
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0; | |||
|
|||
/// Convert a flag to a bool representation | |||
/// get a flag value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First char caps for docstrings and for comments alone on a line. Comments that follow code on the same line are not capitalized. Okay for now, but slightly easier if I don't have to correct it later.
@@ -330,6 +332,25 @@ class Option : public OptionBase<Option> { | |||
return this; | |||
} | |||
|
|||
/// Adds a transformation/validator with a built in type name | |||
Option *transform(const Transformer &tform) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've applied a very similar idea in my PR, but without the strong typing. I could add typing so that a Validator could not be added to ->transform, but the ability to choose whether a tranformer actually transforms or not is really nice for a user, IMO. It also avoids duplication.
/// get the results as a particular type | ||
template <typename T, | ||
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy> | ||
void results(T &output) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting! I've been looking for a way to add types to Option for 2.0, but this partially (not fully) negates the need to do that. The tricky part about the 2.0 idea is that Option<T>
would need to be stored in the shared/unique pointer array, and I was thinking it would need a reference or copy of T, but this might avoid that (at a cost per access).
Does this pick up the results of a transform? I assume so since they should be transformed in place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be broken out into a standalone function(s including overload)? Then it could be reused by the normal parse also, possibly? That is, this would become something like:
template<typename T>
void results(T &output) {
CLI::detail::results(output, results_, defaultval_, multi_option_policy_);
}
And CLI::detail::results
could be used in the parse function, too. Maybe not, just thinking. Also, it might be possible to make CLI::detail::results
a struct, and it could have things like default number of arguments attached, and a user could provide specializations to add support for new types. (now well outside the scope of this PR, just thinking)
|
||
/// output streaming for enumerations | ||
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type> | ||
std::ostream &operator<<(std::ostream &in, const T &level) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice addition that I didn't think of. It might be a good idea to make it opt-in in some way though, so a user doesn't clash if they already have something similar. Like using namespace CLI::enums
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic, inside an exported namespace it works great, I'm adding this to my PR if you don't mind...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free, It took a little while to figure out where to actually put this in the code so it was actually visible to all the functions that might need it. I pulled it from the enum.cpp example, so it would work with the transformer Validator.
const std::vector<std::string> names, | ||
bool ignore_case = false, | ||
bool ignore_underscore = false) { | ||
auto it = std::end(names); | ||
if(ignore_case) { | ||
if(ignore_underscore) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to have a vector of transform functions, and allow a user to add any transforms they want; something like opt->filter(CLI::ignore_underscore)->filter(CLI::ignore_case)
? Vector would be part of default option, etc. Just a thought for later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I ran into was that translate functions should run before the actual validators so I had to make sure they were inserted at the front of the validator vector.
So I think it is kind of an interesting usability discussion of whether check() should be allowed to modify or not. On one had that differentiates check and translate but it disallows a CheckedTranslate since that would translate and make sure it was translated as a single unit. You would need a translate and a separate check.
/// print name for enumeration types | ||
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> | ||
constexpr const char *type_name() { | ||
return "ENUM"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice.
/// create a validator that fails when a given validator succeeds | ||
Validator operator!() const { | ||
Validator newval; | ||
newval.tname = "NOT " + tname; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to support tname_function, too.
@@ -41,15 +41,15 @@ class TempFile { | |||
}; | |||
|
|||
inline void put_env(std::string name, std::string value) { | |||
#ifdef _MSC_VER | |||
#ifdef _WIN32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not very familiar with non-MSVC compilers on Windows anymore, so never know what is best here. I'll assume that non-MSVC compilers are still able to access the Windows calls, rather than GNU counterparts? It would be nice to add a test to AppVeyor (or Azure) someday (not as part of this PR, though).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue was MSYS, which is mingw, deep in some of the libraries is uses some of the windows subsystems, so has most of the posix calls, but when you get down into environmental variables you use the windows calls, which I think was the case here.
This might to much to adequately review, I will try to split some things up into different PR's, they were all interconnected but I think there are some distinct pieces that are are valid on their own. |
I am not going to fix this PR so it will be closed eventually, but am leaving in place for the moments since it contains a useful discussion at least until the PR's are merged/closed out. |
This will be closed when #239 is merged or closed |
All parts of this have now been merged |
This PR will make flag more general in that they can handle any non-vector type and have default values of any type.
It adds better enum support
adds a transformer and checkedTransformer validators.
Also adds ["option_name"] syntax for getting options and as for querying results to be translated to any available type.