Add flag parsing functionality and implement global flag registry
This commit is contained in:
@@ -8,8 +8,13 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "cli/tui.h"
|
#include "cli/tui.h"
|
||||||
|
#include "util/flag.h"
|
||||||
#include "util/macro.h"
|
#include "util/macro.h"
|
||||||
|
|
||||||
|
DECLARE_FLAG(std::string, rom_file);
|
||||||
|
|
||||||
|
DEFINE_FLAG(std::string, rom_file, "", "The ROM file to load.");
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
/**
|
/**
|
||||||
* @namespace yaze::cli
|
* @namespace yaze::cli
|
||||||
@@ -87,6 +92,19 @@ int RunCommandHandler(int argc, char *argv[]) {
|
|||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
tokens.emplace_back(argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
yaze::util::FlagParser flag_parser(yaze::util::global_flag_registry());
|
||||||
|
try {
|
||||||
|
flag_parser.Parse(&tokens);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cerr << "Error parsing flags: " << e.what() << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
yaze::cli::ShowMain();
|
yaze::cli::ShowMain();
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
// return yaze::cli::RunCommandHandler(argc, argv);
|
// return yaze::cli::RunCommandHandler(argc, argv);
|
||||||
|
|||||||
187
src/util/flag.h
Normal file
187
src/util/flag.h
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#ifndef YAZE_UTIL_FLAG_H_
|
||||||
|
#define YAZE_UTIL_FLAG_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
// Base interface for all flags.
|
||||||
|
class IFlag {
|
||||||
|
public:
|
||||||
|
virtual ~IFlag() = default;
|
||||||
|
|
||||||
|
// Returns the full name (e.g. "--count") used for this flag.
|
||||||
|
virtual const std::string& name() const = 0;
|
||||||
|
|
||||||
|
// Returns help text describing how to use this flag.
|
||||||
|
virtual const std::string& help() const = 0;
|
||||||
|
|
||||||
|
// Parses a string value into the underlying type.
|
||||||
|
virtual void ParseValue(const std::string& text) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Flag : public IFlag {
|
||||||
|
public:
|
||||||
|
Flag(const std::string& name, const T& default_value,
|
||||||
|
const std::string& help_text)
|
||||||
|
: name_(name),
|
||||||
|
value_(default_value),
|
||||||
|
default_(default_value),
|
||||||
|
help_(help_text) {}
|
||||||
|
|
||||||
|
const std::string& name() const override { return name_; }
|
||||||
|
const std::string& help() const override { return help_; }
|
||||||
|
|
||||||
|
// Attempts to parse a string into type T using a stringstream.
|
||||||
|
void ParseValue(const std::string& text) override {
|
||||||
|
std::stringstream ss(text);
|
||||||
|
T parsed;
|
||||||
|
if (!(ss >> parsed)) {
|
||||||
|
throw std::runtime_error("Failed to parse flag: " + name_);
|
||||||
|
}
|
||||||
|
value_ = parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the current (parsed or default) value of the flag.
|
||||||
|
const T& Get() const { return value_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
T value_;
|
||||||
|
T default_;
|
||||||
|
std::string help_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FlagRegistry {
|
||||||
|
public:
|
||||||
|
// Registers a flag in the global registry.
|
||||||
|
// The return type is a pointer to the newly created flag.
|
||||||
|
template <typename T>
|
||||||
|
Flag<T>* RegisterFlag(const std::string& name, const T& default_value,
|
||||||
|
const std::string& help_text) {
|
||||||
|
auto flag = std::make_unique<Flag<T>>(name, default_value, help_text);
|
||||||
|
Flag<T>* raw_ptr =
|
||||||
|
flag.get(); // We keep a non-owning pointer to use later.
|
||||||
|
flags_[name] = std::move(flag);
|
||||||
|
return raw_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a shared interface pointer if found, otherwise nullptr.
|
||||||
|
IFlag* GetFlag(const std::string& name) const {
|
||||||
|
auto it = flags_.find(name);
|
||||||
|
if (it == flags_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns all registered flags for iteration, help text, etc.
|
||||||
|
std::vector<IFlag*> AllFlags() const {
|
||||||
|
std::vector<IFlag*> result;
|
||||||
|
result.reserve(flags_.size());
|
||||||
|
for (auto const& kv : flags_) {
|
||||||
|
result.push_back(kv.second.get());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<IFlag>> flags_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
inline FlagRegistry* global_flag_registry() {
|
||||||
|
// Guaranteed to be initialized once per process.
|
||||||
|
static FlagRegistry* registry = new FlagRegistry();
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DECLARE_FLAG(type, name) extern yaze::util::Flag<type>* FLAGS_##name
|
||||||
|
|
||||||
|
// Defines a global Flag<type>* FLAGS_<name> and registers it.
|
||||||
|
#define DEFINE_FLAG(type, name, default_val, help_text) \
|
||||||
|
yaze::util::Flag<type>* FLAGS_##name = \
|
||||||
|
yaze::util::global_flag_registry()->RegisterFlag<type>("--" #name, (default_val), (help_text))
|
||||||
|
|
||||||
|
// Retrieves the current value of a declared flag.
|
||||||
|
#define FLAG_VALUE(name) (FLAGS_##name->Get())
|
||||||
|
|
||||||
|
class FlagParser {
|
||||||
|
public:
|
||||||
|
explicit FlagParser(FlagRegistry* registry) : registry_(registry) {}
|
||||||
|
|
||||||
|
// Parses flags out of the given token list. Recognizes forms:
|
||||||
|
// --flag=value or --flag value
|
||||||
|
// Any token not recognized as a flag is left in `leftover`.
|
||||||
|
void Parse(std::vector<std::string>* tokens) {
|
||||||
|
std::vector<std::string> leftover;
|
||||||
|
leftover.reserve(tokens->size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < tokens->size(); i++) {
|
||||||
|
const std::string& token = (*tokens)[i];
|
||||||
|
if (token.rfind("--", 0) == 0) {
|
||||||
|
// Found a token that starts with "--".
|
||||||
|
std::string flag_name;
|
||||||
|
std::string value_string;
|
||||||
|
if (!ExtractFlagAndValue(token, &flag_name, &value_string)) {
|
||||||
|
// If no value found after '=', see if next token is a value.
|
||||||
|
if ((i + 1) < tokens->size()) {
|
||||||
|
const std::string& next_token = (*tokens)[i + 1];
|
||||||
|
// If next token is NOT another flag, treat it as the value.
|
||||||
|
if (next_token.rfind("--", 0) != 0) {
|
||||||
|
value_string = next_token;
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
// If no explicit value, treat it as boolean 'true'.
|
||||||
|
value_string = "true";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value_string = "true";
|
||||||
|
}
|
||||||
|
flag_name = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse the flag (strip leading dashes in the registry).
|
||||||
|
IFlag* flag_ptr = registry_->GetFlag(flag_name);
|
||||||
|
if (!flag_ptr) {
|
||||||
|
throw std::runtime_error("Unrecognized flag: " + flag_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the parsed value on the matching flag.
|
||||||
|
flag_ptr->ParseValue(value_string);
|
||||||
|
} else {
|
||||||
|
leftover.push_back(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*tokens = leftover;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FlagRegistry* registry_;
|
||||||
|
|
||||||
|
// Checks if there is an '=' sign in the token, extracting flag name and
|
||||||
|
// value. e.g. "--count=42" -> flag_name = "--count", value_string = "42"
|
||||||
|
// returns true if '=' was found
|
||||||
|
bool ExtractFlagAndValue(const std::string& token, std::string* flag_name,
|
||||||
|
std::string* value_string) {
|
||||||
|
const size_t eq_pos = token.find('=');
|
||||||
|
if (eq_pos == std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*flag_name = token.substr(0, eq_pos);
|
||||||
|
*value_string = token.substr(eq_pos + 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_UTIL_FLAG_H_
|
||||||
Reference in New Issue
Block a user