tools/eupak: Heavily refactor tasks.
This starts the beginning of probably one of the bigger refactor jobs, and one I've been hinting at and FIXME jabbing for a while now. Basically, tasks now are a lot closer coupled to their arguments. This is a good thing since it means that changing or adding features only requires modifying the task, not needlessly modifying main over and over. CreateTask is the only one that works currently for this commit and has been converted to the new ITask model.
This commit is contained in:
parent
447b134118
commit
7fee38be55
7 changed files with 238 additions and 80 deletions
|
@ -12,6 +12,7 @@ add_executable(eupak
|
|||
Utils.cpp
|
||||
|
||||
# Tasks
|
||||
tasks/Task.cpp
|
||||
tasks/InfoTask.cpp
|
||||
tasks/CreateTask.cpp
|
||||
tasks/ExtractTask.cpp
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <tasks/InfoTask.hpp>
|
||||
|
||||
#include "europa/structs/Pak.hpp"
|
||||
#include "tasks/Task.hpp"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// FIXME: At some point we should just have task classes register their arguments
|
||||
|
@ -50,35 +51,12 @@ int main(int argc, char** argv) {
|
|||
.default_value(false)
|
||||
.implicit_value(true);
|
||||
|
||||
argparse::ArgumentParser createParser("create", EUPAK_VERSION_STR, argparse::default_arguments::help);
|
||||
createParser.add_description("Create a package file.");
|
||||
createParser.add_argument("-d", "--directory")
|
||||
.required()
|
||||
.metavar("DIRECTORY")
|
||||
.help("Directory to create archive from");
|
||||
|
||||
createParser.add_argument("-V", "--archive-version")
|
||||
.default_value("starfighter")
|
||||
.help(R"(Output archive version. Either "pmdl", "starfighter" or "jedistarfighter".)")
|
||||
.metavar("VERSION");
|
||||
|
||||
createParser.add_argument("-s", "--sector-aligned")
|
||||
.help(R"(Aligns all files in this new package to CD-ROM sector boundaries. Only valid for -V jedistarfighter.)")
|
||||
.flag();
|
||||
|
||||
createParser.add_argument("output")
|
||||
.required()
|
||||
.help("Output archive")
|
||||
.metavar("ARCHIVE");
|
||||
|
||||
createParser.add_argument("--verbose")
|
||||
.help("Increase creation output verbosity")
|
||||
.default_value(false)
|
||||
.implicit_value(true);
|
||||
|
||||
parser.add_subparser(infoParser);
|
||||
parser.add_subparser(extractParser);
|
||||
parser.add_subparser(createParser);
|
||||
|
||||
auto tasks = std::vector {
|
||||
eupak::tasks::TaskFactory::CreateNamed("create", parser)
|
||||
};
|
||||
|
||||
try {
|
||||
// No command was specified, display the help and then exit with a failure code.
|
||||
|
@ -100,6 +78,17 @@ int main(int argc, char** argv) {
|
|||
|
||||
// Run the given task
|
||||
|
||||
for(auto task : tasks) {
|
||||
if(task->ShouldRun(parser)) {
|
||||
if(auto res = task->Parse(); res != 0)
|
||||
return res;
|
||||
|
||||
return task->Run();
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
if(parser.is_subcommand_used("extract")) {
|
||||
eupak::tasks::ExtractTask task;
|
||||
eupak::tasks::ExtractTask::Arguments args;
|
||||
|
@ -132,52 +121,15 @@ int main(int argc, char** argv) {
|
|||
return task.Run(std::move(args));
|
||||
}
|
||||
|
||||
// FIXME: At some point for accurate rebuilds we should also accept a JSON manifest file
|
||||
// that contains: Package version, sector alignment, package build time, order of all files (as original) and their modtime, so on.
|
||||
// Then a user can just do `eupak create --manifest manifest.json` and it'll all be figured out
|
||||
// (I have not dreamt up the schema for this yet and this relies on other FIXMEs being done so this will have to wait.)
|
||||
|
||||
if(parser.is_subcommand_used("create")) {
|
||||
eupak::tasks::CreateTask task;
|
||||
eupak::tasks::CreateTask::Arguments args;
|
||||
|
||||
args.verbose = createParser.get<bool>("--verbose");
|
||||
args.inputDirectory = eupak::fs::path(createParser.get("--directory"));
|
||||
args.outputFile = eupak::fs::path(createParser.get("output"));
|
||||
|
||||
if(createParser.is_used("--archive-version")) {
|
||||
const auto& versionStr = createParser.get("--archive-version");
|
||||
|
||||
if(versionStr == "pmdl") {
|
||||
args.pakVersion = europa::structs::PakVersion::Ver3;
|
||||
} else if(versionStr == "starfighter") {
|
||||
args.pakVersion = europa::structs::PakVersion::Ver4;
|
||||
} else if(versionStr == "jedistarfighter") {
|
||||
args.pakVersion = europa::structs::PakVersion::Ver5;
|
||||
} else {
|
||||
std::cout << "Error: Invalid version \"" << versionStr << "\"\n"
|
||||
<< createParser;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
args.pakVersion = europa::structs::PakVersion::Ver4;
|
||||
}
|
||||
|
||||
args.sectorAligned = createParser.get<bool>("--sector-aligned");
|
||||
|
||||
if(args.sectorAligned && args.pakVersion != eupak::estructs::PakVersion::Ver5) {
|
||||
std::cout << "Error: --sector-aligned is only valid for creating a package with \"-V jedistarfighter\".\n"
|
||||
<< createParser;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!eupak::fs::is_directory(args.inputDirectory)) {
|
||||
std::cout << "Error: Provided input isn't a directory\n"
|
||||
<< createParser;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return task.Run(std::move(args));
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
#include <chrono>
|
||||
#include <EupakConfig.hpp>
|
||||
#include <europa/io/PakWriter.hpp>
|
||||
#include <fstream>
|
||||
#include <indicators/cursor_control.hpp>
|
||||
|
@ -15,7 +16,10 @@
|
|||
#include <tasks/CreateTask.hpp>
|
||||
#include <Utils.hpp>
|
||||
|
||||
#include "argparse/argparse.hpp"
|
||||
#include "europa/io/PakFile.hpp"
|
||||
#include "europa/structs/Pak.hpp"
|
||||
#include "tasks/Task.hpp"
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
|
@ -77,7 +81,105 @@ namespace eupak::tasks {
|
|||
};
|
||||
};
|
||||
|
||||
int CreateTask::Run(Arguments&& args) {
|
||||
std::optional<estructs::PakVersion> ParsePakVersion(const std::string& str) {
|
||||
// FIXME: PMDL should be "starfighter-prerelease"
|
||||
if(str == "pmdl") {
|
||||
return estructs::PakVersion::Ver3;
|
||||
} else if(str == "starfighter") {
|
||||
return estructs::PakVersion::Ver4;
|
||||
} else if(str == "jedistarfighter") {
|
||||
return estructs::PakVersion::Ver5;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
CreateTask::CreateTask()
|
||||
: parser("create", EUPAK_VERSION_STR, argparse::default_arguments::help) {
|
||||
// Setup argparse
|
||||
// clang-format off
|
||||
parser.add_description("Create a package file.");
|
||||
parser.add_argument("-d", "--directory")
|
||||
.required()
|
||||
.metavar("DIRECTORY")
|
||||
.help("Directory to create archive from");
|
||||
|
||||
parser.add_argument("-V", "--archive-version")
|
||||
.default_value("starfighter")
|
||||
.help(R"(Output archive version. Either "pmdl", "starfighter" or "jedistarfighter".)")
|
||||
.metavar("VERSION");
|
||||
|
||||
parser.add_argument("-s", "--sector-aligned")
|
||||
.help(R"(Aligns all files in this new package to CD-ROM sector boundaries. Only valid for -V jedistarfighter.)")
|
||||
.flag();
|
||||
|
||||
parser.add_argument("output")
|
||||
.required()
|
||||
.help("Output archive")
|
||||
.metavar("ARCHIVE");
|
||||
|
||||
parser.add_argument("--verbose")
|
||||
.help("Increase creation output verbosity")
|
||||
.default_value(false)
|
||||
.implicit_value(true);
|
||||
|
||||
// FIXME: At some point for accurate rebuilds we should also accept a JSON manifest file
|
||||
// that contains: Package version, sector alignment, package build time, order of all files (as original) and their modtime, so on.
|
||||
// Then a user can just do `eupak create --manifest manifest.json` and it'll all be figured out
|
||||
// (I have not dreamt up the schema for this yet and this relies on other FIXMEs being done so this will have to wait.)
|
||||
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void CreateTask::Init(argparse::ArgumentParser& parentParser) {
|
||||
parentParser.add_subparser(parser);
|
||||
}
|
||||
|
||||
bool CreateTask::ShouldRun(argparse::ArgumentParser& parentParser) const {
|
||||
return parentParser.is_subcommand_used("create");
|
||||
}
|
||||
|
||||
int CreateTask::Parse() {
|
||||
auto& args = currentArgs;
|
||||
|
||||
args.verbose = parser.get<bool>("--verbose");
|
||||
args.inputDirectory = eupak::fs::path(parser.get("--directory"));
|
||||
args.outputFile = eupak::fs::path(parser.get("output"));
|
||||
|
||||
if(parser.is_used("--archive-version")) {
|
||||
const auto& versionStr = parser.get("--archive-version");
|
||||
|
||||
if(auto opt = ParsePakVersion(versionStr); opt.has_value()) {
|
||||
} else {
|
||||
std::cout << "Error: Invalid version \"" << versionStr << "\"\n"
|
||||
<< parser;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
args.pakVersion = europa::structs::PakVersion::Ver4;
|
||||
}
|
||||
|
||||
args.sectorAligned = parser.get<bool>("--sector-aligned");
|
||||
|
||||
if(args.sectorAligned && args.pakVersion != eupak::estructs::PakVersion::Ver5) {
|
||||
std::cout << "Error: --sector-aligned is only valid for creating a package with \"-V jedistarfighter\".\n"
|
||||
<< parser;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!eupak::fs::is_directory(args.inputDirectory)) {
|
||||
std::cout << "Error: Provided input isn't a directory\n"
|
||||
<< parser;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CreateTask::Run() {
|
||||
// we should not be modifying arguments past this point
|
||||
const auto& args = currentArgs;
|
||||
|
||||
auto currFile = 0;
|
||||
auto fileCount = 0;
|
||||
|
||||
|
@ -171,4 +273,6 @@ namespace eupak::tasks {
|
|||
return 0;
|
||||
}
|
||||
|
||||
EUPAK_REGISTER_TASK("create", CreateTask);
|
||||
|
||||
} // namespace eupak::tasks
|
|
@ -10,25 +10,41 @@
|
|||
#define EUROPA_EUPAK_TASKS_CREATETASK_HPP
|
||||
|
||||
#include <CommonDefs.hpp>
|
||||
|
||||
#include <europa/structs/Pak.hpp>
|
||||
#include <tasks/Task.hpp>
|
||||
|
||||
#include "argparse/argparse.hpp"
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
struct CreateTask {
|
||||
struct Arguments {
|
||||
fs::path inputDirectory;
|
||||
fs::path outputFile;
|
||||
struct CreateTask : ITask {
|
||||
/// Arguments.
|
||||
/// This is no longer directly used by clients
|
||||
/// and should later be factored out of surface.
|
||||
struct Arguments {
|
||||
fs::path inputDirectory;
|
||||
fs::path outputFile;
|
||||
|
||||
bool verbose;
|
||||
europa::structs::PakVersion pakVersion;
|
||||
bool sectorAligned;
|
||||
};
|
||||
|
||||
int Run(Arguments&& args);
|
||||
bool verbose;
|
||||
europa::structs::PakVersion pakVersion;
|
||||
bool sectorAligned;
|
||||
};
|
||||
|
||||
} // namespace europa
|
||||
CreateTask();
|
||||
|
||||
void Init(argparse::ArgumentParser& parentParser) override;
|
||||
|
||||
bool ShouldRun(argparse::ArgumentParser& parentParser) const override;
|
||||
|
||||
int Parse() override;
|
||||
|
||||
int Run() override;
|
||||
|
||||
private:
|
||||
argparse::ArgumentParser parser;
|
||||
Arguments currentArgs;
|
||||
};
|
||||
|
||||
} // namespace eupak::tasks
|
||||
|
||||
#endif // EUROPA_EUPAK_TASKS_CREATETASK_HPP
|
||||
|
|
|
@ -20,6 +20,8 @@ namespace eupak::tasks {
|
|||
bool verbose;
|
||||
};
|
||||
|
||||
|
||||
|
||||
int Run(Arguments&& args);
|
||||
};
|
||||
|
||||
|
|
17
src/tools/eupak/tasks/Task.cpp
Normal file
17
src/tools/eupak/tasks/Task.cpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include "Task.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
std::shared_ptr<ITask> TaskFactory::CreateNamed(const std::string& name, argparse::ArgumentParser& parentParser) {
|
||||
const auto& m = factoryMap();
|
||||
if(m.contains(name)) {
|
||||
auto task = m.at(name)();
|
||||
task->Init(parentParser);
|
||||
return task;
|
||||
} else
|
||||
throw std::runtime_error("Invalid task factory creation request");
|
||||
}
|
||||
|
||||
} // namespace eupak::tasks
|
66
src/tools/eupak/tasks/Task.hpp
Normal file
66
src/tools/eupak/tasks/Task.hpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// EuropaTools
|
||||
//
|
||||
// (C) 2021-2025 modeco80 <lily.modeco80@protonmail.ch>
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <CommonDefs.hpp>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "argparse/argparse.hpp"
|
||||
|
||||
namespace eupak::tasks {
|
||||
|
||||
/// Base-class for all eupak tasks.
|
||||
struct ITask {
|
||||
virtual ~ITask() = 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;
|
||||
};
|
||||
|
||||
/// Creates tasks.
|
||||
struct TaskFactory {
|
||||
using FactoryMethod = std::shared_ptr<ITask> (*)();
|
||||
|
||||
/// Creates a task.
|
||||
static std::shared_ptr<ITask> CreateNamed(const std::string& name, argparse::ArgumentParser& parentParser);
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
friend struct TaskFactoryRegister;
|
||||
|
||||
static auto& factoryMap() {
|
||||
static std::unordered_map<std::string, FactoryMethod> m;
|
||||
return m;
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct TaskFactoryRegister {
|
||||
TaskFactoryRegister(const std::string& name) {
|
||||
static_assert(std::is_base_of_v<ITask, T>, "cannot register a type which does not derive from ITask");
|
||||
TaskFactory::factoryMap().insert({ name,
|
||||
[]() -> std::shared_ptr<ITask> {
|
||||
return std::make_shared<T>();
|
||||
} });
|
||||
}
|
||||
};
|
||||
|
||||
#define EUPAK_REGISTER_TASK(Name, TTask) \
|
||||
static ::eupak::tasks::TaskFactoryRegister<TTask> __register__##TTask(Name)
|
||||
|
||||
} // namespace eupak::tasks
|
Loading…
Reference in a new issue