Skip to content
This repository has been archived by the owner on Oct 28, 2021. It is now read-only.

A simple working demo? #49

Open
wgwi opened this issue Jan 6, 2018 · 16 comments
Open

A simple working demo? #49

wgwi opened this issue Jan 6, 2018 · 16 comments

Comments

@wgwi
Copy link

wgwi commented Jan 6, 2018

Hello, is there a simple working demo here? With just include "clara.hpp", and a main function.

@joede
Copy link

joede commented Feb 22, 2018

This is what I have done for a first check:

bool showHelp = true;
int width = 0;
std::string name;
bool doIt = false;
std::string command;

int main ( int argc, char** argv )
{
    auto cli = clara::detail::Help(showHelp)
             | clara::detail::Opt( width, "width" )["-w"]["--width"]("How wide should it be?")
             | clara::detail::Opt( name, "name" )["-n"]["--name"]("By what name should I be known")
             | clara::detail::Opt( doIt )["-d"]["--doit"]("Do the thing" )
             | clara::detail::Arg( command, "command" )("which command to run").required();
    auto result = cli.parse( clara::detail::Args( argc, argv ) );
    if( !result )
    {
	std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
	exit(1);
    }
    std::cerr << "Show Help:" << showHelp << std::endl;
    std::cerr << "Width:" << width << std::endl;
    std::cerr << "Name:" << name << std::endl;
    std::cerr << "Doit:" << doIt << std::endl;
    std::cerr << "Command:" << command << std::endl;

    if ( showHelp )
	std::cerr << cli << std::endl;

    return 0;
}

@hassanfarid
Copy link

Can we verify if a parameter was provided or not? Except that if they have a default value or not?

@abishekh
Copy link

I tried this example and it seems to compile for me - I was wondering what I need to do pass the arguments to Catch ?

@joede
Copy link

joede commented Apr 29, 2018

@hassanfarid Sorry for the long delay, but I hadn't time to reply. I'm not the developer of Clara and I have no idea how to check this. My first thought was an invalid default value too.

@abishekh I'm afraid I don't understand your question. The arguments passed to main() are argv and argc. These variables are passed to Clara in the call to cli.parse(). After that, the parser changes all variables associated with the option behind the scenes. In my example, passing "--doit" to the program leads to a change of the variable doIt. After the call to cli.parse, all of the associated variables are either default or changed. ;-) That is the reason why @hassanfarid asked for a way to check if a option was passed or not. ;-)

@philsquared
Copy link
Collaborator

@hassanfarid if you bind to an optional variable (std::optional, boost::optional, or similar) then you can use the optionality to check if it was set or not

@joede
Copy link

joede commented Apr 30, 2018

@philsquared with std::optional, a "useable" default value still has to be set after calling cli.parse(). On the other side, detecting a change by comparing the default value duplicates the usage of these defaults and doesn't detect parameters passed with this default. Nevertheless, IMO both solutions can become unclear / complex very fast.

I thought he want to have something like a "callback" (lamda) for the case the variable is written (not only changed) by Clara. But I'm not sure. ;-) ...IMO a very special requirement.

@philsquared
Copy link
Collaborator

philsquared commented Apr 30, 2018

@joede I'm afraid I'm not quite following.
What do you mean by a "usable" default value?
And when you say, "still has to be set after calling cli.parse(), do you mean set during the call? Or will be set externally afterwards... or something else?

The only thing I can think of that you might mean here is that you want to be able to distinguish between values that were set to None in your code, vs values that were never set at all by Clara?
If so that does seem to be a special requirement. What you could do is bind to a lambda, and in there set your variable and some sort of state tracking auxiliary variable (or perhaps make it a std::variant or boost::variant or similar to track the different empty states).

@joede
Copy link

joede commented Apr 30, 2018

First of all, I am not the original poster of this question. This was only my interpretation of the problem. ;-) So we must wait until @hassanfarid clarifies the problem.

int with = 100;                   // (1) the default value
std::optional<int> height = {};   // (2) without default
...
  auto result = cli.parse( clara::detail::Args( argc, argv ) );
  // variant (1)
  if ( width != 100 )
      // has been changed
  // variant (2)
  if ( height )
      // has been changed
  else
      height = 50;                // we must set the (useable) default value

If you have a lot of parameters, the list of if-else will get large and..... ugly. ;-)

Update: just a fast thought. I don't know a use case for that, but a solution could be to allow to pass lambda's instead of variables to Opt(). So the user must decide if and where to store the passed arguments. Again, I don't see a use-case for that. It just came to mind...

@philsquared
Copy link
Collaborator

Sorry, it sounds obvious, now that that's what you meant :-)

So you want to be able to specify a ("usable") default value in case it's not set on the CL, but also be able to tell, after the fact, if it was actually set on the CL? (and by "you" I mean @hassanfarid)?

First, it might be worth pointing out that, rather than the if you can coalesce the default value on an optional using .value_or():

height.value_or(50);

Which is slightly less verbose, but I can understand is still not quite what is wanted. You want that default value to be set at, or close to, the original declaration.

