From a1f16e6f6b4d4512266c0250626b3eb10e6bed3d Mon Sep 17 00:00:00 2001 From: BodgeMaster <> Date: Fri, 12 Aug 2022 11:50:33 +0200 Subject: [PATCH] lib/cli: Fix the usage text generator not dealing well with absent sections --- src/lib/cli.cpp | 46 +++++++++------ src/test/cli_argument_parser.cpp | 96 +++++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 20 deletions(-) diff --git a/src/lib/cli.cpp b/src/lib/cli.cpp index a8a3fbb..a1d63a1 100644 --- a/src/lib/cli.cpp +++ b/src/lib/cli.cpp @@ -260,10 +260,14 @@ namespace CLI { } std::string ArgumentsParser::getUsage(){ - std::string usageString = "Help: " + this->programName + "\n\n\t" + this->description + "\n\n" + "Usage: " + this->programName; - + std::string usageString = ""; + if (this->description != "") { + usageString += "Help: " + this->programName + "\n\n\t" + this->description + "\n\n"; + } + usageString += "Usage: " + this->programName + " "; + if(!this->flagsByShortName.empty()){ - usageString += " [-"; + usageString += "[-"; } for(const auto& [key, value]: this->flagsByShortName){ @@ -272,7 +276,7 @@ namespace CLI { if(!this->flagsByShortName.empty()){ usageString += "] "; - } + } for(const auto& [key, value]: this->optionsByShortName){ usageString += "[-"; @@ -284,26 +288,34 @@ namespace CLI { usageString += argument.placeholder + " "; } - usageString += "\n\nFlags:\n"; + usageString.push_back('\n'); - for(const auto& [key, value]: this->flagsByShortName){ - usageString += "\t-"; - usageString.push_back(key); - usageString += ", --" + value->longName + "\n\t\t" + value->description + "\n"; + if(!this->flagsByShortName.empty()){ + usageString += "\nFlags:\n"; + + for(const auto& [key, value]: this->flagsByShortName){ + usageString += "\t-"; + usageString.push_back(key); + usageString += ", --" + value->longName + "\n\t\t" + value->description + "\n"; + } } - usageString += "\nOptions:\n"; + if(!this->optionsByShortName.empty()){ + usageString += "\nOptions:\n"; - for(const auto& [key, value]: this->optionsByShortName){ - usageString += "\t-"; - usageString.push_back(key); - usageString += " " + value->placeholder + ", --" + value->longName + "=" + value->placeholder + "\n\t\t" + value->description + "\n"; + for(const auto& [key, value]: this->optionsByShortName){ + usageString += "\t-"; + usageString.push_back(key); + usageString += " " + value->placeholder + ", --" + value->longName + "=" + value->placeholder + "\n\t\t" + value->description + "\n"; + } } - usageString += "\nArguments:\n"; + if(!this->arguments.empty()){ + usageString += "\nArguments:\n"; - for(const auto& argument: this->arguments){ - usageString += "\t" + argument.placeholder + "\n\t\t" + argument.description + "\n"; + for(const auto& argument: this->arguments){ + usageString += "\t" + argument.placeholder + "\n\t\t" + argument.description + "\n"; + } } return usageString; diff --git a/src/test/cli_argument_parser.cpp b/src/test/cli_argument_parser.cpp index 5c502e3..26adc05 100644 --- a/src/test/cli_argument_parser.cpp +++ b/src/test/cli_argument_parser.cpp @@ -23,7 +23,7 @@ int main() { std::cout << "################################################################################" << std::endl; - std::cout << "CLI argument parser tests" << std::endl; + std::cout << "CLI argument parsing tests" << std::endl; std::cout << "################################################################################" << std::endl; // Valid parameter test ############################################ @@ -636,6 +636,11 @@ int main() { delete[] tooManyArgumentsTestParameterList; std::cout << "Passed too many arguments test." << std::endl; + std::cout << "################################################################################" << std::endl; + std::cout << "CLI argument parser help tests" << std::endl; + std::cout << "################################################################################" << std::endl; + + // normal usage with all types of CLI input ######################## std::vector helpTestFlags; helpTestFlags.push_back(CLI::Flag('a', "apple", "throws an apple on Newton's head.")); helpTestFlags.push_back(CLI::Flag('y', "yapple", "throws an yapple on Newton's head.")); @@ -653,8 +658,93 @@ int main() { CLI::ArgumentsParser helpTestParser = CLI::ArgumentsParser(1, helpTestCommand, helpTestFlags, helpTestOptions, helpTestArguments, "Create a universe with a banana and an apple."); ASSERT(helpTestParser.getUsage() == "Help: universecreator\n\n\tCreate a universe with a banana and an apple.\n\nUsage: universecreator [-ay] [-b BANANA] [-c CORNHUBBBBB] WASH SHAKE \n\nFlags:\n\t-a, --apple\n\t\tthrows an apple on Newton's head.\n\t-y, --yapple\n\t\tthrows an yapple on Newton's head.\n\nOptions:\n\t-b BANANA, --banana=BANANA\n\t\tsmack someone with a ripe banana.\n\t-c CORNHUBBBBB, --corn=CORNHUBBBBB\n\t\tvisit cornhub.\n\nArguments:\n\tWASH\n\t\tNumber of times to wash my shark.\n\tSHAKE\n\t\tNumber of times to shake fist at cloud.\n"); - std::cout << "Passed Argument Parser usage test." << std::endl; - + std::cout << "Passed normal usage test." << std::endl; + + // no description ################################################## + std::vector usageOnlyTestFlags; + usageOnlyTestFlags.push_back(CLI::Flag('a', "apple", "throws an apple on Newton's head.")); + usageOnlyTestFlags.push_back(CLI::Flag('y', "yapple", "throws an yapple on Newton's head.")); + + std::vector usageOnlyTestOptions; + usageOnlyTestOptions.push_back(CLI::Option('b', "banana", "BANANA", "smack someone with a ripe banana.")); + usageOnlyTestOptions.push_back(CLI::Option('c', "corn", "CORNHUBBBBB", "visit cornhub.")); + + std::vector usageOnlyTestArguments; + usageOnlyTestArguments.push_back(CLI::Argument("WASH", "Number of times to wash my shark.")); + usageOnlyTestArguments.push_back(CLI::Argument("SHAKE", "Number of times to shake fist at cloud.")); + + const char** usageOnlyTestCommand = new const char*[1]; + usageOnlyTestCommand[0] = "universecreator"; + CLI::ArgumentsParser usageOnlyTestParser = CLI::ArgumentsParser(1, usageOnlyTestCommand, usageOnlyTestFlags, usageOnlyTestOptions, usageOnlyTestArguments); + + ASSERT(usageOnlyTestParser.getUsage() == "Usage: universecreator [-ay] [-b BANANA] [-c CORNHUBBBBB] WASH SHAKE \n\nFlags:\n\t-a, --apple\n\t\tthrows an apple on Newton's head.\n\t-y, --yapple\n\t\tthrows an yapple on Newton's head.\n\nOptions:\n\t-b BANANA, --banana=BANANA\n\t\tsmack someone with a ripe banana.\n\t-c CORNHUBBBBB, --corn=CORNHUBBBBB\n\t\tvisit cornhub.\n\nArguments:\n\tWASH\n\t\tNumber of times to wash my shark.\n\tSHAKE\n\t\tNumber of times to shake fist at cloud.\n"); + std::cout << "Passed normal usage without description test." << std::endl; + + // no flags ######################################################## + std::vector noFlagsFlags; + + std::vector noFlagsOptions; + noFlagsOptions.push_back(CLI::Option('b', "banana", "BANANA", "smack someone with a ripe banana.")); + noFlagsOptions.push_back(CLI::Option('c', "corn", "CORNHUBBBBB", "visit cornhub.")); + + std::vector noFlagsArguments; + noFlagsArguments.push_back(CLI::Argument("WASH", "Number of times to wash my shark.")); + noFlagsArguments.push_back(CLI::Argument("SHAKE", "Number of times to shake fist at cloud.")); + + const char** noFlagsCommand = new const char*[1]; + noFlagsCommand[0] = "universecreator"; + CLI::ArgumentsParser noFlagsParser = CLI::ArgumentsParser(1, noFlagsCommand, noFlagsFlags, noFlagsOptions, noFlagsArguments, "Create a universe with a banana and an apple."); + + ASSERT(noFlagsParser.getUsage() == "Help: universecreator\n\n\tCreate a universe with a banana and an apple.\n\nUsage: universecreator [-b BANANA] [-c CORNHUBBBBB] WASH SHAKE \n\nOptions:\n\t-b BANANA, --banana=BANANA\n\t\tsmack someone with a ripe banana.\n\t-c CORNHUBBBBB, --corn=CORNHUBBBBB\n\t\tvisit cornhub.\n\nArguments:\n\tWASH\n\t\tNumber of times to wash my shark.\n\tSHAKE\n\t\tNumber of times to shake fist at cloud.\n"); + + // no options ###################################################### + std::vector noOptionsFlags; + noOptionsFlags.push_back(CLI::Flag('a', "apple", "throws an apple on Newton's head.")); + noOptionsFlags.push_back(CLI::Flag('y', "yapple", "throws an yapple on Newton's head.")); + + std::vector noOptionsOptions; + + std::vector noOptionsArguments; + noOptionsArguments.push_back(CLI::Argument("WASH", "Number of times to wash my shark.")); + noOptionsArguments.push_back(CLI::Argument("SHAKE", "Number of times to shake fist at cloud.")); + + const char** noOptionsCommand = new const char*[1]; + noOptionsCommand[0] = "universecreator"; + CLI::ArgumentsParser noOptionsParser = CLI::ArgumentsParser(1, noOptionsCommand, noOptionsFlags, noOptionsOptions, noOptionsArguments, "Create a universe with a banana and an apple."); + + ASSERT(noOptionsParser.getUsage() == "Help: universecreator\n\n\tCreate a universe with a banana and an apple.\n\nUsage: universecreator [-ay] WASH SHAKE \n\nFlags:\n\t-a, --apple\n\t\tthrows an apple on Newton's head.\n\t-y, --yapple\n\t\tthrows an yapple on Newton's head.\n\nArguments:\n\tWASH\n\t\tNumber of times to wash my shark.\n\tSHAKE\n\t\tNumber of times to shake fist at cloud.\n"); + + // no arguments #################################################### + std::vector noArgumentsFlags; + noArgumentsFlags.push_back(CLI::Flag('a', "apple", "throws an apple on Newton's head.")); + noArgumentsFlags.push_back(CLI::Flag('y', "yapple", "throws an yapple on Newton's head.")); + + std::vector noArgumentsOptions; + noArgumentsOptions.push_back(CLI::Option('b', "banana", "BANANA", "smack someone with a ripe banana.")); + noArgumentsOptions.push_back(CLI::Option('c', "corn", "CORNHUBBBBB", "visit cornhub.")); + + std::vector noArgumentsArguments; + + const char** noArgumentsCommand = new const char*[1]; + noArgumentsCommand[0] = "universecreator"; + CLI::ArgumentsParser noArgumentsParser = CLI::ArgumentsParser(1, noArgumentsCommand, noArgumentsFlags, noArgumentsOptions, noArgumentsArguments, "Create a universe with a banana and an apple."); + + ASSERT(noArgumentsParser.getUsage() == "Help: universecreator\n\n\tCreate a universe with a banana and an apple.\n\nUsage: universecreator [-ay] [-b BANANA] [-c CORNHUBBBBB] \n\nFlags:\n\t-a, --apple\n\t\tthrows an apple on Newton's head.\n\t-y, --yapple\n\t\tthrows an yapple on Newton's head.\n\nOptions:\n\t-b BANANA, --banana=BANANA\n\t\tsmack someone with a ripe banana.\n\t-c CORNHUBBBBB, --corn=CORNHUBBBBB\n\t\tvisit cornhub.\n"); + std::cout << "Passed absent section usage test." << std::endl; + + // no CLI input #################################################### + std::vector noCLIFlags; + + std::vector noCLIOptions; + + std::vector noCLIArguments; + + const char** noCLICommand = new const char*[1]; + noCLICommand[0] = "universecreator"; + CLI::ArgumentsParser noCLIParser = CLI::ArgumentsParser(1, noCLICommand, noCLIFlags, noCLIOptions, noCLIArguments, "Create a universe with a banana and an apple."); + + ASSERT(noCLIParser.getUsage() == "Help: universecreator\n\n\tCreate a universe with a banana and an apple.\n\nUsage: universecreator \n"); + std::cout << "Passed no CLI input usage test." << std::endl; return 0; }