mirror of
https://github.com/obsqrbtz/clrsync.git
synced 2026-04-09 04:29:04 +03:00
init
This commit is contained in:
169
src/core/config/config.cpp
Normal file
169
src/core/config/config.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "config.hpp"
|
||||
|
||||
#include <core/palette/color.hpp>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
config &config::instance()
|
||||
{
|
||||
static config inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
void config::initialize(std::unique_ptr<clrsync::core::io::file> file)
|
||||
{
|
||||
copy_default_configs();
|
||||
m_file = std::move(file);
|
||||
if (m_file)
|
||||
if (!m_file->parse())
|
||||
throw std::runtime_error{"Could not parse config file"};
|
||||
}
|
||||
|
||||
std::filesystem::path config::get_user_config_dir()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (const char *appdata = std::getenv("APPDATA"))
|
||||
return fs::path(appdata) / "clrsync";
|
||||
else
|
||||
return fs::path("C:/clrsync");
|
||||
#else
|
||||
if (const char *xdg = std::getenv("XDG_CONFIG_HOME"))
|
||||
return std::filesystem::path(xdg) / "clrsync";
|
||||
else if (const char *home = std::getenv("HOME"))
|
||||
return std::filesystem::path(home) / ".config/clrsync";
|
||||
else
|
||||
return std::filesystem::path("/tmp/clrsync");
|
||||
#endif
|
||||
}
|
||||
|
||||
void config::copy_default_configs()
|
||||
{
|
||||
std::filesystem::path user_config = get_user_config_dir();
|
||||
|
||||
if (!std::filesystem::exists(user_config))
|
||||
{
|
||||
std::filesystem::create_directories(user_config);
|
||||
|
||||
std::filesystem::path default_dir = CLRSYNC_DATADIR;
|
||||
std::filesystem::copy(default_dir / "config.toml", user_config / "config.toml");
|
||||
std::filesystem::copy(default_dir / "templates", user_config / "templates",
|
||||
std::filesystem::copy_options::recursive);
|
||||
std::filesystem::copy(default_dir / "palettes", user_config / "palettes",
|
||||
std::filesystem::copy_options::recursive);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &config::palettes_path()
|
||||
{
|
||||
if (m_palettes_dir.empty() && m_file)
|
||||
m_palettes_dir = m_file->get_string_value("general", "palettes_path");
|
||||
return m_palettes_dir;
|
||||
}
|
||||
|
||||
const std::string config::default_theme() const
|
||||
{
|
||||
if (m_file)
|
||||
return m_file->get_string_value("general", "default_theme");
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::string config::font() const
|
||||
{
|
||||
if (m_file)
|
||||
return m_file->get_string_value("general", "font");
|
||||
return {};
|
||||
}
|
||||
|
||||
const uint32_t config::font_size() const
|
||||
{
|
||||
if (m_file)
|
||||
return m_file->get_uint_value("general", "font_size");
|
||||
return 14;
|
||||
}
|
||||
|
||||
void config::set_default_theme(const std::string &theme)
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
m_file->set_value("general", "default_theme", theme);
|
||||
m_file->save_file();
|
||||
}
|
||||
}
|
||||
|
||||
void config::set_palettes_path(const std::string &path)
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
m_file->set_value("general", "palettes_path", path);
|
||||
m_file->save_file();
|
||||
}
|
||||
}
|
||||
|
||||
void config::set_font(const std::string &font)
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
m_file->set_value("general", "font", font);
|
||||
m_file->save_file();
|
||||
}
|
||||
}
|
||||
void config::set_font_size(int font_size)
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
m_file->set_value("general", "font_size", font_size);
|
||||
m_file->save_file();
|
||||
}
|
||||
}
|
||||
|
||||
void config::update_template(const std::string &key,
|
||||
const clrsync::core::theme_template &theme_template)
|
||||
{
|
||||
m_themes[key] = theme_template;
|
||||
m_file->set_value("templates." + key, "input_path", theme_template.template_path());
|
||||
m_file->set_value("templates." + key, "output_path", theme_template.output_path());
|
||||
m_file->set_value("templates." + key, "enabled", theme_template.enabled());
|
||||
m_file->set_value("templates." + key, "reload_cmd", theme_template.reload_command());
|
||||
m_file->save_file();
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, clrsync::core::theme_template> config::templates()
|
||||
{
|
||||
if (m_themes.empty() && m_file)
|
||||
{
|
||||
auto themes = m_file->get_table("templates");
|
||||
for (const auto &t : themes)
|
||||
{
|
||||
auto current = m_file->get_table("templates." + t.first);
|
||||
clrsync::core::theme_template theme(t.first,
|
||||
std::get<std::string>(current["input_path"]),
|
||||
std::get<std::string>(current["output_path"]));
|
||||
if (std::holds_alternative<bool>(current["enabled"]))
|
||||
{
|
||||
theme.set_enabled(std::get<bool>(current["enabled"]));
|
||||
}
|
||||
else
|
||||
{
|
||||
theme.set_enabled(false);
|
||||
}
|
||||
theme.set_reload_command(std::get<std::string>(current["reload_cmd"]));
|
||||
theme.load_template();
|
||||
m_themes.insert({theme.name(), theme});
|
||||
}
|
||||
}
|
||||
return m_themes;
|
||||
}
|
||||
|
||||
const clrsync::core::theme_template &config::template_by_name(const std::string &name) const
|
||||
{
|
||||
auto it = m_themes.find(name);
|
||||
if (it != m_themes.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
throw std::runtime_error("Template not found: " + name);
|
||||
}
|
||||
|
||||
} // namespace clrsync::core
|
||||
48
src/core/config/config.hpp
Normal file
48
src/core/config/config.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef CLRSYNC_CORE_CONFIG_HPP
|
||||
#define CLRSYNC_CORE_CONFIG_HPP
|
||||
|
||||
#include <core/io/file.hpp>
|
||||
#include <core/theme/theme_template.hpp>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
class config
|
||||
{
|
||||
public:
|
||||
static config &instance();
|
||||
|
||||
void initialize(std::unique_ptr<clrsync::core::io::file> file);
|
||||
|
||||
const std::string font() const;
|
||||
const uint32_t font_size() const;
|
||||
const std::string &palettes_path();
|
||||
const std::string default_theme() const;
|
||||
const std::unordered_map<std::string, clrsync::core::theme_template> templates();
|
||||
const clrsync::core::theme_template &template_by_name(const std::string &name) const;
|
||||
std::filesystem::path get_user_config_dir();
|
||||
|
||||
|
||||
void set_default_theme(const std::string &theme);
|
||||
void set_palettes_path(const std::string &path);
|
||||
void set_font(const std::string &font);
|
||||
void set_font_size(int font_size);
|
||||
|
||||
void update_template(const std::string &key,
|
||||
const clrsync::core::theme_template &theme_template);
|
||||
|
||||
private:
|
||||
config() = default;
|
||||
config(const config &) = delete;
|
||||
config &operator=(const config &) = delete;
|
||||
|
||||
std::string m_palettes_dir{};
|
||||
std::unique_ptr<io::file> m_file;
|
||||
std::unordered_map<std::string, theme_template> m_themes{};
|
||||
void copy_default_configs();
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
45
src/core/io/file.hpp
Normal file
45
src/core/io/file.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef CLRSYNC_CORE_IO_FILE_HPP
|
||||
#define CLRSYNC_CORE_IO_FILE_HPP
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
using value_type = std::variant<std::string, uint32_t, int, bool>;
|
||||
|
||||
namespace clrsync::core::io
|
||||
{
|
||||
class file
|
||||
{
|
||||
public:
|
||||
file() = default;
|
||||
file(std::string path) {};
|
||||
virtual bool parse() { return false; };
|
||||
virtual const std::string get_string_value(const std::string §ion,
|
||||
const std::string &key) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual uint32_t get_uint_value(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual uint32_t get_bool_value(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual std::map<std::string, value_type> get_table(const std::string §ion_path) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual void set_value(const std::string §ion, const std::string &key,
|
||||
const value_type &value)
|
||||
{
|
||||
insert_or_update_value(section, key, value);
|
||||
}
|
||||
virtual void insert_or_update_value(const std::string §ion, const std::string &key,
|
||||
const value_type &value) {};
|
||||
virtual void save_file() {};
|
||||
};
|
||||
} // namespace clrsync::core::io
|
||||
#endif
|
||||
124
src/core/io/toml_file.cpp
Normal file
124
src/core/io/toml_file.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "toml_file.hpp"
|
||||
#include "core/utils.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
namespace clrsync::core::io
|
||||
{
|
||||
toml_file::toml_file(std::string path)
|
||||
{
|
||||
m_path = expand_user(path);
|
||||
}
|
||||
|
||||
bool toml_file::parse()
|
||||
{
|
||||
if (!std::filesystem::exists(m_path))
|
||||
return false;
|
||||
m_file = toml::parse_file(m_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string toml_file::get_string_value(const std::string §ion,
|
||||
const std::string &key) const
|
||||
{
|
||||
return m_file[section][key].value_or("");
|
||||
}
|
||||
|
||||
uint32_t toml_file::get_uint_value(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
return m_file[section][key].value_or(0);
|
||||
}
|
||||
|
||||
uint32_t toml_file::get_bool_value(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
return m_file[section][key].value_or(false);
|
||||
}
|
||||
|
||||
std::map<std::string, value_type> toml_file::get_table(const std::string §ion_path) const
|
||||
{
|
||||
auto parts = split(section_path, '.');
|
||||
const toml::table *tbl = m_file.as_table();
|
||||
|
||||
for (const auto &part : parts)
|
||||
{
|
||||
if (auto subtbl = tbl->at(part).as_table())
|
||||
tbl = subtbl;
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
std::map<std::string, value_type> result;
|
||||
|
||||
for (const auto &p : *tbl)
|
||||
{
|
||||
const auto &val = p.second;
|
||||
|
||||
if (auto b = val.value<bool>())
|
||||
result[std::string(p.first.str())] = *b;
|
||||
else if (auto s = val.value<std::string>())
|
||||
result[std::string(p.first.str())] = *s;
|
||||
else if (auto i = val.value<int64_t>())
|
||||
result[std::string(p.first.str())] = static_cast<uint32_t>(*i);
|
||||
else if (auto d = val.value<double>())
|
||||
result[std::string(p.first.str())] = static_cast<uint32_t>(*d);
|
||||
else
|
||||
result[std::string(p.first.str())] = {}; // fallback for unsupported types
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void toml_file::insert_or_update_value(const std::string §ion, const std::string &key,
|
||||
const value_type &value)
|
||||
{
|
||||
toml::table *tbl = m_file.as_table();
|
||||
|
||||
std::stringstream ss(section);
|
||||
std::string part;
|
||||
|
||||
while (std::getline(ss, part, '.'))
|
||||
{
|
||||
auto *sub = (*tbl)[part].as_table();
|
||||
if (!sub)
|
||||
{
|
||||
(*tbl).insert_or_assign(part, toml::table{});
|
||||
sub = (*tbl)[part].as_table();
|
||||
}
|
||||
tbl = sub;
|
||||
}
|
||||
|
||||
std::visit([&](auto &&v) { tbl->insert_or_assign(key, v); }, value);
|
||||
}
|
||||
|
||||
void toml_file::save_file()
|
||||
{
|
||||
std::filesystem::create_directories(std::filesystem::path(m_path).parent_path());
|
||||
std::ofstream stream(m_path, std::ios::binary);
|
||||
stream << m_file;
|
||||
}
|
||||
|
||||
std::vector<std::string> toml_file::split(const std::string &s, char delim) const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::string current;
|
||||
for (char c : s)
|
||||
{
|
||||
if (c == delim)
|
||||
{
|
||||
if (!current.empty())
|
||||
{
|
||||
result.push_back(current);
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current += c;
|
||||
}
|
||||
}
|
||||
if (!current.empty())
|
||||
result.push_back(current);
|
||||
return result;
|
||||
}
|
||||
} // namespace clrsync::core::io
|
||||
31
src/core/io/toml_file.hpp
Normal file
31
src/core/io/toml_file.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef CLRSYNC_CORE_IO_TOML_FILE_HPP
|
||||
#define CLRSYNC_CORE_IO_TOML_FILE_HPP
|
||||
#include <core/io/file.hpp>
|
||||
#include <string>
|
||||
#include <toml/toml.hpp>
|
||||
|
||||
namespace clrsync::core::io
|
||||
{
|
||||
class toml_file : public file
|
||||
{
|
||||
public:
|
||||
explicit toml_file(std::string path);
|
||||
bool parse() override;
|
||||
const std::string get_string_value(const std::string §ion,
|
||||
const std::string &key) const override;
|
||||
uint32_t get_uint_value(const std::string §ion, const std::string &key) const override;
|
||||
uint32_t get_bool_value(const std::string §ion, const std::string &key) const override;
|
||||
std::map<std::string, value_type> get_table(const std::string §ion_path) const override;
|
||||
void insert_or_update_value(const std::string §ion, const std::string &key,
|
||||
const value_type &value) override;
|
||||
void save_file() override;
|
||||
|
||||
private:
|
||||
toml::parse_result m_file{};
|
||||
std::string m_path{};
|
||||
|
||||
std::vector<std::string> split(const std::string &s, char delim) const;
|
||||
};
|
||||
} // namespace clrsync::core::io
|
||||
|
||||
#endif
|
||||
203
src/core/palette/color.cpp
Normal file
203
src/core/palette/color.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#include "color.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
uint32_t color::hex() const
|
||||
{
|
||||
return m_hex;
|
||||
}
|
||||
|
||||
rgb color::to_rgb() const
|
||||
{
|
||||
rgb result{};
|
||||
result.r = (m_hex >> 24) & 0xFF;
|
||||
result.g = (m_hex >> 16) & 0xFF;
|
||||
result.b = m_hex >> 8;
|
||||
return result;
|
||||
}
|
||||
|
||||
rgba color::to_rgba() const
|
||||
{
|
||||
rgba result{};
|
||||
result.r = (m_hex >> 24) & 0xFF;
|
||||
result.g = (m_hex >> 16) & 0xFF;
|
||||
result.b = (m_hex >> 8) & 0xFF;
|
||||
result.a = m_hex & 0xFF;
|
||||
return result;
|
||||
}
|
||||
|
||||
hsl color::to_hsl() const
|
||||
{
|
||||
rgb c = to_rgb();
|
||||
float r = c.r / 255.0f;
|
||||
float g = c.g / 255.0f;
|
||||
float b = c.b / 255.0f;
|
||||
|
||||
float max = std::max({r, g, b});
|
||||
float min = std::min({r, g, b});
|
||||
float h, s, l = (max + min) / 2.0f;
|
||||
|
||||
if (max == min)
|
||||
{
|
||||
h = s = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
float d = max - min;
|
||||
s = l > 0.5f ? d / (2.0f - max - min) : d / (max + min);
|
||||
if (max == r)
|
||||
{
|
||||
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
|
||||
}
|
||||
else if (max == g)
|
||||
{
|
||||
h = (b - r) / d + 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
h = (r - g) / d + 4.0f;
|
||||
}
|
||||
h /= 6.0f;
|
||||
}
|
||||
|
||||
return hsl{h * 360.0f, s, l};
|
||||
}
|
||||
|
||||
hsla color::to_hsla() const
|
||||
{
|
||||
rgba c = to_rgba();
|
||||
float r = c.r / 255.0f;
|
||||
float g = c.g / 255.0f;
|
||||
float b = c.b / 255.0f;
|
||||
float a = c.a / 255.0f;
|
||||
|
||||
float max = std::max({r, g, b});
|
||||
float min = std::min({r, g, b});
|
||||
float h, s, l = (max + min) / 2.0f;
|
||||
|
||||
if (max == min)
|
||||
{
|
||||
h = s = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
float d = max - min;
|
||||
s = l > 0.5f ? d / (2.0f - max - min) : d / (max + min);
|
||||
if (max == r)
|
||||
{
|
||||
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
|
||||
}
|
||||
else if (max == g)
|
||||
{
|
||||
h = (b - r) / d + 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
h = (r - g) / d + 4.0f;
|
||||
}
|
||||
h /= 6.0f;
|
||||
}
|
||||
|
||||
return hsla{h * 360.0f, s, l, a};
|
||||
}
|
||||
|
||||
void color::from_hex_string(const std::string &str)
|
||||
{
|
||||
if (str.empty() || str[0] != '#')
|
||||
throw std::invalid_argument("Invalid hex color format");
|
||||
|
||||
if (str.size() == 7) {
|
||||
uint32_t rgb = static_cast<uint32_t>(std::stoul(str.substr(1), nullptr, 16));
|
||||
m_hex = (rgb << 8) | 0xFF;
|
||||
}
|
||||
else if (str.size() == 9) {
|
||||
m_hex = static_cast<uint32_t>(std::stoul(str.substr(1), nullptr, 16));
|
||||
}
|
||||
else {
|
||||
throw std::invalid_argument("Invalid hex color format");
|
||||
}
|
||||
}
|
||||
|
||||
const std::string color::to_hex_string() const
|
||||
{
|
||||
char buffer[8];
|
||||
std::snprintf(buffer, sizeof(buffer), "#%06X", m_hex >> 8);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
const std::string color::to_hex_string_with_alpha() const
|
||||
{
|
||||
char buffer[10];
|
||||
std::snprintf(buffer, sizeof(buffer), "#%08X", m_hex);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
std::string color::format(const std::string& field) const
|
||||
{
|
||||
auto rgb = to_rgb();
|
||||
auto rgba = to_rgba();
|
||||
auto hslv = to_hsl();
|
||||
auto hslav = to_hsla();
|
||||
|
||||
if (field == "hex") return to_hex_string();
|
||||
if (field == "hex_stripped") {
|
||||
auto s = to_hex_string();
|
||||
return s.substr(1);
|
||||
}
|
||||
|
||||
if (field == "hexa") return to_hex_string_with_alpha();
|
||||
if (field == "hexa_stripped") {
|
||||
auto s = to_hex_string_with_alpha();
|
||||
return s.substr(1);
|
||||
}
|
||||
|
||||
if (field == "r") return std::to_string(rgb.r);
|
||||
if (field == "g") return std::to_string(rgb.g);
|
||||
if (field == "b") return std::to_string(rgb.b);
|
||||
|
||||
if (field == "a") {
|
||||
float af = rgba.a / 255.0f;
|
||||
return std::format("{:.2f}", af);
|
||||
}
|
||||
|
||||
if (field == "rgb")
|
||||
return std::format("rgb({},{},{})", rgb.r, rgb.g, rgb.b);
|
||||
|
||||
if (field == "rgba")
|
||||
return std::format("rgba({},{},{},{:.2f})",
|
||||
rgba.r, rgba.g, rgba.b,
|
||||
rgba.a / 255.0f);
|
||||
|
||||
if (field == "h")
|
||||
return std::format("{:.0f}", hslv.h);
|
||||
if (field == "s")
|
||||
return std::format("{:.2f}", hslv.s);
|
||||
if (field == "l")
|
||||
return std::format("{:.2f}", hslv.l);
|
||||
|
||||
// TODO: probably unneded
|
||||
if (field == "hsla_a")
|
||||
return std::format("{:.2f}", hslav.a);
|
||||
|
||||
if (field == "hsl")
|
||||
return std::format("hsl({:.0f},{:.2f},{:.2f})",
|
||||
hslv.h, hslv.s, hslv.l);
|
||||
|
||||
if (field == "hsla")
|
||||
return std::format("hsla({:.0f},{:.2f},{:.2f},{:.2f})",
|
||||
hslav.h, hslav.s, hslav.l, hslav.a);
|
||||
|
||||
throw std::runtime_error("Unknown color format: " + field);
|
||||
}
|
||||
|
||||
void color::set(uint32_t hex)
|
||||
{
|
||||
m_hex = hex;
|
||||
}
|
||||
} // namespace clrsync::core
|
||||
72
src/core/palette/color.hpp
Normal file
72
src/core/palette/color.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_COLOR_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_COLOR_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
struct rgb
|
||||
{
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
struct rgba
|
||||
{
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
};
|
||||
|
||||
struct hsl
|
||||
{
|
||||
float h;
|
||||
float s;
|
||||
float l;
|
||||
};
|
||||
|
||||
struct hsla
|
||||
{
|
||||
float h;
|
||||
float s;
|
||||
float l;
|
||||
float a;
|
||||
};
|
||||
|
||||
class color
|
||||
{
|
||||
public:
|
||||
color() = default;
|
||||
explicit color(uint32_t hex) : m_hex(hex)
|
||||
{
|
||||
}
|
||||
uint32_t hex() const;
|
||||
|
||||
rgb to_rgb() const;
|
||||
|
||||
rgba to_rgba() const;
|
||||
|
||||
hsl to_hsl() const;
|
||||
|
||||
hsla to_hsla() const;
|
||||
|
||||
void from_hex_string(const std::string &str);
|
||||
|
||||
const std::string to_hex_string() const;
|
||||
|
||||
const std::string to_hex_string_with_alpha() const;
|
||||
|
||||
std::string format(const std::string& field) const;
|
||||
|
||||
void set(uint32_t hex);
|
||||
|
||||
private:
|
||||
uint32_t m_hex = 0x00000000;
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
55
src/core/palette/color_keys.hpp
Normal file
55
src/core/palette/color_keys.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_COLOR_KEYS_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_COLOR_KEYS_HPP
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
constexpr const char *COLOR_KEYS[] = {
|
||||
// UI / Surfaces
|
||||
"background", // main window / editor background
|
||||
"surface", // panels, cards
|
||||
"surface_variant", // alternate rows, subtle panels
|
||||
"foreground", // main text
|
||||
"foreground_secondary", // secondary text / hints
|
||||
"accent", // buttons, highlights, selection
|
||||
"outline", // borders, outlines
|
||||
"shadow", // drop shadows / depth
|
||||
"cursor", // caret / text cursor
|
||||
|
||||
// Editor-specific surfaces
|
||||
"editor_background", "sidebar_background", "popup_background", "floating_window_background",
|
||||
"menu_option_background",
|
||||
|
||||
// Editor text roles
|
||||
"text_main", "text_emphasis", "text_command", "text_inactive", "text_disabled",
|
||||
"text_line_number", "text_selected", "text_selection_inactive",
|
||||
|
||||
// Editor / Window borders
|
||||
"border_window", "border_focused", "border_emphasized",
|
||||
|
||||
// Syntax highlighting
|
||||
"syntax_function", "syntax_error", "syntax_keyword", "syntax_special_keyword",
|
||||
"syntax_operator",
|
||||
|
||||
// Semantic text colors
|
||||
"text_error", "text_warning", "text_link", "text_comment", "text_string", "text_success",
|
||||
"warning_emphasis", "foreground_emphasis",
|
||||
|
||||
// Extra
|
||||
"terminal_gray",
|
||||
|
||||
// Semantic / Status
|
||||
"error", "warning", "success", "info",
|
||||
|
||||
// Terminal colors (normal)
|
||||
"term_black", "term_red", "term_green", "term_yellow", "term_blue", "term_magenta", "term_cyan",
|
||||
"term_white",
|
||||
|
||||
// Terminal colors (bright)
|
||||
"term_black_bright", "term_red_bright", "term_green_bright", "term_yellow_bright",
|
||||
"term_blue_bright", "term_magenta_bright", "term_cyan_bright", "term_white_bright"};
|
||||
|
||||
constexpr size_t NUM_COLOR_KEYS = std::size(COLOR_KEYS);
|
||||
} // namespace clrsync::core
|
||||
#endif
|
||||
64
src/core/palette/palette.hpp
Normal file
64
src/core/palette/palette.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_PALETTE_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_PALETTE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <core/palette/color.hpp>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
class palette
|
||||
{
|
||||
public:
|
||||
palette() = default;
|
||||
palette(const std::string &name) : m_name(name)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &file_path() const
|
||||
{
|
||||
return m_file_path;
|
||||
}
|
||||
|
||||
void set_file_path(const std::string &path)
|
||||
{
|
||||
m_file_path = path;
|
||||
}
|
||||
|
||||
const std::string &name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
const color &get_color(const std::string &key) const
|
||||
{
|
||||
auto it = m_colors.find(key);
|
||||
if (it != m_colors.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
static color default_color{};
|
||||
return default_color;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, color> &colors() const
|
||||
{
|
||||
return m_colors;
|
||||
}
|
||||
|
||||
void set_name(const std::string &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
void set_color(const std::string &key, const color &col)
|
||||
{
|
||||
m_colors[key] = col;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_name{};
|
||||
std::unordered_map<std::string, color> m_colors{};
|
||||
std::string m_file_path{};
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
#endif
|
||||
75
src/core/palette/palette_file.hpp
Normal file
75
src/core/palette/palette_file.hpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_PALETTE_FILE_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_PALETTE_FILE_HPP
|
||||
|
||||
#include "core/palette/color.hpp"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <core/io/file.hpp>
|
||||
#include <core/palette/color_keys.hpp>
|
||||
#include <core/palette/palette.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
template <typename FileType> class palette_file
|
||||
{
|
||||
public:
|
||||
palette_file() = default;
|
||||
palette_file(std::string file_path) : m_file(std::make_unique<FileType>(file_path))
|
||||
{
|
||||
m_palette.set_file_path(file_path);
|
||||
}
|
||||
bool parse()
|
||||
{
|
||||
if (!m_file->parse())
|
||||
return false;
|
||||
m_palette.set_name(m_file->get_string_value("general", "name"));
|
||||
for (const auto &color_key : COLOR_KEYS)
|
||||
{
|
||||
auto color_str = m_file->get_string_value("colors", color_key);
|
||||
core::color color{0x000000FF};
|
||||
if (!color_str.empty())
|
||||
color.from_hex_string(color_str);
|
||||
m_palette.set_color(color_key, color);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
core::palette palette() const
|
||||
{
|
||||
return m_palette;
|
||||
}
|
||||
void save_palette(const core::palette &pal)
|
||||
{
|
||||
set_palette(pal);
|
||||
save();
|
||||
}
|
||||
void set_color(const std::string &color_key, uint32_t color)
|
||||
{
|
||||
core::color col(color);
|
||||
m_file->insert_or_update_value("colors", color_key, col.to_hex_string());
|
||||
}
|
||||
void save()
|
||||
{
|
||||
m_file->save_file();
|
||||
}
|
||||
|
||||
private:
|
||||
core::palette m_palette{};
|
||||
std::unique_ptr<io::file> m_file;
|
||||
|
||||
void set_palette(const core::palette &pal)
|
||||
{
|
||||
m_palette = pal;
|
||||
m_file->insert_or_update_value("general", "name", pal.name());
|
||||
for (const auto &color_key : COLOR_KEYS)
|
||||
{
|
||||
const auto &col = pal.get_color(color_key);
|
||||
m_file->insert_or_update_value("colors", color_key, col.to_hex_string_with_alpha());
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
73
src/core/palette/palette_manager.hpp
Normal file
73
src/core/palette/palette_manager.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef CLRSYNC_CORE_PALETTE_PALETTE_MANAGER_HPP
|
||||
#define CLRSYNC_CORE_PALETTE_PALETTE_MANAGER_HPP
|
||||
|
||||
#include "core/utils.hpp"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <core/config/config.hpp>
|
||||
#include <core/palette/palette.hpp>
|
||||
#include <core/palette/palette_file.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
template <typename FileType> class palette_manager
|
||||
{
|
||||
public:
|
||||
palette_manager() = default;
|
||||
void load_palettes_from_directory(const std::string &directory_path)
|
||||
{
|
||||
auto directory_path_expanded = expand_user(directory_path);
|
||||
for (const auto &entry : std::filesystem::directory_iterator(directory_path_expanded))
|
||||
{
|
||||
if (entry.is_regular_file())
|
||||
{
|
||||
palette_file<FileType> pal_file(entry.path().string());
|
||||
if (pal_file.parse())
|
||||
add_palette(pal_file.palette());
|
||||
}
|
||||
}
|
||||
}
|
||||
void save_palette_to_file(const palette &pal, const std::string &directory_path) const
|
||||
{
|
||||
std::string file_path = directory_path + "/" + pal.name() + ".toml";
|
||||
palette_file<FileType> pal_file(file_path);
|
||||
pal_file.save_palette(pal);
|
||||
}
|
||||
|
||||
const palette load_palette_from_file(const std::string &file_path) const
|
||||
{
|
||||
palette_file<FileType> pal_file(file_path);
|
||||
if (pal_file.parse())
|
||||
return pal_file.palette(); // TODO: report missing/invalid file
|
||||
return {};
|
||||
}
|
||||
void add_palette(const palette &pal)
|
||||
{
|
||||
m_palettes[pal.name()] = pal;
|
||||
}
|
||||
void delete_palette(const std::string &file_path, const std::string &name)
|
||||
{
|
||||
std::filesystem::remove(file_path);
|
||||
m_palettes.erase(name);
|
||||
}
|
||||
const palette *get_palette(const std::string &name) const
|
||||
{
|
||||
auto it = m_palettes.find(name);
|
||||
if (it != m_palettes.end())
|
||||
{
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
const std::unordered_map<std::string, palette> &palettes() const
|
||||
{
|
||||
return m_palettes;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, palette> m_palettes{};
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
#endif
|
||||
35
src/core/theme/template_manager.hpp
Normal file
35
src/core/theme/template_manager.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef CLRSYNC_CORE_THEME_TEMPLATE_MANAGER_HPP
|
||||
#define CLRSYNC_CORE_THEME_TEMPLATE_MANAGER_HPP
|
||||
|
||||
#include <core/config/config.hpp>
|
||||
#include <core/theme/theme_template.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
template <typename FileType>
|
||||
class template_manager
|
||||
{
|
||||
public:
|
||||
template_manager() = default;
|
||||
std::unordered_map<std::string, theme_template> &templates()
|
||||
{
|
||||
auto themes = config::instance().templates();
|
||||
m_templates.clear();
|
||||
for (const auto &t : themes)
|
||||
{
|
||||
m_templates.insert({t.first, t.second});
|
||||
}
|
||||
return m_templates;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, theme_template> m_templates{};
|
||||
};
|
||||
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
58
src/core/theme/theme_renderer.hpp
Normal file
58
src/core/theme/theme_renderer.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef CLRSYNC_CORE_THEME_THEME_RENDERER_HPP
|
||||
#define CLRSYNC_CORE_THEME_THEME_RENDERER_HPP
|
||||
#include <core/config/config.hpp>
|
||||
#include <core/palette/palette_manager.hpp>
|
||||
#include <core/theme/template_manager.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
template <typename FileType> class theme_renderer
|
||||
{
|
||||
public:
|
||||
theme_renderer()
|
||||
{
|
||||
auto &cfg = config::instance();
|
||||
m_pal_manager.load_palettes_from_directory(cfg.palettes_path());
|
||||
m_template_manager = template_manager<FileType>();
|
||||
}
|
||||
|
||||
void apply_theme(const std::string &theme_name)
|
||||
{
|
||||
auto palette = m_pal_manager.get_palette(theme_name);
|
||||
if (!palette)
|
||||
throw std::runtime_error("Palette not found: " + theme_name);
|
||||
apply_palette_to_all_templates(*palette);
|
||||
}
|
||||
void apply_theme_from_path(const std::string &path)
|
||||
{
|
||||
auto palette = m_pal_manager.load_palette_from_file(path);
|
||||
apply_palette_to_all_templates(palette);
|
||||
}
|
||||
|
||||
private:
|
||||
palette_manager<FileType> m_pal_manager;
|
||||
template_manager<FileType> m_template_manager;
|
||||
|
||||
void apply_palette_to_all_templates(const palette &pal)
|
||||
{
|
||||
for (auto &t_pair : m_template_manager.templates())
|
||||
{
|
||||
auto &tmpl = t_pair.second;
|
||||
if (!tmpl.enabled())
|
||||
continue;
|
||||
tmpl.load_template();
|
||||
tmpl.apply_palette(pal);
|
||||
tmpl.save_output();
|
||||
if (!tmpl.reload_command().empty())
|
||||
{
|
||||
std::system(tmpl.reload_command().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
138
src/core/theme/theme_template.cpp
Normal file
138
src/core/theme/theme_template.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "theme_template.hpp"
|
||||
#include "core/utils.hpp"
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
theme_template::theme_template(const std::string &name, const std::string &template_path,
|
||||
const std::string &out_path)
|
||||
: m_name(name), m_template_path(expand_user(template_path)),
|
||||
m_output_path(expand_user(out_path))
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &theme_template::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void theme_template::set_name(const std::string &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
const std::string &theme_template::template_path() const
|
||||
{
|
||||
return m_template_path;
|
||||
}
|
||||
|
||||
void theme_template::set_template_path(const std::string &path)
|
||||
{
|
||||
m_template_path = expand_user(path);
|
||||
}
|
||||
|
||||
const std::string &theme_template::output_path() const
|
||||
{
|
||||
return m_output_path;
|
||||
}
|
||||
|
||||
void theme_template::set_output_path(const std::string &path)
|
||||
{
|
||||
m_output_path = expand_user(path);
|
||||
}
|
||||
|
||||
void theme_template::load_template()
|
||||
{
|
||||
std::ifstream input(m_template_path, std::ios::binary);
|
||||
if (!input)
|
||||
throw std::runtime_error("Failed to open template file: " + m_template_path);
|
||||
|
||||
m_template_data.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
void theme_template::apply_palette(const core::palette &palette)
|
||||
{
|
||||
m_processed_data = m_template_data;
|
||||
|
||||
for (const auto& [key, color] : palette.colors())
|
||||
{
|
||||
// simple replacement: {foreground}
|
||||
replace_all(m_processed_data, "{" + key + "}", color.format("hex"));
|
||||
|
||||
// mutli-component: {foreground.r}, {foreground.rgb}, etc.
|
||||
std::string prefix = "{" + key + ".";
|
||||
size_t pos = 0;
|
||||
while ((pos = m_processed_data.find(prefix, pos)) != std::string::npos)
|
||||
{
|
||||
size_t end = m_processed_data.find('}', pos);
|
||||
if (end == std::string::npos)
|
||||
break;
|
||||
|
||||
const size_t field_start = pos + prefix.size();
|
||||
std::string field = m_processed_data.substr(field_start, end - field_start);
|
||||
|
||||
std::string value = color.format(field);
|
||||
|
||||
m_processed_data.replace(pos, end - pos + 1, value);
|
||||
|
||||
pos += value.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void theme_template::save_output() const
|
||||
{
|
||||
std::filesystem::create_directories(std::filesystem::path(m_output_path).parent_path());
|
||||
std::ofstream output(m_output_path, std::ios::binary);
|
||||
if (!output)
|
||||
throw std::runtime_error("Failed to write output file: " + m_output_path);
|
||||
|
||||
output << m_processed_data;
|
||||
}
|
||||
|
||||
const std::string &theme_template::raw_template() const
|
||||
{
|
||||
return m_template_data;
|
||||
}
|
||||
|
||||
const std::string &theme_template::processed_template() const
|
||||
{
|
||||
return m_processed_data;
|
||||
}
|
||||
|
||||
void theme_template::replace_all(std::string &str, const std::string &from, const std::string &to)
|
||||
{
|
||||
if (from.empty())
|
||||
return;
|
||||
|
||||
size_t pos = 0;
|
||||
while ((pos = str.find(from, pos)) != std::string::npos)
|
||||
{
|
||||
str.replace(pos, from.length(), to);
|
||||
pos += to.length();
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &theme_template::reload_command() const
|
||||
{
|
||||
return m_reload_cmd;
|
||||
}
|
||||
|
||||
void theme_template::set_reload_command(const std::string &cmd)
|
||||
{
|
||||
m_reload_cmd = cmd;
|
||||
}
|
||||
|
||||
bool theme_template::enabled() const
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
void theme_template::set_enabled(bool enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
} // namespace clrsync::core
|
||||
60
src/core/theme/theme_template.hpp
Normal file
60
src/core/theme/theme_template.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef clrsync_CORE_IO_THEME_TEMPLATE_HPP
|
||||
#define clrsync_CORE_IO_THEME_TEMPLATE_HPP
|
||||
|
||||
#include <core/palette/palette.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
class theme_template
|
||||
{
|
||||
public:
|
||||
theme_template() = default;
|
||||
theme_template(const std::string &name, const std::string &template_path,
|
||||
const std::string &out_path);
|
||||
|
||||
const std::string &name() const;
|
||||
|
||||
void set_name(const std::string &name);
|
||||
|
||||
const std::string &template_path() const;
|
||||
|
||||
void set_template_path(const std::string &path);
|
||||
|
||||
const std::string &output_path() const;
|
||||
|
||||
void set_output_path(const std::string &path);
|
||||
|
||||
void load_template();
|
||||
|
||||
void apply_palette(const core::palette &palette);
|
||||
|
||||
void save_output() const;
|
||||
|
||||
const std::string &raw_template() const;
|
||||
|
||||
const std::string &processed_template() const;
|
||||
|
||||
const std::string &reload_command() const;
|
||||
|
||||
void set_reload_command(const std::string &cmd);
|
||||
|
||||
bool enabled() const;
|
||||
|
||||
void set_enabled(bool enabled);
|
||||
|
||||
private:
|
||||
std::string m_name{};
|
||||
std::string m_template_path{};
|
||||
std::string m_output_path{};
|
||||
bool m_enabled = true;
|
||||
std::string m_template_data{};
|
||||
std::string m_processed_data{};
|
||||
std::string m_reload_cmd{};
|
||||
|
||||
static void replace_all(std::string &str, const std::string &from, const std::string &to);
|
||||
};
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif
|
||||
45
src/core/utils.cpp
Normal file
45
src/core/utils.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "utils.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
void print_color_keys()
|
||||
{
|
||||
for (const auto &key : clrsync::core::COLOR_KEYS)
|
||||
{
|
||||
std::cout << key << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_default_config_path()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const char *appdata = std::getenv("APPDATA"); // "C:\Users\<User>\AppData\Roaming"
|
||||
if (!appdata)
|
||||
throw std::runtime_error("APPDATA environment variable not set");
|
||||
return std::string(appdata) + "\\clrsync\\config.toml";
|
||||
#else
|
||||
const char *home = std::getenv("HOME");
|
||||
if (!home)
|
||||
throw std::runtime_error("HOME environment variable not set");
|
||||
return std::string(home) + "/.config/clrsync/config.toml";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string expand_user(const std::string &path)
|
||||
{
|
||||
if (!path.empty() && path[0] == '~')
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const char *home = std::getenv("USERPROFILE");
|
||||
#else
|
||||
const char *home = std::getenv("HOME");
|
||||
#endif
|
||||
if (!home)
|
||||
return path;
|
||||
return std::string(home) + path.substr(1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
} // namespace clrsync::core
|
||||
14
src/core/utils.hpp
Normal file
14
src/core/utils.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef CLRSYNC_CORE_UTILS_HPP
|
||||
#define CLRSYNC_CORE_UTILS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <core/palette/color_keys.hpp>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
void print_color_keys();
|
||||
std::string get_default_config_path();
|
||||
std::string expand_user(const std::string &path);
|
||||
} // namespace clrsync::core
|
||||
#endif // CLRSYNC_CORE_UTILS_HPP
|
||||
10
src/core/version.cpp
Normal file
10
src/core/version.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "version.hpp"
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
const std::string version_string()
|
||||
{
|
||||
return "v" + std::to_string(VERSION_MAJOR) + "." + std::to_string(VERSION_MINOR) + "." +
|
||||
std::to_string(VERSION_PATCH);
|
||||
}
|
||||
} // namespace clrsync::core
|
||||
17
src/core/version.hpp
Normal file
17
src/core/version.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef CLRSYNC_CORE_VERSION_HPP
|
||||
#define CLRSYNC_CORE_VERSION_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace clrsync::core
|
||||
{
|
||||
|
||||
constexpr uint8_t VERSION_MAJOR = 0;
|
||||
constexpr uint8_t VERSION_MINOR = 0;
|
||||
constexpr uint8_t VERSION_PATCH = 1;
|
||||
|
||||
const std::string version_string();
|
||||
} // namespace clrsync::core
|
||||
|
||||
#endif // CLRSYNC_CORE_VERSION_HPP
|
||||
Reference in New Issue
Block a user