This commit is contained in:
2025-12-07 01:35:33 +03:00
commit 6cc0a613dc
342 changed files with 166529 additions and 0 deletions

169
src/core/config/config.cpp Normal file
View 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

View 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
View 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 &section,
const std::string &key) const
{
return {};
}
virtual uint32_t get_uint_value(const std::string &section, const std::string &key) const
{
return {};
}
virtual uint32_t get_bool_value(const std::string &section, const std::string &key) const
{
return {};
}
virtual std::map<std::string, value_type> get_table(const std::string &section_path) const
{
return {};
}
virtual void set_value(const std::string &section, const std::string &key,
const value_type &value)
{
insert_or_update_value(section, key, value);
}
virtual void insert_or_update_value(const std::string &section, 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
View 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 &section,
const std::string &key) const
{
return m_file[section][key].value_or("");
}
uint32_t toml_file::get_uint_value(const std::string &section, const std::string &key) const
{
return m_file[section][key].value_or(0);
}
uint32_t toml_file::get_bool_value(const std::string &section, 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 &section_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 &section, 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
View 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 &section,
const std::string &key) const override;
uint32_t get_uint_value(const std::string &section, const std::string &key) const override;
uint32_t get_bool_value(const std::string &section, const std::string &key) const override;
std::map<std::string, value_type> get_table(const std::string &section_path) const override;
void insert_or_update_value(const std::string &section, 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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
View 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
View 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
View 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