Skip to content

Commit 7c111c6

Browse files
committed
introduced 'greedy' option for parameters
1 parent 8a14a2e commit 7c111c6

9 files changed

+514
-55
lines changed

README.md

+118-37
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,20 @@ There are two kinds of building blocks for command line interfaces: parameters a
136136
```cpp
137137
bool a = false, f = false;
138138
string s; vector<string> vs;
139-
auto cli = ( // matches required positional repeatable
140-
command("push"), // exactly yes yes no
141-
required("-f", "--file").set(f), // exactly yes no no
142-
option("-a", "--all", "-A").set(a), // exactly no no no
139+
auto cli = ( // matches required positional repeatable
140+
command("push"), // exactly yes yes no
141+
required("-f", "--file").set(f), // exactly yes no no
142+
option("-a", "--all", "-A").set(a), // exactly no no no
143143

144-
value("file", s), // any arg yes yes no
145-
values("file", vs), // any arg yes yes yes
146-
opt_value("file", s), // any arg no yes no
147-
opt_values("file", vs), // any arg no yes yes
144+
value("file", s), // any arg yes yes no
145+
values("file", vs), // any arg yes yes yes
146+
opt_value("file", s), // any arg no yes no
147+
opt_values("file", vs), // any arg no yes yes
148148

149149
//"catch all" parameter - useful for error handling
150-
any_other(vs) // any arg no no yes
150+
any_other(vs) // any arg no no yes
151+
//catches arguments that fulfill a predicate and aren't matched by other parameters
152+
any(predicate, vs) // predicate no no yes
151153
);
152154
```
153155
The functions above are convenience factories:
@@ -161,10 +163,10 @@ auto r1 = required("-f", "--file").set(f);
161163
// is equivalent to:
162164
auto r2 = parameter{"-f", "--file"}.required(true).set(f);
163165
```
164-
* a required parameter has to match at least one command line argument
165-
* a repeatable parameter can match any number of arguments
166-
* non-positional (=non-blocking) parameters can match arguments in any order
167-
* a positional (blocking) parameter defines a "stop point", i.e., until it matches all parameters following it are not allowed to match; once it matched, all parameters preceding it (wihtin the current group) will become unreachable
166+
- a required parameter has to match at least one command line argument
167+
- a repeatable parameter can match any number of arguments
168+
- non-positional (=non-blocking) parameters can match arguments in any order
169+
- a positional (blocking) parameter defines a "stop point", i.e., until it matches all parameters following it are not allowed to match; once it matched, all parameters preceding it (wihtin the current group) will become unreachable
168170
169171
##### [Flags + Values](#options-with-values)
170172
If you want parameters to be matched in sequence, you can tie them together using either ```operator &``` or the grouping function ```in_sequence```:
@@ -189,7 +191,6 @@ auto cli = (
189191
);
190192
```
191193

