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 <iomanip>
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include <iostream>
ArgParse::ArgParser::ArgParser( ArgParse::ArgParser::ArgParser(
const std::string_view argv0, const std::string_view argv0,
std::initializer_list<Option> options, Options options,
HandleOption&& handler HandleOption&& handler
) noexcept : ) noexcept :
name(std::filesystem::path(argv0).filename().string()), name(std::filesystem::path(argv0).filename().string()),
@@ -16,7 +17,7 @@ ArgParse::ArgParser::ArgParser(
handler(std::forward<HandleOption>(handler)) {} 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; out << "Usage: " << name;
for (const auto& it : options) for (const auto& it : options)
@@ -39,7 +40,7 @@ void ArgParse::ArgParser::ShowShortUsage(std::ostream& out) const
out << std::endl; 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 // Base usage
out << "Usage: " << name << " [-"; out << "Usage: " << name << " [-";
@@ -56,8 +57,9 @@ void ArgParse::ArgParser::ShowHelpUsage(std::ostream& out) const
auto paramLength = [](const Option& p) -> int { return p.argumentName auto paramLength = [](const Option& p) -> int { return p.argumentName
? static_cast<int>(std::strlen(p.argumentName) + 3) ? static_cast<int>(std::strlen(p.argumentName) + 3)
: 1; }; : 1; };
auto longestParam = std::max(options, [=](auto a, auto b) -> bool { return paramLength(a) < paramLength(b); }); auto longestParam = std::max_element(options.begin(), options.end(),
auto alignWidth = paramLength(longestParam) + 3; [=](auto a, auto b) -> bool { return paramLength(a) < paramLength(b); });
auto alignWidth = paramLength(*longestParam) + 3;
// print argument descriptions // print argument descriptions
for (const auto& it : options) 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 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>> auto getOption = [&](int flag) -> std::optional<std::reference_wrapper<const Option>>
{ {
for (auto& opt : options) for (auto& opt : options.options)
if (opt.flag == flag) if (opt.flag == flag)
return std::optional(std::cref(opt)); return std::optional(std::cref(opt));
return {}; 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; bool inQuote = false;
std::string quoteStr; std::string quoteStr;

View File

@@ -17,9 +17,29 @@ namespace ArgParse
struct Option struct Option
{ {
char flag; char flag;
const char* argumentName;
bool required; bool required;
const char* argumentName;
const char* helpString; 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 enum class ParseCtrl
@@ -50,10 +70,10 @@ namespace ArgParse
bool expectArg = false; bool expectArg = false;
int flagChar; int flagChar;
HandleOption handler; HandleOption handler;
const std::initializer_list<Option>& options; const Options& options;
public: public:
ParserState(HandleOption handler, const std::initializer_list<Option>& options) noexcept ParserState(HandleOption handler, const Options& options) noexcept
: handler(handler), options(options) {} : handler(handler), options(options) {}
[[nodiscard]] bool ExpectingArg() const { return expectArg; } [[nodiscard]] bool ExpectingArg() const { return expectArg; }
[[nodiscard]] ParseCtrl Next(const std::string_view token); [[nodiscard]] ParseCtrl Next(const std::string_view token);
@@ -62,49 +82,52 @@ namespace ArgParse
class ArgParser class ArgParser
{ {
const std::string name; const std::string name;
std::initializer_list<Option> options; Options options;
HandleOption handler; HandleOption handler;
[[nodiscard]] bool CheckParse(ArgParse::ParseErr err) const;
public: 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; } [[nodiscard]] const std::string_view GetName() const noexcept { return name; }
void DisplayError(const std::string_view message, bool helpPrompt = true) const;
void ShowShortUsage(std::ostream& out) const;
void ShowHelpUsage(std::ostream& out) const;
template <typename V> template <typename V>
ParseErr Parse(V args) [[nodiscard]] bool Parse(V args)
{ {
ParserState state(handler, options); ParserState state(handler, options);
for (auto arg : args) for (auto arg : args)
{ {
ParseErr err;
switch (state.Next(arg)) switch (state.Next(arg))
{ {
case ParseCtrl::CONTINUE: continue; case ParseCtrl::CONTINUE: continue;
case ParseCtrl::QUIT_EARLY: return ParseErr::OK; case ParseCtrl::QUIT_EARLY: err = ParseErr::OK; break;
case ParseCtrl::QUIT_ERR_UNKNOWN: return ParseErr::OPT_UNKNOWN; case ParseCtrl::QUIT_ERR_UNKNOWN: err = ParseErr::OPT_UNKNOWN; break;
case ParseCtrl::QUIT_ERR_UNEXPECTED: return ParseErr::UNEXPECTED; case ParseCtrl::QUIT_ERR_UNEXPECTED: err = ParseErr::UNEXPECTED; break;
case ParseCtrl::QUIT_ERR_EXPECTARG: return ParseErr::ARG_EXPECTED; case ParseCtrl::QUIT_ERR_EXPECTARG: err = ParseErr::ARG_EXPECTED; break;
case ParseCtrl::QUIT_ERR_INVALID: return ParseErr::ARG_INVALID; case ParseCtrl::QUIT_ERR_INVALID: err = ParseErr::ARG_INVALID; break;
case ParseCtrl::QUIT_ERR_RANGE: return ParseErr::ARG_RANGE; 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); 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); })); 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 #endif//ARGPARSE_HPP

View File

@@ -11,11 +11,6 @@
#include <algorithm> #include <algorithm>
using ArgParse::ArgParser;
using ArgParse::ParseCtrl;
using ArgParse::ParseErr;
struct Arguments struct Arguments
{ {
bool help = false; bool help = false;
@@ -27,54 +22,29 @@ struct Arguments
std::vector<std::string> objMappings; std::vector<std::string> objMappings;
}; };
static void DisplayError(const ArgParser& parser, const std::string& message, bool helpPrompt = true) using ArgParse::Option;
{
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;
}
bool CheckParse(const ArgParser& parser, ParseErr err) static const ArgParse::Options options =
{ {
switch (err) Option::Optional('h', nullptr, "Display this help & command info"),
{ Option::Optional('l', "name", "Name of layer to use (default first layer in TMX)"),
case ParseErr::OK: Option::Optional('y', "name", "Layer for palette mappings"),
return true; Option::Optional('c', "name", "Output a separate 8bit collision map of the specified layer"),
case ParseErr::OPT_UNKNOWN: Option::Optional('r', "offset", "Offset tile indices (default 0)"),
DisplayError(parser, "Unrecognised option."); Option::Optional('p', "0-15", "Select which palette to use for 4-bit tilesets"),
return false; Option::Optional('m', "name;id", "Map an object name to an ID, will enable object exports"),
case ParseErr::UNEXPECTED: Option::Required('i', "inpath", "Path to input TMX file"),
DisplayError(parser, "Unexpected token."); Option::Required('o', "outpath", "Path to output files"),
return false; Option::Optional('f', "file", "Specify a file to use for flags, will override any options"
case ParseErr::ARG_EXPECTED: " specified on the command line")
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;
}
}
bool ParseArgs(int argc, char** argv, Arguments& params) bool ParseArgs(int argc, char** argv, Arguments& params)
{ {
auto parser = ArgParser(argv[0], { auto parser = ArgParse::ArgParser(argv[0], options, [&](int opt, const std::string_view arg)
{ 'h', nullptr, false, "Display this help & command info" }, -> ArgParse::ParseCtrl
{ '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
{ {
using ArgParse::ParseCtrl;
try try
{ {
switch (opt) 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; } 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; return false;
if (params.help) if (params.help)
{
parser.ShowHelpUsage(std::cout);
return true; return true;
}
if (!params.flagFile.empty()) if (!params.flagFile.empty())
{ {
@@ -116,30 +83,30 @@ bool ParseArgs(int argc, char** argv, Arguments& params)
} }
std::vector<std::string> tokens; 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; std::cerr << "Failed to read param file: Unterminated quote string." << std::endl;
return false; return false;
} }
if (!CheckParse(parser, parser.Parse(tokens))) if (!parser.Parse(tokens))
return false; return false;
} }
// Check my paranoia // Check my paranoia
if (params.inPath.empty()) if (params.inPath.empty())
{ {
DisplayError(parser, "No input file specified."); parser.DisplayError("No input file specified.");
return false; return false;
} }
if (params.outPath.empty()) if (params.outPath.empty())
{ {
DisplayError(parser, "No output file specified."); parser.DisplayError("No output file specified.");
return false; return false;
} }
if (params.palette < 0 || params.palette > 15) if (params.palette < 0 || params.palette > 15)
{ {
DisplayError(parser, "Invalid palette index."); parser.DisplayError("Invalid palette index.");
return false; return false;
} }
@@ -190,7 +157,10 @@ int main(int argc, char** argv)
if (!ParseArgs(argc, argv, p)) if (!ParseArgs(argc, argv, p))
return 1; return 1;
if (p.help) if (p.help)
{
options.ShowHelpUsage(argv[0], std::cout);
return 0; return 0;
}
// Object mappings // Object mappings
std::map<std::string, uint32_t> objMapping; std::map<std::string, uint32_t> objMapping;