Commit b1036a1ad0d5569c4f00b3c4a44560b626eb9a27
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
Showing
5 changed files
with
162 additions
and
6 deletions
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 |