1
0
mirror of https://github.com/ScrelliCopter/tmx2gba.git synced 2025-02-21 03:29:25 +11:00

argparse refactor

This commit is contained in:
2024-03-20 07:29:29 +11:00
parent 5c164b239d
commit c3bbe8135d
3 changed files with 112 additions and 84 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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;