Giter VIP home page Giter VIP logo

lyra's People

Contributors

a-ludi avatar alxe avatar bmburstein avatar burningenlightenment avatar claremacrae avatar gegles avatar girtsf avatar grafikrobot avatar horenmar avatar jayeshbadwaik avatar jhasse avatar miezhiko avatar mikecrowe avatar mklimenko avatar philsquared avatar remigastaldi avatar rioderelfte avatar ronen avatar sadiecat avatar shreyaspotnis avatar tusharpm avatar unepierre avatar vadz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lyra's Issues

Several options with multiple arguments

Hello,
after first successful experiments with Lyra I'm now stuck with something. I try to apply multiple arguments to several options, like this:

#include <lyra/lyra.hpp>

int main(int argc, const char** argv)
{
  bool showHelp = false;
  std::string opA = "OFF";
  std::string opB = "OFF";
  std::vector<std::string> argsA;
  std::vector<std::string> argsB;

  auto cli = lyra::cli();
  cli.add_argument(lyra::help(showHelp));

  cli.add_argument(lyra::opt(opA, "ON|OFF")["--optionA"]
    | lyra::arg(argsA, "args"));
  cli.add_argument(lyra::opt(opB, "ON|OFF")["--optionB"]
    | lyra::arg(argsB, "args"));

  auto result = cli.parse({ argc, argv });
  if (!result) {
    std::cerr << "ERROR:" << cli << "\n";
    return EXIT_FAILURE;
  }
  std::cout << "\nopA: " << opA << " opB: " << opB<< '\n';
  if(!argsA.empty()){
    for(auto a: argsA){
      std::cout << "argsA: " << a << "\n";
    }
  }
  if(!argsB.empty()){
    for(auto a: argsB){
      std::cout << "argsB: " << a << "\n";
    }
  }

  std::cout << " \n";
  std::cout << cli << " \n";
  return EXIT_SUCCESS;
}

when executing with ./testargs --optionA=ON a1 a2 a3 --optionB=ON b1 b2 I'd expect to get a1, a2, a3 in argsA and b1,b2 in argsB. However, all end up in argsA, and --optionB seems not to be recognised. The usage info from the help looks ok imo. The output for info:

 ./testargs --optionA=ON a1 a2 a3 --optionB=ON b1 b2 

opA: ON opB: OFF
argsA: a1
argsA: a2
argsA: a3
argsA: --optionB=ON
argsA: b1
argsA: b2
 
USAGE:
  testargs [-?|-h|--help] [--optionA <ON|OFF>] [[<args>...]] [--optionB <ON|OFF>] [[<args>...]]

Display usage information.

OPTIONS, ARGUMENTS:
  -?, -h, --help          
  --optionA <ON|OFF>      
  [<args>...]             
  --optionB <ON|OFF>      
  [<args>...]             

Am I doing something entirely wrong, or is there another way to obtain the arguments per option? Thanks in advance!

Output formatting differs from clara

I just started a new project using Lyra (after heeding the warning on the Clara repo, thanks for that!)

Under clara my help cam out like this:

usage:
  pt_three_ways <output> options

where options are:
  -w, --width <width>           output image width
  -h, --height <height>         output image height
  --max-cpus <#cpus>            maximum number of CPUs to use (0 for all)
  --spp <samples>               number of samples per pixel

etc

In a new project using Lyra 1.1, the formatting of the help is very different:

Usage:
  ticks2hdf5 [-?|-h|--help] <output> <input...>

Options, arguments:
  -?, -h, --help

  Display usage information.

  <output>

  HDF5 output filename

  <input...>

  Input files, in gzip compressed CSV format

Am I missing a changed configuration option somewhere? How might I get the nice formatting back?

Unknown options are silently ignored and mixed with positional arguments

When there are options as well as positional arguments, some of which may be optional, there is a problem with unknown options. Consider this repro case:

int foo = 0;
std::string positional;
std::string optional = "default";

auto cli = lyra::cli();
cli |= lyra::opt(foo, "foo")["--foo"];
cli |= lyra::arg(positional, "positional");
cli |= lyra::arg(optional, "optional").optional();

auto result = cli.parse(lyra::args({ argv[0], "--foo", "1", "--bar", "a" }));
if (!result) {
    std::cerr << "Error in command line: " << result.errorMessage() << std::endl;
    std::cerr << cli << std::endl;
    return EXIT_FAILURE;
}

std::cout << "foo = " << foo << std::endl;
std::cout << "positional = " << positional << std::endl;
std::cout << "optional = " << optional << std::endl;

which leads to this output:

foo = 1
positional = --bar
optional = a

This is a problem, because --bar as is not reported as unknown/invalid option, but passed to the next positional argument. It is pretty unlikely that the user intended to use --bar as a positional argument -- most likely they made a typo or another mistake and need to read the help page.

single_include_test.cpp fails

All tests pass except for this one:

single_include_test.cpp:8:10: fatal error: 'lyra/lyra.hpp' file not found
#include <lyra/lyra.hpp>
         ^~~~~~~~~~~~~~~
1 error generated.

