Unlinker: Make parsing specified command line arguments its own class

This commit is contained in:
Jan
2020-02-14 23:40:47 +01:00
parent f3779bac03
commit 23f77bb335
14 changed files with 373 additions and 262 deletions

View File

@ -1,7 +1,6 @@
#include "Unlinker.h"
#include "Utils/Arguments/ArgumentParser.h"
#include "Utils/Arguments/UsageInformation.h"
#include "ZoneLoading.h"
#include "ObjWriting.h"
#include "ContentPrinter.h"
@ -15,67 +14,19 @@
#include <regex>
#include <filesystem>
#include "ObjContainer/IWD/IWD.h"
const CommandLineOption* optionHelp = CommandLineOption::Builder::Create()
.WithShortName("?")
.WithLongName("help")
.WithDescription("Displays usage information.")
.Build();
const CommandLineOption* optionVerbose = CommandLineOption::Builder::Create()
.WithShortName("v")
.WithLongName("verbose")
.WithDescription("Outputs a lot more and more detailed messages.")
.Build();
const CommandLineOption* optionMinimalZoneFile = CommandLineOption::Builder::Create()
.WithShortName("min")
.WithLongName("minimal-zone")
.WithDescription(
"Minimizes the size of the zone file output by only including assets that are not a dependency of another asset.")
.Build();
const CommandLineOption* optionList = CommandLineOption::Builder::Create()
.WithShortName("l")
.WithLongName("list")
.WithDescription(
"Lists the contents of a zone instead of writing them to the disk.")
.Build();
const CommandLineOption* optionOutputFolder = CommandLineOption::Builder::Create()
.WithShortName("o")
.WithLongName("output-folder")
.WithDescription(
"Specifies the output folder containing the contents of the unlinked zones. Defaults to ./%zoneName%")
.WithParameter("outputFolderPath")
.Build();
const CommandLineOption* optionSearchPath = CommandLineOption::Builder::Create()
.WithLongName("search-path")
.WithDescription(
"Specifies a semi-colon separated list of paths to search for additional game files.")
.WithParameter("searchPathString")
.Build();
const CommandLineOption* commandLineOptions[]
{
optionHelp,
optionVerbose,
optionMinimalZoneFile,
optionList,
optionOutputFolder,
optionSearchPath
};
#include "UnlinkerArgs.h"
class Unlinker::Impl
{
UnlinkerArgs m_args;
SearchPaths m_search_paths;
SearchPathFilesystem* m_last_zone_search_path;
std::set<std::string> m_absolute_search_paths;
ArgumentParser m_argument_parser;
bool m_verbose;
bool m_should_load_obj;
bool ShouldLoadObj() const
{
return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST;
}
/**
* \brief Loads a search path.
@ -83,9 +34,9 @@ class Unlinker::Impl
*/
void LoadSearchPath(ISearchPath* searchPath) const
{
if (m_should_load_obj)
if (ShouldLoadObj())
{
if (m_verbose)
if (m_args.m_verbose)
{
printf("Loading search path: \"%s\"\n", searchPath->GetPath().c_str());
}
@ -100,9 +51,9 @@ class Unlinker::Impl
*/
void UnloadSearchPath(ISearchPath* searchPath) const
{
if (m_should_load_obj)
if (ShouldLoadObj())
{
if (m_verbose)
if (m_args.m_verbose)
{
printf("Unloading search path: \"%s\"\n", searchPath->GetPath().c_str());
}
@ -141,105 +92,30 @@ class Unlinker::Impl
return searchPathsForZone;
}
/**
* \brief Splits a path string as user input into a list of paths.
* \param pathsString The path string that was taken as user input.
* \param output A set for strings to save the output to.
* \return \c true if the user input was valid and could be processed successfully, otherwise \c false.
*/
static bool ParsePathsString(const std::string& pathsString, std::set<std::string>& output)
{
std::ostringstream currentPath;
bool pathStart = true;
int quotationPos = 0; // 0 = before quotations, 1 = in quotations, 2 = after quotations
for (auto character : pathsString)
{
switch (character)
{
case '"':
if (quotationPos == 0 && pathStart)
{
quotationPos = 1;
}
else if (quotationPos == 1)
{
quotationPos = 2;
pathStart = false;
}
else
{
return false;
}
break;
case ';':
if (quotationPos != 1)
{
std::string path = currentPath.str();
if (!path.empty())
{
output.insert(path);
currentPath = std::ostringstream();
}
pathStart = true;
quotationPos = 0;
}
else
{
currentPath << character;
}
break;
default:
currentPath << character;
pathStart = false;
break;
}
}
if (currentPath.tellp() > 0)
{
output.insert(currentPath.str());
}
return true;
}
/**
* \brief Initializes the Unlinker object's search paths based on the user's input.
* \return \c true if building the search paths was successful, otherwise \c false.
*/
bool BuildSearchPaths()
{
if (m_argument_parser.IsOptionSpecified(optionSearchPath))
for (const auto& path : m_args.m_user_search_paths)
{
std::set<std::string> pathList;
if (!ParsePathsString(m_argument_parser.GetValueForOption(optionSearchPath), pathList))
std::string absolutePath = std::filesystem::absolute(path).string();
if (!FileAPI::DirectoryExists(absolutePath))
{
printf("Could not find directory of search path: \"%s\"\n", path.c_str());
return false;
}
for (const auto& path : pathList)
{
std::string absolutePath = std::filesystem::absolute(path).string();
auto* searchPath = new SearchPathFilesystem(absolutePath);
LoadSearchPath(searchPath);
m_search_paths.CommitSearchPath(searchPath);
if (!FileAPI::DirectoryExists(absolutePath))
{
printf("Could not find directory of search path: \"%s\"\n", path.c_str());
return false;
}
auto* searchPath = new SearchPathFilesystem(absolutePath);
LoadSearchPath(searchPath);
m_search_paths.CommitSearchPath(searchPath);
m_absolute_search_paths.insert(absolutePath);
}
m_absolute_search_paths.insert(absolutePath);
}
if (m_verbose)
if (m_args.m_verbose)
{
printf("%u SearchPaths%s\n", m_absolute_search_paths.size(), !m_absolute_search_paths.empty() ? ":" : "");
for (const auto& absoluteSearchPath : m_absolute_search_paths)
@ -256,62 +132,21 @@ class Unlinker::Impl
return true;
}
/**
* \brief Prints a command line usage help text for the Unlinker tool to stdout.
*/
static void PrintUsage()
{
UsageInformation usage("unlinker.exe");
for (auto commandLineOption : commandLineOptions)
{
usage.AddCommandLineOption(commandLineOption);
}
usage.AddArgument("pathToZone");
usage.SetVariableArguments(true);
usage.Print();
}
/**
* \brief Converts the output path specified by command line arguments to a path applies for the specified zone.
* \param path The path that was specified as user input.
* \param zone The zone to resolve the path input for.
* \return An output path for the zone based on the user input.
*/
std::string ResolveOutputFolderPath(const std::string& path, Zone* zone) const
{
return std::regex_replace(path, std::regex("%zoneName%"), zone->m_name);
}
/**
* \brief Performs the tasks specified by the command line arguments on the specified zone.
* \param zone The zone to handle.
* \return \c true if handling the zone was successful, otherwise \c false.
*/
bool HandleZone(Zone* zone)
bool HandleZone(Zone* zone) const
{
if (m_argument_parser.IsOptionSpecified(optionList))
if (m_args.m_task == UnlinkerArgs::ProcessingTask::LIST)
{
const ContentPrinter printer(zone);
printer.PrintContent();
}
else
else if (m_args.m_task == UnlinkerArgs::ProcessingTask::DUMP)
{
const bool minimalisticZoneDefinition = m_argument_parser.IsOptionSpecified(optionMinimalZoneFile);
std::string outputFolderPath;
if (m_argument_parser.IsOptionSpecified(optionOutputFolder))
{
outputFolderPath = ResolveOutputFolderPath(m_argument_parser.GetValueForOption(optionOutputFolder),
zone);
}
else
{
outputFolderPath = ResolveOutputFolderPath("./%zoneName%", zone);
}
const std::string outputFolderPath = m_args.GetOutputFolderPathForZone(zone);
FileAPI::DirectoryCreate(outputFolderPath);
const std::string zoneDefinitionFileFolder = utils::Path::Combine(outputFolderPath, "zone_source");
@ -323,7 +158,7 @@ class Unlinker::Impl
if (zoneDefinitionFile.IsOpen())
{
ObjWriting::WriteZoneDefinition(zone, &zoneDefinitionFile, minimalisticZoneDefinition);
ObjWriting::WriteZoneDefinition(zone, &zoneDefinitionFile);
ObjWriting::DumpZone(zone, outputFolderPath);
}
else
@ -338,19 +173,10 @@ class Unlinker::Impl
return true;
}
void SetVerbose(const bool verbose)
{
m_verbose = verbose;
ObjLoading::Configuration.Verbose = verbose;
}
public:
Impl()
: m_argument_parser(commandLineOptions, _countof(commandLineOptions))
{
m_last_zone_search_path = nullptr;
m_verbose = false;
m_should_load_obj = false;
}
/**
@ -358,41 +184,15 @@ public:
*/
bool Start(const int argc, const char** argv)
{
if (!m_argument_parser.ParseArguments(argc, argv))
{
PrintUsage();
if (!m_args.ParseArgs(argc, argv))
return false;
}
SetVerbose(m_argument_parser.IsOptionSpecified(optionVerbose));
m_should_load_obj = !m_argument_parser.IsOptionSpecified(optionList);
// Check if the user requested help
if (m_argument_parser.IsOptionSpecified(optionHelp))
{
PrintUsage();
return true;
}
const std::vector<std::string> arguments = m_argument_parser.GetArguments();
const size_t argCount = arguments.size();
if (argCount <= 1)
{
// No zones to load specified...
PrintUsage();
return false;
}
if (!BuildSearchPaths())
{
return false;
}
for (unsigned argIndex = 1; argIndex < argCount; argIndex++)
for (const auto& zonePath : m_args.m_zones_to_load)
{
const std::string& zonePath = arguments[argIndex];
if(!FileAPI::FileExists(zonePath))
if (!FileAPI::FileExists(zonePath))
{
printf("Could not find file \"%s\".\n", zonePath.c_str());
continue;
@ -410,12 +210,12 @@ public:
return false;
}
if (m_verbose)
if (m_args.m_verbose)
{
printf("Loaded zone \"%s\"\n", zone->m_name.c_str());
}
if (m_should_load_obj)
if (ShouldLoadObj())
{
ObjLoading::LoadReferencedContainersForZone(&searchPathsForZone, zone);
ObjLoading::LoadObjDataForZone(&searchPathsForZone, zone);
@ -426,7 +226,7 @@ public:
return false;
}
if (m_should_load_obj)
if (ShouldLoadObj())
{
ObjLoading::UnloadContainersOfZone(zone);
}