Commit b1036a1ad0d5569c4f00b3c4a44560b626eb9a27

Authored by Philip Top
Committed by Henry Schreiner
1 parent 734af661

add a function to get the remaining arguments in a valid order (#265)

* add a function to get the remaining arguments in a valid order for parse.  and add rvalue reference overloads for parse and _parse so args is not refilled if not needed.

* check a few more tests and verify ExtrasError works on parse(rValue vector)

remove impossible to reach branches in _parse function

* add callback_passthrough example and tests
README.md
@@ -324,6 +324,7 @@ On the command line, options can be given as: @@ -324,6 +324,7 @@ On the command line, options can be given as:
324 324
325 Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments. 325 Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments.
326 If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included). 326 If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included).
  327 +If the remaining arguments are to processed by another `App` then the function `remaining_for_passthrough()`🚧 can be used to get the remaining arguments in reverse order such that `app.parse(vector)` works directly and could even be used inside a subcommand callback.
327 328
328 You can access a vector of pointers to the parsed options in the original order using `parse_order()`. 329 You can access a vector of pointers to the parsed options in the original order using `parse_order()`.
329 If `--` is present in the command line that does not end an unlimited option, then 330 If `--` is present in the command line that does not end an unlimited option, then
examples/CMakeLists.txt
@@ -189,6 +189,14 @@ set_property(TEST prefix_command PROPERTY PASS_REGULAR_EXPRESSION @@ -189,6 +189,14 @@ set_property(TEST prefix_command PROPERTY PASS_REGULAR_EXPRESSION
189 "Prefix: 3 : 2 : 1" 189 "Prefix: 3 : 2 : 1"
190 "Remaining commands: other one two 3") 190 "Remaining commands: other one two 3")
191 191
  192 +add_cli_exe(callback_passthrough callback_passthrough.cpp)
  193 +add_test(NAME callback_passthrough1 COMMAND callback_passthrough --argname t2 --t2 test)
  194 +set_property(TEST callback_passthrough1 PROPERTY PASS_REGULAR_EXPRESSION
  195 + "the value is now test")
  196 +add_test(NAME callback_passthrough2 COMMAND callback_passthrough --arg EEEK --argname arg)
  197 +set_property(TEST callback_passthrough2 PROPERTY PASS_REGULAR_EXPRESSION
  198 + "the value is now EEEK")
  199 +
192 add_cli_exe(enum enum.cpp) 200 add_cli_exe(enum enum.cpp)
193 add_test(NAME enum_pass COMMAND enum -l 1) 201 add_test(NAME enum_pass COMMAND enum -l 1)
194 add_test(NAME enum_fail COMMAND enum -l 4) 202 add_test(NAME enum_fail COMMAND enum -l 4)
examples/callback_passthrough.cpp 0 → 100644
  1 +#include "CLI/CLI.hpp"
  2 +
  3 +int main(int argc, char **argv) {
  4 +
  5 + CLI::App app("callback_passthrough");
  6 + app.allow_extras();
  7 + std::string argName;
  8 + std::string val;
  9 + app.add_option("--argname", argName, "the name of the custom command line argument");
  10 + app.callback([&app, &val, &argName]() {
  11 + if(!argName.empty()) {
  12 + CLI::App subApp;
  13 + subApp.add_option("--" + argName, val, "custom argument option");
  14 + subApp.parse(app.remaining_for_passthrough());
  15 + }
  16 + });
  17 +
  18 + CLI11_PARSE(app, argc, argv);
  19 + std::cout << "the value is now " << val << '\n';
  20 +}
include/CLI/App.hpp
@@ -1295,7 +1295,7 @@ class App { @@ -1295,7 +1295,7 @@ class App {
1295 std::vector<std::string> args; 1295 std::vector<std::string> args;
1296 for(int i = argc - 1; i > 0; i--) 1296 for(int i = argc - 1; i > 0; i--)
1297 args.emplace_back(argv[i]); 1297 args.emplace_back(argv[i]);
1298 - parse(args); 1298 + parse(std::move(args));
1299 } 1299 }
1300 1300
1301 /// Parse a single string as if it contained command line arguments. 1301 /// Parse a single string as if it contained command line arguments.
@@ -1325,7 +1325,7 @@ class App { @@ -1325,7 +1325,7 @@ class App {
1325 args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end()); 1325 args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end());
1326 std::reverse(args.begin(), args.end()); 1326 std::reverse(args.begin(), args.end());
1327 1327
1328 - parse(args); 1328 + parse(std::move(args));
1329 } 1329 }
1330 1330
1331 /// The real work is done here. Expects a reversed vector. 1331 /// The real work is done here. Expects a reversed vector.
@@ -1349,6 +1349,26 @@ class App { @@ -1349,6 +1349,26 @@ class App {
1349 run_callback(); 1349 run_callback();
1350 } 1350 }
1351 1351
  1352 + /// The real work is done here. Expects a reversed vector.
  1353 + void parse(std::vector<std::string> &&args) {
  1354 + // Clear if parsed
  1355 + if(parsed_ > 0)
  1356 + clear();
  1357 +
  1358 + // parsed_ is incremented in commands/subcommands,
  1359 + // but placed here to make sure this is cleared when
  1360 + // running parse after an error is thrown, even by _validate or _configure.
  1361 + parsed_ = 1;
  1362 + _validate();
  1363 + _configure();
  1364 + // set the parent as nullptr as this object should be the top now
  1365 + parent_ = nullptr;
  1366 + parsed_ = 0;
  1367 +
  1368 + _parse(std::move(args));
  1369 + run_callback();
  1370 + }
  1371 +