192-
193194
##### [Filtering Value Parameters](#value-filters)
194195
Value parameters use a filter function to test if they are allowed to match an argument string. The default filter ```match::nonempty``` that is used by ```value```, ```values```, ```opt_value``` and ```opt_values``` will match any non-empty argument string.
195196
You can either supply other filter functions/function objects as first argument of ```value```, ```values```, etc. or use one of these built-in shorthand factory functions covering the most common cases:
@@ -296,6 +297,7 @@ settings cmdline_settings(int argc, char* argv[]) {
296297
}
297298
```
298299
300+
299301
#### Generating Documentation ([see also here](#documentation-generation))
300302
Docstrings for groups and for parameters can either be set with the member function ```doc``` or with ```operator %```:
301303
```cpp
@@ -341,6 +343,7 @@ auto fmt = doc_formatting{}.start_column(2);
341343
cout << make_man_page(cli, "progname", fmt) << '\n';
342344
```
343345

346+
344347
#### (Error) Event Handlers ([see here](#error-handling), [and here](#per-parameter-parsing-report))
345348
Each parameter can have event handler functions attached to it. These are invoked once for each argument that is mapped to the parameter (or once per missing event):
346349
```cpp
@@ -368,6 +371,21 @@ auto param = required("-nof").set(file,"") |
368371
```
369372

370373

374+
#### Special Cases
375+
If we give ```-f -b``` or ```-b -f -a``` as command line arguments for the following CLI, an error will be reported, since the value after ```-f``` is not optional:
376+
```cpp
377+
auto cli = ( option("-a"), option("-f") & value("filename"), option("-b") );
378+
```
379+
This behavior is fine for most use cases.
380+
But what if we want our program to take any string as a filename, because our filenames might also collide with flag names? We can make the value parameter [greedy](#greedy-parameters) with ```operator !```. This way, the next string after ```-f``` will always be matched with highest priority as soon as ```-f``` was given:
381+
```cpp
382+
auto cli = ( option("-a"), option("-f") & !value("filename"), option("-b") );
383+
// ^~~~~~
384+
```
385+
Be **very careful** with greedy parameters!
386+
387+
388+
371389
#### Parsing Result Analysis
372390
```cpp
373391
auto cli = ( /* your interface here */ );
@@ -419,10 +437,11 @@ The repository folder "examples" contains code for most of the following example
419437
- [complex nestings](#complex-nestings)
420438
- [example from docopt](#an-example-from-docopt)
421439
- [value filters](#value-filters)
440+
- [greedy parameters](#greedy-parameters)
422441
- [generalized joinable parameters](#generalized-joinable-parameters)
423442
- [custom value filters](#custom-value-filters)
424443
- [sanity checks](#sanity-checks)
425-
- [error handling](#error-handling)
444+
- [basic error handling](#basic-error-handling)
426445
- [parsing](#parsing)
427446
- [documentation generation](#documentation-generation)
428447
- [documentation filtering](#documentation-filtering)
@@ -711,9 +730,9 @@ auto cli = (
711730
```
712731
```cpp
713732
auto cli = (
714-
(option("-n", "--count") & value("count").set(n)) % "number of iterations",
715-
(option("-r", "--ratio") & value("ratio").call(print_ratio)) % "compression ratio",
716-
(option("-m").set(domerge) & opt_value("lines=5").set(m)) % "merge lines (default: 5)"
733+
(option("-n", "--count") & value("count").set(n)) % "number of iterations",
734+
(option("-r", "--ratio") & value("ratio")(print_ratio)) % "compression ratio",
735+
(option("-m").set(domerge) & opt_value("lines=5").set(m)) % "merge lines (default: 5)"
717736
);
718737
```
719738
See [here](#coding-styles) for more on coding styles.
@@ -919,7 +938,7 @@ OPTIONS
919938
-b use backup file
920939
-s use swap file
921940
922-
:vi, :st3, :atom, :emacs
941+
:vim, :st3, :atom, :emacs
923942
editor(s) to use; multiple possible
924943
```
925944

@@ -945,17 +964,14 @@ auto cli = (
945964
) % "editor(s) to use; multiple possible"
946965
);
947966
```
948-
- Flags of parameters that take values cannot be joined with other flags.
949967
- Flags can be joined regardless of their length (second group in the example).
950-
- Flags can only be joined if they have a non-empty common prefix (otherwise it would be too easy to confuse them with value parameters).
951-
- The common prefix (```-``` or ```:``` in the example) must be given at least
968+
- If the flags have a common prefix (```-``` or ```:``` in the example) it must be given at least
952969
once as leading prefix in the command line argument.
953970
- Allowed args for the first group are:
954971
```-r```, ```-b```, ```-s```, ```-rb```, ```-br```, ```-rs```, ```-sr```,
955972
```-sb```, ```-bs```, ```-rbs```, ```-rsb```, ...
956973
- Allowed args for the second group are:
957-
```:vim```, ```:vim:atom```, ```:emacs:st3```, ...
958-
974+
```:vim```, ```:vim:atom```, ```:emacs:st3```, ```:vimatom```, ...
959975

960976
#### More Examples
961977

@@ -1092,6 +1108,7 @@ SYNOPSIS
10921108
OPTIONS
10931109
-v, --verbose
10941110
print detailed report
1111+
10951112
-b, --buffer [<size=1024>]
10961113
sets buffer size in KiByte
10971114
@@ -1318,15 +1335,18 @@ auto shipmove = (
13181335
auto shipshoot = ( command("shoot").set(selected,mode::shipshoot),
13191336
coordinates );
13201337

1338+
auto mines = (
1339+
command("mine"),
1340+
(command("set" ).set(selected,mode::mineset) |
1341+
command("remove").set(selected,mode::minerem) ),
1342+
coordinates,
1343+
(option("--moored" ).set(drift,false) % "Moored (anchored) mine." |
1344+
option("--drifting").set(drift,true) % "Drifting mine." )
1345+
);
1346+
13211347
auto navalcli = (
1322-
( command("ship"), ( shipnew | shipmove | shipshoot ) )
1323-
| ( command("mine"),
1324-
( command("set" ).set(selected,mode::mineset)
1325-
| command("remove").set(selected,mode::minerem) ),
1326-
coordinates,
1327-
( option("--moored" ).set(drift,false) % "Moored (anchored) mine."
1328-
| option("--drifting").set(drift,true) % "Drifting mine." )
1329-
)
1348+
( command("ship"), ( shipnew | shipmove | shipshoot ) )
1349+
| mines,
13301350
| command("-h", "--help").set(selected,mode::help) % "Show this screen."
13311351
| command("--version")([]{ cout << "version 1.0\n"; }) % "Show version."
13321352
);
@@ -1355,12 +1375,13 @@ switch(m) {
13551375

13561376

13571377

1378+
13581379
### Value Filters
13591380
If a parameter doesn't have flags, i.e. it is a value-parameter, a filter function will be used to test if it matches an argument string. The default filter is ```clipp::match::nonempty``` which will match any non-empty argument string.
13601381
If you want more control over what is matched, you can use some other predefined filters or you can write your own ones (see [here](#custom-value-filters)).
13611382

13621383
```man
1363-
Usage: exec [-n <times>] [-l <line>...] [-r <ratio>] [-f <term>]
1384+
Usage: exec [-n <times>] [-l <line>...] [-b <ratio>] [-f <term>]
13641385
```
13651386

13661387
```cpp
@@ -1410,6 +1431,15 @@ auto p = parameter{ match::length{1,5} }
14101431
```
14111432
There are a couple of predefined filters in ```namespace clipp::match```, but you can of course write your own ones (see [here](#custom-value-filters)).
14121433
1434+
Here is another example that makes sure we don't catch any value starting with "-" as a filename:
1435+
```cpp
1436+
auto cli = (
1437+
option("-a")
1438+
option("-f") & value(match::prefix_not("-"), "filename"),
1439+
option("-b")
1440+
);
1441+
```
1442+
14131443
```cpp
14141444
namespace clipp {
14151445
namespace match {
@@ -1448,8 +1478,8 @@ namespace match {
14481478
};
14491479

14501480
class length {
1451-
explicit numbers(size_t exact);
1452-
explicit numbers(size_t min, size_t max);
1481+
explicit length(size_t exact);
1482+
explicit length(size_t min, size_t max);
14531483
subrange operator () (const string& arg);
14541484
};
14551485

@@ -1458,6 +1488,55 @@ namespace match {
14581488
14591489
14601490
1491+
### Greedy Parameters
1492+
1493+
By default, the parser tries to identify a command line argument (in that order) as
1494+
- a single flag
1495+
- a concatenation of multiple, _joinable_ flags (in any order)
1496+
- a concatenation of a _joinable_ flag sequence in the order defined in the CLI code
1497+
- a single value parameter
1498+
- a concatenation of a _joinable_ flag/value sequence in the order defined in the CLI code
1499+
- a concatenation of _joinable_ flags & values in no particular order
1500+
1501+
If no match was found, the parser tries the same list again without any restrictions imposed by blocking (positional) parameters, conflicting alternatives, etc. If this leads to any match, an error will be reported. This way, _potential_, but illegal matches can be found and, e.g., conflicting alternatives can be reported.
1502+
1503+
Consider this CLI:
1504+
```cpp
1505+
auto cli = ( option("-a"), option("-f") & value("filename"), option("-b") );
1506+
```
1507+
If we give ```-f -b``` or ```-b -f -a``` as command line arguments, an error will be reported, since the value after ```-f``` is not optional.
1508+
1509+
This behavior is fine for most use cases.
1510+
But what if we want our program to take any string as a filename, because our filenames might also collide with flag names? We can make the ```filename``` value parameter greedy, so that the next string after ```-f``` will always be matched with highest priority as soon as ```-f``` was given:
1511+
```cpp
1512+
auto cli = ( option("-a"), option("-f") & greedy(value("filename")), option("-b") );
1513+
```
1514+
or using ```operator !```:
1515+
```cpp
1516+
auto cli = ( option("-a"), option("-f") & !value("filename"), option("-b") );
1517+
```
1518+
1519+
Now, every string coming after an ```-f``` will be used as filename.
1520+
1521+
If we don't want just *any* kind of match accepted, but still retain a higher priority for a value parameter, we could use a [value filter](#value-filters):
1522+
```cpp
1523+
auto cli = (
1524+
( command("A"),
1525+
option("-f") & !value(match::prefix_not("-"), "filename"),
1526+
option("-b")
1527+
) |
1528+
( command("B"),
1529+
option("-x")
1530+
)
1531+
);
1532+
```
1533+
This way, the command line arguments ```A -f B``` will set the filename to "B" and produce no conflict error between the alternative commands ```A``` and ```B``` but ```A -f -b``` will still give an error.
1534+
1535+
Note, that there is an inherent decision problem: either we want the ```filename``` value to match no matter what, or we won't get proper error handling if someone forgets to specify a filename and gives ```A -f -b``` Also, there might be interfaces where we really want to catch something like ```A -f B``` as a command conflict.
1536+
1537+
1538+
1539+
14611540
### Generalized Joinable Parameters
14621541

14631542
Not only flags, but arbitrary combinations of flags and values can be made joinable. This feature is especially powerful if combined with repeatable groups.
@@ -1620,7 +1699,7 @@ assert( cli.common_flag_prefix() == "-" );
16201699

16211700

16221701

1623-
### Error Handling
1702+
### Basic Error Handling
16241703

16251704
Each parameter can have error handler functions/lambdas/function objects for different fail cases attached to it:
16261705
- ```if_repeated``` is raised each time an argument is mapped to a parameter regardless of that parameter's repeatability setting
@@ -1639,12 +1718,14 @@ vector<string> targets;
16391718
vector<string> wrong;
16401719
bool http = true;
16411720

1721+
auto istarget = match::prefix_not("-");
1722+
16421723
auto cli = (
16431724
value("file", filename)
16441725
.if_missing([]{ cout << "You need to provide a source filename!\n"; } )
16451726
.if_repeated([](int idx){ cout << "Only one source file allowed! (index " << idx << ")\n"; } )
16461727
,
1647-
required("-t") & values(match::prefix_not("-"), "target", targets)
1728+
required("-t") & values(istarget, "target", targets)
16481729
.if_missing([]{ cout << "You need to provide at least one target filename!\n"; } )
16491730
.if_blocked([]{ cout << "Target names must not be given before the source file name!\n"; })
16501731
,

examples/align.cpp

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*****************************************************************************
2+
*
3+
* demo program - part of CLIPP (command line interfaces for modern C++)
4+
*
5+
* released under MIT license
6+
*
7+
* (c) 2017 André Müller; [email protected]
8+
*
9+
*****************************************************************************/
10+
11+
#include <iostream>
12+
#include <vector>
13+
#include <cmath>
14+
15+
#include <clipp.h>
16+
17+
18+
int main(int argc, char* argv[])
19+
{
20+
using namespace clipp;
21+
22+
enum class imode { file, args, stdio, random };
23+
enum class omode { file, stdio };
24+
auto input = imode::file;
25+
auto output = omode::stdio;
26+
std::int64_t minlen = 256;
27+
std::int64_t maxlen = 1024;
28+
std::string query, subject;
29+
std::string outfile;
30+
std::vector<std::string> wrong;
31+
32+
auto cli = (
33+
(option("-o", "--out").set(output,omode::file) &
34+
value("file", outfile)) % "write results to file"
35+
,
36+
"read sequences from input files" % (
37+
command("-i", "--in"),
38+
value("query file", query),
39+
value("subject file", subject)
40+
) |
41+
"specify sequences on the command line" % (
42+
command("-a", "--args").set(input,imode::args),
43+
value("query string", query),
44+
value("subject string", subject)
45+
) |
46+
"generate random input sequences" % (
47+
command("-r", "--rand").set(input,imode::random),
48+
opt_integer("min len", minlen) &
49+
opt_integer("max len", maxlen)
50+
) | (
51+
"read sequences from stdin" %
52+
command("-").set(input,imode::stdio)
53+
),
54+
any_other(wrong)
55+
);
56+
57+
58+
if(!parse(argc,argv, cli) || !wrong.empty()) {
59+
if(!wrong.empty()) {
60+
std::cout << "Unknown command line arguments:\n";
61+
for(const auto& a : wrong) std::cout << "'" << a << "'\n";
62+
std::cout << '\n';
63+
}
64+
std::cout << make_man_page(cli, argv[0]) << '\n';
65+
return 0;
66+
}
67+
68+
switch(input) {
69+
default:
70+
case imode::file: /* ... */ break;
71+
case imode::args: /* ... */ break;
72+
case imode::stdio: /* ... */ break;
73+
case imode::random: /* ... */ break;
74+
}
75+
76+
switch(output) {
77+
default:
78+
case omode::stdio: /* ... */ break;
79+
case omode::file: /* ... */ break;
80+
}
81+
}

examples/alternatives.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ int main(int argc, char* argv[])
3333

3434
if(parse(argc, argv, cli)) {
3535
cout << "find '" << expr << "' in files: ";
36-
for(const auto& f : files) cout << "'" << f << "' "; cout << '\n';
36+
for(const auto& f : files) { cout << "'" << f << "' "; } cout << '\n';
3737
if(ifany) cout << "using 'any' mode\n";
3838
if(ifall) cout << "using 'all' mode\n";
3939
}

0 commit comments

Comments
 (0)