Giter Club home page Giter Club logo

argparse's People

Contributors

aashwinr avatar aayush749 avatar abellgithub avatar arthapz avatar bedzior avatar bryanflynt avatar bufferbase avatar chuvi-w avatar cobyj33 avatar crustyauklet avatar ericonr avatar hokacci avatar ismagilli avatar jackojc avatar jun-sheaf avatar kolanich avatar lichray avatar mu001999 avatar p-ranav avatar rouault avatar rysson avatar sam4uk avatar serge-sans-paille avatar sergiusthebest avatar skrobinson avatar stripe2933 avatar svanveen avatar ubpa avatar wtdcode avatar zhihaoy 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

argparse's Issues

Switch to doctest and use parallel build

Unit tests compile too slow. We should compile different test translation units in parallel. Including and compile catch2 header 20 times is still, but to doctest it isn't an issue. By doing so, when changing a test file, you don't have to recompile all the tests.

bind_front in action

action currently accept only a callable, but since pending member function call obj.fn isn't a callable object in C++, user can't even use a member function as action.
Note that mem_fn doesn't solve the problem. mem_fn is to wrap a callable object into a function object, but std::function ctor already does that. So user ends up with three choices:

  1. lambda
  2. the old and convoluted std::bind
  3. the new bind_front in C++20
    However, the standard library facilities have been solving this issue quite well for a long time. For example, you can use member call in std::thread ctor like this
thread(&Class::fn, obj)  // running obj.fn()

This is done as if forming a bind_front(&Class::fn, obj) function object and call it with empty argument list. We can do similar thing for action:

.action(&Class::mem, obj)  // calling obj.mem(arg_str)

by implicitly using bind_front for multi-argument calls to action.

Note: to get this working for C++17, we can include a homegrown version. Take a look at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0356r5.html#implementability and https://reviews.llvm.org/D60368 . It doesn't have to be perfect.

What if actions want to return std::vector?

If an option wants to parse a list of values --foo 1,4,2,6, then it cannot return std::vector<int> because this type is considered a container. We probably will never want to parse a list like that (because there are so many good parsing libraries for structural data), so we cannot return the existing containers functionality to "make get<std::vector<T>>() work" for this demand.