Other tests found installed headers in /usr/local/include/lyra/* except for this case for some reason.

ignore help of subcommands

I have sub-commands with own arguments and I see them if I do app foo --help

but I don't want to see those when I type foo --help I only want to see that sub-command itself exists, is it currently possible to do?

Support for boolean flags with cardinality?

If I am not mistaken, it is currently impossible to have more than one identical bool flag. More precisely, it is ok to assign a cardinality to a flag,

bool verbose = false;

lyra::opt(verbose)["-v"]["--verbose"]("Verbosity").cardinality(0, 3)

but this doesn't have any effect as the underlying type is obviously binary. However, the above example is sometimes desirable (take ssh -vvv for example). Would it make sense (and be within reasonable bounds w.r.t the effort) to implement this, or is it too feature-creepy?

Wrong error message for unrecognized sub-command

There appears to be a regression in Lyra 1.6 vs. 1.5.1. Take the following parser with a single sub-command named sub:

#include <iostream>
#include <lyra/lyra.hpp>

std::string get_error(const lyra::parse_result& result)
{
    const std::string& error =
#if LYRA_VERSION_MINOR == 5
        result.errorMessage()
#else
        result.message()
#endif
    ;
    return error;
}

int main(int argc, const char **argv)
{
    auto cli = lyra::cli()
        .add_argument(
            lyra::command("sub")
        );

    const auto result = cli.parse({ argc, argv });
    if(!result)
    {
        std::cout << "ERROR: " << get_error(result) << "\n";
        return 1;
    }

    std::cout << "OK\n";
    return 0;
}

With 1.5.1 we get:

$ ./test
OK
$ ./test sub
OK
$ ./test bad
ERROR: Unrecognized token: bad

With 1.6.1 (and the latest develop) however:

$ ./test
OK
$ ./test sub
OK
$ ./test bad
ERROR: Expected: sub

This is quite confusing to users since sub isn't a "required sub-command".

The regression got introduced in 36e22c6 but that's a fairly big commit and I haven't been able yet to track down the exact spot.

I've also written up a small unit test in case that's helpful:

// unrecognized_command_run_test.cpp

#include <lyra/lyra.hpp>
#include "mini_test.hpp"

bool error_contains(const lyra::parse_result& result, const std::string& expectedError)
{
    const std::string& error =
#if LYRA_VERSION_MINOR == 5
        result.errorMessage()
#else
        result.message()
#endif
    ;
    return error.find(expectedError) != std::string::npos;
}

int main()
{
    using namespace lyra;
    bfg::mini_test::scope test;

    auto cli = lyra::cli()
        .add_argument(lyra::command("sub"));
    {
        const auto result = cli.parse( { "TestApp" } );
        test
            (REQUIRE( result ))
        ;
    }
    {
        const auto result = cli.parse( { "TestApp", "sub" } );
        test
            (REQUIRE( result ))
        ;
    }
    {
        const auto result = cli.parse( { "TestApp", "bad" } );
        test
            (REQUIRE( !result ))
            (REQUIRE( error_contains(result, "Unrecognized token") ))
        ;
    }

    return test;
}

Linker error

I am getting linker errors (multiple definition of ...) when I include Lyra in both a library and an executable that links to the previously mentioned library.
The issue seems to be related to definition of several methods outside the their class declaration scope.
For example:
include/lyra/exe_name.hpp:74: multiple definition of `lyra::exe_name::exe_name(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&)';

Harmless but annoying -Woverloaded-virtual warnings from gcc

Compiling the header with g++ -Wall results in several warnings:

In file included from lyra/include/lyra/arg.hpp:10,
                 from lyra/include/lyra/lyra.hpp:12,
                 from main.cpp:31:
lyra/include/lyra/parser.hpp:165:23: warning: ‘virtual lyra::parse_result lyra::parser::parse(const string&, const lyra::detail::token_iterator&, const lyra::parser_customization&) const’ was hidden [-Woverloaded-virtual]
  virtual parse_result parse(std::string const & exe_name,
                       ^~~~~
In file included from lyra/include/lyra/arguments.hpp:10,
                 from lyra/include/lyra/lyra.hpp:13,
                 from main.cpp:31:
lyra/include/lyra/exe_name.hpp:45:23: warning:   by ‘virtual lyra::parse_result lyra::exe_name::parse(const lyra::detail::token_iterator&, const lyra::parser_customization&) const’ [-Woverloaded-virtual]
  virtual parse_result parse(
                       ^~~~~
In file included from lyra/include/lyra/arg.hpp:10,
                 from lyra/include/lyra/lyra.hpp:12,
                 from main.cpp:31:
lyra/include/lyra/parser.hpp:165:23: warning: ‘virtual lyra::parse_result lyra::parser::parse(const string&, const lyra::detail::token_iterator&, const lyra::parser_customization&) const’ was hidden [-Woverloaded-virtual]
  virtual parse_result parse(std::string const & exe_name,
                       ^~~~~
In file included from lyra/include/lyra/lyra.hpp:13,
                 from main.cpp:31:
lyra/include/lyra/arguments.hpp:146:15: warning:   by ‘virtual lyra::parse_result lyra::arguments::parse(const lyra::detail::token_iterator&, const lyra::parser_customization&) const’ [-Woverloaded-virtual]
  parse_result parse(detail::token_iterator const & tokens,
               ^~~~~

Getting rid of them seems to be relatively simple using the usual trick of making parse() itself non-virtual and adding pure virtual do_parse(), but maybe it would be even better to avoid having two overloaded parse() versions just to use it in cli? It looks like this could be solved by just setting m_exeName in the "normal" parse() overload, but maybe I'm missing something here, as I've just started using Lyra and don't know it at all.

I could propose a PR doing this if you'd be interested.

Question on how to return error in lyra::opt lambdas

I like to return an error if parsing of an argument inside a lambda expression fails. Is there such an option? How can I return an error if the parsing fails, in this case if the IP address is invalid?

lyra::opt([&](const std::string& ip_string) {
      bool success = cmdpara.SourceIpAddress(ip_string);
      return success;
    }, "ip  address ")
      ["-i"]
      ["--ip_address"]
      ("address virtual nic rtx side (default localhost)"));

lyra::group in "|" operator chain

For lyra::group, I'd like to use the "|" operator chain way, but no example in the docs, and it seems that the callback passed into lyra::group isn't called.

#include "lyra/lyra.hpp"

#include "spdlog/fmt/fmt.h"
#include "spdlog/fmt/ostr.h"

int main(int argc, const char *argv[]) {

    bool show_help = false;
    auto name = std::string {};
    bool use_grade_id = false;
    int grade = 0;
    int id = 0;

    auto cli = lyra::help(show_help) | lyra::opt(name, "name")["-n"]["--name"].required()
               | (lyra::group([&](const lyra::group &) {
                      use_grade_id = true;
                      fmt::print("{}\n", use_grade_id);
                  })
                  | lyra::opt(id, "id")["--id"].required()
                  | lyra::opt(grade, "grade")["--grade"].required());

    auto cli_result = cli.parse({argc, argv});
    if (!cli_result) {
        fmt::print(stderr, "Error in command line: {}\n{}", cli_result.message(), cli);
        return EXIT_FAILURE;
    }

    if (show_help) {
        fmt::print("{}\n", cli);
        return EXIT_SUCCESS;
    }

    fmt::print("name: {}\n", name);
    fmt::print("use_grade_id: {}\n", use_grade_id);
    fmt::print("grade: {}\n", grade);
    fmt::print("id: {}\n", id);

    return EXIT_SUCCESS;
}

Am I using it wrong? or is it a bug?

Thanks!

required argument in sub command

Hello,

thanks a lot for this nice library !

when trying the sample code from 7. Sub-commands, it seems the required() call on process_name doesn't kicks in, i.e.:

$ ./main.out kill
KILL: signal=9 process=
$

Is there something I misunderstood ?

Compiler warning in from_string.hpp

When I compile my code in Release mode (higher level of Warnings enabled), I get the following:

In file included from ../../3rdparty/lyra/include/lyra/detail/parse.hpp:10,
                 from ../../3rdparty/lyra/include/lyra/detail/invoke_lambda.hpp:10,
                 from ../../3rdparty/lyra/include/lyra/detail/bound.hpp:10,
                 from ../../3rdparty/lyra/include/lyra/parser.hpp:11,
                 from ../../3rdparty/lyra/include/lyra/arg.hpp:10,
                 from ../../3rdparty/lyra/include/lyra/lyra.hpp:10,
                 from ../../cpp/echo/as_echo_main.cpp:7:
../../3rdparty/lyra/include/lyra/detail/from_string.hpp: In function 'bool lyra::detail::from_string(const S&, T&) [with S = std::basic_string<char>; T = spdlog::level::level_enum]':
../../3rdparty/lyra/include/lyra/detail/from_string.hpp:61:33: error: 'temp' may be used uninitialized in this function [-Werror=maybe-uninitialized]
   if (!ss.fail() && ss.eof()) { target = temp; return true; }
                                 ^~~~~~

Environment:

bash-4.2$ c++ --version
c++ (GCC) 8.3.1 20190311 (Red Hat 8.3.1-3)
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

bash-4.2$ more /etc/redhat-release
Red Hat Enterprise Linux Server release 7.7 (Maipo)
bash-4.2$

Support sub-parsers.

It's more difficult than it should be to support sub-commands and more complex argument syntax with the current squashed argument model. Add proper sub-command support in the form of sub-parsers with appropriate help text display. See #25 for use case..

[CMake problem] LINK : fatal error LNK1104: unable to open file 'bfg::lyra.lib'

I sticked to the instructions written in your CMakeLists.txt which are:

  • in your host project, put add_subdirectory( <path-to-lyra> ) - checked add_subdirectory(vendor/Lyra-master ./liblyra)
  • link to bfg::lyra with target_link_libraries( <my-exe/lib> PUBLIC bfg::lyra ) - checked target_link_libraries(ao-avalonian-roads-gps LINK_PUBLIC bfg::lyra)

On windows, I get the late link error

LINK : fatal error LNK1104: unable to open file 'bfg::lyra.lib'

Sure, the directory where the lib should be is empty (in my case build\liblyra\Debug).

Is Lyra supposed to build just by adding the add_subdirectory? Because it has its own .sln... which could be a problem I guess.

More info incomming from Linux:

Target "ao-avalonian-roads-gps" links to target "bfg::lyra" but the target was not found. Perhaps a find_package() call is missing for an IMPORTED target, or an ALIAS target is missing?

Modifiable bound_parser::hint()

Almost all attributes of a bound_parser can be changed or added later on, except the hint().

We needed that, when we decided to construct the parsers from some structured definition (that can be imported more easily into some handbook PDF).

Solvable through pull-request #48.

Unable to set boolean flag without specifying value

I can't figure out how to make a flag "-f" set a bool to true without specifying "-f1". I've looked through the docs and this doesn't seem to be supported at the moment (latest develop commit bf6d28a).

execution: ./exec -f

#include <iostream>

#include "lyra.h"

int main(int argc, char** argv){
	bool flag = false;

	auto engine = lyra::cli_parser()
		| lyra::opt(flag, "some_flag")
			.name("-f")
	;
	auto parsed = engine.parse({argc, argv});
	std::cout << !!parsed << " " << flag << "\n";
	return 0;
}

Add a feature comparison with other parser libraries

There are several popular C++ command-line parsers other than Lyra - some probably even more popular if we count GitHub stars: cxxopt, CLI11, the venerable Boost program_options and possibly others.

A developer finding this repository may be impressed by the "composable parsing" feature, showcased in the README.md, but has no idea whether how the feature set of Lyra compares to that of other popular options.

As a service to potential adopters, I suggest/ask that you create such a comparative table. It would be a good fit as a wiki page

As an example and inspiration, have a look at this table, comparing several unit-test frameworks for C++.

Help for cardinality-specified values

It would be great if the cardinality was represented in some way in the help. Currently if I specify a cardinality of (1, 1000) (see #11), the help text is still: test <input...> (I specify "input..." as the name for now as a workaround.

Ideally this would be something like the GNU help system's: test: <input> [<input>...] to show there's 1 or more parameters (although because of #11 we can't say "any number" currently)

Arguments with spaces are not working

I wan't to parse an argument with spaces so I put the argument inside double quotation marks.
For example:
myapp.exe --log "C:\path with spaces\log.txt"
Lyra will set the log variable to "C:\path" without the quotes.

No Install Target for CMake

Installing a CMake based library should be doable with the following commands

cmake -DCMAKE_INSTALL_PREFIX=<installation-path> <path-to-source>
cmake --build . --target install

This requires a target install to be provided by the CMakeLists. Currently such a target is not provided.

→ cmake ../src -DCMAKE_INSTALL_PREFIX=../run --log-level=WARNING
-- Configuring done
-- Generating done
-- Build files have been written to: /home/jayesh/software/lyra/build
 
→ cmake --build . --target install
make: *** No rule to make target 'install'.  Stop.

Value passed to a flag option is ignored (but gives no errors)

I realized that there is a corner case when parsing flag options: if the command-line contains a value for the flag, it will get ignored, which can be confusing:

bool wait_for_input = false;

(lyra::cli()
  |= lyra::opt(wait_for_input)
  ["--wait-for-input"]).parse(lyra::args({ argv[0], "--wait-for-input=no" }));

// wait_for_input == true
// Although we specified '=no'

The reason for that seems to be located in opt::parse:

if (m_ref->isFlag())
{
  // remove 'token' without checking whether it contains
  // a value with a delimiter, i.e. --option=myvalue
  // vvvvvvvvvvvvvvvvvvvvvvvv
  remainingTokens.pop(token);
  auto flagRef
    = static_cast<detail::BoundFlagRefBase *>(m_ref.get());
  auto result = flagRef->setFlag(true);

I initially tried to make flags optionally accept values, so that if no value is provided, true is used, and the value otherwise:
--wait-for-input -> true
--wait-for-input=yes -> true
--wait-for-input=no -> false

The code looked something like this:

auto const& argToken = remainingTokens.value();
auto result = (argToken.type == detail::token_type::unknown)
  ? flagRef->setFlag(true)
  : flagRef->setValue(argToken.name);

Unfortunately, this doesn't work in practice:

  • If a value is provided (--wait-for-input=yes or --wait-for-input no) it works ✅
  • If no value is provided (--wait-for-input), it seems to work...
  • ...unless there are more tokens afterwards, in which case they get interpreted as values for the flag, and it fails
    --wait-for-input --other-option=3 -> Unable to convert '--other-option' to destination type

Therefore, my suggestion would be to not accept values for flag options at all, but make sure that no value is provided with a delimiter:

if (m_ref->isFlag())
{
  // don't pop the token just yet
  auto flagRef
    = static_cast<detail::BoundFlagRefBase *>(m_ref.get());

  if (remainingTokens.has_option_prefix() && remainingTokens.has_value_delimiter()) // from token_iterator::value()
  {
    // --option=x, -o=x
    auto const& argToken = remainingTokens.value();

    return parse_result::runtimeError(
      { parser_result_type::no_match, remainingTokens },
      "Flag option '" + token.name + "' contains value '" + argToken.name + "'. Flag options cannot contain values ");
  }
  remainingTokens.pop(token); // now we can pop the token

  auto result = flagRef->setFlag(true);
  // ...

While we are at it, we might also add some extra validation that checks that no value_choices are provided for flags (choices don't make sense for them, as far as I know):

if (value_choices)
{
  return parse_result::logicError(
    { parser_result_type::no_match, remainingTokens },
    "Flag options cannot contain choices. Token: '" + token.name + '\'');
}

Please let me know if I misunderstood something, and give me feedback if you like the idea so that I can create a pull request. Thank you for the awesome library!

Named args with parameters' args don't appear in usage

In the config:

#include <lyra/lyra.hpp>
#include <string>

int main(int argc, const char *argv[]) {
  std::string named_required;
  std::string optional;
  auto cli = lyra::opt(named_required,
                  "required-arg")["--required"]("You must supply this arg")
            .required() |
        lyra::arg(optional, "optional")("This is an optional arg");

    std::cout << cli << "\n";
}

The output is:

USAGE:
  <executable> --required [<optional>]

OPTIONS, ARGUMENTS:
  --required <required-arg>
                          You must supply this arg
  <optional>              This is an optional arg

The --required in the USAGE: line does not refer to the required parameter name. I'd expect to see:

USAGE:
  <executable> --required <required-arg> [<optional>]

OPTIONS, ARGUMENTS:
  --required <required-arg>
                          You must supply this arg
  <optional>              This is an optional arg

Infinite loop when non-existent argument is specified

I've been testing the proposed snippet from #51 showing how to have options with multiple arguments and so compiled and ran this simple example:

#include <lyra/lyra.hpp>

int main(int argc, const char** argv)
{
  bool gotArgs = false;
  std::vector<std::string> args;

  auto cli = lyra::cli();

  cli.add_argument(lyra::group().sequential()
    | lyra::opt(gotArgs)["--args"]
    | lyra::arg(args, "args"));

  auto result = cli.parse({ argc, argv });
  if (!result) {
    std::cerr << "ERROR:" << cli << "\n";
    return EXIT_FAILURE;
  }

  std::cout << "got args = " << gotArgs << "\n";
  if(!args.empty()){
    for(auto const& a: args) {
      std::cout << "\targ: " << a << "\n";
    }
  }

  return EXIT_SUCCESS;
}

It works more or less as expected, but suffers from a fatal problem: running it with an unknown option or any arguments without using --args option simply hangs the program. I didn't have time to really debug this yet, but I see that it gets stuck in the do/while loop in lyra::arguments::parse_sequence and I don't really understand how is this supposed to work, i.e. why is p_result.value().have_tokens() always remains true.

cli_parser::parse overwrites exe_name

when you call cli_parser::parse(), whatever value you set via lyra::exe_name is lost and replaced with the default executable name. If you constructed lyra::exe_name with a string in the stack, that string is also overwritten.

An exemple:

// my executable is called "myapp"
bool show_help; std::string command;
auto cli = lyra::cli_parser()
    | lyra::help(show_help)
    | lyra::arg(command, "command")("Command to perform.").required();

std::string program_name = "different";
cli.add_argument(lyra::exe_name(program_name)); // changes the name to "different"

auto result = cli.parse({argc, argv});
// exeName is is back to "myapp" at this point
// the value of 'program_name' silently changes to "myapp"  😱 

if (show_help) {
    // won't have the expected name
    std::cout << cli;
    exit(EXIT_SUCCESS);
}

optional, non-container lyra::arg does not show up in usage

Optional lyra::args like:

int foo = 0;
auto cli = lyra::cli_parser()
    | lyra::help(help)
    | lyra::arg(foo, "foo").help("Help text for foo.")
;

will not show up on the usage line,
and their help text will be shown without the "hint" string. Example output:

> ./main -?
Usage:
  main.exe [-?|-h|--help]

Options, arguments:
  -?, -h, --help

  Display usage information.



  Help text for foo.

Is this intentional, or a bug?

Unknown option gives user unfriendly error

If you misspell or provide an option that doesn't have a parser, when at least one argument is present in the parser, the returned error code isn't very user friendly.

Easiest to show with an example.

Given this code

#include <lyra/lyra.hpp>
#include <filesystem>
#include <iostream>

int main(int argc, char** argv)
{
    bool showHelp=false;
    bool verbose = false;
    auto cli = lyra::cli_parser();

    cli.add_argument(lyra::help(showHelp));
    cli.add_argument(lyra::opt(verbose)
        .name("--verbose").help("Verbose logging").optional());

    std::filesystem::path projectDirectory;
    cli.add_argument(lyra::arg(projectDirectory, "ProjectDirectory")
        .required().help("Project directory"));

    auto result = cli.parse({ argc, argv });

    if (!result)
    {
        std::cerr << result.errorMessage() << std::endl;
        return 1;
    }

    std::cout << projectDirectory.string() << std::endl;
    return 0;
}

and the commandline arguments

--foo /my/project/directory

The returned error is

Unrecognized token: /my/project/directory

Expected: Parser should recognize the '--' or '-' prefix on '--foo' and give an error about the missing option.

Surprising behavior of lyra::group, is it a bug?

Hi, I'm in the process of migrating my project to Lyra, and I've encountered some surprising behavior of the "group" feature where I'm not sure if it is intentional.

Specifically, it seems that as soon as I specify an option belonging to a group, the very next subsequently given option will always be treated as a positional argument even if it should instead be matched by another option. This is easiest to explain by example. Consider the following program (ready for copy & paste and build):

#include <lyra/lyra.hpp>

#include <iostream>
#include <string>

int main(int argc, char** argv) {
  bool someFlag = false;
  std::string requiredArgInGroup;
  std::string optionalArgInGroup;
  std::string positionalArg;

  auto optionsParser =
    lyra::opt(someFlag).name("-f")
    | lyra::group([](const lyra::group&){})
      .add_argument(
        lyra::opt(requiredArgInGroup, "req")
        .name("--req-in-group")
        .required())
      .add_argument(
        lyra::opt(optionalArgInGroup, "opt")
        .name("--opt-in-group"))
    | lyra::arg(positionalArg, "positional");

  if (auto result = optionsParser.parse({argc, argv})) {
    std::cout << std::boolalpha << someFlag << ", " << positionalArg << '\n';
  } else {
    std::cout << result.errorMessage() << '\n';
  }
}

Expected behavior

If I invoke the program and specify the -f option after --req-in-group, I would expect it to result in setting someFlag, and a subsequent positional argument to be assigned to positionalArg.

Expected output:

$ ./a.out --req-in-group test1 -f test2
true, test2

Expected output without positional arg:

$ ./a.out --req-in-group test1 -f
true, 

Actual behavior

What actually happens is that -f is assigned to positionalArg:

$ ./a.out --req-in-group test1 -f
false, -f

And adding a subsequent positional argument (i.e. --req-in-group test1 -f test2) results in a parse error (unrecognized token: test2).

Interestingly, specifying the positional argument first after the group argument results in the behavior I would expect:

$ ./a.out --req-in-group test1 test2 -f
true, test2

This seems like a bug to me, but maybe I'm misunderstanding something about how groups work? Any clarification would be appreciated 🙂 I really like the groups feature and would love to use it!

Allow single-hyphenated long names

There are some cases where the platform or environment of an application requires it to support a single-hyphenated command line option with a long name.

Framework & Documentation Example Argument
OpenAutomate -openautomate [key]
Epic Games Store -AUTH_TYPE=[type]

I'd like Lyra to have an option to interpret single-hyphenated options as a single option rather than a list of options. This could be a build-time setting or a global setting, I don't think it needs to be per-option, though I would accept that solution as well.

lyra::arg(arg, "arg").choices(array_of_strings) does not compile

Info

Version: 1.3.0 (via Conan)
OS/Compiler: Windows 10/MSVC 2019
C++ Standard: c++11, c++17

Description

When passing the acceptable choices to an argument via .choices(), it seems to also accept an array of strings. However, attempting to compile will throw an error:

binary '>>': no operator found which takes a right-hand operand of type 'T' (or there is no acceptable conversion)

Offending Snippet that was indicated by the compiler (more specifically, lines 59/60):

template <typename S, typename T>
inline bool from_string(S const& source, T& target)
{
std::stringstream ss;
ss << source;
T temp{};
ss >> temp;
if (!ss.fail() && ss.eof()) { target = temp; return true; }
return false;
}

Minimal Reproduction

#include <lyra/lyra.hpp>
#include <iostream>

using namespace std;

const string CMD_VERB[] = {"opt1", "opt2"};

int main(int argc, char **argv)
{
    string command;
    const auto parser = lyra::cli_parser() |
                        lyra::arg(command, "command").choices(CMD_VERB).required();
}

Single header?

Clara was a single header parser. That options seems to have been removed. Any chance it could be re-added? It is really handy to be able to drop in a single file to an app.

When building with -Wshadow on GCC there are many shadowing warnings.

As Lyra is a header-only library it is affected by project build flags so there is no reasonable way for a project to work around this short of disabling the flag.

Here is a sanitised log from my project:

In file included from [PATH]/main.cpp:39:
[PATH]/lyra.hpp: In constructor ‘lyra::detail::print::print(const char*)’:
[PATH]/lyra.hpp:55:3: warning: declaration of ‘scope’ shadows a member of ‘lyra::detail::print’ [-Wshadow]
   55 |   : scope(scope)
      |   ^
[PATH]/lyra.hpp:84:15: note: shadowed declaration is here
   84 |  const char * scope;
      |               ^~~~~
[PATH]/lyra.hpp: In constructor ‘lyra::detail::print::print(const char*)’:
[PATH]/lyra.hpp:59:2: warning: declaration of ‘scope’ shadows a member of ‘lyra::detail::print’ [-Wshadow]
   59 |  }
      |  ^
[PATH]/lyra.hpp:84:15: note: shadowed declaration is here
   84 |  const char * scope;
      |               ^~~~~
[PATH]/lyra.hpp: In constructor ‘lyra::detail::print::print(const char*)’:
[PATH]/lyra.hpp:59:2: warning: declaration of ‘scope’ shadows a member of ‘lyra::detail::print’ [-Wshadow]
   59 |  }
      |  ^
[PATH]/lyra.hpp:84:15: note: shadowed declaration is here
   84 |  const char * scope;
      |               ^~~~~
In file included from [PATH]/main.cpp:39:
[PATH]/lyra.hpp: In constructor ‘lyra::args::args(std::initializer_list<std::__cxx11::basic_string<char> >)’:
[PATH]/lyra.hpp:136:3: warning: declaration of ‘args’ shadows a member of ‘lyra::args’ [-Wshadow]
  136 |   : m_exeName(*args.begin())
      |   ^
[PATH]/lyra.hpp:128:1: note: shadowed declaration is here
  128 | {
      | ^
[PATH]/lyra.hpp: In constructor ‘lyra::args::args(std::initializer_list<std::__cxx11::basic_string<char> >)’:
[PATH]/lyra.hpp:138:3: warning: declaration of ‘args’ shadows a member of ‘lyra::args’ [-Wshadow]
  138 |  {}
      |   ^
[PATH]/lyra.hpp:128:1: note: shadowed declaration is here
  128 | {
      | ^
[PATH]/lyra.hpp: In constructor ‘lyra::args::args(std::initializer_list<std::__cxx11::basic_string<char> >)’:
[PATH]/lyra.hpp:138:3: warning: declaration of ‘args’ shadows a member of ‘lyra::args’ [-Wshadow]
  138 |  {}
      |   ^
[PATH]/lyra.hpp:128:1: note: shadowed declaration is here
  128 | {
      | ^
	BUILD:		logger.cpp
In file included from [PATH]/main.cpp:39:
[PATH]/lyra.hpp: In constructor ‘lyra::detail::choices_check<Lambda>::choices_check(const Lambda&)’:
[PATH]/lyra.hpp:984:3: warning: declaration of ‘checker’ shadows a member of ‘lyra::detail::choices_check<Lambda>’ [-Wshadow]
  984 |   : checker(checker)
      |   ^
[PATH]/lyra.hpp:980:9: note: shadowed declaration is here
  980 |  Lambda checker;
      |         ^~~~~~~
[PATH]/lyra.hpp: In constructor ‘lyra::option_style::option_style(std::string&&, std::string&&, std::size_t, std::string&&, std::size_t)’:
[PATH]/lyra.hpp:1063:3: warning: declaration of ‘short_option_size’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1063 |   : value_delimiters(std::move(value_delimiters))
      |   ^
[PATH]/lyra.hpp:1055:14: note: shadowed declaration is here
 1055 |  std::size_t short_option_size = 0;
      |              ^~~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1063:3: warning: declaration of ‘short_option_prefix’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1063 |   : value_delimiters(std::move(value_delimiters))
      |   ^
[PATH]/lyra.hpp:1054:14: note: shadowed declaration is here
 1054 |  std::string short_option_prefix;
      |              ^~~~~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1063:3: warning: declaration of ‘long_option_size’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1063 |   : value_delimiters(std::move(value_delimiters))
      |   ^
[PATH]/lyra.hpp:1053:14: note: shadowed declaration is here
 1053 |  std::size_t long_option_size = 0;
      |              ^~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1063:3: warning: declaration of ‘long_option_prefix’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1063 |   : value_delimiters(std::move(value_delimiters))
      |   ^
[PATH]/lyra.hpp:1052:14: note: shadowed declaration is here
 1052 |  std::string long_option_prefix;
      |              ^~~~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1063:3: warning: declaration of ‘value_delimiters’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1063 |   : value_delimiters(std::move(value_delimiters))
      |   ^
[PATH]/lyra.hpp:1051:14: note: shadowed declaration is here
 1051 |  std::string value_delimiters;
      |              ^~~~~~~~~~~~~~~~
[PATH]/lyra.hpp: In constructor ‘lyra::option_style::option_style(std::string&&, std::string&&, std::size_t, std::string&&, std::size_t)’:
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘short_option_size’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1055:14: note: shadowed declaration is here
 1055 |  std::size_t short_option_size = 0;
      |              ^~~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘short_option_prefix’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1054:14: note: shadowed declaration is here
 1054 |  std::string short_option_prefix;
      |              ^~~~~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘long_option_size’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1053:14: note: shadowed declaration is here
 1053 |  std::size_t long_option_size = 0;
      |              ^~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘long_option_prefix’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1052:14: note: shadowed declaration is here
 1052 |  std::string long_option_prefix;
      |              ^~~~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘value_delimiters’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1051:14: note: shadowed declaration is here
 1051 |  std::string value_delimiters;
      |              ^~~~~~~~~~~~~~~~
[PATH]/lyra.hpp: In constructor ‘lyra::option_style::option_style(std::string&&, std::string&&, std::size_t, std::string&&, std::size_t)’:
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘short_option_size’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1055:14: note: shadowed declaration is here
 1055 |  std::size_t short_option_size = 0;
      |              ^~~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘short_option_prefix’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1054:14: note: shadowed declaration is here
 1054 |  std::string short_option_prefix;
      |              ^~~~~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘long_option_size’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1053:14: note: shadowed declaration is here
 1053 |  std::size_t long_option_size = 0;
      |              ^~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘long_option_prefix’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1052:14: note: shadowed declaration is here
 1052 |  std::string long_option_prefix;
      |              ^~~~~~~~~~~~~~~~~~
[PATH]/lyra.hpp:1068:3: warning: declaration of ‘value_delimiters’ shadows a member of ‘lyra::option_style’ [-Wshadow]
 1068 |  {}
      |   ^
[PATH]/lyra.hpp:1051:14: note: shadowed declaration is here
 1051 |  std::string value_delimiters;
      |              ^~~~~~~~~~~~~~~~
[PATH]/lyra.hpp: In member function ‘virtual lyra::parse_result lyra::arg::parse(const lyra::detail::token_iterator&, const lyra::option_style&) const’:
[PATH]/lyra.hpp:2182:8: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 2182 |   auto result = valueRef->setValue(token.name);
      |        ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘virtual lyra::result lyra::arguments::validate() const’:
[PATH]/lyra.hpp:2471:9: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 2471 |    auto result = p->validate();
      |         ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘lyra::parse_result lyra::arguments::parse_any(const lyra::detail::token_iterator&, const lyra::option_style&) const’:
[PATH]/lyra.hpp:2508:8: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 2508 |   auto result = parse_result::ok(
      |        ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘lyra::parse_result lyra::arguments::parse_sequence(const lyra::detail::token_iterator&, const lyra::option_style&) const’:
[PATH]/lyra.hpp:2589:8: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 2589 |   auto result = parse_result::ok(
      |        ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘virtual const lyra::parser* lyra::arguments::get_named(const string&) const’:
[PATH]/lyra.hpp:2654:19: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 2654 |    const parser * result = p->get_named(n);
      |                   ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘virtual lyra::parse_result lyra::group::parse(const lyra::detail::token_iterator&, const lyra::option_style&) const’:
[PATH]/lyra.hpp:2926:16: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 2926 |   parse_result result = arguments::parse(tokens, style);
      |                ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘lyra::cli::value_result::operator T() const’:
[PATH]/lyra.hpp:3157:43: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 3157 |    typename detail::remove_cvref<T>::type result {};
      |                                           ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘lyra::cli::value_result::operator std::vector<T>() const’:
[PATH]/lyra.hpp:3168:19: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 3168 |    std::vector<T> result;
      |                   ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘lyra::parse_result lyra::cli::parse(const lyra::args&, const lyra::option_style&) const’:
[PATH]/lyra.hpp:3399:15: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 3399 |  parse_result result = parse(args_tokens, style);
      |               ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘lyra::literal& lyra::literal::operator()(const string&)’:
[PATH]/lyra.hpp:3589:69: warning: declaration of ‘description’ shadows a member of ‘lyra::literal’ [-Wshadow]
 3589 | inline literal & literal::operator()(std::string const & description)
      |                                                                     ^
[PATH]/lyra.hpp:3535:14: note: shadowed declaration is here
 3535 |  std::string description;
      |              ^~~~~~~~~~~
[PATH]/lyra.hpp: In member function ‘virtual std::string lyra::opt::get_usage_text(const lyra::option_style&) const’:
[PATH]/lyra.hpp:3825:15: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 3825 |   std::string result;
      |               ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘virtual lyra::parser::help_text lyra::opt::get_help_text(const lyra::option_style&) const’:
[PATH]/lyra.hpp:3839:21: warning: declaration of ‘opt’ shadows a member of ‘lyra::opt’ [-Wshadow]
 3839 |   for (auto const & opt : opt_names)
      |                     ^~~
[PATH]/lyra.hpp:3773:1: note: shadowed declaration is here
 3773 | {
      | ^
[PATH]/lyra.hpp: In member function ‘virtual lyra::parse_result lyra::opt::parse(const lyra::detail::token_iterator&, const lyra::option_style&) const’:
[PATH]/lyra.hpp:3886:11: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 3886 |      auto result = flagRef->setFlag(true);
      |           ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp:3910:11: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 3910 |      auto result = valueRef->setValue(argToken.name);
      |           ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~
[PATH]/lyra.hpp: In member function ‘lyra::main& lyra::main::operator()(const T&)’:
[PATH]/lyra.hpp:4273:41: warning: declaration of ‘parser’ shadows a member of ‘lyra::main’ [-Wshadow]
 4273 | main & main::operator()(const T & parser)
      |                                         ^
[PATH]/lyra.hpp:1582:1: note: shadowed declaration is here
 1582 | {
      | ^
[PATH]/lyra.hpp: In member function ‘lyra::main& lyra::main::add_argument(const T&)’:
[PATH]/lyra.hpp:4279:43: warning: declaration of ‘parser’ shadows a member of ‘lyra::main’ [-Wshadow]
 4279 | main & main::add_argument(const T & parser)
      |                                           ^
[PATH]/lyra.hpp:1582:1: note: shadowed declaration is here
 1582 | {
      | ^
[PATH]/lyra.hpp: In member function ‘lyra::main& lyra::main::operator|=(const T&)’:
[PATH]/lyra.hpp:4285:41: warning: declaration of ‘parser’ shadows a member of ‘lyra::main’ [-Wshadow]
 4285 | main & main::operator|=(const T & parser)
      |                                         ^
[PATH]/lyra.hpp:1582:1: note: shadowed declaration is here
 1582 | {
      | ^
[PATH]/lyra.hpp: In member function ‘int lyra::main::operator()(const lyra::args&, L)’:
[PATH]/lyra.hpp:4391:7: warning: declaration of ‘result’ shadows a global declaration [-Wshadow]
 4391 |  auto result = cli::parse(argv);
      |       ^~~~~~
[PATH]/lyra.hpp:603:7: note: shadowed declaration is here
  603 | using result = detail::basic_result<void>;
      |       ^~~~~~

I'm using the single header version of 1.6.0 but this should still occur with the multi-header version.

Help not working in sub-command example

I was trying to get lyra::help to work with the add_argument interface and the sub-command example has just that. However, it looks like --help is not working in this case. I got it to work by moving the cli.parse invocation before the check for show_help - I believe that it's the right fix (parsing the command line for --help before checking the corresponding flag makes sense to me) but it's my first time trying Lyra so I can't really be sure.

I believe that it was introduced when implementing the fix for #16.

choices() does not support a single value

I am building a CLI with a single sub-command (And a bunch of general options). It fails to compile with .choices("run"). I could use a lambda, but I would rather leverage the simpler way (to get the right error message... Another workaround could be to use .choice("run", "") but it's ugly and the error message is then: Error: Value 'kill' not expected. Allowed values are: run, with a comma at the end...

Non-iostream conversion.

Currently argument type parsing conversions use iostreams as the base mechanism. It would be beneficial to allow customization of that conversion to extend or replace it.

Long text wrapping in the help output

The length of the "help" strings for options is not limited, but the implementation assumes that they are somewhat short, else the help output looks pretty ugly when it is wrapped in a terminal. For example (hard-wrapped manually to show the issue in HTML):

...

OPTIONS, ARGUMENTS:
  -?, -h, --help
  --detect-features       Before mesh generation, detect sharp features and boundaries of the
internal bounding polyhedron (and the potential internal polyhedron) and inserts them as features
of the domain. Default value: 0.
  --features-angle-bound <SCALAR>
                          The maximum angle (in degrees) between the two normal vectors of adjacent
triangles. For an edge of the polyhedron, if the angle between the two normal vectors of its
incident facets is bigger than the given bound, then the edge is considered as a feature edge.
Default value: 60.
  --edge-size <SCALAR>    A constant providing a uniform upper bound for the lengths of curve edges.
This parameter has to be set to a positive value when 1-dimensional features protection is used.
Default value: 1.
  --facet-angle <SCALAR>  A lower bound for the angles (in degrees) of the surface mesh facets.
Default value: 25.
  --facet-size <SCALAR>   A constant describing a uniform upper-bound or for the radii of the
surface Delaunay balls. Default value: 1.
  --facet-distance <SCALAR>
                          A constant describing a uniform upper bound for the distance between the
facet circumcenter and the center of its surface Delaunay ball. Default value: 0.125.

Instead, Lyra should do the wrapping and print something like this:

OPTIONS, ARGUMENTS:
  -?, -h, --help
  --detect-features       Before mesh generation, detect sharp features and boundaries of the
                          internal bounding polyhedron (and the potential internal polyhedron) and
                          inserts them as features of the domain. Default value: 0.
  --features-angle-bound <SCALAR>
                          The maximum angle (in degrees) between the two normal vectors of adjacent
                          triangles. For an edge of the polyhedron, if the angle between the two
                          normal vectors of its incident facets is bigger than the given bound,
                          then the edge is considered as a feature edge.  Default value: 60.
  --edge-size <SCALAR>    A constant providing a uniform upper bound for the lengths of curve edges.
                          This parameter has to be set to a positive value when 1-dimensional
                          features protection is used.  Default value: 1.
  --facet-angle <SCALAR>  A lower bound for the angles (in degrees) of the surface mesh facets.
                          Default value: 25.
  --facet-size <SCALAR>   A constant describing a uniform upper-bound or for the radii of the
                          surface Delaunay balls. Default value: 1.
  --facet-distance <SCALAR>
                          A constant describing a uniform upper bound for the distance between the
                          facet circumcenter and the center of its surface Delaunay ball. Default
                          value: 0.125.

Add generic sequence for input args.

For more user flexibility add a generic begin/end iterator initialization of lyra::args so that the parsed arguments can come from any container like thing that the user cares to provide.

Cannot print the description text for groups/subcommands

When attempting to print the help text for a subcommand, any description that was set for it does not get printed.

If I define a subcommand using the lyra::command class and attempt to set a description for the subcommand.

const auto on_success = [this](const lyra::group& group) {
    if (showHelp_) {
        std::cout << group << std::endl;

        return;
    }
};

lyra::command("subcommand", on_success)
    .add_argument(lyra::help(showHelp_)
        .description("This is a description."))
    .add_argument(lyra::opt(dryRun_)
                  ["--dry-run"]
                  ("Parse the command line but don't execute the command."));

If I call <exe> subcommand --help I would expect the description text "This is a description." to appear between the USAGE and OPTION, ARGUMENTS for the subcommand, but what I get is:

PS D:\test> .\test.exe subcommand --help
USAGE:
  subcommand { [-?|-h|--help] [--dry-run] }

OPTIONS, ARGUMENTS:
  subcommand

  -?, -h, --help
  --dry-run               Parse the command line but don't execute the command.

The reason for this is that when calling get_description_text it checks if the current class is a group and skips printing the description. arguments.hpp

This seems to be done so that if --help is called on the top level command, the description for every subcommand is not printed. However, there does not seem to be a way to allow the description text to go through when printing the help text for a subcommand.

Add docs on how to build and run tests

I finally had a few minutes to look at some things but couldn't get the tests to build or run, and was a bit bewildered in general. I'm sure there's something obvious I'm missing. I tried:

  • using the CMakeLists.txt (nothing builds; upon closer inspection it seems this doesn't support tests)
  • installed b2 and running what I think the CI system is doing (by looking at the azure thing). After installing b2 in /tmp/b2/bin by doing what I think the azure steps are doing:
~/d/o/Lyra (mg/formatting|✔) $ cd tests
~/d/o/L/tests (mg/formatting|✔) $ /tmp/b2/bin/b2
Unable to load B2: could not find 'boost-build.jam'
---------------------------------------------------
Attempted search from '/home/mgodbolt/dev/oss/Lyra/tests' up to the root at '/tmp/b2/bin/b2'
Please consult the documentation at 'https://boostorg.github.io/build/'.
  • I then tried meson, I made a build dir and ran meson /path/to/Lyra. ninja then ran but ninja test says "No tests defined".

optional option with container

It might be handy to be able to call a program with an [0;n] option. So you might be able to do the following:
myprog -a foo -a bar stuff.txt
or
myprog stuff.txt

In the current version of Lyra (using opt(std::vector, "foo")) I have two cases:

  • Default (seems to be required): [1; n] option when calling the program (using required(0) doesn't change that behavior)
  • .optional(): [0;1] option when calling the program.

Negative numbers not handled consistently

It was looking at switching to use Lyra from an existing command line handling routine and found that negative values on the command line are not handled consistently.

Using

Lyra\examples\doc_example1.cpp

as a test I found that negative values only work if you use '=', with either the short or long options.

"doc_example -w 32" works but "doc_example -w -32" fails

The following Windows batch file shows the passing and failing cases.

-----------------> Cut Here <----------------------

@echo off
@rem
@rem Test to show inconsistancy in handling of negative numbers
@rem
setlocal enableextensions

call :test -w 32
call :test -w -32
call :test -w=32
call :test -w=-32
call :test --width 32
call :test --width -32
call :test --width=32
call :test --width=-32
goto :EOF

:test
@rem echo Test doc_example1 %*
doc_example1 %* > nul: 2>&1
if errorlevel 1 (
echo *FAIL*: doc_example1 %*
) else (
echo Pass: doc_example1 %*
)

goto :EOF

-----------------> Cut Here <----------------------

Binding arg to a vector only allows for a single value

The following code:

int Main(int argc, const char* argv[]) {
  bool help = false;
  std::string out_path{};
  std::vector<std::string> input_paths;
  auto cli = lyra::cli_parser() | lyra::help(help) | lyra::arg(input_paths, "input...").required()("Input files, in gzip compressed CSV format");

  auto result = cli.parse({argc, argv});
  if (!result) {
    std::cerr << "Error in command line: " << result.errorMessage() << "\n";
    std::cerr << cli;
    return 1;
  }
...
}

when passed ./test a b c gives the error:

Error in command line: Unrecognized token: b

How might I set it to take any number of arguments? I saw the cardinality() call, but there's essentially no upper bound on the number of arguments. I could specify some arbitrary high limit, but I suspect I'm doing it wrong :)

Printing help text without ostream

It would be nice if it were possible to print the help text without passing by std::ostream, e.g. I'm migrating existing code to fmt, but still have to use either std::cerr or std::ostrstream to be able to print help message and I'd rather avoid it.

The simplest fix would seem to be to refactor parser::print_help_text() to use get_help_text() returning a std::string, as this function doesn't really use any of the streams functionality and does formatting manually anyhow. As usual, I could make a PR if this could be useful -- please let me know.

Note that while this is somewhat related to #46, but not really the same thing.

Better document cmake use.

Update comments in cmakelists.txt to: be correct with current support, and explain using EXCLUDE_FROM_ALL.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.