1352 /// Provide a function to print a help message. The function gets access to the App pointer and error. 1372 /// Provide a function to print a help message. The function gets access to the App pointer and error.
1353 void failure_message(std::function<std::string(const App *, const Error &e)> function) { 1373 void failure_message(std::function<std::string(const App *, const Error &e)> function) {
1354 failure_message_ = function; 1374 failure_message_ = function;
@@ -1755,6 +1775,13 @@ class App { @@ -1755,6 +1775,13 @@ class App {
1755 return miss_list; 1775 return miss_list;
1756 } 1776 }
1757 1777
  1778 + /// This returns the missing options in a form ready for processing by another command line program
  1779 + std::vector<std::string> remaining_for_passthrough(bool recurse = false) const {
  1780 + std::vector<std::string> miss_list = remaining(recurse);
  1781 + std::reverse(std::begin(miss_list), std::end(miss_list));
  1782 + return miss_list;
  1783 + }
  1784 +
1758 /// This returns the number of remaining options, minus the -- separator 1785 /// This returns the number of remaining options, minus the -- separator
1759 size_t remaining_size(bool recurse = false) const { 1786 size_t remaining_size(bool recurse = false) const {
1760 auto remaining_options = static_cast<size_t>(std::count_if( 1787 auto remaining_options = static_cast<size_t>(std::count_if(
@@ -1878,7 +1905,7 @@ class App { @@ -1878,7 +1905,7 @@ class App {
1878 return detail::Classifier::SHORT; 1905 return detail::Classifier::SHORT;
1879 if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2))) 1906 if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
1880 return detail::Classifier::WINDOWS; 1907 return detail::Classifier::WINDOWS;
1881 - if((current == "++") && !name_.empty()) 1908 + if((current == "++") && !name_.empty() && parent_ != nullptr)
1882 return detail::Classifier::SUBCOMMAND_TERMINATOR; 1909 return detail::Classifier::SUBCOMMAND_TERMINATOR;
1883 return detail::Classifier::NONE; 1910 return detail::Classifier::NONE;
1884 } 1911 }
@@ -2107,6 +2134,21 @@ class App { @@ -2107,6 +2134,21 @@ class App {
2107 } 2134 }
2108 2135
2109 /// Throw an error if anything is left over and should not be. 2136 /// Throw an error if anything is left over and should not be.
  2137 + void _process_extras() {
  2138 + if(!(allow_extras_ || prefix_command_)) {
  2139 + size_t num_left_over = remaining_size();
  2140 + if(num_left_over > 0) {
  2141 + throw ExtrasError(remaining(false));
  2142 + }
  2143 + }
  2144 +
  2145 + for(App_p &sub : subcommands_) {
  2146 + if(sub->count() > 0)
  2147 + sub->_process_extras();
  2148 + }
  2149 + }
  2150 +
  2151 + /// Throw an error if anything is left over and should not be.
2110 /// Modifies the args to fill in the missing items before throwing. 2152 /// Modifies the args to fill in the missing items before throwing.
2111 void _process_extras(std::vector<std::string> &args) { 2153 void _process_extras(std::vector<std::string> &args) {
2112 if(!(allow_extras_ || prefix_command_)) { 2154 if(!(allow_extras_ || prefix_command_)) {
@@ -2149,8 +2191,8 @@ class App { @@ -2149,8 +2191,8 @@ class App {
2149 // Throw error if any items are left over (depending on settings) 2191 // Throw error if any items are left over (depending on settings)
2150 _process_extras(args); 2192 _process_extras(args);
2151 2193
2152 - // Convert missing (pairs) to extras (string only)  
2153 - args = remaining(false); 2194 + // Convert missing (pairs) to extras (string only) ready for processing in another app
  2195 + args = remaining_for_passthrough(false);
2154 } else if(immediate_callback_) { 2196 } else if(immediate_callback_) {
2155 _process_env(); 2197 _process_env();
2156 _process_callbacks(); 2198 _process_callbacks();
@@ -2160,6 +2202,23 @@ class App { @@ -2160,6 +2202,23 @@ class App {
2160 } 2202 }
2161 } 2203 }
2162 2204
  2205 + /// Internal parse function
  2206 + void _parse(std::vector<std::string> &&args) {
  2207 + // this can only be called by the top level in which case parent == nullptr by definition
  2208 + // operation is simplified
  2209 + increment_parsed();
  2210 + _trigger_pre_parse(args.size());
  2211 + bool positional_only = false;
  2212 +
  2213 + while(!args.empty()) {
  2214 + _parse_single(args, positional_only);
  2215 + }
  2216 + _process();
  2217 +
  2218 + // Throw error if any items are left over (depending on settings)
  2219 + _process_extras();
  2220 + }
  2221 +
2163 /// Parse one config param, return false if not found in any subcommand, remove if it is 2222 /// Parse one config param, return false if not found in any subcommand, remove if it is
2164 /// 2223 ///
2165 /// If this has more than one dot.separated.name, go into the subcommand matching it 2224 /// If this has more than one dot.separated.name, go into the subcommand matching it
@@ -2227,6 +2286,7 @@ class App { @@ -2227,6 +2286,7 @@ class App {
2227 } 2286 }
2228 break; 2287 break;
2229 case detail::Classifier::SUBCOMMAND_TERMINATOR: 2288 case detail::Classifier::SUBCOMMAND_TERMINATOR:
  2289 + // treat this like a positional mark if in the parent app
2230 args.pop_back(); 2290 args.pop_back();
2231 retval = false; 2291 retval = false;
2232 break; 2292 break;
tests/AppTest.cpp
@@ -1815,7 +1815,74 @@ TEST_F(TApp, AllowExtrasOrder) { @@ -1815,7 +1815,74 @@ TEST_F(TApp, AllowExtrasOrder) {
1815 1815
1816 std::vector<std::string> left_over = app.remaining(); 1816 std::vector<std::string> left_over = app.remaining();
1817 app.parse(left_over); 1817 app.parse(left_over);
1818 - EXPECT_EQ(app.remaining(), left_over); 1818 + EXPECT_EQ(app.remaining(), std::vector<std::string>({"-f", "-x"}));
  1819 + EXPECT_EQ(app.remaining_for_passthrough(), left_over);
  1820 +}
  1821 +
  1822 +TEST_F(TApp, AllowExtrasCascade) {
  1823 +
  1824 + app.allow_extras();
  1825 +
  1826 + args = {"-x", "45", "-f", "27"};
  1827 + ASSERT_NO_THROW(run());
  1828 + EXPECT_EQ(app.remaining(), std::vector<std::string>({"-x", "45", "-f", "27"}));
  1829 +
  1830 + std::vector<std::string> left_over = app.remaining_for_passthrough();
  1831 +
  1832 + CLI::App capp{"cascade_program"};
  1833 + int v1 = 0;
  1834 + int v2 = 0;
  1835 + capp.add_option("-x", v1);
  1836 + capp.add_option("-f", v2);
  1837 +
  1838 + capp.parse(left_over);
  1839 + EXPECT_EQ(v1, 45);
  1840 + EXPECT_EQ(v2, 27);
  1841 +}
  1842 +// makes sure the error throws on the rValue version of the parse
  1843 +TEST_F(TApp, ExtrasErrorRvalueParse) {
  1844 +
  1845 + args = {"-x", "45", "-f", "27"};
  1846 + EXPECT_THROW(app.parse(std::vector<std::string>({"-x", "45", "-f", "27"})), CLI::ExtrasError);
  1847 +}
  1848 +
  1849 +TEST_F(TApp, AllowExtrasCascadeDirect) {
  1850 +
  1851 + app.allow_extras();
  1852 +
  1853 + args = {"-x", "45", "-f", "27"};
  1854 + ASSERT_NO_THROW(run());
  1855 + EXPECT_EQ(app.remaining(), std::vector<std::string>({"-x", "45", "-f", "27"}));
  1856 +
  1857 + CLI::App capp{"cascade_program"};
  1858 + int v1 = 0;
  1859 + int v2 = 0;
  1860 + capp.add_option("-x", v1);
  1861 + capp.add_option("-f", v2);
  1862 +
  1863 + capp.parse(app.remaining_for_passthrough());
  1864 + EXPECT_EQ(v1, 45);
  1865 + EXPECT_EQ(v2, 27);
  1866 +}
  1867 +
  1868 +TEST_F(TApp, AllowExtrasArgModify) {
  1869 +
  1870 + int v1 = 0;
  1871 + int v2 = 0;
  1872 + app.allow_extras();
  1873 + app.add_option("-f", v2);
  1874 + args = {"27", "-f", "45", "-x"};
  1875 + auto cargs = args;
  1876 + app.parse(args);
  1877 + EXPECT_EQ(args, std::vector<std::string>({"45", "-x"}));
  1878 +
  1879 + CLI::App capp{"cascade_program"};
  1880 +
  1881 + capp.add_option("-x", v1);
  1882 +
  1883 + capp.parse(args);
  1884 + EXPECT_EQ(v1, 45);
  1885 + EXPECT_EQ(v2, 27);
1819 } 1886 }
1820 1887
1821 // Test horrible error 1888 // Test horrible error