We should consider changing the multivalue argument support. Some observations:

  • Blessing types takes away possible return types
  • std::vector<std::string> is verbose to spell
  • std::array is not supported (and is tricky to support, because it's not a container that can grow)
  • std::list is probably never used for lots of reasons

Inventing a new set of API is an option, but the present API (#66) will double the number of names. I think the situation can be improved by blessing a type that is both impossible to be stored in std::any, and is terse to write -- array type.

Here is how the new get may look like:

  • get<T[]>: returns std::vector<T>
  • get<T[N]>: returns std::array<T, N> if N == nargs, throws otherwise
  • get<T>: returns T

The old spelling get<std::vector<std::string>> becomes get<std::string[]> after the change. To not to break everything immediately, we can make the old spelling [[deprecated]].

FAQ

  1. Why T[] and T[N] return entirely different types? I hope users can interpret [] as "returning a type with operator[]."
  2. Why not other kinds of containers? If users want they can append to anything with void actions. We are providing a good default here.
  3. Why not std::span (of static extent or dynamic extent)? Technical issue, we only have storage for std::vector<std::any> rather than std::vector<T>.

Almost [Required] (help text)

Argument with .required() flag and with default value is not so required (can be omitted in command-line).
I think that [Required] is not necessary in this case.

A propose skip this text if argument has default value

    if (argument.mIsRequired && !argument.mDefaultValue.has_value()) 
      stream << "[Required]";

Linking errors when used in multiple places

I have encountered a linker error that I do not fully understand, and a workaround which I do not fully understand either.

I am using a pattern where I parse a global ArgumentParser in main() (ish) and then pass around my options (by non-const reference) to multiple translation units where the different parts of the program know what their option keys are and can retrieve their specialized values. This pattern requires including argparse.hpp in multiple source files so the definition of ArgumentParser is available in each place it is used. I am compiling with clang 9.0.1.

Everything compiles great this way, but fails to link. The problem is that all the template specializations, such as

 template <> constexpr bool standard_signed_integer<signed char> = true;

are multiply defined. Clang only tells me where they are initially defined (exactly one time) but also errors out because they are multiply defined. OK, then:

/usr/bin/ld: src/tickertape/CMakeFiles/tickertape.dir/timescaledb.cpp.o:(.rodata+0x8): multiple definition of `argparse::details::standard_unsigned_integer<unsigned long>'; src/tickertape/CMakeFiles/tickertape.dir/tickertape.cpp.o:(.rodata+0x8): first defined here

I find this confusing because I would have thought that constexpr would be, well, const, and perhaps not instantiated multiple times in a way that could not be resolved at link. I am no constexpr expert though, and clearly something else is happening.

I tried using inline:

inline template <> constexpr bool standard_signed_integer<signed char> = true;

which, to my constexpr-standard-naive mind looks silly, and indeed this had no effect.
I also tried reducing this to a declaration and defining the specialization exactly once (in a '.cpp' file):

template <> constexpr bool standard_signed_integer<signed char>;

which did not compile. Finally, I found that this guy is onto something: https://stackoverflow.com/a/47982008
who suggested to put these template specializations in an "unnamed namespace", which I think means that each object file gets their own copy and these have distinct names at link (avoiding the duplicate symbols). This worked.

So this is what I think:

  1. I don't know enough C++ generally, especially with regard to constexpr.
  2. argparse would ideally support being used in multiple object files linked together without too much hassle. I would actually prefer have to link argparse and lose the header-only benefits than hack around this linkage problem, but I suspect that we can have both features if we just get this constexpr thing right.

So thanks for the great software, but where am I messing this up?

no viable overloaded operator[] for type

Hello, compiler returns an error when trying to build the code. The problem seems related to the inclusion of the header in the cpp file.

#include <argparse.hpp>

Here's the error:

.../argparse/include/argparse.hpp:845:19: error:
no viable overloaded operator[] for type
'const argparse::ArgumentParser'
return (*this)[aArgumentName].get();
~~~~~~~^~~~~~~~~~~~~~

Positional Arguments with Compound Toggle Arguments

I tested this example with the command line:

$ ./main 1 -abc 3.14 2.718 2 --files a.txt b.txt c.txt 3

and it fails to parse the options.

Keeping the positional arguments together as in:

$ ./main 1 2 3 -abc 3.14 2.71 --files a.txt b.txt c.txt

works.
This is where I noticed that negative numbers fail to parse. See: Issue 24

Issues with implicit values

I am currently running into some issues with implicit values (both happening and not happening). Here is my code:

m_bfm.add_argument("-l", "--load")
    .help("load a VMM into the kernel");

m_bfm.add_argument("-u", "--unload")
    .default_value(false)
    .implicit_value(true)
    .help("unload a previously loaded VMM");

m_bfm.add_argument("-x", "--start")
    .default_value(false)
    .implicit_value(true)
    .help("start a previously loaded VMM");

m_bfm.add_argument("-s", "--stop")
    .default_value(false)
    .implicit_value(true)
    .help("stop a previously started VMM");

m_bfm.add_argument("-d", "--dump")
    .default_value(false)
    .implicit_value(true)
    .help("output the contents of the VMM's debug buffer");

m_bfm.add_argument("-m", "--mem")
    .default_value(default_heap_size)
    .action([](const std::string &val) { return std::stoull(val); })
    .help("memory in MB to give the VMM when loading");

m_bfm.parse_args(argc, argv);

I am currently seeing two different issues:

  • The long versions seem to work fine, but the short versions do not. That is, if I use the short versions I get a "No value provided" error. The long versions do not have this issue.
  • The "--mem" option doesn't have an implicit value, just a default value. If I do something like "--mem" it knows that a value is missing, if I do "--load blah --mem" it doesn't know that a value is missing. The correct input should be "--mem 64" or "--load blah --mem 64".

Consider parsing more option value syntax

First we need to define what are short options and long options.
Long options: options with names longer than a single character
Short options: options only one character long
Option name is anything after - or -- (or / for Windows style as a future extension).

  1. Allow short options to have their values concatenated to their names: -g3, -lzlib (if in the future we allow unified option prefix like a single / or -, we may want to disable this parsing mode)
  2. Allow long options to separate their names and values with = (or : for Windows style as a future extension): --foo=a.txt. This does not work for nargs>1.

See also https://docs.python.org/3/library/argparse.html#option-value-syntax

Allow argument alias

I am wondering whether we could add alias for arguments. For example, having --verbose as an argument, we can also allow -v as an alias.

So in the code, maybe we can do:

program.add_argument("--verbose")
  .default_value(false)
  .implicit_value(true)
  .alias("-v");

Any advice?

--help should print help and exit rather than throwing exceptions

From https://www.reddit.com/r/cpp/comments/dzm7lg/argparse_argument_parser_for_modern_c_v20_released/f8b1601?utm_source=share&utm_medium=web2x

Separately, I've noted about exceptions lightly [...] What is unacceptable is using exceptions for control flow. [...]

On a custom compiler, they may have a way to change all exception handling to calls to abort(). Even then, they ask for help, the program just ends. Nothing printed, even if the developer correctly handled it.

Can you create releases?

I'm trying to use this library through hunter package management system. Would you mind creating some releases once in a while to make it easier to track?

thanks!
Marco

wmain support

A boss

https://docs.microsoft.com/en-us/cpp/c-language/using-wmain?view=vs-2019

That all C++ cmdline parsing libraries face.

A strong demand is to construct std::path from command-line arguments, where std::wstring is native string type for std::path on Windows.

In past experience, Boost.Program_options (Sphinx docs) is the only library that allowed me to program wmain in real code. But Boost's choice is to template everything on character type while still supporting some codecvt conversions internally. Using only wvalue may work for some applications, but in general wcommand_line_parser is needed.

Given argparse' type erasure design, we have an opportunity to look at the problem differently.

Infinite recursion on invalid argument

Code:

#include <iostream>

#include <argparse.hpp>

static bool load_xss_libs = true;

int main(int argc, char** argv) {
  argparse::ArgumentParser parser("awcsclient");
  
  parser.add_argument("--bootstrap")
    .help("Do not autoload XS# libraries")
    .default_value(false)
    .implicit_value(true);

  parser.parse_args(argc, argv);

  if (parser["--bootstrap"] == true) {
    load_xss_libs = false;
  }

  std::cout << load_xss_libs << std::endl;
}

A piece of stack trace from GDB:

#2963 0x000055555555a653 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, argc=2, argv=0x5555559640d0) at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:575
#2964 0x0000555555559c10 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, aArguments=std::vector of length 2, capacity 2 = {...})
    at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:507
#2965 0x000055555555a653 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, argc=2, argv=0x555555964050) at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:575
#2966 0x0000555555559c10 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, aArguments=std::vector of length 2, capacity 2 = {...})
    at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:507
#2967 0x000055555555a653 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, argc=2, argv=0x555555963fd0) at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:575
#2968 0x0000555555559c10 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, aArguments=std::vector of length 2, capacity 2 = {...})
    at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:507
#2969 0x000055555555a653 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, argc=2, argv=0x555555963f50) at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:575
#2970 0x0000555555559c10 in argparse::ArgumentParser::parse_args_internal (this=0x7fffffffdb80, aArguments=std::vector of length 2, capacity 2 = {...})
    at /home/handicraftsman/Projects/AirWheel/vendor/argparse/include/argparse.hpp:507

Printing program version

Python's argparse has a special action that prints the program version and exits.

Currently printing the program version with this library can be done, but the presence of required arguments will also cause an error.

Support for printing the program version would be nice to have as it's an expected part of any CLI, akin to the --help option.

Default Value Returns Bad Any Cast

Hello,

Running this with clang on Mac OS, when trying to use the library as such:

argparse::ArgumentParser parser{"program"};
parser.add_argument("-t", "--test")
           .help("Blah")
           .default_value("test");

parser.parse_args(arc, argv);
. . .

parser.get<std::string>("-t"); // Bad any_cast

It seems that the compiler thinks that "test" is of type const char[5].
I tried parser.get<char*>("-t") with no luck as well.
My only workaround was to statically cast within the default value .default_value(static_cast<std::string>("test")).
I realize that this default value arg is getting any casted, but maybe the default value should be templated instead?

Reify values of optional arguments into std::optional

Semantically, an optional argument is "optional" to present. But currently, if default_value isn't set, an optional argument's "not presented" state is not representable. Attempting to access the state (value) will get a logic_error. Representing the "not presented" state is often necessary because the option's type may leave no value for a good default_value. For example, an empty string may be an valid argument to an option.

Python argparse has no such problem because any option may be None. Universally Nullable is a problem, but allowing None for specific types is adopted by many languages. In C++ it's std::optional<T>.

I suggest to add a program.present<T>("--option") API in addition to the existing program.get<T>("--option") API. The new API returns std::optional<T> by reifying T's possible values and the "not presented" state. Its usage looks like the following:

if (auto v = program.present<int>("-n")) {
  do_something_with(*v);
}

The name "present" is taken from Java's Optional.ifPresent. Bike-shedding is mildly welcome.

Allow many positional arguments

It would be nice to be able to have a feature whereby you could gather many positional arguments at the end of a command for something like a compiler.

For example, imagine a use case like this:

compiler file1 file2 file3

Currently I don't believe it is possible to do this.

I imagine using it would look something like:

std::vector<std::string> = program.get_trailing();

which would just return all positional arguments at the end regardless of how many.

Negative integers and floats.

Is it possible to input negative integers or floats? From what I can see -1 or -1.2e3 are being treated as optional arguments. Maybe std::is_integral and std::is_floating_point could be used to first test for integrals or floating points before testing for an optional argument.

Failing tests

These tests fail:

#include <test_parse_args.hpp>
#include <test_positional_arguments.hpp>
#include <test_compound_arguments.hpp>
#include <test_container_arguments.hpp>

Here are the errors and warnings. I'm using Visual Studio 2019 and I get similar errors in Ubuntu 19.04, GCC 8.3

argparse.hpp(444,1): warning C4456:  declaration of 'tCurrentArgument' hides previous local declaration
argparse.hpp(423,21): message :  see declaration of 'tCurrentArgument'
argparse.hpp(445,1): warning C4456:  declaration of 'tIterator' hides previous local declaration
argparse.hpp(434,23): message :  see declaration of 'tIterator'
argparse.hpp(132,23): warning C4018:  '<=': signed/unsigned mismatch
argparse.hpp(432): message :  see reference to function template instantiation 'Iterator argparse::Argument::consume<_InIt>(Iterator,Iterator,std::string)' being compiled
argparse.hpp(432): message :         with
argparse.hpp(432): message :         [
argparse.hpp(432): message :             Iterator=std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::string>>>,
argparse.hpp(432): message :             _InIt=std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::string>>>
argparse.hpp(432): message :         ]
argparse.hpp(255,14): error C2672:  'std::transform': no matching overloaded function found
argparse.hpp(361): message :  see reference to function template instantiation 'std::vector<int,std::allocator<int>> argparse::Argument::get<T>(void) const' being compiled
argparse.hpp(361): message :         with
argparse.hpp(361): message :         [
argparse.hpp(361): message :             T=std::vector<int,std::allocator<int>>
argparse.hpp(361): message :         ]
argparse\test\test_parse_args.hpp(77): message :  see reference to function template instantiation 'T argparse::ArgumentParser::get<std::vector<int,std::allocator<int>>>(const std::string &)' being compiled
argparse\test\test_parse_args.hpp(77): message :         with
argparse\test\test_parse_args.hpp(77): message :         [
argparse\test\test_parse_args.hpp(77): message :             T=std::vector<int,std::allocator<int>>
argparse\test\test_parse_args.hpp(77): message :         ]
argparse.hpp(256,1): error C2783:  '_OutIt std::transform(const _InIt,const _InIt,_OutIt,_Fn)': could not deduce template argument for '_Fn'
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.21.27702\include\algorithm(1314): message :  see declaration of 'std::transform'
argparse.hpp(261,14): error C2672:  'std::transform': no matching overloaded function found
argparse.hpp(262,1): error C2783:  '_OutIt std::transform(const _InIt,const _InIt,_OutIt,_Fn)': could not deduce template argument for '_Fn'
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.21.27702\include\algorithm(1314): message :  see declaration of 'std::transform'

Arguments which start with a dash could be positional arguments

Consider a common pattern: use a dash - to represent stdin or stdout, however, argparse simply considers all arguments which start with a dash as optional arguments. For more information, see this line.

Therefore, I think we can restrict optional arguments to those which

  1. start with a dash.
  2. and have a character following that dash.

For instance, - is a positional argument while -m is an optional argument.

Mark getters const

void run_app(const argparse::ArgumentParser &program)
{
   // it's not possible to use get() or present() from here because of const
}

Question: is -- supported as an argument delimiter?

From https://www.reddit.com/r/cpp/comments/dzm7lg/argparse_argument_parser_for_modern_c_v20_released/f8c7erq?utm_source=share&utm_medium=web2x

It was not clear from the documentation, so I'd like to know if -- is supported as an argument delimiter?

-- is a convention for separating the list of arguments to the program from the list of arguments to be forwarded, an example:

./invoke sed --trace X=1 Y=2 Z=3 -- 's/foo/bar/g' fiddling.txt

Where invoke would:

  • Recognize sed as an optional argument.
  • Recognize --trace as an optional argument.
  • Recognize X=1, Y=2, and Z=3 as remaining arguments.
  • Recognize s/foo/bar/g and fiddling.txt as argument to pass to sed.

Consistent tags format

Hi,

Could you please be more consistent in tag naming?
Either vMajor.Minor or vMajor.Minor.Patch always?
All tags were v2.0 and now we have v2.0.1.

This brokes some scripts and other automation.

Simplify help output

Hey there,
I just tried to print help if no parameter is given but finding the answer was a bit confusing. I would prefer to find a function call in Quick Start that automatically sets this up for me. Shouldn't be a big deal I think :)

A way to require "optional" arguments to present

Currently it's very inconvenient to make an option in the form of -o output required. It is not checked if it does not present, if you get("-o") you get a logic error, which requires lots of post processing. There should be a way to declare that this argument must present. If not presents, exception; if only -o presents, status quo (a different exception).

Also, the current situation makes -o equivalent to not presenting if there is a default_value. I think it's not a very good interface. I tried to simulate a "all or nothing" default value with .implicit_value(x).nargs(1), but no luck.

print_help should either print or return help string

But not both.

We may consider enabling

std::cerr << program;

So that user can use stringstream if they want to. To make it easier, we can even add a function help that returns a stringstream, so that cout << program is calling cout << program.help().rdbuf() and program.help().str() is the previous print_help()'s return value.

Consider deprecating print_help.

Compiler diagnostic with clang from the LLVM 9 download package

clang generates the following diagnostic fault when compiling using this library:

C:\Src\NetworkedGraphics\out\packages\argparse-src\package\include\argparse\argparse.hpp(196,28): error G3F63BFAE: constexpr variable 'generic_strtod' must be initialized by a constant expression [clang-diagnostic-error]
template <> constexpr auto generic_strtod = strtof;
^
C:\Src\NetworkedGraphics\out\packages\argparse-src\package\include\argparse\argparse.hpp(197,28): error G3F63BFAE: constexpr variable 'generic_strtod' must be initialized by a constant expression [clang-diagnostic-error]
template <> constexpr auto generic_strtod = strtod;
^
C:\Src\NetworkedGraphics\out\packages\argparse-src\package\include\argparse\argparse.hpp(198,28): error G3F63BFAE: constexpr variable 'generic_strtod' must be initialized by a constant expression [clang-diagnostic-error]
template <> constexpr auto generic_strtod = strtold;
^

The quick fix appears to be to remove the indicated constexpr qualifier.

This was picked up running an msvc compile (vs 16.6.0) followed by a clang-tidy run (llvm 9.0). The diagnostic is produced by clang within clang-tidy. clang-tidy is being run with msvc build commands from the configured cmake build database (standard usage).

Add an example how to use it

I created an example by copy & pasting things from the README:

#include "include/argparse.hpp"

int main(int argc, char *argv[]) {
	argparse::ArgumentParser program("test");

	program.add_argument("square")
	  .help("display the square of a given number")
	  .action([](const std::string& value) { return std::stoi(value); });

	program.add_argument("--verbose")
	  .default_value(false)
	  .implicit_value(true);

	program.parse_args(argc, argv);

	int input = program.get<int>("square");

	if (program["--verbose"] == true) {
	  std::cout << "The square of " << input << " is " << (input * input) << std::endl;
	}
	else {
	  std::cout << (input * input) << std::endl;
	}

	return 0;
}

And it seems to fail:

$ ./a.out 4
16
$ ./a.out --help
terminate called after throwing an instance of 'std::runtime_error'
  what():  help called
Aborted (core dumped)

subcommand support

I think Git-like subcommand can be done by inserting a positional argument and dispatch on subparsers. But this is something argparse should provide directly, with add_subparsers, for example.

Header install path

The readme states that you just have to #include <argparse.hpp> to use the library. However, CMake installs the header into /usr/include/argparse and the target include directory list only specifies /usr/include, so you actually need to use #include <argparse/argparse.hpp>.

Is this a documentation problem in the readme, or should the CMakeLists.txt be changed from

target_include_directories(argparse INTERFACE
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)

to

target_include_directories(argparse INTERFACE
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/argparse>
  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)

?

Output default value with help text

Hi @p-ranav,

I think it would be nice, if you could print default values specified by "default_value" method when outputting the help text:

    program
        .add_argument("n")
        .help("Number of iterations to be performed")
        .default_value("10");

->

...
Positional arguments:
n               Number of messages to be sent (default: 10)

this would be really useful

Best regards
Philipp

Merged too fast.

I made a mistake.
$&lt;BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/include)

Should be CMAKE_CURRENT_LIST_DIR not SOURCE_DIR. Literally just fixed it as you merged lol.

argparse usage question: can the Action class take different methods in it

Hi,
As of Python 3.8.2 argparse can have a custom action class defined and called to execute the default call method where we customize the type of actions that need to be done. Can we also include custom methods in the class ? so that I can call my add_argument as

parser.add_argument('-r', action=Action1.mymethod)

Regards
Ranga

action that returns nothing

Currently this code isn't allowed in argparse:

program.add_argument(...)
    .action([](std::string const& filename)
    {
        load_cfg(filename);
    });

Because argparse internal expects action to return some value, but this one returns void.
My workaround for that is to define using unit = std::tuple<>; and returns unit(), but this isn't nice to C++'s taste.
But it's possible to bake this workaround into argparse (even though there is no any<void>). Let's say we define a type similar to the unit above, call is none_internal. In action, detects the the input's return type:

  template<class F>
  Argument &action(F&& f) {
    if constexpr (std::is_void_v<std::invoke_result_t<F, std::string>>)
      mAction = [f](std::string const& arg) { f(arg); return none_internal(); };
    else
      mAction = std::move(aAction);
    return *this;
  }

Simplify parsing numeric arguments with predefined actions

Currently, we have no predefined actions at all (other than identity), and this is an obvious use case to improve. Asking users to write lambdas to parse raises the bar for use, and difficult to upgrade to a <charconv> future. For the most common inputs, users should be able to express what do they expect rather than how do they handle.

Python's argparse provides type=int and type=float, which do not take hexadecimal inputs and are lack of support for types of different ranges (int, long, etc.) We need to able to express both for C++ applications.

I propose to add a .scan method to the fluent interface, inspired by scanf. I took the name from scnlib. Usage looks like the following:

program.add_argument("-x")
       .scan<'d', int>();
program.add_argument("scale")
       .scan<'g', double>();

The non-type template argument specifies how the input "looks like" (where I call it shape), and the type template argument specifies what the return value of the predefined action is. The acceptable types are:

  • floating point: float, double, long double
  • integral (+ make_unsigned): signed char, short, int, long, long long

and the acceptable shapes are:

  • floating point:
    • 'a': hexadecimal floating-point
    • 'f': fixed form only
    • 'e': scientific form only
    • 'g': general form (either fixed or scientific)
  • integral:
    • 'd': decimal
    • 'u': decimal, requires an unsigned type
    • 'o': octal, requires an unsigned type
    • 'x': hexadecimal, requires an unsigned type
    • 'i': anything that from_chars's grammar accepts in base == 0

The exact grammar should follow from_chars's requirement. But our first implementation may still use strtol and strtod. When encounters errors, throw exceptions similar to what stoi and stod do.

FAQ

  1. Can I omit the type? No.
  2. Can I omit the shape? Not for these. from_chars and strto? default to parse anything, but Python's type=int and type=float only parse decimal. I'm not sure whether we can agree on a default. But when extending this for other types in the future, we may.
  3. How to extend this to other common types? Let's keep an eye on how the Text parsing proposal evolves.
  4. Why using non-type template parameters? So that we can select predefined actions at compile-time and go straight assigning one to mAction in each call to scan.
  5. Can auto non-type template parameter help? Sadly no. .scan<int('d')>() is okay but .scan<(unsigned long)'x'> is terrible.
  6. Can we support uppercase shapes like 'X'? What do you expect them to do? I guess letting them behave as same as the lowercase counterparts is the only reasonable answer. If we agree on that, I'm okay with it.

Versioning

Really neat library. Will you add tags for releases in the future?

How to behave like ls?

The GNU ls utility allows its flags to be provided anywhere in the commandline, e.g.:

ls -l file1 file2 file3
ls file1 file2 file3 -l

If I understand correctly, .remaining(); does not allow that. Is there a workaround for this or another way to do it?

A simple rule for determining negative numeric values

Currently, specification-wise, the rule isn't clear. Implementation-wise, there is no need to actually interpret the values. I assume that you only want to parse (possibility negative) decimal numbers, without considering locale and thousand separators. In that case, see if the following rule works:

Let x be a variable of type T where std::is_arithmetic_v<T> is true. A pattern S is positional if std::from_chars(&*begin(S), &*end(S), x) points the ptr member of the result to &*end(S).

Note that this rule deems -inf, -infinity, and any form of -nan to be positional. I think it's desired and it's the status quo in code.

If we are agreed on a rule, we can derive a syntax and only parse that syntax.

See also https://en.cppreference.com/w/cpp/utility/from_chars the syntax is not fully documented here, you will have to lookup strtol and strtod as well.

Question: parent parser and value semantics

The mParentParsers member is currently unused. add_parents's semantics is only merging options. But consider the following: if there is only one parent parser, copy construct from an existing parser has the same effect. We only need a way to change the new instance's mProgramName.

Does ArgumentParser has value semantics? ArgumentParser is copy cunstructible, but a copy of ArgumentParser is not a distinct copy because shared_ptr are used without special handling. Parsing in the new parser will affect the old parser. But parent parser also has the "problem," unless this is by design.

If this is not by design, then we'd better to support value semantics in full in both copy-constructor and add_parents.

Conan package

Hello.
At first thank you for the library - it's really easy-to-use and feature-enough complete library.

But I have a suggestion - can you create Conan package for the library? Even if library is single header-only using system-independent package manager has a lof of advantages.

One note - wiil be fine if you create release of the library. With it will be easier to create a package.

If you have any questions - just ask here.

core dump on optional arg

if I add an optional arg with a single "-" something like "-o" but don't define how to handle it, I get a seg fault. If I handle it, it's fine. The issue is if the user ads a single - argument that is not supported it will cause a seg fault and there is nothing I can do on my end

error format issue

should this:
"error: -l: expected 1argument. 0 provided"

be:
"error: -l: expected 1 argument. 0 provided"

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.