mirror of
https://github.com/ScrelliCopter/tmx2gba.git
synced 2025-02-21 03:29:25 +11:00
argparse refactor
This commit is contained in:
@@ -4,11 +4,12 @@
|
||||
#include <iomanip>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
ArgParse::ArgParser::ArgParser(
|
||||
const std::string_view argv0,
|
||||
std::initializer_list<Option> options,
|
||||
Options options,
|
||||
HandleOption&& handler
|
||||
) noexcept :
|
||||
name(std::filesystem::path(argv0).filename().string()),
|
||||
@@ -16,7 +17,7 @@ ArgParse::ArgParser::ArgParser(
|
||||
handler(std::forward<HandleOption>(handler)) {}
|
||||
|
||||
|
||||
void ArgParse::ArgParser::ShowShortUsage(std::ostream& out) const
|
||||
void ArgParse::Options::ShowShortUsage(const std::string_view name, std::ostream& out) const
|
||||
{
|
||||
out << "Usage: " << name;
|
||||
for (const auto& it : options)
|
||||
@@ -39,7 +40,7 @@ void ArgParse::ArgParser::ShowShortUsage(std::ostream& out) const
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void ArgParse::ArgParser::ShowHelpUsage(std::ostream& out) const
|
||||
void ArgParse::Options::ShowHelpUsage(const std::string_view name, std::ostream& out) const
|
||||
{
|
||||
// Base usage
|
||||
out << "Usage: " << name << " [-";
|
||||
@@ -56,8 +57,9 @@ void ArgParse::ArgParser::ShowHelpUsage(std::ostream& out) const
|
||||
auto paramLength = [](const Option& p) -> int { return p.argumentName
|
||||
? static_cast<int>(std::strlen(p.argumentName) + 3)
|
||||
: 1; };
|
||||
auto longestParam = std::max(options, [=](auto a, auto b) -> bool { return paramLength(a) < paramLength(b); });
|
||||
auto alignWidth = paramLength(longestParam) + 3;
|
||||
auto longestParam = std::max_element(options.begin(), options.end(),
|
||||
[=](auto a, auto b) -> bool { return paramLength(a) < paramLength(b); });
|
||||
auto alignWidth = paramLength(*longestParam) + 3;
|
||||
|
||||
// print argument descriptions
|
||||
for (const auto& it : options)
|
||||
@@ -76,7 +78,7 @@ ArgParse::ParseCtrl ArgParse::ParserState::Next(const std::string_view token)
|
||||
auto getFlag = [](const std::string_view s) { return s[0] == '-' && s[1] ? std::optional<int>(s[1]) : std::nullopt; };
|
||||
auto getOption = [&](int flag) -> std::optional<std::reference_wrapper<const Option>>
|
||||
{
|
||||
for (auto& opt : options)
|
||||
for (auto& opt : options.options)
|
||||
if (opt.flag == flag)
|
||||
return std::optional(std::cref(opt));
|
||||
return {};
|
||||
@@ -119,7 +121,40 @@ ArgParse::ParseCtrl ArgParse::ParserState::Next(const std::string_view token)
|
||||
}
|
||||
|
||||
|
||||
bool ReadParamFile(std::vector<std::string>& tokens, std::istream& file)
|
||||
void ArgParse::ArgParser::DisplayError(const std::string_view message, bool helpPrompt) const
|
||||
{
|
||||
std::cerr << GetName() << ": " << message << std::endl;
|
||||
options.ShowShortUsage(GetName(), std::cerr);
|
||||
if (helpPrompt)
|
||||
std::cerr << "Run '" << GetName() << " -h' to view all available options." << std::endl;
|
||||
}
|
||||
|
||||
bool ArgParse::ArgParser::CheckParse(ArgParse::ParseErr err) const
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
case ParseErr::OK:
|
||||
return true;
|
||||
case ParseErr::OPT_UNKNOWN:
|
||||
DisplayError("Unrecognised option.");
|
||||
return false;
|
||||
case ParseErr::UNEXPECTED:
|
||||
DisplayError("Unexpected token.");
|
||||
return false;
|
||||
case ParseErr::ARG_EXPECTED:
|
||||
DisplayError("Requires an argument.");
|
||||
return false;
|
||||
case ParseErr::ARG_INVALID:
|
||||
DisplayError("Invalid argument.", false);
|
||||
return false;
|
||||
case ParseErr::ARG_RANGE:
|
||||
DisplayError("Argument out of range.", false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool ArgParse::ReadParamFile(std::vector<std::string>& tokens, std::istream& file)
|
||||
{
|
||||
bool inQuote = false;
|
||||
std::string quoteStr;
|
||||
|
||||
@@ -17,9 +17,29 @@ namespace ArgParse
|
||||
struct Option
|
||||
{
|
||||
char flag;
|
||||
const char* argumentName;
|
||||
bool required;
|
||||
const char* argumentName;
|
||||
const char* helpString;
|
||||
|
||||
static constexpr Option Optional(char flag, const char* name, const char* help)
|
||||
{
|
||||
return { flag, false, name, help };
|
||||
}
|
||||
static constexpr Option Required(char flag, const char* name, const char* help)
|
||||
{
|
||||
return { flag, true, name, help };
|
||||
}
|
||||
};
|
||||
|
||||
struct Options
|
||||
{
|
||||
const std::vector<const Option> options;
|
||||
|
||||
inline constexpr Options(const std::initializer_list<const Option>&& rhs)
|
||||
: options(std::move(rhs)) {}
|
||||
|
||||
void ShowShortUsage(const std::string_view name, std::ostream& out) const;
|
||||
void ShowHelpUsage(const std::string_view name, std::ostream& out) const;
|
||||
};
|
||||
|
||||
enum class ParseCtrl
|
||||
@@ -50,10 +70,10 @@ namespace ArgParse
|
||||
bool expectArg = false;
|
||||
int flagChar;
|
||||
HandleOption handler;
|
||||
const std::initializer_list<Option>& options;
|
||||
const Options& options;
|
||||
|
||||
public:
|
||||
ParserState(HandleOption handler, const std::initializer_list<Option>& options) noexcept
|
||||
ParserState(HandleOption handler, const Options& options) noexcept
|
||||
: handler(handler), options(options) {}
|
||||
[[nodiscard]] bool ExpectingArg() const { return expectArg; }
|
||||
[[nodiscard]] ParseCtrl Next(const std::string_view token);
|
||||
@@ -62,49 +82,52 @@ namespace ArgParse
|
||||
class ArgParser
|
||||
{
|
||||
const std::string name;
|
||||
std::initializer_list<Option> options;
|
||||
Options options;
|
||||
HandleOption handler;
|
||||
|
||||
[[nodiscard]] bool CheckParse(ArgParse::ParseErr err) const;
|
||||
|
||||
public:
|
||||
explicit ArgParser(const std::string_view argv0, std::initializer_list<Option> options, HandleOption&& handler) noexcept;
|
||||
explicit ArgParser(const std::string_view argv0, Options options, HandleOption&& handler) noexcept;
|
||||
|
||||
[[nodiscard]] const std::string_view GetName() const { return name; }
|
||||
|
||||
void ShowShortUsage(std::ostream& out) const;
|
||||
void ShowHelpUsage(std::ostream& out) const;
|
||||
[[nodiscard]] const std::string_view GetName() const noexcept { return name; }
|
||||
void DisplayError(const std::string_view message, bool helpPrompt = true) const;
|
||||
|
||||
template <typename V>
|
||||
ParseErr Parse(V args)
|
||||
[[nodiscard]] bool Parse(V args)
|
||||
{
|
||||
ParserState state(handler, options);
|
||||
for (auto arg : args)
|
||||
{
|
||||
ParseErr err;
|
||||
switch (state.Next(arg))
|
||||
{
|
||||
case ParseCtrl::CONTINUE: continue;
|
||||
case ParseCtrl::QUIT_EARLY: return ParseErr::OK;
|
||||
case ParseCtrl::QUIT_ERR_UNKNOWN: return ParseErr::OPT_UNKNOWN;
|
||||
case ParseCtrl::QUIT_ERR_UNEXPECTED: return ParseErr::UNEXPECTED;
|
||||
case ParseCtrl::QUIT_ERR_EXPECTARG: return ParseErr::ARG_EXPECTED;
|
||||
case ParseCtrl::QUIT_ERR_INVALID: return ParseErr::ARG_INVALID;
|
||||
case ParseCtrl::QUIT_ERR_RANGE: return ParseErr::ARG_RANGE;
|
||||
case ParseCtrl::QUIT_EARLY: err = ParseErr::OK; break;
|
||||
case ParseCtrl::QUIT_ERR_UNKNOWN: err = ParseErr::OPT_UNKNOWN; break;
|
||||
case ParseCtrl::QUIT_ERR_UNEXPECTED: err = ParseErr::UNEXPECTED; break;
|
||||
case ParseCtrl::QUIT_ERR_EXPECTARG: err = ParseErr::ARG_EXPECTED; break;
|
||||
case ParseCtrl::QUIT_ERR_INVALID: err = ParseErr::ARG_INVALID; break;
|
||||
case ParseCtrl::QUIT_ERR_RANGE: err = ParseErr::ARG_RANGE; break;
|
||||
}
|
||||
if (!CheckParse(err))
|
||||
return false;
|
||||
}
|
||||
return state.ExpectingArg() ? ParseErr::ARG_EXPECTED : ParseErr::OK;
|
||||
return CheckParse(state.ExpectingArg() ? ParseErr::ARG_EXPECTED : ParseErr::OK);
|
||||
}
|
||||
|
||||
inline ParseErr Parse(std::initializer_list<std::string_view> args)
|
||||
[[nodiscard]] inline bool Parse(std::initializer_list<std::string_view> args)
|
||||
{
|
||||
return Parse<std::initializer_list<std::string_view>>(args);
|
||||
}
|
||||
|
||||
inline ParseErr Parse(std::span<char*> args)
|
||||
[[nodiscard]] inline bool Parse(std::span<char*> args)
|
||||
{
|
||||
return Parse(args | std::views::transform([](char const* v){ return std::string_view(v); }));
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] extern bool ReadParamFile(std::vector<std::string>& tokens, std::istream& file);
|
||||
}
|
||||
|
||||
extern bool ReadParamFile(std::vector<std::string>& tokens, std::istream& file);
|
||||
|
||||
#endif//ARGPARSE_HPP
|
||||
|
||||
@@ -11,11 +11,6 @@
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
using ArgParse::ArgParser;
|
||||
using ArgParse::ParseCtrl;
|
||||
using ArgParse::ParseErr;
|
||||
|
||||
|
||||
struct Arguments
|
||||
{
|
||||
bool help = false;
|
||||
@@ -27,54 +22,29 @@ struct Arguments
|
||||
std::vector<std::string> objMappings;
|
||||
};
|
||||
|
||||
static void DisplayError(const ArgParser& parser, const std::string& message, bool helpPrompt = true)
|
||||
{
|
||||
std::cerr << parser.GetName() << ": " << message << std::endl;
|
||||
parser.ShowShortUsage(std::cerr);
|
||||
if (helpPrompt)
|
||||
std::cerr << "Run '" << parser.GetName() << " -h' to view all available options." << std::endl;
|
||||
}
|
||||
using ArgParse::Option;
|
||||
|
||||
bool CheckParse(const ArgParser& parser, ParseErr err)
|
||||
static const ArgParse::Options options =
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
case ParseErr::OK:
|
||||
return true;
|
||||
case ParseErr::OPT_UNKNOWN:
|
||||
DisplayError(parser, "Unrecognised option.");
|
||||
return false;
|
||||
case ParseErr::UNEXPECTED:
|
||||
DisplayError(parser, "Unexpected token.");
|
||||
return false;
|
||||
case ParseErr::ARG_EXPECTED:
|
||||
DisplayError(parser, "Requires an argument.");
|
||||
return false;
|
||||
case ParseErr::ARG_INVALID:
|
||||
DisplayError(parser, "Invalid argument.", false);
|
||||
return false;
|
||||
case ParseErr::ARG_RANGE:
|
||||
DisplayError(parser, "Argument out of range.", false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Option::Optional('h', nullptr, "Display this help & command info"),
|
||||
Option::Optional('l', "name", "Name of layer to use (default first layer in TMX)"),
|
||||
Option::Optional('y', "name", "Layer for palette mappings"),
|
||||
Option::Optional('c', "name", "Output a separate 8bit collision map of the specified layer"),
|
||||
Option::Optional('r', "offset", "Offset tile indices (default 0)"),
|
||||
Option::Optional('p', "0-15", "Select which palette to use for 4-bit tilesets"),
|
||||
Option::Optional('m', "name;id", "Map an object name to an ID, will enable object exports"),
|
||||
Option::Required('i', "inpath", "Path to input TMX file"),
|
||||
Option::Required('o', "outpath", "Path to output files"),
|
||||
Option::Optional('f', "file", "Specify a file to use for flags, will override any options"
|
||||
" specified on the command line")
|
||||
};
|
||||
|
||||
bool ParseArgs(int argc, char** argv, Arguments& params)
|
||||
{
|
||||
auto parser = ArgParser(argv[0], {
|
||||
{ 'h', nullptr, false, "Display this help & command info" },
|
||||
{ 'l', "name", false, "Name of layer to use (default first layer in TMX)" },
|
||||
{ 'y', "name", false, "Layer for palette mappings" },
|
||||
{ 'c', "name", false, "Output a separate 8bit collision map of the specified layer" },
|
||||
{ 'r', "offset", false, "Offset tile indices (default 0)" },
|
||||
{ 'p', "0-15", false, "Select which palette to use for 4-bit tilesets" },
|
||||
{ 'm', "name;id", false, "Map an object name to an ID, will enable object exports" },
|
||||
{ 'i', "inpath", true, "Path to input TMX file" },
|
||||
{ 'o', "outpath", true, "Path to output files" },
|
||||
{ 'f', "file", false, "Specify a file to use for flags, will override any options"
|
||||
" specified on the command line" }
|
||||
}, [&](int opt, const std::string_view arg) -> ParseCtrl
|
||||
auto parser = ArgParse::ArgParser(argv[0], options, [&](int opt, const std::string_view arg)
|
||||
-> ArgParse::ParseCtrl
|
||||
{
|
||||
using ArgParse::ParseCtrl;
|
||||
try
|
||||
{
|
||||
switch (opt)
|
||||
@@ -97,14 +67,11 @@ bool ParseArgs(int argc, char** argv, Arguments& params)
|
||||
catch (std::out_of_range const& e) { return ParseCtrl::QUIT_ERR_RANGE; }
|
||||
});
|
||||
|
||||
if (!CheckParse(parser, parser.Parse(std::span(argv + 1, argc - 1))))
|
||||
if (!parser.Parse(std::span(argv + 1, argc - 1)))
|
||||
return false;
|
||||
|
||||
if (params.help)
|
||||
{
|
||||
parser.ShowHelpUsage(std::cout);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!params.flagFile.empty())
|
||||
{
|
||||
@@ -116,30 +83,30 @@ bool ParseArgs(int argc, char** argv, Arguments& params)
|
||||
}
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
if (!ReadParamFile(tokens, paramFile))
|
||||
if (!ArgParse::ReadParamFile(tokens, paramFile))
|
||||
{
|
||||
std::cerr << "Failed to read param file: Unterminated quote string." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckParse(parser, parser.Parse(tokens)))
|
||||
if (!parser.Parse(tokens))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check my paranoia
|
||||
if (params.inPath.empty())
|
||||
{
|
||||
DisplayError(parser, "No input file specified.");
|
||||
parser.DisplayError("No input file specified.");
|
||||
return false;
|
||||
}
|
||||
if (params.outPath.empty())
|
||||
{
|
||||
DisplayError(parser, "No output file specified.");
|
||||
parser.DisplayError("No output file specified.");
|
||||
return false;
|
||||
}
|
||||
if (params.palette < 0 || params.palette > 15)
|
||||
{
|
||||
DisplayError(parser, "Invalid palette index.");
|
||||
parser.DisplayError("Invalid palette index.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -190,7 +157,10 @@ int main(int argc, char** argv)
|
||||
if (!ParseArgs(argc, argv, p))
|
||||
return 1;
|
||||
if (p.help)
|
||||
{
|
||||
options.ShowHelpUsage(argv[0], std::cout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Object mappings
|
||||
std::map<std::string, uint32_t> objMapping;
|
||||
|
||||
Reference in New Issue
Block a user