Coder Social home page Coder Social logo

adishavit / argh Goto Github PK

View Code? Open in Web Editor NEW
1.3K 19.0 93.0 284 KB

Argh! A minimalist argument handler.

License: BSD 3-Clause "New" or "Revised" License

CMake 0.77% C++ 98.81% Python 0.26% Starlark 0.16%
command-line cpp11 cli-args getopt getopts header-only single-file argument-parser cli command-line-parser

argh's Introduction

logo

Frustration-free command line processing

Language Standard License Try it online Build Status

So many different command line processing libraries out there and none of them just work!
Some bring their whole extended family of related and unrelated external dependencies (yes, I'm looking at you Boost).
Some require quirky syntax and/or very verbose setups that sacrifice simplicity for the generation of a cute usage message and validation. Many come to dominate your main() file and yet others do not build on multiple plaforms - for some even their own tests and trivial usage cause crashes on some systems. Argh!

If you're writing a highly-sophisticated command line tool, then Boost.Program_options and its kind might give you many advanced options. However, if you need to get up and running quickly, effectively and with minimal fuss, give the single header-file argh a try.

TL;DR

It doesn't get much simpler than this:

#include <iostream>
#include "argh.h"

int main(int, char* argv[])
{
    argh::parser cmdl(argv);

    if (cmdl[{ "-v", "--verbose" }])
        std::cout << "Verbose, I am.\n";

    return EXIT_SUCCESS;
}

TL;DR Videos

Philosophy

Contrary to many alternatives, argh takes a minimalist laissez-faire approach, very suitable for fuss-less prototyping with the following rules:

The API is:

  • Minimalistic but expressive:
    • No getters nor binders
    • Just the [] and () operators
    • Easy iteration (range-for too)
  • You don't pay for what you don't use
  • Conversion to typed variables happens (via std::istream >>) on the user side after the parsing phase
  • No exceptions thrown for failures
  • Liberal BSD license
  • Single header file
  • No non-std dependencies

argh does not care about:

  • How many '-' preceded your option
  • Which flags and options you support - that is your responsibility
  • Syntax validation: any command line is a valid (not necessarily unique) combination of positional parameters, flags and options
  • Automatically producing a usage message

Tutorial

Create parser:

auto cmdl = argh::parser(argc, argv);

In fact, you can even drop argc. This will also work:

argh::parser cmdl(argv);

Positional argument access by (integer) index with [<size_t>]:

cout << "Exe name is: " << cmdl[0] << '\n';
                               ^^^
assert(cmdl[10000].empty()); // out-of-bound index returns empty string
            ^^^^^

Boolean flag argument access by (string) name with [<std::string>]:

cout << "Verbose mode is " << ( cmdl["verbose"] ? "ON" : "OFF" ) << '\n';
                                    ^^^^^^^^^^^

Any dashes are trimmed so are not required.

Your flag can have several alternatives, just list them with [{ "<name-1>", "<name-2>", ... }]:

cout << "Verbose mode is " << ( cmdl[{ "-v", "--verbose" }] ? "ON" : "OFF" ) << '\n';
                                    ^^^^^^^^^^^^^^^^^^^^^^^

Beyond bool and std::string access with [], as shown above, we can also access the argument values as an std::istream. This is very useful for type conversions.

std::istream positional argument access by (integer) index with (<size_t>):

std::string my_app_name;
cmdl(0) >> my_app_name; // streaming into a string
    ^^^
cout << "Exe name is: " << my_app_name << '\n';

We can also check if a particular positional arg was given or not (this is like using [<std::string>] above):

if (!cmdl(10))
  cerr << "Must provide at least 10 arguments!" << '\n';
else if (cmdl(11))
  cout << "11th argument  is: " << cmdl[11] << '\n';

But we can also set default values for positional arguments. These are passed as the second argument:

float scale_factor;
cmdl(2, 1.0f) >> scale_factor;
     ^^^^^^^

If the position argument was not given or the streaming conversion failed, the default value will be used.

Similarly, parameters can be accessed by name(s) (i.e. by string or list of string literals) with:
(<std::string> [, <default value>]) or ({ "<name-1>", "<name-2>", ... } [, <default value>]):

float scale_factor;
cmdl("scale", 1.0f) >> scale_factor; // Use 1.0f as default value
     ^^^^^^^^^^^^^

float threshold;
if (!(cmdl({ "-t", "--threshold"}) >> threshold)) // Check for missing param and/or bad (inconvertible) param value
  cerr << "Must provide a valid threshold value! Got '" << cmdl("threshold").str() << "'" << endl;
else                                                                        ^^^^^^
  cout << "Threshold set to: " << threshold << '\n';

As shown above, use std::istream::str() to get the param value as a std:string or just stream the value into a variable of a suitable type. Standard stream state indicates failure, including when the argument was not given.
When using multiple names, the first value found will be returned.

Positional arguments can be iterated upon directly using range-for:

cout << "Positional args:\n";
for (auto& pos_arg : cmdl)
  cout << '\t' << pos_arg << '\n';

Similarly, cmdl.size() will return the count of positional arguments.

Positional arguments, flags and parameters are accessible as "ranges":

cout << "Positional args:\n";
for (auto& pos_arg : cmdl.pos_args())
  cout << '\t' << pos_arg << '\n';

cout << "\nFlags:\n";
for (auto& flag : cmdl.flags())
  cout << '\t' << flag << '\n';

cout << "\nParameters:\n";
for (auto& param : cmdl.params())
  cout << '\t' << param.first << " : " << param.second << '\n';

If a parameter appears several times in the command line, all its duplicates may be accessed, in order, like so:

cout << "\nValues for all `--input` parameters:\n";
for (auto& param : cmdl.params("input"))  // iterate on all params called "input"
  cout << '\t' << param.first << " : " << param.second << '\n';

By default, options are assumed to be boolean flags. When this is not what you want, there are several ways to specify when an option is a parameter with an associated value.

  1. Specify PREFER_PARAM_FOR_UNREG_OPTION mode to interpret any <option> <non-option> as <parameter-name> <parameter-value>:

    using namespace argh;
    auto cmdl = parser(argc, argv, parser::PREFER_PARAM_FOR_UNREG_OPTION);
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    cout << cmdl("--threshold").str() << '\n';
  2. Pre-register an expected parameter name with add_param() (before calling parse()):

    argh::parser cmdl;
    cmdl.add_param("threshold"); // pre-register "threshold" as a param: name + value
    cmdl.parse(argc, argv);
    cout << cmdl("threshold").str() << '\n';

    You may also batch pre-register multiple options as parameters with add_params({ ... }):

    argh::parser cmdl;
    cmdl.add_params({ "-t", "--threshold", "-s", "--scale" }); // batch pre-register multiple params: name + value
    cmdl.parse(argc, argv);
    cout << cmdl("threshold").str() << '\n';

    Unregistered options will default to boolean flags.

  3. Since pre-registration has to be done before parsing, we might as well just use the ctor:

    argh::parser cmdl({ "-t", "--threshold", "-s", "--scale" }); // batch pre-register multiple params: name + value
    cmdl.parse(argc, argv);
    cout << cmdl("threshold").str() << '\n';
  4. Use a = with no spaces around it within the option when calling the app:

    >> my_app --threshold=42
    42

    This will automatically be interpreted as a named parameter-value pair.

Tips

  • By default, arguments of the form --<name>=<value> (with no spaces, one or more dashes), e.g. --answer=42, will be parsed as <parameter-name> <parameter-value>. To disable this specify the NO_SPLIT_ON_EQUALSIGN mode.
  • Specifying the SINGLE_DASH_IS_MULTIFLAG mode, a.k.a Compound Arguments, will split a single-hyphen argument into multiple single-character flags (as is common in various POSIX tools).
  • When using SINGLE_DASH_IS_MULTIFLAG, you can still pre-register the last character as a param with the value, such that if we pre-register f as a param, >> myapp -xvf 42 will be parsed with two boolean flags x and v and a one param f=42.
  • When parsing parameter values as strings that may contain spaces (e.g. --config="C:\Folder\With Space\Config.ini"), prefer using .str() instead of >> to avoid the default automatic whitespace input stream tokenization:
    cout << cmdl({ "-c", "--config" }).str().

Terminology

Any command line is composed of 2 types of Args:

  1. Positional Args:
    Free standing, in-order, values
    e.g. config.json
  2. Options:
    Args beginning with - (and that are not negative numbers).
    We identify 2 kinds of Options:
    1. Flags:
      Boolean options => (appear ? true : false)
      e.g. -v, --verbose
    2. Parameters:
      A named value followed by a non-option value
      e.g. --gamma 2.2

Thus, any command line can always be broken into some combination of (1) positional args (2) flags and (3) parameters.

API Summary

Parsing

Parse the command line using either

  • The parse() method: parser::parse([argc,] argv [, mode]); or
  • The shorter form using the ctor directly: argh::parser([argc,] argv [, mode]);
  • The shortest form does not even require argc, so in default mode just use:
    parser(argv);

Special Parsing Modes

Extra flexibility can be added by specifying parsing modes:

  • NO_SPLIT_ON_EQUALSIGN: By default, an option of the form --pi=22/7 will be parsed as a parameter pi with an associated value "22/7". By setting this mode, it will be not be broken at the =.
  • PREFER_FLAG_FOR_UNREG_OPTION: Split <option> <non-option> into <flag> and <pos_arg>. e.g. myapp -v config.json will have v as a lit flag and config.json as a positional arg. This is the default mode.
  • PREFER_PARAM_FOR_UNREG_OPTION: Interpret <option> <non-option> as <parameter-name> <parameter-value>. e.g. myapp --gamma 2.2 will have gamma as a parameter with the value "2.2".
  • SINGLE_DASH_IS_MULTIFLAG: Splits an option with a single dash into separate boolean flags, one for each letter (a.k.a Compound Arguments). e.g. in this mode, -xvf will be parsed as 3 separate flags: x, v, f.

Argument Access

  • Use bracket operators to access flags and positional args:

    • Use operator[index] to access position arg strings by index:
      • e.g. assert(cmdl[0] == argv[0]), the app name.
    • Use operator[string] to access boolean flags by name:
      • e.g. if (cmdl["v"]) make_verbose();
    • Use operator[{...}] to access boolean flags by multiple names:
      • e.g. if (cmdl[{ "v", "verbose" }]) make_verbose();
  • Use the parenthesis operators to get an std::istream to stream values from parameters and positional args:

    • Use operator(index) to access position arg istream by index:
      • e.g. cmdl(0) >> my_app_name.
    • Use operator(string) to access parameter values by name:
      • e.g. cmdl("scale") >> scale_factor;
    • Use operator({...}) to access parameter values by multiple names:
      • e.g. cmdl({ "-s", "--scale" }) >> scale_factor;
    • Use operator(index, <default>) and operator(string/{list}, <default>) to stream a default value if the arg did not appear on the command line:
      • e.g. cmdl("scale", 1.0f) >> scale_factor;

The streaming happens at the user's side, so conversion failure can be checked there: e.g

if (!(cmdl("scale") >> scale_factor))
  cerr << "Must provide valid scale factor!" << '\n';

Use the .str() method to get the parameter value as a string: e.g. cmdl("name").str();

More Methods

  • Use parser::add_param(), parser::add_params() or the parser({...}) constructor to optionally pre-register a parameter name when in PREFER_FLAG_FOR_UNREG_OPTION mode.
  • Use parser, parser::pos_args(), parser::flags() and parser::params() to access and iterate over the Arg containers directly.

Finding Argh!

  • copy argh.h somewhere into your projects directories
  • or include the repository as a submodule
  • or use CMake!

Finding Argh! - CMake

The provided CMakeLists.txt generates targets for tests, a demo application and an install target to install argh system-wide and make it known to CMake. You can control generation of test and example targets using the options BUILD_TESTS and BUILD_EXAMPLES. Only argh alongside its license and readme will be installed - not tests and demo!

Add argh to your CMake-project by using

find_package(argh)

The package exports argh INTERFACE library target and argh_INCLUDE_DIR variable. Make argh.h known to your compiler by using one of the following methods; both will make the location of argh.h known to the compiler, not link in a precompiled library - even when using target_link_libraries().

target_include_directories(${MY_TARGET_NAME} PRIVATE "${argh_INCLUDE_DIR}")
#OR
target_link_libraries(${MY_TARGET_NAME} argh)

Additional Build Systems

Buck

Buck support:

Run the example:

buck run :example

Run the tests:

buck run :tests
buck run test_package

If you take argh as a submodule, then the visible target is //:argh.

Colophon

I ❤ your feedback.
If you found Argh! useful - do Tweet about it to let me know.
If you found it lacking, please post an issue.

argh's People

Contributors

0x1f9f1 avatar a4z avatar abigagli avatar adishavit avatar barracuda156 avatar billyoneal avatar bitmeal avatar chaoscabbage avatar danimtb avatar ddovod avatar grishakirilin avatar jhh avatar jpyllman avatar juev avatar jyaif avatar manu343726 avatar marcomagdy avatar memsharded avatar nikoladucak avatar njlr avatar sehe avatar superfola avatar symbitic avatar terencejefferies avatar ufoq avatar woodruffw 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

argh's Issues

Add Functionality To Find Unused Commandline Options

Many thanks for this library - saved a lot of time!

One feature which I think might be useful is a way to find unused parameters or flags.
As the library returns argument details, that part of the arg string could be marked as used, and then the set of unused things would be available to query.
The utility of this would be in commandline error handling - if at the end of processing you can see unused parameters/flags, that might be evidence of spelling mistakes etc, which can be reported to potentially puzzled users.

doctest.h:1398:92: error: use of deleted function

Hi,
the Debian packaged version of argh received a bug report which says:

    In file included from /<<PKGBUILDDIR>>/argh_tests.cpp:4:
    /usr/include/doctest/doctest.h: In instantiation of ‘doctest::detail::Expression_lhs<const L> doctest::detail::ExpressionDecomposer::operator<<(const L&&) [with L = std::__cxx11::  basic_istringstream<char>]’:
    /<<PKGBUILDDIR>>/argh_tests.cpp:62:5:   required from here
    /usr/include/doctest/doctest.h:1398:92: error: use of deleted function ‘doctest::detail::Expression_lhs<const std::__cxx11::basic_istringstream<char> >::Expression_lhs(doctest::    detail::Expression_lhs<const std::__cxx11::basic_istringstream<char> >&&)’
     1398 |             return Expression_lhs<const L>(doctest::detail::forward<const L>(operand), m_at);
          |                                                                                            ^
    In file included from /<<PKGBUILDDIR>>/argh_tests.cpp:4:
    /usr/include/doctest/doctest.h:1317:12: note: ‘doctest::detail::Expression_lhs<const std::__cxx11::basic_istringstream<char> >::Expression_lhs(doctest::detail::Expression_lhs<const std::__cxx11::basic_istringstream<char> >&&)’ is implicitly deleted because the default definition would be ill-formed:
     1317 |     struct Expression_lhs
          |            ^~~~~~~~~~~~~~
    /usr/include/doctest/doctest.h:1317:12: error: use of deleted function ‘std::__cxx11::basic_istringstream<_CharT, _Traits, _Alloc>::basic_istringstream(const std::__cxx11::         basic_istringstream<_CharT, _Traits, _Alloc>&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’
    In file included from /<<PKGBUILDDIR>>/argh.h:4,
                     from /<<PKGBUILDDIR>>/argh_tests.cpp:1:
    /usr/include/c++/11/sstream:619:7: note: declared here
      619 |       basic_istringstream(const basic_istringstream&) = delete;
          |       ^~~~~~~~~~~~~~~~~~~
    [ 75%] Linking CXX executable argh_example

Any idea how to fix this?
Kind regards, Andreas.

Multiple Parameters for 1 Argument

Hello,
I'm wondering if this great library has the functionality of entering multiple parameters for 1 argument. An example would be --arrayOf4 1 2 3 4. Then, I could get these 4 numbers and put them into an array. Is this already a feature in this library? If so, could I get a pointer on achieving that goal?
Thanks

how to set boolean arguments as False when default is True?

I have a function with a boolean argument whose default is set at True.
I set up argh like this:

def function(test=True):
    return 

import argh
parser = argh.ArghParser()
parser.add_commands([function])

So on the command line, I would like to disable the boolean argument (test)
How do I do this?
If I run

python script.py function 

The default value (True) is taken.

If I run

python script.py function --test False

I get this error: error: unrecognized arguments: --test False.

I tried to use prefixes (--no- and --disable-).

python script.py function --disable-test

That also did not work.

Uncompileable with clang 14

In file included from ../argh_tests.cpp:4:
../doctest.h:4403:33: error: variable length array declaration not allowed at file scope
        static char             altStackMem[SIGSTKSZ];
                                ^           ~~~~~~~~
In file included from ../argh_tests.cpp:4:
../doctest.h:4453:36: error: variable length array declaration not allowed at file scope
    char    FatalConditionHandler::altStackMem[SIGSTKSZ] = {};
                                   ^           ~~~~~~~~

`argh==0.31.0` breaks (my) `argh.dispatch_command` call

Sorry not to give further details but I had the choice between sending a quick issue or not mentioning it at all.

My script, that uses argh, stopped working when I updated to argh==0.31.0 from argh==0.26.2.

The error

    argh.dispatch_command(populate_pkg_dir)
  File "~/.pyenv/versions/p10/lib/python3.10/site-packages/argh/dispatching.py", line 470, in dispatch_command
    set_default_command(parser, function, name_mapping_policy=name_mapping_policy)
  File "~/.pyenv/versions/p10/lib/python3.10/site-packages/argh/assembling.py", line 420, in set_default_command
    inferred_args: List[ParserAddArgumentSpec] = list(
  File "~/.pyenv/versions/p10/lib/python3.10/site-packages/argh/assembling.py", line 183, in infer_argspecs_from_function
    TypingHintArgSpecGuesser.typing_hint_to_arg_spec_params(
  File "~/.pyenv/versions/p10/lib/python3.10/site-packages/argh/assembling.py", line 778, in typing_hint_to_arg_spec_params
    item_type = cls._extract_item_type_from_list_type(first_subtype)
  File "~.pyenv/versions/p10/lib/python3.10/site-packages/argh/assembling.py", line 799, in _extract_item_type_from_list_type
    if args[0] in cls.BASIC_TYPES:
IndexError: tuple index out of range

My guess is that the len(args) == 0 case is not handled properly?

Without looking further, I'd suggest:

 if args and (args[0] in cls.BASIC_TYPES):

instead?

If I have time, I'll look at it further this week and send a PR.

Consider adding named accessor methods

There has been a request to add named accessor methods in addition to operator[] and operator().
See Twitter thread by @azadkuh here.
Though simple to do, there are several subtle aspects that need to be addressed.
I will use this issue to contemplate, document and discuss them.

I have an (as yet non-public) experimental branch with this, but I'm not yet sure about this.
(cc: @arnemertz)

How parse compound params

Hi there,

I would like to know how I can capture a complex parameter and parse it accordingly.

For example, I have a complex type vec3f composed of 3 floats named x, y, z.

I want to define a parameter to be parsed where on the command-line I can specify —position=1.00,-3.14,3 and the result is that these in turn are parsed to fill vec3f( 1.00, -3.14, 3) inside my code.

Any comment would be welcome.

API: Consider not including argv[0] in pos_args

Hey there! Thanks a ton for creating and maintaining argh, it's a pleasure to use.

I'm currently using it to rewrite one of my company's CLI tools, and I noticed that both the default iterator and pos_args() both include argv[0] (i.e., typically the executable path) in the positional argument iterator.

IMO, this should not be included, at least for pos_args():

  • As far as I know, it's not common to refer to argv[0] as a positional argument. The main context where I'm aware of argv[0] being treated as a normal positional is shell programming, but special meta-arguments like $* and $@ don't include it.

  • Including argv[0] in pos_args() makes at least one intuitive use of argh more difficult than it needs to be. Consider the following use for a program whose CLI looks like whatever [flags] <file1 [file2 ...]>:

    {
      argh::parser cmdl(argc, argv);
      
      // ... flag parsing ...
      
      // sanity check: exit unless we have at least one positional
      if (cmdl.size() < 2) { ... }
      
      for (auto &pos : cmdl) {
        // BOOM: first pos here is argv[0], which we probably didn't mean to process
        process(pos);
      }
    }

    The above would ideally work, but doesn't with the current default iterator or pos_args() iterator.

As a solution, I propose the following changes:

  • Retain the current size() and default iterator APIs without changes
  • Add a pos_size() API that should always be just the positional count without argv[0], and modify the pos_args() iterator to not emit argv[0]

Let me know what you think! I'm also happy to take a stab at implementing these changes.

Do not double process multi-flags

If PREFER_*_UNREG_OPTION is given, multi-flag sequences get doubly
interpreted as unknown parameters/flags.

In the case of a parameter this then swallows a (positional) argument. Oops.

Using argh.argh(args=... , default=...) causes double nesting of passed arguments

Given the code

import argh

@argh.arg("paths", nargs="*", default=["x", "z"])
def fun(paths:list[str]):
    print(paths)

if __name__ == "__main__":
    argh.dispatch_command(fun)
> ./app.py
["x", "z"]    # as expected - the value of 'default'

> ./app.py a b  
[["a"], ["b"]]   # Bug? I would expect ["a", "b"]

If I use argparse, it works fine

parser = argparse.ArgumentParser()
parser.add_argument("paths", nargs="*", default=["x", "y"])
print(parser.parse_args([]))
print(parser.parse_args(["a", "b"]))
> Namespace(paths=['x', 'y'])
> Namespace(paths=['a', 'b'])

If I don't specify the 'default' arg things work fine:

@argh.arg("paths", nargs="*")
def with_argh(paths:list[str]):
    print(paths)
>  ./app.py a b  
["a", "b"]

Ensure a default mode for unregistered options

Default prefer flags for unregistered options

This seemed to be the intent of the actual default mode, but if the user
supplied a specific mode (say, SINGLE_DASH_IS_MULTIFLAG) the net result
was that unregistered options were being ignored without a trace.

Support literal `char` in [] and ()

Allow using literal chars as lookup strings:
cmdl['v'] should call the string lookup not the by index lookup (the char is automatically cast to an integer.

Set Up CI

We've had various reports of backward compatibility issues with certain platforms.
It would be great if someone can help set up a CI service for multiple platforms to continuously check the project.

Add const correctness

Add consts to appropriate methods and operations.
Not so important for most use cases, but still.

Default string value doesn't support spaces

#include <iostream>
#include "argh.h"

int main(int argc, char *argv[]) {
  argh::parser cmdl(argc, argv);

  std::string input = "Some default string value with spaces";
  std::string output;
  cmdl({"-o", "--output"}, input) >> output;

  std::cout << input << std::endl;
  std::cout << output<< std::endl;
  return 0;
}

Run it without any arguments.
Expected: output = "Some default string value with spaces"
Actual: output = "Some"

getter for registeredParams_?

After calling add_param(s) I'd like to be able to read them back out. Can we add a registered params getter? For example:

std::set<std::string> const& registered_params() const { return registeredParams_; }

This would allow us to write our own "--help" flag handling:

argh::parser cmdl(argc, argv, argh::parser::PREFER_PARAM_FOR_UNREG_OPTION);

cmdl.add_param("a");
cmdl.add_param("b");
cmdl.add_param("c");
cmdl.add_param("help");

if (cmdl.params().empty() || cmdl["help"]) {
    cout << "Help:\n";
    for (auto &param : cmdl.registered_params()) {
        cout << "--" << param << std::endl;
    }
    return 0;
}

question: Several arguments for a param support

Is there an easy way to parse things like:

--output outputdir --input file1 file2 file3

I need several values for each parameter in some cases, and cannot know in advance the position of each argument.

parse() should be idempotent

Calling parse() multiple times on the same input should yield the same result every time.
Currently it appends the extracted inputs to the positional results (vector) anew, instead of replacing them. (Didn't check other cases.)
(Not that it's a common case of course, but a nice-to-have correctness safeguard; one less thing to worry about with this lovely little package.)

Just reset the affected container(s) at the beginning of parse().

Support list of same parameters

Hi,

Can you add support list of same parameters?

Example:

nativium --build=debug  --build=release  --build=relwithdebinfo

Get all --build parameters and a vector of it.

Thanks.

Problem space in string value

Hello,

I would like to put string value with space. Currently I could have only the first element.
Example :

my-app --config="test test"

I have :
test

Here is the code I used :


  argh::parser cmdl;
  cmdl.add_params({"-t", "--config", "-s", "--scale"});
  cmdl.parse(argc, argv);
  std::cout << cmdl("config").str() << '\n';

I don't know if someone already handle this problem ?

Best regards

Detect conflicting mode flags

In particular, it is possible to pass both PREFER_FLAG_FOR_UNREG_OPTION and PREFER_PARAM_FOR_UNREG_OPTION at the same time, and the result will be that options get treated as both at the same time.

Git-like subcommand

How can a command-line interface be called "modern" without subcommands ;)

Check if value of option is X

I don't know argh enough to say whether this is possible or not, but I would like do something like the following:

if(commandLine["-option", "value"])
{
// do something
}

At the moment I need to do something like:

if(commandLine("-option").str().compare("value") == 0)
{
// do something
}

It's not a lot, of course, but say you wanted to OR them or AND them or you have a long sequence of ifs

Is this possible? Thanks!

Multiple params with the same name?

Is it possible to do something like this:

myprogram -p foo -p bar -p baz

and be able to get all three -p parameter values? When I try I can only get the first one.

Support '-' args

in shell, '-' means stdin/stdout and very useful, eg
echo hello | cat - world.txt

but argh will ignore it

Add ctor that parses

Since argh does not require up front specifications, it is possible to create a ctor that calls the parsing function.

So instead of:

    argh::parser cmdl
    cmdl.parse(argc, argv);

we could write:
auto cmdl = argh::parser(argc, argv);
or
argh::parser cmdl(argc, argv, SINGLE_DASH_IS_MULTIFLAG);

CMake deprecation warning with cmake <3.5

Configuring with CMake causes build-system warnings

When configuring with cmake version 3.27 (and perhaps earlier versions as well), this warning is issued:

CMake Deprecation Error at external/argh/CMakeLists.txt:1 (cmake_minimum_required):
  Compatibility with CMake < 3.5 will be removed from a future version of
  CMake.

  Update the VERSION argument <min> value or use a ...<max> suffix to tell
  CMake that the project does not need compatibility with older versions.

To Reproduce

Configure argh using cmake

cmake -B build -S .

Expected behavior

No warnings on cmake configure.

Environment

Tested on Fedora Linux, but this probably manifests on all operating systems.

Additional context

  • CMake 3.1.0 was released on Dec 17, 2014
  • CMake 3.5.0 was released on Mar 8, 2016
  • Latest release is 3.28.3 which was Feb 5, 2024

Make uniform add_param / add_params

add_param takes a std::string as an input, and add_params a std::initializer_list<char const* const>.

However it is easy to forget the final 's' or add an extra 's', and not realizing where the error comes from even after carefully reading the compilation error message. A missing/extra 's' is something easy to skip while reading the error message.

Why not having add_param and add_params taking both kind of parameters, or removing one function? The latter is certainly clearner but will brake some projects using the deleted function.

Wrong value if space is in argument-value

I want my program to start with a custom configuration file using the --config parameter. If the path to the configuration file contains a space, it doesn't work anymore.

myapp.exe --config="C:\Folder\With Space\Config.ini"

Results in value C:\Folder\With for --config.

	argh::parser cmdl(argc, argv);
	std::string argConfigFilePath;
	cmdl({ "-c", "--config" }) >> argConfigFilePath;

Passing the rest of arguments to the next stage parser

#include <string>
#include <iostream>

#include <argh.h>


int main(){
	argh::parser app;
	app.add_param("odf");
	app.add_param("ods");


	const char *argv[]{"test", "--ods", "ods", "--odf", "1", "aaa", "10", "-b", "--bbb", nullptr};
	//                 argv[0]           ods      odf   mpi  [       remaining          ]

	int argc = sizeof(argv) / sizeof(argv[0]) - 1;

	app.parse(argc, argv);

	int i = 0;

	// `test ods aaa 10`
	// All the dashed arguments : `-b` `--bbb` - are missing
	while(auto curArgObj = app(i++)){
		std::string curArg;
		curArgObj >> curArg;
		std::cout << curArg << " ";
	}
	std::cout << std::endl;

	return 0;
}

As one can see, argh eats even the args that have not been registered, but we wanna pass them to the next stage cli args parser.

Case insensitivity mode

Hi, thank you for your amazing work!
I wonder if you could add a mode to enable cases insensitivity for option/flag name matching?
Sure, i could manually preprocess arguments beforehand, but that would require to make my own flag parser and then apply case transformations, which is something i don't want to bother with.
I just wonder if something like CASE_INSENSITIVE_FLAG_MATCH could be added, this would be really important feature for my project. This would just transform any flag name into lower cased one on parsing and store it, but the operators don't have to bother doing transformations, it would expect you to always enter lower cased names to decrease the complexity.

The lower cased transformation could be something as simple as:

std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c) });

which is completely in bounds of std libs and c++11 requirements.

Support combined single-letter parameters

If a single-letter parameter option has been registered, allow it to occur in the last position of a "multi-flag" option argument, as described in this twitter conversation:

https://twitter.com/sehetw/status/1014607765887766528

The motivating example is a stereotypical tar command invocation:

#include "argh.h"
#include <iostream>
#include <iterator>
#include <iomanip>

using namespace argh;

int main(int, char** argv) {
    parser p({"f"});
    p.parse(argv, parser::SINGLE_DASH_IS_MULTIFLAG);

    std::copy(begin(p.flags()), end(p.flags()), std::ostream_iterator<std::string>(std::cout << "Flags: ", " "));
    std::cout << "\noutput " << std::quoted(p("f").str()) << "\n";
    for (auto& f : p.pos_args())
        std::cout << "File arg: " << std::quoted(f) << "\n";
}

The output was Live On Coliru

Flags: c f v 
output ""
File arg: "./a.out"
File arg: "out.tar"
File arg: "a.txt"
File arg: "b txt"
File arg: "c.txt"

But this would be more useful: Live On Coliru

Flags: c v 
output "out.tar"
File arg: "./a.out"
File arg: "a.txt"
File arg: "b txt"
File arg: "c.txt"

Missing iostream in example

The TL;DR example in the README.md lacks a #include <iostream> to be complete. This would be better for fast copy / paste / compile / run :)

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.