@@ -854,45 +854,35 @@ BOOST_AUTO_TEST_CASE(test_the_parser_reads_the_same_a_level_down) {
854854 for (auto options : {Parser::Standard, Parser::AllowUnixGroupFlags}) {
855855 Parser flat (declare (" prog" ));
856856 Parser deep (Command (" prog" ).addCommand (declare (" inner" )));
857+ Parser deeper (Command (" prog" ).addCommand (Command (" mid" ).addCommand (declare (" inner" ))));
857858
858859 for (const auto &line : lines) {
859- std::vector<std::string> flatArgs = {" prog" };
860- flatArgs.insert (flatArgs.end (), line.begin (), line.end ());
861- std::vector<std::string> deepArgs = {" prog" , " inner" };
862- deepArgs.insert (deepArgs.end (), line.begin (), line.end ());
863-
864860 std::string what;
865861 for (const auto &item : line) {
866862 what += " " + item;
867863 }
868- BOOST_CHECK_MESSAGE (readBack (flat.parse (flatArgs, options)) ==
869- readBack (deep.parse (deepArgs, options)),
870- " [" + what + " ] reads one way at the root and another a level "
871- " down:\n root " +
872- readBack (flat.parse (flatArgs, options)) + " \n down " +
873- readBack (deep.parse (deepArgs, options)));
864+
865+ const auto &at = [&line, options](const Parser &parser,
866+ std::vector<std::string> path) {
867+ path.insert (path.end (), line.begin (), line.end ());
868+ return parser.parse (path, options);
869+ };
870+ auto root = readBack (at (flat, {" prog" }));
871+ for (const auto &deeperOne : {std::make_pair (std::vector<std::string>{" prog" , " inner" },
872+ &deep),
873+ std::make_pair (std::vector<std::string>{" prog" , " mid" ,
874+ " inner" },
875+ &deeper)}) {
876+ auto below = readBack (at (*deeperOne.second , deeperOne.first ));
877+ BOOST_CHECK_MESSAGE (root == below,
878+ " [" + what + " ] reads one way at the root and another " +
879+ std::to_string (deeperOne.first .size () - 1 ) +
880+ " down:\n root " + root + " \n down " + below);
881+ }
874882 }
875883 }
876884}
877885
878- // A response file is expanded before the command is looked for, so it may name one.
879- BOOST_AUTO_TEST_CASE (test_a_response_file_may_name_a_subcommand) {
880- auto path = std::filesystem::temp_directory_path () / " stdc_cli_response_nested.txt" ;
881- {
882- std::ofstream file (path, std::ios::binary);
883- file << " build\n -j\n 4\n target\n " ;
884- }
885-
886- Parser parser (Command (" prog" ).addCommand (
887- Command (" build" ).addArgument (Argument (" target" )).addOption (Option ({" -j" }, " Jobs" ).arg (" n" ))));
888- auto result = ok (parser, {" @" + path.string ()}, Parser::EnableResponseFile);
889- BOOST_CHECK (result.commandPath () == std::vector<std::string>({" prog" , " build" }));
890- BOOST_CHECK_EQUAL (must (result.value (0 )), " target" );
891- BOOST_CHECK_EQUAL (must (result.valueForOption <int >(" -j" )), 4 );
892-
893- std::filesystem::remove (path);
894- }
895-
896886// Everything the prior ladder does, asked once at the root and once a level down, because the
897887// two only tell each other apart there. A root only tree makes "the line was empty" and "this
898888// command was given nothing" the same value, which is how AutoSetWhenNoSymbols was broken for
@@ -2481,6 +2471,102 @@ BOOST_AUTO_TEST_CASE(test_a_subcommand_lists_what_it_inherited) {
24812471 BOOST_CHECK (!has (parser.parse (argv ({})).helpText (), " Global options:" ));
24822472}
24832473
2474+ // A global written wherever it is in scope, on a line that walks down through three commands and
2475+ // picks one up at each. Every command below where it was declared can be written before, and the
2476+ // ones above have nothing to say about it.
2477+ BOOST_AUTO_TEST_CASE (test_globals_written_between_the_commands_that_declare_them) {
2478+ const auto &tree = [] {
2479+ Parser parser (Command (" prog" )
2480+ .addOption (Option ({" --root-wide" }, " From the top" ).global ())
2481+ .addCommand (Command (" remote" )
2482+ .addOption (Option ({" --mid" }, " From the middle" ).global ())
2483+ .addCommand (Command (" add" )
2484+ .addOption (Option ({" --leaf" }, " Here" ))
2485+ .addArgument (Argument (" name" )))));
2486+ parser.setTextWidth (80 );
2487+ return parser;
2488+ };
2489+
2490+ // One picked up at each step down, which is the shape a program with layered options has.
2491+ auto parser = tree ();
2492+ auto walked = ok (parser, {" --root-wide" , " remote" , " --mid" , " add" , " --leaf" , " x" });
2493+ BOOST_CHECK (walked.commandPath () == std::vector<std::string>({" prog" , " remote" , " add" }));
2494+ BOOST_CHECK (walked.option (" --root-wide" ).has_value ());
2495+ BOOST_CHECK (walked.option (" --mid" ).has_value ());
2496+ BOOST_CHECK (walked.option (" --leaf" ).has_value ());
2497+ BOOST_CHECK_EQUAL (must (walked.value (0 )), " x" );
2498+
2499+ // All of them at the end reads the same, since a global stays in scope rather than being
2500+ // spent where it was written.
2501+ auto trailing = ok (parser, {" remote" , " add" , " --root-wide" , " --mid" , " --leaf" , " x" });
2502+ BOOST_CHECK (trailing.option (" --root-wide" ).has_value ());
2503+ BOOST_CHECK (trailing.option (" --mid" ).has_value ());
2504+ BOOST_CHECK (trailing.option (" --leaf" ).has_value ());
2505+
2506+ // And so does every arrangement in between.
2507+ for (const auto &line : {
2508+ std::vector<std::string>{" --root-wide" , " remote" , " add" , " --mid" , " --leaf" , " x" },
2509+ std::vector<std::string>{" remote" , " --root-wide" , " --mid" , " add" , " --leaf" , " x" },
2510+ std::vector<std::string>{" remote" , " --mid" , " add" , " --root-wide" , " x" , " --leaf" },
2511+ }) {
2512+ std::vector<std::string> args{" prog" };
2513+ args.insert (args.end (), line.begin (), line.end ());
2514+ auto result = parser.parse (args);
2515+ BOOST_REQUIRE_MESSAGE (result.isValid (), result.errorText ());
2516+ BOOST_CHECK (result.option (" --root-wide" ).has_value ());
2517+ BOOST_CHECK (result.option (" --mid" ).has_value ());
2518+ BOOST_CHECK (result.option (" --leaf" ).has_value ());
2519+ BOOST_CHECK_EQUAL (must (result.value (0 )), " x" );
2520+ }
2521+
2522+ // Written above where it was declared it is nobody's option, which is the rule that lets a
2523+ // subcommand name one thing what its parent names another.
2524+ bad (parser, {" --mid" , " remote" , " add" , " x" }, ParseResult::UnknownOption);
2525+ bad (parser, {" remote" , " --leaf" , " add" , " x" }, ParseResult::UnknownOption);
2526+
2527+ // Twice is once too many unless it said otherwise, on either side of a step down the same
2528+ // as anywhere else. Being global buys it no extra turns.
2529+ bad (parser, {" --root-wide" , " remote" , " --root-wide" , " add" , " x" },
2530+ ParseResult::OptionOccurTooMuch);
2531+
2532+ // Having said otherwise, both are counted, and the step down between them is nothing to it.
2533+ Parser repeatable (Command (" prog" )
2534+ .addOption (Option ({" -v" }, " Say more" ).global ().multi ())
2535+ .addCommand (Command (" remote" ).addCommand (
2536+ Command (" add" ).addArgument (Argument (" name" )))));
2537+ auto twice = ok (repeatable, {" -v" , " remote" , " -v" , " add" , " x" });
2538+ BOOST_REQUIRE (twice.option (" -v" ).has_value ());
2539+ BOOST_CHECK_EQUAL (twice.option (" -v" )->count (), 2 );
2540+
2541+ // Two levels of them are both listed, under the heading that says they came from above.
2542+ auto text = parser.parse (argv ({" remote" , " add" , " x" })).helpText ();
2543+ BOOST_CHECK (has (text, " Global options:" ));
2544+ BOOST_CHECK (has (text, " --root-wide" ));
2545+ BOOST_CHECK (has (text, " --mid" ));
2546+ BOOST_CHECK (has (text, " Usage:\n prog remote add" ));
2547+ // Its own stays where its own goes.
2548+ BOOST_CHECK (at (text, " Options:" ) < at (text, " Global options:" ));
2549+ BOOST_CHECK (has (text, " --leaf" ));
2550+ }
2551+
2552+ // A response file is expanded before the command is looked for, so it may name one.
2553+ BOOST_AUTO_TEST_CASE (test_a_response_file_may_name_a_subcommand) {
2554+ auto path = std::filesystem::temp_directory_path () / " stdc_cli_response_nested.txt" ;
2555+ {
2556+ std::ofstream file (path, std::ios::binary);
2557+ file << " build\n -j\n 4\n target\n " ;
2558+ }
2559+
2560+ Parser parser (Command (" prog" ).addCommand (
2561+ Command (" build" ).addArgument (Argument (" target" )).addOption (Option ({" -j" }, " Jobs" ).arg (" n" ))));
2562+ auto result = ok (parser, {" @" + path.string ()}, Parser::EnableResponseFile);
2563+ BOOST_CHECK (result.commandPath () == std::vector<std::string>({" prog" , " build" }));
2564+ BOOST_CHECK_EQUAL (must (result.value (0 )), " target" );
2565+ BOOST_CHECK_EQUAL (must (result.valueForOption <int >(" -j" )), 4 );
2566+
2567+ std::filesystem::remove (path);
2568+ }
2569+
24842570// Inheritance is from every command above, not only the one directly above.
24852571BOOST_AUTO_TEST_CASE (test_globals_reach_a_grandchild) {
24862572 Parser parser (Command (" prog" )
0 commit comments