From 9179f76d11efb2541ebc455728936a7fa6870477 Mon Sep 17 00:00:00 2001 From: captainurist <73941350+captainurist@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:16:45 +0100 Subject: [PATCH] Added Library/Cli --- src/Bin/CodeGen/CMakeLists.txt | 2 +- src/Bin/CodeGen/CodeGenOptions.cpp | 54 +++++------------------- src/Bin/LodTool/CMakeLists.txt | 2 +- src/Bin/LodTool/LodToolOptions.cpp | 22 +++------- src/Bin/OpenEnroth/CMakeLists.txt | 4 +- src/Bin/OpenEnroth/OpenEnrothOptions.cpp | 30 +++++-------- src/Library/CMakeLists.txt | 1 + src/Library/Cli/CMakeLists.txt | 11 +++++ src/Library/Cli/Cli.cpp | 18 ++++++++ src/Library/Cli/Cli.h | 25 +++++++++++ test/Bin/GameTest/CMakeLists.txt | 2 +- test/Bin/GameTest/GameTestOptions.cpp | 17 ++------ test/Bin/UnitTest/CMakeLists.txt | 2 +- 13 files changed, 90 insertions(+), 100 deletions(-) create mode 100644 src/Library/Cli/CMakeLists.txt create mode 100644 src/Library/Cli/Cli.cpp create mode 100644 src/Library/Cli/Cli.h diff --git a/src/Bin/CodeGen/CMakeLists.txt b/src/Bin/CodeGen/CMakeLists.txt index e1fb09353be7..2227cc7ab73d 100644 --- a/src/Bin/CodeGen/CMakeLists.txt +++ b/src/Bin/CodeGen/CMakeLists.txt @@ -13,6 +13,6 @@ set(BIN_CODEGEN_HEADERS if(NOT BUILD_PLATFORM STREQUAL "android") add_executable(CodeGen ${BIN_CODEGEN_SOURCES} ${BIN_CODEGEN_HEADERS}) - target_link_libraries(CodeGen PUBLIC platform_main application CLI11::CLI11) + target_link_libraries(CodeGen PUBLIC platform_main application library_cli) target_check_style(CodeGen) endif() diff --git a/src/Bin/CodeGen/CodeGenOptions.cpp b/src/Bin/CodeGen/CodeGenOptions.cpp index 54105cd8bc78..1b04b82ec3fe 100644 --- a/src/Bin/CodeGen/CodeGenOptions.cpp +++ b/src/Bin/CodeGen/CodeGenOptions.cpp @@ -2,62 +2,28 @@ #include -#include - -#include "Utility/Format.h" +#include "Library/Cli/Cli.h" CodeGenOptions CodeGenOptions::parse(int argc, char **argv) { CodeGenOptions result; result.useConfig = false; // CodeGen doesn't use external config. result.logLevel = LOG_CRITICAL; // CodeGen doesn't need logging. - std::unique_ptr app = std::make_unique(); + std::unique_ptr app = std::make_unique(); app->add_option("--data-path", result.dataPath, "Path to game data dir")->check(CLI::ExistingDirectory)->option_text("PATH"); app->set_help_flag("-h,--help", "Print help and exit."); app->require_subcommand(); - CLI::App *items = app->add_subcommand("items", "Generate item ids enum.")->fallthrough(); - items->callback([&] { result.subcommand = SUBCOMMAND_ITEM_ID; }); - - CLI::App *maps = app->add_subcommand("maps", "Generate map ids enum.")->fallthrough(); - maps->callback([&] { result.subcommand = SUBCOMMAND_MAP_ID; }); - - CLI::App *beacons = app->add_subcommand("beacons", "Generate beacons mapping.")->fallthrough(); - beacons->callback([&] { result.subcommand = SUBCOMMAND_BEACON_MAPPING; }); - - CLI::App *houses = app->add_subcommand("houses", "Generate house ids enum.")->fallthrough(); - houses->callback([&] { result.subcommand = SUBCOMMAND_HOUSE_ID; }); - - CLI::App *monsters = app->add_subcommand("monsters", "Generate monster ids enum")->fallthrough(); - monsters->callback([&] { result.subcommand = SUBCOMMAND_MONSTER_ID; }); - - CLI::App *monsterTypes = app->add_subcommand("monster_types", "Generate monster types enum")->fallthrough(); - monsterTypes->callback([&] { result.subcommand = SUBCOMMAND_MONSTER_TYPE; }); - - CLI::App *bountyHunt = app->add_subcommand("bounty_hunt", "Generate monster type / town hall table for bounty hunts")->fallthrough(); - bountyHunt->callback([&] { result.subcommand = SUBCOMMAND_BOUNTY_HUNT; }); - - try { - app->parse(argc, argv); - } catch (const CLI::ParseError &e) { - // TODO(captainurist): this is getting out of hand. - bool isHelp = - app->get_help_ptr()->as() || - items->get_help_ptr()->as() || - maps->get_help_ptr()->as() || - beacons->get_help_ptr()->as() || - monsters->get_help_ptr()->as() || - monsterTypes->get_help_ptr()->as() || - bountyHunt->get_help_ptr()->as(); - if (isHelp) { - app->exit(e); - result.helpPrinted = true; - } else { - throw; // Genuine parse error => propagate. - } - } + app->add_subcommand("items", "Generate item ids enum.", result.subcommand, SUBCOMMAND_ITEM_ID)->fallthrough(); + app->add_subcommand("maps", "Generate map ids enum.", result.subcommand, SUBCOMMAND_MAP_ID)->fallthrough(); + app->add_subcommand("beacons", "Generate beacons mapping.", result.subcommand, SUBCOMMAND_BEACON_MAPPING)->fallthrough(); + app->add_subcommand("houses", "Generate house ids enum.", result.subcommand, SUBCOMMAND_HOUSE_ID)->fallthrough(); + app->add_subcommand("monsters", "Generate monster ids enum.", result.subcommand, SUBCOMMAND_MONSTER_ID)->fallthrough(); + app->add_subcommand("monster_types", "Generate monster types enum.", result.subcommand, SUBCOMMAND_MONSTER_TYPE)->fallthrough(); + app->add_subcommand("bounty_hunt", "Generate monster type / town hall table for bounty hunts.", result.subcommand, SUBCOMMAND_BOUNTY_HUNT)->fallthrough(); + app->parse(argc, argv, result.helpPrinted); return result; } diff --git a/src/Bin/LodTool/CMakeLists.txt b/src/Bin/LodTool/CMakeLists.txt index f1282852a1c5..d99c8bd4ac32 100644 --- a/src/Bin/LodTool/CMakeLists.txt +++ b/src/Bin/LodTool/CMakeLists.txt @@ -9,6 +9,6 @@ set(BIN_LODTOOL_HEADERS if(NOT BUILD_PLATFORM STREQUAL "android") add_executable(LodTool ${BIN_LODTOOL_SOURCES} ${BIN_LODTOOL_HEADERS}) - target_link_libraries(LodTool PUBLIC library_lod library_lodformats CLI11::CLI11) + target_link_libraries(LodTool PUBLIC library_lod library_lodformats library_cli) target_check_style(LodTool) endif() diff --git a/src/Bin/LodTool/LodToolOptions.cpp b/src/Bin/LodTool/LodToolOptions.cpp index e29cecffc4e5..523b6ec4b4d1 100644 --- a/src/Bin/LodTool/LodToolOptions.cpp +++ b/src/Bin/LodTool/LodToolOptions.cpp @@ -2,30 +2,18 @@ #include -#include +#include "Library/Cli/Cli.h" LodToolOptions LodToolOptions::parse(int argc, char **argv) { LodToolOptions result; - std::unique_ptr app = std::make_unique(); + std::unique_ptr app = std::make_unique(); app->set_help_flag("-h,--help", "Print help and exit."); + app->require_subcommand(); - CLI::App *dump = app->add_subcommand("dump", "Dump a lod file.")->fallthrough(); + CLI::App *dump = app->add_subcommand("dump", "Dump a lod file.", result.subcommand, SUBCOMMAND_DUMP)->fallthrough(); dump->add_option("LOD", result.lodPath, "Path to lod file.")->check(CLI::ExistingFile)->required()->option_text(" "); - dump->callback([&] { - result.subcommand = SUBCOMMAND_DUMP; - }); - - try { - app->parse(argc, argv); - } catch (const CLI::ParseError &e) { - if (app->get_help_ptr()->as() || dump->get_help_ptr()->as()) { - app->exit(e); - result.helpPrinted = true; - } else { - throw; // Genuine parse error => propagate. - } - } + app->parse(argc, argv, result.helpPrinted); return result; } diff --git a/src/Bin/OpenEnroth/CMakeLists.txt b/src/Bin/OpenEnroth/CMakeLists.txt index a38f5316f65d..63a5f2f1553a 100644 --- a/src/Bin/OpenEnroth/CMakeLists.txt +++ b/src/Bin/OpenEnroth/CMakeLists.txt @@ -11,13 +11,13 @@ if(BUILD_PLATFORM STREQUAL "android") add_library(main SHARED) target_sources(main PUBLIC ${BIN_OPENENROTH_HEADERS} ${BIN_OPENENROTH_SOURCES}) target_check_style(main) - target_link_libraries(main PUBLIC platform_main application CLI11::CLI11) + target_link_libraries(main PUBLIC platform_main application library_cli) target_link_options(main PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libmain.map") else() add_executable(OpenEnroth MACOSX_BUNDLE) target_sources(OpenEnroth PUBLIC ${BIN_OPENENROTH_HEADERS} ${BIN_OPENENROTH_SOURCES}) target_check_style(OpenEnroth) - target_link_libraries(OpenEnroth PUBLIC platform_main application CLI11::CLI11) + target_link_libraries(OpenEnroth PUBLIC platform_main application library_cli) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT OpenEnroth) endif() diff --git a/src/Bin/OpenEnroth/OpenEnrothOptions.cpp b/src/Bin/OpenEnroth/OpenEnrothOptions.cpp index 53ad852e2cd3..aefec5fe3af1 100644 --- a/src/Bin/OpenEnroth/OpenEnrothOptions.cpp +++ b/src/Bin/OpenEnroth/OpenEnrothOptions.cpp @@ -2,16 +2,16 @@ #include -#include - -#include "Utility/Format.h" - #include "Application/GamePathResolver.h" #include "Application/GameConfig.h" // For PlatformLogLevel serialization. +#include "Library/Cli/Cli.h" + +#include "Utility/Format.h" + OpenEnrothOptions OpenEnrothOptions::parse(int argc, char **argv) { OpenEnrothOptions result; - std::unique_ptr app = std::make_unique(); + std::unique_ptr app = std::make_unique(); app->add_option( "--data-path", result.dataPath, @@ -30,24 +30,14 @@ OpenEnrothOptions OpenEnrothOptions::parse(int argc, char **argv) { "Set log level to 'verbose'."); app->set_help_flag("-h,--help", "Print help and exit."); - CLI::App *retrace = app->add_subcommand("retrace", "Retrace traces and exit.")->fallthrough(); + CLI::App *retrace = app->add_subcommand("retrace", "Retrace traces and exit.", result.subcommand, SUBCOMMAND_RETRACE)->fallthrough(); retrace->add_option("TRACE", result.retrace.traces, "Path to trace file(s) to retrace.")->check(CLI::ExistingFile)->required()->option_text("..."); - retrace->callback([&] { + + app->parse(argc, argv, result.helpPrinted); + + if (result.subcommand == SUBCOMMAND_RETRACE) result.useConfig = false; // Don't use external config if retracing. - result.subcommand = SUBCOMMAND_RETRACE; - }); - - try { - app->parse(argc, argv); - } catch (const CLI::ParseError &e) { - if (app->get_help_ptr()->as() || retrace->get_help_ptr()->as()) { - app->exit(e); - result.helpPrinted = true; - } else { - throw; // Genuine parse error => propagate. - } - } return result; } diff --git a/src/Library/CMakeLists.txt b/src/Library/CMakeLists.txt index a9c217d41b7c..ebbf506177c6 100644 --- a/src/Library/CMakeLists.txt +++ b/src/Library/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.24 FATAL_ERROR) add_subdirectory(Application) add_subdirectory(Binary) add_subdirectory(BuildInfo) +add_subdirectory(Cli) add_subdirectory(Color) add_subdirectory(Compression) add_subdirectory(Config) diff --git a/src/Library/Cli/CMakeLists.txt b/src/Library/Cli/CMakeLists.txt new file mode 100644 index 000000000000..eff4b2735212 --- /dev/null +++ b/src/Library/Cli/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.21.1) + +set(LIBRARY_CLI_SOURCES + Cli.cpp) + +set(LIBRARY_CLI_HEADERS + Cli.h) + +add_library(library_cli ${LIBRARY_CLI_SOURCES} ${LIBRARY_CLI_HEADERS}) +target_link_libraries(library_cli CLI11::CLI11) +target_check_style(library_cli) diff --git a/src/Library/Cli/Cli.cpp b/src/Library/Cli/Cli.cpp new file mode 100644 index 000000000000..083a8c0aa17b --- /dev/null +++ b/src/Library/Cli/Cli.cpp @@ -0,0 +1,18 @@ +#include "Cli.h" + +void CliApp::parse(int argc, const char *const *argv, bool &helpPrinted) { + // Note: we don't call base_type::ensure_utf8 here, it's the caller's responsibility. + try { + base_type::parse(argc, argv); + } catch (const CLI::CallForHelp &e) { + base_type::exit(e); + helpPrinted = true; + } catch (const CLI::CallForAllHelp &e) { + base_type::exit(e); + helpPrinted = true; + } catch (const CLI::CallForVersion &e) { + base_type::exit(e); + helpPrinted = true; + } + // Other exception are propagated. +} diff --git a/src/Library/Cli/Cli.h b/src/Library/Cli/Cli.h new file mode 100644 index 000000000000..83452cbbfc29 --- /dev/null +++ b/src/Library/Cli/Cli.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include + +/** + * A thin wrapper around `CLI:App` that simplifies the code for our use cases. + */ +class CliApp : public CLI::App { + using base_type = CLI::App; +public: + CliApp() = default; + + template + CliApp *add_subcommand(std::string name, std::string description, SubcommandEnum &target, SubcommandEnum targetValue) { + auto callback = [target = &target, targetValue] { + *target = targetValue; + }; + return static_cast(base_type::add_subcommand(std::move(name), std::move(description))->callback(std::move(callback))); + } + + void parse(int argc, const char *const *argv, bool &helpPrinted); +}; diff --git a/test/Bin/GameTest/CMakeLists.txt b/test/Bin/GameTest/CMakeLists.txt index aa65157b4ad8..efd3ce63e30b 100644 --- a/test/Bin/GameTest/CMakeLists.txt +++ b/test/Bin/GameTest/CMakeLists.txt @@ -9,7 +9,7 @@ if(OE_BUILD_TESTS) GameTestOptions.h) add_executable(OpenEnroth_GameTest ${GAME_TEST_MAIN_SOURCES} ${GAME_TEST_MAIN_HEADERS}) - target_link_libraries(OpenEnroth_GameTest PUBLIC platform_main application testing_game GTest::gtest CLI11::CLI11) + target_link_libraries(OpenEnroth_GameTest PUBLIC platform_main application testing_game library_cli GTest::gtest) target_check_style(OpenEnroth_GameTest) diff --git a/test/Bin/GameTest/GameTestOptions.cpp b/test/Bin/GameTest/GameTestOptions.cpp index c90d886b0ddc..3a7a6c208b3f 100644 --- a/test/Bin/GameTest/GameTestOptions.cpp +++ b/test/Bin/GameTest/GameTestOptions.cpp @@ -2,7 +2,7 @@ #include -#include +#include "Library/Cli/Cli.h" #include "Application/GamePathResolver.h" @@ -12,7 +12,7 @@ GameTestOptions GameTestOptions::parse(int argc, char **argv) { result.useConfig = false; // Tests don't need an external config. std::optional testPath; - std::unique_ptr app = std::make_unique(); + std::unique_ptr app = std::make_unique(); std::string requiredOptions = "Required Options"; std::string otherOptions = "Other Options"; @@ -28,20 +28,11 @@ GameTestOptions GameTestOptions::parse(int argc, char **argv) { app->set_help_flag("-h,--help", "Print help and exit.")->group(otherOptions); app->allow_extras(); - try { - app->parse(argc, argv); - } catch (const CLI::ParseError &e) { - if (app->get_help_ptr()->as()) { - app->exit(e); - result.helpPrinted = true; - } else { - throw; // Genuine parse error => propagate. - } - } + app->parse(argc, argv, result.helpPrinted); if (!result.listRequested && !result.helpPrinted && !testPath) throw CLI::RequiredError(testPathOption->get_name()); - result.testPath = testPath.value_or(""); + return result; } diff --git a/test/Bin/UnitTest/CMakeLists.txt b/test/Bin/UnitTest/CMakeLists.txt index b7c040c12327..02a81c68fd9a 100644 --- a/test/Bin/UnitTest/CMakeLists.txt +++ b/test/Bin/UnitTest/CMakeLists.txt @@ -5,7 +5,7 @@ if(OE_BUILD_TESTS) UnitTestMain.cpp) add_executable(OpenEnroth_UnitTest ${UNIT_TEST_MAIN_SOURCES}) - target_link_libraries(OpenEnroth_UnitTest PUBLIC testing_unit CLI11::CLI11) + target_link_libraries(OpenEnroth_UnitTest PUBLIC testing_unit) add_custom_target(UnitTest OpenEnroth_UnitTest DEPENDS OpenEnroth_UnitTest