tools: Implement new "toollib"
This essentially is a even-more refactored version of the new eupak "Task" setup, but done in a generic way, so multiple tools can be written like eupak easily, and I Don't Need to Duplicate It Every Time. This commit also comes with a new test binary, "toollib_test". This will be removed once it's no longer needed and I'm done sketching out the API set (and eupak is refactored to use toollib instead), but for now this provides a two-way thing: - Allows me to hack on toollib without breaking everything - Shows how to use toollib.
This commit is contained in:
parent
6a565e3244
commit
29a78724e8
9 changed files with 341 additions and 6 deletions
|
@ -7,11 +7,14 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
function(europa_target target)
|
function(europa_target target)
|
||||||
# Set binary products to output in the build directory for easier access
|
# Set binary products to output in the build directory for easier access
|
||||||
set_target_properties(
|
set_target_properties(
|
||||||
${target} PROPERTIES
|
${target} PROPERTIES
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}"
|
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Require C++20
|
||||||
|
target_compile_features(${target} PUBLIC cxx_std_20)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(europa_set_alternate_linker)
|
function(europa_set_alternate_linker)
|
||||||
|
|
|
@ -20,4 +20,21 @@ add_executable(jsfscramble jsfscramble.cpp)
|
||||||
target_link_libraries(jsfscramble PUBLIC
|
target_link_libraries(jsfscramble PUBLIC
|
||||||
europa
|
europa
|
||||||
)
|
)
|
||||||
europa_target(jsfscramble)
|
europa_target(jsfscramble)
|
||||||
|
|
||||||
|
# Toollib
|
||||||
|
add_subdirectory(toollib)
|
||||||
|
|
||||||
|
# Temporary test target.
|
||||||
|
add_executable(toollib_test
|
||||||
|
toollib_test.cpp
|
||||||
|
toollib_test_cmd.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(toollib_test PUBLIC
|
||||||
|
europa
|
||||||
|
toollib
|
||||||
|
)
|
||||||
|
|
||||||
|
europa_target(toollib_test)
|
||||||
|
|
||||||
|
|
13
src/tools/toollib/CMakeLists.txt
Normal file
13
src/tools/toollib/CMakeLists.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
add_library(toollib
|
||||||
|
ToolCommand.cpp
|
||||||
|
ToolMain.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(toollib PUBLIC
|
||||||
|
argparse::argparse
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(toollib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
europa_target(toollib)
|
58
src/tools/toollib/ToolCommand.cpp
Normal file
58
src/tools/toollib/ToolCommand.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
//
|
||||||
|
// EuropaTools
|
||||||
|
//
|
||||||
|
// (C) 2021-2025 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <toollib/ToolCommand.hpp>
|
||||||
|
|
||||||
|
namespace tool {
|
||||||
|
|
||||||
|
IToolCommandObjectCreator* pToolCommandListHead = nullptr;
|
||||||
|
|
||||||
|
void ToolCommandFactory::RegisterToolCommand(IToolCommandObjectCreator* pCreate) {
|
||||||
|
if(pToolCommandListHead != nullptr) {
|
||||||
|
// Find the first linked creator with a
|
||||||
|
// nullptr next link (the end of the list).
|
||||||
|
//
|
||||||
|
// Once we do, attach the command creator
|
||||||
|
auto* p = pToolCommandListHead;
|
||||||
|
while(p->next != nullptr) {
|
||||||
|
p = p->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->next = pCreate;
|
||||||
|
} else {
|
||||||
|
pToolCommandListHead = pCreate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IToolCommandObjectCreator* FindCreator(const std::string& name) {
|
||||||
|
if(!pToolCommandListHead)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto* p = pToolCommandListHead;
|
||||||
|
while(p != nullptr) {
|
||||||
|
if(p->name == name)
|
||||||
|
return p;
|
||||||
|
|
||||||
|
p = p->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<IToolCommand> ToolCommandFactory::CreateNamed(const std::string& name) {
|
||||||
|
if(auto pCreator = FindCreator(name); pCreator) {
|
||||||
|
auto cmd = pCreator->Create();
|
||||||
|
return cmd;
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error(std::format("Invalid tool command \"{}\"", name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tool
|
52
src/tools/toollib/ToolMain.cpp
Normal file
52
src/tools/toollib/ToolMain.cpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// EuropaTools
|
||||||
|
//
|
||||||
|
// (C) 2021-2025 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <toollib/ToolCommand.hpp>
|
||||||
|
#include <toollib/ToolMain.hpp>
|
||||||
|
|
||||||
|
namespace tool {
|
||||||
|
int ToolMain(const ToolInfo& toolInfo, const ToolMainInput& mainInput) {
|
||||||
|
// :( Again, FUCK argparse.
|
||||||
|
argparse::ArgumentParser parser(std::string(toolInfo.name), std::string(toolInfo.version));
|
||||||
|
parser.add_description(std::string(toolInfo.description));
|
||||||
|
|
||||||
|
// Add commands to this parser
|
||||||
|
for(auto& toolCmds : mainInput.toolCommands) {
|
||||||
|
toolCmds->Init(parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// No command was specified, display the help and then exit with a failure code.
|
||||||
|
// For some reason the `argparse` library does not have something like this on its own.
|
||||||
|
//
|
||||||
|
// I guess it's simple though so I can't really complain that much
|
||||||
|
if(mainInput.argc == 1) {
|
||||||
|
auto s = parser.help();
|
||||||
|
printf("%s\n", s.str().c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.parse_args(mainInput.argc, mainInput.argv);
|
||||||
|
} catch(std::runtime_error& error) {
|
||||||
|
std::cout << error.what() << '\n'
|
||||||
|
<< parser;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& toolCmds : mainInput.toolCommands) {
|
||||||
|
if(toolCmds->ShouldRun(parser)) {
|
||||||
|
if(auto res = toolCmds->Parse(); res != 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
return toolCmds->Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace tool
|
77
src/tools/toollib/toollib/ToolCommand.hpp
Normal file
77
src/tools/toollib/toollib/ToolCommand.hpp
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
//
|
||||||
|
// EuropaTools
|
||||||
|
//
|
||||||
|
// (C) 2021-2025 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <argparse/argparse.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace tool {
|
||||||
|
|
||||||
|
/// Base-class for all ToolLib tasks.
|
||||||
|
struct IToolCommand {
|
||||||
|
virtual ~IToolCommand() = default;
|
||||||
|
|
||||||
|
/// Do any creation-time initalizatiion.
|
||||||
|
virtual void Init(argparse::ArgumentParser& parentParser) = 0;
|
||||||
|
|
||||||
|
/// Query if this task has been selected
|
||||||
|
virtual bool ShouldRun(argparse::ArgumentParser& parentParser) const = 0;
|
||||||
|
|
||||||
|
/// Parse arguments from the user
|
||||||
|
virtual int Parse() = 0;
|
||||||
|
|
||||||
|
/// Run the task.
|
||||||
|
virtual int Run() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IToolCommandObjectCreator {
|
||||||
|
virtual ~IToolCommandObjectCreator() = default;
|
||||||
|
|
||||||
|
/// Creates the IToolCommand.
|
||||||
|
virtual std::shared_ptr<IToolCommand> Create() = 0;
|
||||||
|
|
||||||
|
// dont touch :)
|
||||||
|
IToolCommandObjectCreator* next;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates IToolCommand instances for clients.
|
||||||
|
struct ToolCommandFactory {
|
||||||
|
using FactoryMethod = std::shared_ptr<IToolCommand> (*)();
|
||||||
|
|
||||||
|
/// Creates a task.
|
||||||
|
static std::shared_ptr<IToolCommand> CreateNamed(const std::string& name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class T>
|
||||||
|
friend struct ToolCommandRegister;
|
||||||
|
|
||||||
|
static void RegisterToolCommand(IToolCommandObjectCreator* pCreate);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Helper template to register into the [ToolCommandFactory].
|
||||||
|
template <class T>
|
||||||
|
struct ToolCommandRegister : IToolCommandObjectCreator {
|
||||||
|
ToolCommandRegister(const std::string& name) {
|
||||||
|
static_assert(std::is_base_of_v<IToolCommand, T>, "what you doing sir.");
|
||||||
|
this->name = name;
|
||||||
|
ToolCommandFactory::RegisterToolCommand(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<IToolCommand> Create() override {
|
||||||
|
return std::make_shared<T>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Registers a tool command.
|
||||||
|
/// Should be put in the .cpp implementation source file of the tool command itself.
|
||||||
|
#define TOOLLIB_REGISTER_TOOLCOMMAND(Name, TTask) \
|
||||||
|
static ::tool::ToolCommandRegister<TTask> __register__##TTask(Name)
|
||||||
|
|
||||||
|
} // namespace tool
|
38
src/tools/toollib/toollib/ToolMain.hpp
Normal file
38
src/tools/toollib/toollib/ToolMain.hpp
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// EuropaTools
|
||||||
|
//
|
||||||
|
// (C) 2021-2025 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace tool {
|
||||||
|
|
||||||
|
struct IToolCommand;
|
||||||
|
|
||||||
|
struct ToolInfo {
|
||||||
|
std::string_view name; // "Eupak"
|
||||||
|
std::string_view version; // v1.0.0
|
||||||
|
std::string_view description; // "bla"
|
||||||
|
// FIXME: (authors?)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ToolMainInput {
|
||||||
|
/// Tool commands to run.
|
||||||
|
std::span<std::shared_ptr<IToolCommand>> toolCommands;
|
||||||
|
|
||||||
|
// C arguments
|
||||||
|
int argc;
|
||||||
|
char** argv;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The shared toollib main. When in doubt, use this.
|
||||||
|
int ToolMain(const ToolInfo& toolInfo, const ToolMainInput& mainInput);
|
||||||
|
|
||||||
|
} // namespace tool
|
25
src/tools/toollib_test.cpp
Normal file
25
src/tools/toollib_test.cpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
|
||||||
|
// Test/excercise of new toollib system.
|
||||||
|
|
||||||
|
#include <argparse/argparse.hpp>
|
||||||
|
#include <toollib/ToolCommand.hpp>
|
||||||
|
#include <toollib/ToolMain.hpp>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
auto tasks = std::vector {
|
||||||
|
tool::ToolCommandFactory::CreateNamed("test")
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
return tool::ToolMain(tool::ToolInfo {
|
||||||
|
.name = "toollib_test",
|
||||||
|
.version = "0.0.1",
|
||||||
|
.description = "A test/excercise of the new toollib APIs."
|
||||||
|
}, tool::ToolMainInput {
|
||||||
|
.toolCommands = tasks,
|
||||||
|
.argc = argc,
|
||||||
|
.argv = argv
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
}
|
52
src/tools/toollib_test_cmd.cpp
Normal file
52
src/tools/toollib_test_cmd.cpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
#include <toollib/ToolCommand.hpp>
|
||||||
|
|
||||||
|
#include "argparse/argparse.hpp"
|
||||||
|
|
||||||
|
struct TestCmd : tool::IToolCommand {
|
||||||
|
TestCmd()
|
||||||
|
: parser("test", "", argparse::default_arguments::help) {
|
||||||
|
// clang-format off
|
||||||
|
parser
|
||||||
|
.add_description("Does something cool");
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME: Probably just print this always, in a thinner format, but use
|
||||||
|
// the existing thicker format for verbosity.
|
||||||
|
parser
|
||||||
|
.add_argument("--verbose")
|
||||||
|
.help("Increase information output verbosity")
|
||||||
|
.default_value(false)
|
||||||
|
.implicit_value(true);
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init(argparse::ArgumentParser& parent) override {
|
||||||
|
parent.add_subparser(parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShouldRun(argparse::ArgumentParser& parent) const override {
|
||||||
|
return parent.is_subcommand_used("test");
|
||||||
|
}
|
||||||
|
|
||||||
|
int Parse() override {
|
||||||
|
verbose = parser.get<bool>("--verbose");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Run() override {
|
||||||
|
std::printf("Pretend this does something useful\n");
|
||||||
|
|
||||||
|
if(verbose)
|
||||||
|
std::printf("I printed you a cake! it might be a lie thogh x3\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
argparse::ArgumentParser parser;
|
||||||
|
|
||||||
|
// Parsed arguments
|
||||||
|
bool verbose = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
TOOLLIB_REGISTER_TOOLCOMMAND("test", TestCmd);
|
Loading…
Reference in a new issue