In which case I can only think of two ways - both of which are in user code (although, arguably, at least one could have out-of-the-box support to give you a leg up):

  1. As suggested before: bind to a lambda that sets both the value, and another flag that say that it has been explicitly set on the CL.
  2. Wrap this in a new, optional-like, type that supports the idea of a fallback value. If you give it the same concept-interface as std::optional (! to check and * to dereference) then it should "just work" (*)

(*) to use a non-std optional you'll need to #define CLARA_CONFIG_OPTIONAL_TYPE <your optional type> before #includeing Clara.

HTH

@philsquared
Copy link
Collaborator

WRT your update (which crossed with my last reply) - if I understand you correctly, you can "pass lambda's instead of variables to Opt()".

e.g.:

https://github.com/catchorg/Clara/blob/master/src/ClaraTests.cpp#L166

@joede
Copy link

joede commented Apr 30, 2018

@philsquared thanks for your replies. Your reference to the test passing a lamba is interesting. I feared Clara couldn't determine a data type, so the lamba must convert the string parameter to the option itself. But i seems the parameter of the lamba is used in this case. Am I right? In this case I could update my example. Just to memorize it myself. ;-)

@philsquared
Copy link
Collaborator

That is correct (here's the code that deduces it):

https://github.com/catchorg/Clara/blob/master/single_include/clara.hpp#L377

I appreciate this area of Clara is not really documented yet.

@joede
Copy link

joede commented Apr 30, 2018

OK, just to have it complete. Here is an updated example. Feel free to move it into a wiki page or so.

// Small example of how to use Clara
//
// Save this snippet as `clara-sample.cpp` together with the simgle-header
// verion of Clara and call `g++` as shown below.
//
// g++ -Wall -std=c++11 -o clara-sample clara-sample.cpp

#include <string>
#include <iostream>
#include "clara.hpp"

// dont show the help by default. Use `-h or `-?` to enable it.
//
bool showHelp = false;

// this block of variables are changed by Clara with the corresponding options passed
// to the program.
//
int width = 0;
std::string name;
bool doIt = false;
std::string command;

// This is a special value which is set indirectly by an lamba function!
//
int index = 0;


int main ( int argc, char** argv )
{
    auto cli = clara::detail::Help(showHelp)
             | clara::detail::Opt( width, "width" )["-w"]["--width"]("How wide should it be?")
             | clara::detail::Opt( name, "name" )["-n"]["--name"]("By what name should I be known")
             | clara::detail::Opt( doIt )["-d"]["--doit"]("Do the thing" )
             | clara::detail::Opt( [&]( int i )
                  {
                    if (i < 0 || i > 10)
                        return clara::detail::ParserResult::runtimeError("index must be between 0 and 10");
                    else {
                        index = i;
                        return clara::detail::ParserResult::ok( clara::detail::ParseResultType::Matched );
                    }
                  }, "index" )
                  ["-i"]( "An index, which is an integer between 0 and 10, inclusive" )
             | clara::detail::Arg( command, "command" )("which command to run").required();
    auto result = cli.parse( clara::detail::Args( argc, argv ) );
    if( !result )
    {
	std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
	return 1;
    }

    if ( showHelp )
    {
	std::cerr << cli << std::endl;
        return 0;
    }

    // show the results!
    std::cerr << "Show Help:" << showHelp << std::endl;
    std::cerr << "Index:" << index << std::endl;
    std::cerr << "Width:" << width << std::endl;
    std::cerr << "Name:" << name << std::endl;
    std::cerr << "Doit:" << doIt << std::endl;
    std::cerr << "Command:" << command << std::endl;

    return 0;
}

Thanks for your reference, but that is much more "modern C++" than I currently working with. ;-) I'm a long time "embedded C" coder and most C++ code I write is limited to GUI's written in FLTK or Qt5. But I'm currently look at it. Type traits and decltype are on my TODO list. ;-)

@lordadamson
Copy link

What is .required() used for?

@joede
Copy link

joede commented Feb 24, 2019

It tell's the parser that this option is "required". This means, the option must be passed to the application.

@diegoferigo
Copy link

Since this issue is currently a kind of only documentation, I report here my solution to parse an option that can be optional:

#include "clara.hpp"
#include <cstdlib>
#include <iostream>
#include <optional>

using namespace clara;

struct Config
{
    bool help = false;
    bool gui = false;
    std::optional<size_t> seed;
};

int main(int argc, char* argv[])
{
    Config config;

    // Create the command line parser
    auto cli = Help(config.help)
               | Opt(config.gui)["-g"]["--gui"]("open the gui")
               | Opt([&](unsigned value) { config.seed = value; }, "seed")["-s"]["--seed"](
                     "use a specific seed for randomness");

    // Parse the command line
    if (auto result = cli.parse(Args(argc, argv)); !result) {
        std::cout << "Error in command line: " << result.errorMessage() << std::endl;
        exit(EXIT_FAILURE);
    }

    if (config.help) {
        std::cout << cli;
        exit(EXIT_SUCCESS);
    }

    // Initialize the seed
    if (config.seed) {
        // Use the seed
        // ...
    }

It requires C++17.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants