1
0

feat: add cli support for BMapInspector

This commit is contained in:
2026-01-31 11:28:03 +08:00
parent 103cb496a2
commit 8f5cc51de4
9 changed files with 418 additions and 136 deletions

View File

@@ -35,7 +35,8 @@ PRIVATE
# Install binary and headers
install(TARGETS BMap
EXPORT BMapTargets
LIBRARY DESTINATION ${NEMO_INSTALL_LIB_PATH}
RUNTIME DESTINATION ${NEMO_INSTALL_BIN_PATH}
LIBRARY DESTINATION ${NEMO_INSTALL_BIN_PATH}
ARCHIVE DESTINATION ${NEMO_INSTALL_LIB_PATH}
INCLUDES DESTINATION ${NEMO_INSTALL_INCLUDE_PATH}
FILE_SET HEADERS DESTINATION ${NEMO_INSTALL_INCLUDE_PATH}

View File

@@ -1,4 +1,6 @@
#include "Utils.hpp"
#include "Cli.hpp"
#include "Reporter.hpp"
#include "Ruleset.hpp"
#include <VTAll.hpp>
#include <yycc.hpp>
@@ -11,16 +13,28 @@ using namespace yycc::patch::stream;
namespace strop = yycc::string::op;
namespace termcolor = yycc::carton::termcolor;
static bool ProcessCli() {
}
static void LoadVirtools() {
}
static void CheckRules() {
}
int main(int argc, char *argv[]) {
// Show splash
std::cout << termcolor::colored(u8"Ballance Map Inspector", termcolor::Color::LightYellow)
std::cout << termcolor::colored(u8"" BMAPINSP_NAME, termcolor::Color::LightYellow)
<< " (based on LibCmo " LIBCMO_VER_STR ") built at " __DATE__ " " __TIME__ << std::endl
<< u8"The inspector for checking whether your Ballance custom map can be loaded without any issues." << std::endl
<< u8"" BMAPINSP_DESC << std::endl
<< std::endl;
// Create reporter
BMapInspector::Utils::Reporter reporter;
BMapInspector::Reporter reporter;
// Get rule collection
BMapInspector::Ruleset::RuleCollection rule_collection;

View File

@@ -5,6 +5,8 @@ target_sources(BMapInspector
PRIVATE
BMapInspector.cpp
Utils.cpp
Reporter.cpp
Cli.cpp
Ruleset.cpp
)
# Setup headers
@@ -13,6 +15,8 @@ PRIVATE
FILE_SET HEADERS
FILES
Utils.hpp
Reporter.hpp
Cli.hpp
Ruleset.hpp
)
# Setup header infomation

View File

@@ -0,0 +1,149 @@
#include "Cli.hpp"
#include <yycc.hpp>
#include <yycc/carton/clap.hpp>
namespace clap = yycc::carton::clap;
namespace BMapInspector::Cli {
#pragma region Request
Request Request::FromHelpRequest() {
return Request(RequestKind::Help, std::nullopt, std::nullopt, std::nullopt, std::nullopt);
}
Request Request::FromVersionRequest() {
return Request(RequestKind::Version, std::nullopt, std::nullopt, std::nullopt, std::nullopt);
}
Request Request::FromWorkRequest(Utils::ReportLevel level,
const std::u8string_view& file_path,
const std::u8string_view& encoding,
const std::u8string_view& ballance_path) {
return Request(RequestKind::Work, level, file_path, encoding, ballance_path);
}
Request::Request(RequestKind kind,
std::optional<Utils::ReportLevel> level,
std::optional<std::u8string_view> file_path,
std::optional<std::u8string_view> encoding,
std::optional<std::u8string_view> ballance_path) :
kind(kind), level(level), file_path(file_path), encoding(encoding), ballance_path(ballance_path) {}
Request::~Request() {}
RequestKind Request::GetRequestKind() const {
return this->kind;
}
Utils::ReportLevel Request::GetLevel() const {
if (this->level.has_value()) return this->level.value();
else throw std::logic_error("can not visit this property in current kind");
}
std::u8string_view Request::GetFilePath() const {
if (this->file_path.has_value()) return this->file_path.value();
else throw std::logic_error("can not visit this property in current kind");
}
std::u8string_view Request::GetEncoding() const {
if (this->encoding.has_value()) return this->encoding.value();
else throw std::logic_error("can not visit this property in current kind");
}
std::u8string_view Request::GetBallancePath() const {
if (this->ballance_path.has_value()) return this->ballance_path.value();
else throw std::logic_error("can not visit this property in current kind");
}
#pragma endregion
#pragma region Custom Validators
struct ReportLevelValidator {
using ReturnType = Utils::ReportLevel;
std::optional<ReturnType> validate(const std::u8string_view& sv) const { return Utils::ParseReportLevel(sv); }
};
#pragma endregion
Result<Request> parse() {
// Create options
clap::option::OptionCollection opt_collection;
auto opt_file = opt_collection.add_option(
clap::option::Option(u8"i", u8"file", u8"FILE", u8R"(The path to map file loaded by this program.
This field is required.)"));
auto opt_ballance = opt_collection.add_option(
clap::option::Option(u8"b", u8"ballance", u8"DIR", u8R"(The path to your Ballance root directory for finding resources.
This field is required.)"));
auto opt_encoding = opt_collection.add_option(
clap::option::Option(u8"e", u8"encoding", u8"ENC", u8R"(The encoding used when loading this map file.
Frequently used encoding is "cp1252" and "gbk".
Default value is "cp1252".)"));
auto opt_level = opt_collection.add_option(
clap::option::Option(u8"l", u8"level", u8"LEVEL", u8R"(Set the filter level for checker output.
Available levels are "error", "warning" and "info".
Default value is "info".)"));
auto opt_version = opt_collection.add_option(
clap::option::Option(u8"v", u8"version", std::nullopt, u8"Print version of this program."));
auto opt_help = opt_collection.add_option(clap::option::Option(u8"h", u8"help", std::nullopt, u8"Print this page."));
// Create variables
clap::variable::VariableCollection var_collection;
// Create manifest
clap::summary::Summary summary(u8"" BMAPINSP_NAME, u8"yyc12345", u8"Universal", u8"" BMAPINSP_DESC);
// Create application
clap::application::Application app(std::move(summary), std::move(opt_collection), std::move(var_collection));
// Create parser and parse command line arguments
auto rv_parser = clap::parser::Parser::from_system(app);
if (!rv_parser.has_value()) return std::unexpected(Error::BadParse);
auto& parser = rv_parser.value();
// Check version and help first
if (auto help_flag = parser.get_flag_option(opt_help); help_flag.has_value()) {
return Request::FromHelpRequest();
}
if (auto version_flag = parser.get_flag_option(opt_version); version_flag.has_value()) {
return Request::FromVersionRequest();
}
// Check other args
std::u8string file_rv;
if (parser.has_option(opt_file)) {
auto file_value = parser.get_value_option<clap::validator::StringValidator>(opt_file);
if (!file_value.has_value()) return std::unexpected(Error::BadFile);
file_rv = std::move(file_value.value());
} else {
return std::unexpected(Error::NoFile);
}
std::u8string ballance_rv;
if (parser.has_option(opt_ballance)) {
auto ballance_value = parser.get_value_option<clap::validator::StringValidator>(opt_ballance);
if (!ballance_value.has_value()) return std::unexpected(Error::BadBallance);
ballance_rv = std::move(ballance_value.value());
} else {
return std::unexpected(Error::NoBallance);
}
std::u8string encoding_rv;
if (parser.has_option(opt_encoding)) {
auto encoding_value = parser.get_value_option<clap::validator::StringValidator>(opt_encoding);
if (!encoding_value.has_value()) return std::unexpected(Error::BadEncoding);
encoding_rv = std::move(encoding_value.value());
} else {
encoding_rv = u8"cp1252";
}
Utils::ReportLevel level_rv;
if (parser.has_option(opt_level)) {
auto level_value = parser.get_value_option<ReportLevelValidator>(opt_level);
if (!level_value.has_value()) return std::unexpected(Error::BadLevel);
level_rv = std::move(level_value.value());
} else {
level_rv = Utils::ReportLevel::Info;
}
// Return result
return Request::FromWorkRequest(level_rv, file_rv, encoding_rv, ballance_rv);
}
} // namespace BMapInspector::Cli

View File

@@ -0,0 +1,68 @@
#pragma once
#include "Utils.hpp"
#include "Reporter.hpp"
#include <yycc.hpp>
#include <yycc/macro/class_copy_move.hpp>
#include <string>
#include <optional>
#include <expected>
namespace BMapInspector::Cli {
enum class RequestKind {
Help,
Version,
Work,
};
class Request {
public:
static Request FromHelpRequest();
static Request FromVersionRequest();
static Request FromWorkRequest(Utils::ReportLevel level,
const std::u8string_view& file_path,
const std::u8string_view& encoding,
const std::u8string_view& ballance_path);
private:
Request(RequestKind kind,
std::optional<Utils::ReportLevel> level,
std::optional<std::u8string_view> file_path,
std::optional<std::u8string_view> encoding,
std::optional<std::u8string_view> ballance_path);
public:
~Request();
YYCC_DEFAULT_COPY_MOVE(Request)
public:
RequestKind GetRequestKind() const;
Utils::ReportLevel GetLevel() const;
std::u8string_view GetFilePath() const;
std::u8string_view GetEncoding() const;
std::u8string_view GetBallancePath() const;
private:
RequestKind kind; ///< The kind of this request.
std::optional<Utils::ReportLevel> level; ///< The filter level.
std::optional<std::u8string> file_path; ///< The path to loaded map file.
std::optional<std::u8string> encoding; ///< The encoding used when loading map file.
std::optional<std::u8string> ballance_path; ///< The path to Ballance root directory for loading resources.
};
enum class Error {
BadParse, ///< Error occurs when executing parser.
NoFile, ///< User do not specify file path for loading.
BadFile, ///< User specified file path is bad.
NoBallance, ///< User do not specify Ballance directory for loading.
BadBallance, ///< User specified Ballance directory is bad.
BadEncoding, ///< User given encoding value is bad.
BadLevel, ///< User given level name is bad.
};
template<typename T>
using Result = std::expected<T, Error>;
Result<Request> parse();
} // namespace BMapInspector::Cli

View File

@@ -0,0 +1,107 @@
#include "Reporter.hpp"
#include <yycc/carton/termcolor.hpp>
#include <yycc/string/op.hpp>
#include <yycc/patch/stream.hpp>
#include <iostream>
#include <cstdarg>
using namespace yycc::patch::stream;
namespace strop = yycc::string::op;
namespace termcolor = yycc::carton::termcolor;
namespace BMapInspector {
#pragma region Reporter
Reporter::Reporter() {}
Reporter::~Reporter() {}
void Reporter::AddReport(Utils::ReportLevel level, const std::u8string_view &rule, const std::u8string_view &content) {
this->reports.emplace_back(Report{
.level = level,
.rule = std::u8string(rule),
.content = std::u8string(content),
});
}
void Reporter::WriteInfo(const std::u8string_view &rule, const std::u8string_view &content) {
this->AddReport(Utils::ReportLevel::Info, rule, content);
}
void Reporter::FormatInfo(const std::u8string_view &rule, const char8_t *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
this->WriteInfo(rule, strop::vprintf(fmt, argptr));
va_end(argptr);
}
void Reporter::WriteWarning(const std::u8string_view &rule, const std::u8string_view &content) {
this->AddReport(Utils::ReportLevel::Warning, rule, content);
}
void Reporter::FormatWarning(const std::u8string_view &rule, const char8_t *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
this->WriteWarning(rule, strop::vprintf(fmt, argptr));
va_end(argptr);
}
void Reporter::WriteError(const std::u8string_view &rule, const std::u8string_view &content) {
this->AddReport(Utils::ReportLevel::Error, rule, content);
}
void Reporter::FormatError(const std::u8string_view &rule, const char8_t *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
this->WriteError(rule, strop::vprintf(fmt, argptr));
va_end(argptr);
}
void Reporter::PrintConclusion() const {
// Conclude count
size_t cnt_err = 0, cnt_warn = 0, cnt_info = 0;
for (const auto &report : this->reports) {
switch (report.level) {
case Utils::ReportLevel::Error:
++cnt_err;
break;
case Utils::ReportLevel::Warning:
++cnt_warn;
break;
case Utils::ReportLevel::Info:
++cnt_info;
break;
}
}
// Show in console
termcolor::cprintln(strop::printf(u8"Total %" PRIuSIZET " error(s), %" PRIuSIZET " warning(s) and %" PRIuSIZET " info(s).",
cnt_err,
cnt_warn,
cnt_info),
termcolor::Color::LightBlue);
}
void Reporter::PrintReport() const {
// Print all entries by different color
for (const auto &report : this->reports) {
switch (report.level) {
case Utils::ReportLevel::Error:
termcolor::cprintln(strop::printf(u8"[ERROR] [RULE: %s] %s", report.rule.c_str(), report.content.c_str()),
termcolor::Color::LightRed);
break;
case Utils::ReportLevel::Warning:
termcolor::cprintln(strop::printf(u8"[WARNING] [RULE: %s] %s", report.rule.c_str(), report.content.c_str()),
termcolor::Color::LightYellow);
break;
case Utils::ReportLevel::Info:
termcolor::cprintln(strop::printf(u8"[INFO] [RULE: %s] %s", report.rule.c_str(), report.content.c_str()),
termcolor::Color::Default);
break;
}
}
}
#pragma endregion
} // namespace BMapInspector::Utils

View File

@@ -0,0 +1,42 @@
#pragma once
#include "Utils.hpp"
#include <yycc.hpp>
#include <yycc/macro/class_copy_move.hpp>
#include <string>
#include <string_view>
#include <vector>
namespace BMapInspector {
struct Report {
Utils::ReportLevel level; ///< The level of this report.
std::u8string rule; ///< The name of rule adding this report.
std::u8string content; ///< The content of this report.
};
class Reporter {
public:
Reporter();
~Reporter();
YYCC_DEFAULT_COPY_MOVE(Reporter)
private:
void AddReport(Utils::ReportLevel level, const std::u8string_view& rule, const std::u8string_view& content);
public:
void WriteInfo(const std::u8string_view& rule, const std::u8string_view& content);
void FormatInfo(const std::u8string_view& rule, const char8_t* fmt, ...);
void WriteWarning(const std::u8string_view& rule, const std::u8string_view& content);
void FormatWarning(const std::u8string_view& rule, const char8_t* fmt, ...);
void WriteError(const std::u8string_view& rule, const std::u8string_view& content);
void FormatError(const std::u8string_view& rule, const char8_t* fmt, ...);
public:
void PrintConclusion() const;
void PrintReport() const;
private:
std::vector<Report> reports;
};
} // namespace BMapInspector::Utils

View File

@@ -1,107 +1,20 @@
#include "Utils.hpp"
#include <yycc/carton/termcolor.hpp>
#include <yycc/string/op.hpp>
#include <yycc/patch/stream.hpp>
#include <iostream>
#include <cstdarg>
using namespace yycc::patch::stream;
namespace strop = yycc::string::op;
namespace termcolor = yycc::carton::termcolor;
#include <yycc.hpp>
#include <yycc/cenum.hpp>
namespace BMapInspector::Utils {
#pragma region Reporter
Reporter::Reporter() {}
Reporter::~Reporter() {}
void Reporter::AddReport(ReportKind kind, const std::u8string_view &rule, const std::u8string_view &content) {
this->reports.emplace_back(Report{
.kind = kind,
.rule = std::u8string(rule),
.content = std::u8string(content),
});
std::optional<ReportLevel> ParseReportLevel(const std::u8string_view &value) {
if (value == u8"error") return ReportLevel::Error;
else if (value == u8"warning") return ReportLevel::Warning;
else if (value == u8"info") return ReportLevel::Info;
return std::nullopt;
}
void Reporter::WriteInfo(const std::u8string_view &rule, const std::u8string_view &content) {
this->AddReport(ReportKind::Info, rule, content);
bool FilterReportLevel(ReportLevel check, ReportLevel filter) {
auto num_check = yycc::cenum::integer(check);
auto num_filter = yycc::cenum::integer(filter);
return num_check <= num_filter;
}
void Reporter::FormatInfo(const std::u8string_view &rule, const char8_t *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
this->WriteInfo(rule, strop::vprintf(fmt, argptr));
va_end(argptr);
}
void Reporter::WriteWarning(const std::u8string_view &rule, const std::u8string_view &content) {
this->AddReport(ReportKind::Warning, rule, content);
}
void Reporter::FormatWarning(const std::u8string_view &rule, const char8_t *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
this->WriteWarning(rule, strop::vprintf(fmt, argptr));
va_end(argptr);
}
void Reporter::WriteError(const std::u8string_view &rule, const std::u8string_view &content) {
this->AddReport(ReportKind::Error, rule, content);
}
void Reporter::FormatError(const std::u8string_view &rule, const char8_t *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
this->WriteError(rule, strop::vprintf(fmt, argptr));
va_end(argptr);
}
void Reporter::PrintConclusion() const {
// Conclude count
size_t cnt_err = 0, cnt_warn = 0, cnt_info = 0;
for (const auto &report : this->reports) {
switch (report.kind) {
case ReportKind::Error:
++cnt_err;
break;
case ReportKind::Warning:
++cnt_warn;
break;
case ReportKind::Info:
++cnt_info;
break;
}
}
// Show in console
termcolor::cprintln(strop::printf(u8"Total %" PRIuSIZET " error(s), %" PRIuSIZET " warning(s) and %" PRIuSIZET " info(s).",
cnt_err,
cnt_warn,
cnt_info),
termcolor::Color::LightBlue);
}
void Reporter::PrintReport() const {
// Print all entries by different color
for (const auto &report : this->reports) {
switch (report.kind) {
case ReportKind::Error:
termcolor::cprintln(strop::printf(u8"[ERROR] [RULE: %s] %s", report.rule.c_str(), report.content.c_str()),
termcolor::Color::LightRed);
break;
case ReportKind::Warning:
termcolor::cprintln(strop::printf(u8"[WARNING] [RULE: %s] %s", report.rule.c_str(), report.content.c_str()),
termcolor::Color::LightYellow);
break;
case ReportKind::Info:
termcolor::cprintln(strop::printf(u8"[INFO] [RULE: %s] %s", report.rule.c_str(), report.content.c_str()),
termcolor::Color::Default);
break;
}
}
}
#pragma endregion
} // namespace BMapInspector::Utils

View File

@@ -1,45 +1,29 @@
#pragma once
#include <yycc.hpp>
#include <yycc/macro/class_copy_move.hpp>
#include <string>
#include <cstdint>
#include <optional>
#include <string_view>
#include <vector>
#define PRIuSIZET "zu"
namespace BMapInspector::Utils {
enum class ReportKind { Error, Warning, Info };
#define PRIuSIZET "zu"
struct Report {
ReportKind kind; ///< The kind of this report.
std::u8string rule; ///< The name of rule adding this report.
std::u8string content; ///< The content of this report.
#define BMAPINSP_NAME "Ballance Map Inspector"
#define BMAPINSP_DESC "The inspector for checking whether your Ballance custom map can be loaded without any issues."
enum class ReportLevel : std::uint32_t {
Error = 0,
Warning = 1,
Info = 2,
};
class Reporter {
public:
Reporter();
~Reporter();
YYCC_DEFAULT_COPY_MOVE(Reporter)
std::optional<ReportLevel> ParseReportLevel(const std::u8string_view& value);
private:
void AddReport(ReportKind kind, const std::u8string_view& rule, const std::u8string_view& content);
public:
void WriteInfo(const std::u8string_view& rule, const std::u8string_view& content);
void FormatInfo(const std::u8string_view& rule, const char8_t* fmt, ...);
void WriteWarning(const std::u8string_view& rule, const std::u8string_view& content);
void FormatWarning(const std::u8string_view& rule, const char8_t* fmt, ...);
void WriteError(const std::u8string_view& rule, const std::u8string_view& content);
void FormatError(const std::u8string_view& rule, const char8_t* fmt, ...);
public:
void PrintConclusion() const;
void PrintReport() const;
private:
std::vector<Report> reports;
};
/**
* @brief Check whether given report level can pass given filter.
* @param[in] check The level for checking whether it can pass filter.
* @param[in] filter The level of filter.
* @return True if is can pass, otherwise false.
*/
bool FilterReportLevel(ReportLevel check, ReportLevel filter);
} // namespace BMapInspector::Utils