commit 9a30e9b42667e63e83bb0c18a6941af4fd31d64f Author: Vulpovile Date: Sun Mar 29 19:38:17 2026 -0700 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b30ae0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.so +*.so.* + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f2eadf4 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +CXX ?= g++ + +VERSION := 0.0.1 + +TARGET_NOVERSION := liboptions.so +TARGET := $(TARGET_NOVERSION).$(VERSION) + +SRCS := $(wildcard *.cpp) + +OBJS := $(SRCS:.cpp=.o) + +CXXFLAGS := -std=c++17 -fPIC -shared +OPTFLAGS := -O3 +DBGFLAGS := -g +LDFLAGS := + +# Default build +all: release + +release: CXXFLAGS += $(OPTFLAGS) +release: $(TARGET) + +debug: CXXFLAGS += $(DBGFLAGS) +debug: $(TARGET) + +# Linker +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + +# Compiler +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJS) $(TARGET_NOVERSION) $(TARGET_NOVERSION).* + +install: + cp $(TARGET) /usr/lib/$(TARGET) + cp options.h /usr/include/options.h + ln -s /usr/lib/$(TARGET) /usr/lib/$(TARGET_NOVERSION) + +uninstall: + unlink /usr/lib/$(TARGET_NOVERSION) + rm /usr/lib/$(TARGET) + rm /usr/include/options.h \ No newline at end of file diff --git a/options.cpp b/options.cpp new file mode 100644 index 0000000..9d4e25f --- /dev/null +++ b/options.cpp @@ -0,0 +1,63 @@ + +#include "options.h" + +using namespace OptionLib; + +bool isValidKeyChar(char c) { + return (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + (c == '_' || c == '-'); +} + +const std::string validateKey(const std::string &key) { + for (char c : key) { + if (!isValidKeyChar(c)) { + throw std::invalid_argument("Options key contains invalid characters, must match [a-z0-9\\_\\-]"); + } + } + return key; +} + +__OptionBase::__OptionBase(const std::string &key) : key(validateKey(key)) {} + +void OptionsBase::registerOption(__OptionBase* opt) { + options.push_back(opt); +} + +const std::vector<__OptionBase*>& OptionsBase::getOptions() const { + return options; +} + +void OptionsBase::store(const std::string& filename, std::string) { + std::ofstream file(filename); + if(!comment.empty()) { + file << "# " << comment << std::endl; + } + for (auto* opt : getOptions()) { + file << opt->key << "="; + opt->store(file); + file << std::endl; + } +} + +void OptionsBase::read(const std::string& filename) { + std::ifstream file(filename); + std::unordered_map kv; + + std::string key, value, line; + + while (std::getline(file, line)) { + if (line.size() < 1 || line[0] == '#') continue; // skip comment lines TODO make better + auto pos = line.find('='); + if (pos == std::string::npos) continue; // skip invalid lines + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1); + kv[key] = value; + } + + for (auto* opt : getOptions()) { + if (kv.count(opt->key)) { + opt->setFromString(kv[opt->key]); + } + } +} \ No newline at end of file diff --git a/options.h b/options.h new file mode 100644 index 0000000..0b347d4 --- /dev/null +++ b/options.h @@ -0,0 +1,63 @@ +#ifndef OPTIONS_H +#define OPTIONS_H + +#include +#include +#include +#include +#include + +namespace OptionLib { + class __OptionBase { + public: + const std::string key; + + __OptionBase(const std::string &key); + virtual ~__OptionBase() = default; + + virtual void setFromString(const std::string& value) = 0; + + virtual void store(std::ostream& stream) = 0; + }; + + + class OptionsBase { + private: + std::vector<__OptionBase*> options; + std::string comment = ""; + + public: + void registerOption(__OptionBase* opt); + const std::vector<__OptionBase*>& getOptions() const; + void store(const std::string& filename, std::string comment=""); + void read(const std::string& filename); + }; + + template + class Option : public __OptionBase{ + private: + T value; + public: + Option(OptionsBase * parent, std::string name) : __OptionBase(name) { + parent->registerOption(this); + } + Option(OptionsBase * parent, std::string name, T defaultValue) : __OptionBase(name) { + parent->registerOption(this); + set(defaultValue); + } + + const T& get() const {return value;} + void set(const T& v) {value = v;} + + void setFromString(const std::string& str) override { + std::istringstream iss(str); + iss >> value; + } + + void store(std::ostream& stream) { + stream << value; + }; + }; +} + +#endif diff --git a/options_headeronly.h b/options_headeronly.h new file mode 100644 index 0000000..88ee271 --- /dev/null +++ b/options_headeronly.h @@ -0,0 +1,118 @@ +#ifndef OPTIONS_H +#define OPTIONS_H + +#include +#include +#include +#include +#include + +namespace OptionLib { + + // TODO There has to be a better way of doing this + bool isValidKeyChar(char c) { + return (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + (c == '_' || c == '-'); + } + + const std::string validateKey(const std::string &key) { + for (char c : key) { + if (!isValidKeyChar(c)) { + throw std::invalid_argument("Options key contains invalid characters, must match [a-z0-9\\_\\-]"); + } + } + return key; + } + + class __OptionBase { + public: + const std::string key; + + __OptionBase(const std::string &key) : key(validateKey(key)) {} + virtual ~__OptionBase() = default; + + virtual void setFromString(const std::string& value) = 0; + + virtual void store(ostream& stream) = 0; + }; + + + class OptionsBase { + private: + std::vector<__OptionBase*> options; + std::string comment = ""; + + public: + void registerOption(__OptionBase* opt) { + options.push_back(opt); + } + + const std::vector<__OptionBase*>& getOptions() const { + return options; + } + + void store(const std::string& filename, std::string comment="") { + std::ofstream file(filename); + if(!comment.empty()) { + file << "# " << comment << endl; + } + for (auto* opt : getOptions()) { + file << opt->key << "="; + opt->store(file); + file << endl; + } + } + + void read(const std::string& filename) { + std::ifstream file(filename); + std::unordered_map kv; + + std::string key, value, line; + + while (std::getline(file, line)) { + if (line.size() < 1 || line[0] == '#') continue; // skip comment lines TODO make better + auto pos = line.find('='); + if (pos == std::string::npos) continue; // skip invalid lines + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1); + kv[key] = value; + } + + for (auto* opt : getOptions()) { + if (kv.count(opt->key)) { + opt->setFromString(kv[opt->key]); + } + } + } + + }; + + template + class Option : public __OptionBase{ + private: + T value; + public: + Option(OptionsBase * parent, std::string name) : __OptionBase(name) { + parent->registerOption(this); + } + Option(OptionsBase * parent, std::string name, T defaultValue) : __OptionBase(name) { + parent->registerOption(this); + set(defaultValue); + } + + const T& get() const {return value;} + void set(const T& v) {value = v;} + + void setFromString(const std::string& str) override { + std::istringstream iss(str); + iss >> value; + } + + void store(ostream& stream) { + stream << value; + }; + }; +} + +#endif