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
|
@ -12,6 +12,9 @@ function(europa_target target)
|
|||
${target} PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}"
|
||||
)
|
||||
|
||||
# Require C++20
|
||||
target_compile_features(${target} PUBLIC cxx_std_20)
|
||||
endfunction()
|
||||
|
||||
function(europa_set_alternate_linker)
|
||||
|
|
|
@ -21,3 +21,20 @@ target_link_libraries(jsfscramble PUBLIC
|
|||
europa
|
||||
)
|
||||
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