cxxopts icon indicating copy to clipboard operation
cxxopts copied to clipboard

Optional and mandatory options

Open da2r-20 opened this issue 8 years ago • 13 comments

I think it would be nice to have optional and mandatory options that way no validation is needed to be done by the user

da2r-20 avatar Sep 24 '17 13:09 da2r-20

As I've said in several previous issues #44 #25, I don't think that this sort of logic belongs in this library. The main reasoning is that usually options are not just mandatory or optional., but something more complicated. Take for example tar, it has a mutually exclusive set of options, and a bunch of options that only go with other options.

tar: You must specify one of the '-Acdtrux', '--delete' or '--test-label' options

That said, since this has been requested a few times, I think a separate validation library would be appreciated by a few people. I will work on this at some point, but I can't guarantee getting to it straight away.

jarro2783 avatar Sep 26 '17 05:09 jarro2783

Thanks! Some how I missed #44 when searching the issues.

I agree a validation should be on different defined combinations of the arguments In-order to decide if a set of arguments is valid or not.

I'll be happy to contribute, let me know.

da2r-20 avatar Sep 26 '17 07:09 da2r-20

I think a separate validation library is not necessary. If each option could optionally specify required and/or excluded arguments, resolving conflicts is then left to the user to define and the validation simply enforces the supplied rules.

dakka avatar Oct 18 '17 00:10 dakka

For simple cases it would be very useful to have required options (like boost::program_options::value<>()->required()). At least it allows to drop annoying checks for an every required option. Of course, for more complicated cases, users can figure out it with own logic.

vladimirgamalyan avatar Dec 18 '17 01:12 vladimirgamalyan

I think this is critical issue it breaks open/close principle. e.g.: script calls: app.exe -p "test1" after some time I've updated application with the additional parameter and now script calls:" app.exe -p "test1" -x "test2" -> previous version of the app.exe can not be used with the extended command line how to fix this problem? Library does not allow any extensions -> and forces to update all external environment my case: somehow try to retrieve app.exe_version if ( app.exe_version == 1 ) app.exe -p "test1" else if ( app.exe_version == 2 ) app.exe -p "test1" -x "test2"

slidertom avatar Jan 10 '18 17:01 slidertom

I think this is critical issue it breaks open/close principle. e.g.: script calls: app.exe -p "test1" after some time I've updated application with the additional parameter and now script calls:" app.exe -p "test1" -x "test2" -> previous version of the app.exe can not be used with the extended command line how to fix this problem?

I don't understand the problem. I don't see how this is related to whether the library throws an exception when a mandatory option is missing.

Are you talking about allowing unrecognised options without raising an error?

jarro2783 avatar Jan 10 '18 22:01 jarro2783

>Are you talking about allowing unrecognised options without raising an error? I do expect, that mine old app.exe will work even some options are unrecognized. Yes, error should not be raised, but command line also must be parsed. -> should I be forced to update all the old app.exe binary versions in case of any command line extension for the new versions? I do not think so. Or maybe command line versioning must be tracked by the script developer? I do not think so.

slidertom avatar Jan 11 '18 16:01 slidertom

That's a separate discussion then which I will move to another issue.

Although it is easy to do, the main problem with unrecognised options is how to recognise their arguments. For example is --foo bar an unrecognised option --foo with argument bar, or is it a boolean that takes no argument followed by a positional argument?

jarro2783 avatar Jan 11 '18 21:01 jarro2783

For simple cases it would be very useful to have required options (like boost::program_options::value<>()->required()). At least it allows to drop annoying checks for an every required option. Of course, for more complicated cases, users can figure out it with own logic.

I'm not sure if this is still open to discussion or not, because this is quite an old issue, however the only thing that would make this library be perfect to me is the required arguments and if the required arguments were not given it would simply print the --help message.

Considering that this can be an optional parameter it wouldn't force users to use this but instead would allow small more linear programs to keep it's code simpler and avoid checking things manually.

jpmvferreira avatar Mar 01 '21 11:03 jpmvferreira

I still have some plans to address this. I wanted to make a separate validator that you can run against the parsed options.

jarro2783 avatar Mar 02 '21 21:03 jarro2783

Hi! What's the status on this issue?

carlosgalvezp avatar Aug 13 '21 10:08 carlosgalvezp

I am expecting this feature.

liwei46 avatar Nov 10 '22 07:11 liwei46

Here's a simple and elegant way to handle "required", "optional" and actually any complex rule: by using template expressions

Here's a small working example that handles constraints of type "option 'foo' must appear exactly once and option 'bar' exactly twice" :

#include <cxxopts.hpp>
#include <cstddef>
#include <iostream>

namespace cxxopts {
namespace rules {

// =============================================================
class Option
{
public:
  Option(std::string option_name) : name(option_name) {}

  inline size_t count(const ParseResult &parsing) const
  {
    return parsing.count(name);
  }

  std::string name;
};
// =============================================================

// =============================================================
class ExactCount
{
public:
  ExactCount(Option option, size_t target_count)
    : option(std::move(option)), target_count(target_count) {};

  inline bool operator () (const ParseResult& parsing) const
  {
    return option.count(parsing) == target_count;
  }

  inline explicit operator std::string () const
  {
    if (target_count == 1)
      return "option '--" + option.name + "' must be provided exactly once";
    else return "option '--" + option.name + "' must be provided exactly " + std::to_string(target_count) + " times";
  }

protected:
  Option option;
  size_t target_count;
};

ExactCount operator == (Option option, size_t target_count)
{
  return ExactCount(std::move(option), target_count);
}
// =============================================================


// =============================================================
template <class SubExpression1, class SubExpression2>
class AND
{
public:
  AND(SubExpression1 expr1, SubExpression2 expr2)
    : expr1(std::move(expr1)), expr2(std::move(expr2)) {}

  inline bool operator () (const ParseResult& parsing) const
  {
    return expr1(parsing) && expr2(parsing);
  }

  inline explicit operator std::string () const
  {
    return "(" + std::string(expr1) + ") and (" + std::string(expr2) + ")";
  }

protected:
  SubExpression1 expr1;
  SubExpression2 expr2;

};

template <class SubExpression1, class SubExpression2>
auto operator && (SubExpression1 expr1, SubExpression2 expr2)
{
  return AND(std::move(expr1), std::move(expr2));
}
// =============================================================

template <class Rule>
void enforce(const Rule& rule, const ParseResult& parsing)
{
  if (not rule(parsing))
    throw std::runtime_error("rule not respected: " + std::string(rule));
}

}
}

int main(int argc, char *argv[])
{
  cxxopts::Options options(
    "hash-id",
    "Advertising-ID hasher");

  options.add_options()
    ("i,input", "input", cxxopts::value<std::string>())
    ("o,output", "output", cxxopts::value<std::string>())
    ("h,help", "Print usage")
    ;

  auto parsing = options.parse(argc, argv);

  auto rule = (cxxopts::rules::Option("input") == 1) and (cxxopts::rules::Option("output") == 1);

  cxxopts::rules::enforce(rule, parsing);

  std::cout << parsing["output"].as<std::string>() << std::endl;

  return 0;
}

After implementing the or and not boolean operation, and all the comparison operations >, >=... any rule can be expressed and everyone is happy !

AdelKS avatar Mar 30 '23 22:03 AdelKS