From cadf7cb42e16957fbd8bfab69c59db45352ceaa3 Mon Sep 17 00:00:00 2001 From: deeaitch Date: Sat, 28 Feb 2026 22:02:56 -0500 Subject: [PATCH] type factory based kindle 2 latex converter initial commit. --- fsm.h | 295 --------------------------- main.cpp | 372 ----------------------------------- typefactory/.gitignore | 74 +++++++ typefactory/kindle2latex.pro | 7 + typefactory/main.cpp | 325 ++++++++++++++++++++++++++++++ typefactory/typefactory.h | 118 +++++++++++ 6 files changed, 524 insertions(+), 667 deletions(-) delete mode 100644 fsm.h delete mode 100644 main.cpp create mode 100644 typefactory/.gitignore create mode 100644 typefactory/kindle2latex.pro create mode 100644 typefactory/main.cpp create mode 100644 typefactory/typefactory.h diff --git a/fsm.h b/fsm.h deleted file mode 100644 index d674496..0000000 --- a/fsm.h +++ /dev/null @@ -1,295 +0,0 @@ -#pragma once - -#include -#include -#include - -/** @brief Missing transition policy. */ -enum class missing_transition_policy { - /** @brief Missing transition is considered required by validation. */ - strict, - - /** @brief Missing transition is allowed (event ignored). */ - ignore -}; - -/** @brief Marker for undefined transition. */ -struct no_transition {}; - -/** @brief Default guard: always allow. */ -struct always_allow { - template - bool operator() (TContext &, const TFrom &, const TEvent &) const noexcept { - return true; - } -}; - -/** @brief Default action: do nothing. */ -struct do_nothing { - template - void operator() (TContext &, const TFrom &, const TTo &, const TEvent &) const noexcept { - } -}; - -/** - * @brief Transition descriptor. - * - * @tparam TTo Destination state type. - * @tparam TGuard Guard functor type. - * @tparam TAction Action functor type. - */ -template -struct transition_to { - using to_state = TTo; - using guard = TGuard; - using action = TAction; -}; - -/** - * @brief Transition table mapping: specialize to define transitions. - * Default: no_transition. - */ -template -struct transition { - using type = no_transition; -}; - -/** @brief Optional mapping to names for logs. */ -template -struct name_of { - static constexpr const char *value = "?"; -}; - -/** - * @brief Logger interface (default no-op). - * - * You can implement: - * - ignored_transition(from, event) - * - unhandled_transition(from, event) // policy=strict but missing transition at runtime - * - guard_blocked(from, event) - */ -struct null_logger { - void ignored_transition (const char *, const char *) noexcept {} - void unhandled_transition (const char *, const char *) noexcept {} - void guard_blocked (const char *, const char *) noexcept {} -}; - -/** @brief Hooks: state-only. */ -template -struct on_exit_state { - template - static void call (TContext &, const TState &) noexcept {} -}; - -template -struct on_entry_state { - template - static void call (TContext &, const TState &) noexcept {} -}; - -/** @brief Hooks: event-specific. */ -template -struct on_exit_event { - template - static void call (TContext &, const TState &, const TEvent &) noexcept {} -}; - -template -struct on_entry_event { - template - static void call (TContext &, const TState &, const TEvent &) noexcept {} -}; - -/** - * @brief Factory: construct destination state. - * - * IMPORTANT: Now includes TContext in template parameters to avoid templated make(). - * - * Default: To{}. - * Specialize state_factory as needed. - */ -template -struct state_factory { - static TTo make (TContext &, const TFrom &, const TEvent &) { - return TTo{}; - } -}; - -/** - * @brief Per (State,Event) missing transition policy. - * - * Default is ignore. Mark only required pairs strict. - */ -template -struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::ignore; -}; - -/** @brief Helper: is T one of Ts... */ -template -struct is_one_of : std::disjunction...> {}; - -/** @brief Helper: transition exists? */ -template -struct has_transition - : std::bool_constant < !std::is_same_v::type, no_transition >> {}; - -/** @brief Factory validation (call-form). */ -template -struct is_factory_invocable : std::false_type {}; - -template -struct is_factory_invocable < - TContext, TFrom, TEvent, TTo, -std::void_t::make ( - std::declval(), - std::declval(), - std::declval())) >> - : std::bool_constant::make ( - std::declval(), - std::declval(), - std::declval())), - TTo>> {}; - -/** - * @brief FSM with runtime state storage and explicit compile-time validation. - * - * dispatch(): - * - never static_asserts on missing transitions (avoids State×Event explosion with std::visit) - * - handles missing transitions via missing_transition::policy (ignore/strict) - * - * validate_events(): - * - compile-time checks ONLY for pairs marked strict. - */ -template -class fsm { - public: - using state_variant = std::variant; - - template ::value>> - explicit fsm (TContext &ctx, TInitial initial, TLogger logger = TLogger{}) - : m_ctx (ctx) - , m_logger (std::move (logger)) - , m_state (std::move (initial)) { - std::visit (init_entry_visitor{*this}, m_state); - } - - template - bool dispatch (const TEvent &ev) { - dispatch_visitor v{*this, ev}; - return std::visit (v, m_state); - } - - template - static constexpr void validate_events() { - (validate_one_event(), ...); - } - - const state_variant &state() const noexcept { - return m_state; - } - state_variant &state() noexcept { - return m_state; - } - - private: - template - struct dispatch_visitor { - fsm &self; - const TEvent &ev; - - template - bool operator() (TFrom &from) const { - return self.template dispatch_from (from, ev); - } - }; - - struct init_entry_visitor { - fsm &self; - template - void operator() (TState &st) const { - on_entry_state::call (self.m_ctx, st); - } - }; - - template - struct entry_after_commit_visitor { - fsm &self; - const TEvent &ev; - - template - void operator() (TState &st) const { - on_entry_state::call (self.m_ctx, st); - on_entry_event::call (self.m_ctx, st, ev); - } - }; - - template - bool dispatch_from (TFrom &from, const TEvent &ev) { - using tr = typename transition::type; - - if constexpr (std::is_same_v) { - if constexpr (missing_transition::policy == missing_transition_policy::strict) - m_logger.unhandled_transition (name_of::value, name_of::value); - else - m_logger.ignored_transition (name_of::value, name_of::value); - return false; - } else { - using to_state = typename tr::to_state; - using guard_t = typename tr::guard; - using action_t = typename tr::action; - - static_assert (is_one_of::value, - "FSM: transition target state not in FSM state list."); - - static_assert (std::is_invocable_r_v, - "FSM: guard must be callable as bool(Context&, const From&, const Event&)."); - - static_assert (std::is_invocable_r_v, - "FSM: action must be callable as void(Context&, const From&, const To&, const Event&)."); - - static_assert (is_factory_invocable::value, - "FSM: state_factory::make must be callable as " - "To make(Context&, const From&, const Event&)."); - - guard_t guard{}; - if (!guard (m_ctx, from, ev)) { - m_logger.guard_blocked (name_of::value, name_of::value); - return false; - } - - on_exit_event::call (m_ctx, from, ev); - on_exit_state::call (m_ctx, from); - - to_state to = state_factory::make (m_ctx, from, ev); - - action_t action{}; - action (m_ctx, from, to, ev); - - m_state = std::move (to); - - std::visit (entry_after_commit_visitor {*this, ev}, m_state); - return true; - } - } - - template - static constexpr void validate_one_event() { - (validate_pair(), ...); - } - - template - static constexpr void validate_pair() { - if constexpr (missing_transition::policy == missing_transition_policy::strict) { - static_assert (has_transition::value, - "FSM validation: required (strict) transition is missing for (State, Event)."); - } - } - - private: - TContext &m_ctx; - TLogger m_logger; - state_variant m_state; -}; diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 131c0bc..0000000 --- a/main.cpp +++ /dev/null @@ -1,372 +0,0 @@ -#include "fsm.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// ------------------------------------------------------------ -// Data model -// ------------------------------------------------------------ - -struct Quote { - std::string meta; - std::vector text_lines; -}; - -static std::string rstrip_cr (std::string s) { - if (!s.empty() && s.back() == '\r') - s.pop_back(); - return s; -} - -static std::string latex_escape (const std::string &s) { - // Conservative escaping for LaTeX special chars. - std::string out; - out.reserve (s.size() + s.size() / 8); - for (const unsigned char ch : s) { - switch (ch) { - case '\\': - out += "\\textbackslash{}"; - break; - case '{': - out += "\\{"; - break; - case '}': - out += "\\}"; - break; - case '%': - out += "\\%"; - break; - case '$': - out += "\\$"; - break; - case '#': - out += "\\#"; - break; - case '&': - out += "\\&"; - break; - case '_': - out += "\\_"; - break; - case '^': - out += "\\textasciicircum{}"; - break; - case '~': - out += "\\textasciitilde{}"; - break; - default: - out.push_back (static_cast (ch)); - break; - } - } - return out; -} - -// ------------------------------------------------------------ -// FSM: parsing Kindle "My Clippings" style format -// ------------------------------------------------------------ - -struct StExpectTitle {}; -struct StExpectMeta {}; -struct StExpectBlank {}; -struct StCollectText {}; - -struct EvTitle { - std::string s; -}; -struct EvMeta { - std::string s; -}; -struct EvBlank {}; -struct EvText { - std::string s; -}; -struct EvSep {}; -struct EvEof {}; - -struct ParserContext { - // group storage - std::vector order; - std::unordered_map> by_title; - std::unordered_map seen; - - // currently parsed quote - std::string cur_title; - std::string cur_meta; - std::vector cur_text; - - void start_title (std::string t) { - cur_title = std::move (t); - cur_meta.clear(); - cur_text.clear(); - } - void set_meta (std::string m) { - cur_meta = std::move (m); - } - void add_text (std::string line) { - cur_text.push_back (std::move (line)); - } - - void finalize_quote_if_ready() { - if (cur_title.empty() || cur_meta.empty()) { - cur_meta.clear(); - cur_text.clear(); - return; - } - - if (seen.find (cur_title) == seen.end()) { - seen.emplace (cur_title, order.size()); - order.push_back (cur_title); - } - - by_title[cur_title].push_back (Quote{cur_meta, cur_text}); - - cur_meta.clear(); - cur_text.clear(); - } -}; - -struct SilentLogger : null_logger {}; - -// Actions -struct ActSetTitle { - void operator() (ParserContext &ctx, const StExpectTitle &, const StExpectMeta &, const EvTitle &ev) const { - ctx.start_title (ev.s); - } -}; - -struct ActSetMeta { - void operator() (ParserContext &ctx, const StExpectMeta &, const StExpectBlank &, const EvMeta &ev) const { - ctx.set_meta (ev.s); - } -}; - -struct ActAddText { - template - void operator() (ParserContext &ctx, const TFrom &, const StCollectText &, const EvText &ev) const { - ctx.add_text (ev.s); - } -}; - -struct ActFinalizeOnSep { - template - void operator() (ParserContext &ctx, const TFrom &, const StExpectTitle &, const EvSep &) const { - ctx.finalize_quote_if_ready(); - } -}; - -struct ActFinalizeOnEof { - template - void operator() (ParserContext &ctx, const TFrom &, const StExpectTitle &, const EvEof &) const { - ctx.finalize_quote_if_ready(); - } -}; - -// Transitions -template <> struct transition { - using type = transition_to; -}; -template <> struct transition { - using type = transition_to; -}; -template <> struct transition { - using type = transition_to; -}; - -template <> struct transition { - using type = transition_to; -}; - -template <> struct transition { - using type = transition_to; -}; -template <> struct transition { - using type = transition_to; -}; - -template <> struct transition { - using type = transition_to; -}; -template <> struct transition { - using type = transition_to; -}; -template <> struct transition { - using type = transition_to; -}; - -// Strict contract for the pairs we dispatch. -template <> struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::strict; -}; -template <> struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::strict; -}; -template <> struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::strict; -}; - -template <> struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::strict; -}; - -template <> struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::strict; -}; -template <> struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::strict; -}; - -template <> struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::strict; -}; -template <> struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::strict; -}; -template <> struct missing_transition { - static constexpr missing_transition_policy policy = missing_transition_policy::strict; -}; - -using ParserFsm = fsm; - -static constexpr void validate_fsm_contract() { - ParserFsm::validate_events(); -} - -// ------------------------------------------------------------ -// CLI -// ------------------------------------------------------------ - -struct CliArgs { - std::string input; - std::string output; -}; - -static void print_usage (const char *argv0) { - std::cerr << "Usage: " << argv0 << " --input --output \n"; -} - -static bool parse_args (int argc, char **argv, CliArgs &out) { - static option long_opts[] = { - {"input", required_argument, nullptr, 'i'}, - {"output", required_argument, nullptr, 'o'}, - {"help", no_argument, nullptr, 'h'}, - {nullptr, 0, nullptr, 0 } - }; - - int c = 0; - while ((c = ::getopt_long (argc, argv, "i:o:h", long_opts, nullptr)) != -1) { - switch (c) { - case 'i': - out.input = optarg; - break; - case 'o': - out.output = optarg; - break; - case 'h': - print_usage (argv[0]); - return false; - default: - print_usage (argv[0]); - return false; - } - } - - if (out.input.empty() || out.output.empty()) { - print_usage (argv[0]); - return false; - } - return true; -} - -// ------------------------------------------------------------ -// Conversion -// ------------------------------------------------------------ - -static int convert (const std::string &in_path, const std::string &out_path) { - std::ifstream in (in_path); - if (!in.is_open()) { - std::cerr << "Failed to open input file: " << in_path << " (" << std::strerror (errno) << ")\n"; - return 2; - } - - ParserContext ctx{}; - ParserFsm fsm (ctx, StExpectTitle{}, SilentLogger{}); - - std::string line; - while (std::getline (in, line)) { - line = rstrip_cr (std::move (line)); - - if (line == "==========") { - fsm.dispatch (EvSep{}); - continue; - } - - const bool is_blank = line.empty(); - const auto &st = fsm.state(); - - if (std::holds_alternative (st)) { - if (is_blank) - fsm.dispatch (EvBlank{}); - else - fsm.dispatch (EvTitle{line}); - } else if (std::holds_alternative (st)) { - // Kindle format expects meta here. If missing, keep going. - fsm.dispatch (EvMeta{line}); - } else if (std::holds_alternative (st)) { - if (is_blank) - fsm.dispatch (EvBlank{}); - else - fsm.dispatch (EvText{line}); // some clippings have no blank line - } else { - // StCollectText - fsm.dispatch (EvText{line}); - } - } - - fsm.dispatch (EvEof{}); - - std::ofstream out (out_path, std::ios::trunc); - if (!out.is_open()) { - std::cerr << "Failed to open output file: " << out_path << " (" << std::strerror (errno) << ")\n"; - return 3; - } - - for (std::size_t i = 0; i < ctx.order.size(); ++i) { - const auto &title = ctx.order[i]; - out << "\\section {" << latex_escape (title) << "}\n"; - - const auto it = ctx.by_title.find (title); - if (it == ctx.by_title.end()) - continue; - - for (const auto &q : it->second) { - out << " \\subsection {" << latex_escape (q.meta) << "}\n"; - for (const auto &tl : q.text_lines) - out << " " << latex_escape (tl) << "\n"; - out << " \\subsubsection{notes}\n\n"; - } - - if (i + 1 < ctx.order.size()) - out << "\n"; - } - - return 0; -} - -int main (int argc, char **argv) { - validate_fsm_contract(); - - CliArgs args; - if (!parse_args (argc, argv, args)) - return 1; - - return convert (args.input, args.output); -} diff --git a/typefactory/.gitignore b/typefactory/.gitignore new file mode 100644 index 0000000..4a0b530 --- /dev/null +++ b/typefactory/.gitignore @@ -0,0 +1,74 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/typefactory/kindle2latex.pro b/typefactory/kindle2latex.pro new file mode 100644 index 0000000..595bdaa --- /dev/null +++ b/typefactory/kindle2latex.pro @@ -0,0 +1,7 @@ +TEMPLATE = app +CONFIG += console c++17 +CONFIG -= app_bundle +CONFIG -= qt + +SOURCES += \ + main.cpp diff --git a/typefactory/main.cpp b/typefactory/main.cpp new file mode 100644 index 0000000..341f237 --- /dev/null +++ b/typefactory/main.cpp @@ -0,0 +1,325 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "typefactory.h" // <-- твой хедер + +// ------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------ + +static std::string rstrip_cr (std::string s) { + if (!s.empty() && s.back() == '\r') + s.pop_back(); + return s; +} + +static std::string latex_escape (const std::string &s) { + std::string out; + out.reserve (s.size() + s.size() / 8); + for (unsigned char ch : s) { + switch (ch) { + case '\\': + out += "\\textbackslash{}"; + break; + case '{': + out += "\\{"; + break; + case '}': + out += "\\}"; + break; + case '%': + out += "\\%"; + break; + case '$': + out += "\\$"; + break; + case '#': + out += "\\#"; + break; + case '&': + out += "\\&"; + break; + case '_': + out += "\\_"; + break; + case '^': + out += "\\textasciicircum{}"; + break; + case '~': + out += "\\textasciitilde{}"; + break; + default: + out.push_back (static_cast (ch)); + break; + } + } + return out; +} + +// ------------------------------------------------------------ +// Model +// ------------------------------------------------------------ + +struct Quote { + std::string meta; + std::vector text_lines; +}; + +struct ParseContext { + std::vector order; + std::unordered_map> by_title; + std::unordered_map seen; + + std::string cur_title; + std::string cur_meta; + std::vector cur_text; + + void start_title (std::string t) { + cur_title = std::move (t); + cur_meta.clear(); + cur_text.clear(); + } + + void set_meta (std::string m) { + cur_meta = std::move (m); + } + void add_text (std::string line) { + cur_text.push_back (std::move (line)); + } + + void finalize_quote() { + if (cur_title.empty() || cur_meta.empty()) { + cur_meta.clear(); + cur_text.clear(); + return; + } + + if (seen.find (cur_title) == seen.end()) { + seen.emplace (cur_title, order.size()); + order.push_back (cur_title); + } + + by_title[cur_title].push_back (Quote{cur_meta, cur_text}); + + cur_meta.clear(); + cur_text.clear(); + } +}; + +// ------------------------------------------------------------ +// Handlers +// ------------------------------------------------------------ + +enum class Stage { Title, Meta, Body }; + +static constexpr std::array kAllStages = { + Stage::Title, Stage::Meta, Stage::Body +}; + +struct ILineHandler { + virtual ~ILineHandler() = default; + virtual void handle (const std::string &line, ParseContext &ctx, Stage &next) = 0; +}; + +struct TitleHandler final : ILineHandler { + void handle (const std::string &line, ParseContext &ctx, Stage &next) override { + if (line.empty()) { + next = Stage::Title; // пропускаем пустые между блоками + return; + } + ctx.start_title (line); + next = Stage::Meta; + } +}; + +struct MetaHandler final : ILineHandler { + void handle (const std::string &line, ParseContext &ctx, Stage &next) override { + ctx.set_meta (line); + next = Stage::Body; + } +}; + +struct BodyHandler final : ILineHandler { + void handle (const std::string &line, ParseContext &ctx, Stage &next) override { + if (line == "==========") { + ctx.finalize_quote(); + next = Stage::Title; + return; + } + ctx.add_text (line); // в том числе пустые строки внутри цитаты + next = Stage::Body; + } +}; + +// ------------------------------------------------------------ +// Factory wiring (using your typefactory.h) + caching handlers +// ------------------------------------------------------------ + +using HandlerPtr = std::shared_ptr; +using HandlerMap = std::unordered_map; + +static TypeFactory build_factory() { + TypeFactory f; + f.registerType (Stage::Title); + f.registerType (Stage::Meta); + f.registerType (Stage::Body); + return f; +} + +static HandlerMap build_handlers_cache (const TypeFactory &factory) { + HandlerMap handlers; + handlers.reserve (kAllStages.size()); + + // Рантайм-валидация: для каждого Stage обязаны уметь создать handler + for (Stage st : kAllStages) { + try { + auto h = factory.create (st); // shared_ptr + if (!h) + throw std::runtime_error ("Factory returned null handler"); + handlers.emplace (st, std::move (h)); + } catch (const std::out_of_range &) { + throw std::runtime_error ("Missing handler registration for some Stage"); + } + } + + // Доп.проверка: ensure all are present (на случай коллизий/ошибок emplace) + if (handlers.size() != kAllStages.size()) + throw std::runtime_error ("Handler cache size mismatch"); + + return handlers; +} + +// ------------------------------------------------------------ +// CLI +// ------------------------------------------------------------ + +struct CliArgs { + std::string input; + std::string output; +}; + +static void print_usage (const char *argv0) { + std::cerr << "Usage: " << argv0 << " --input --output \n"; +} + +static bool parse_args (int argc, char **argv, CliArgs &out) { + static option long_opts[] = { + {"input", required_argument, nullptr, 'i'}, + {"output", required_argument, nullptr, 'o'}, + {"help", no_argument, nullptr, 'h'}, + {nullptr, 0, nullptr, 0 } + }; + + int c = 0; + while ((c = ::getopt_long (argc, argv, "i:o:h", long_opts, nullptr)) != -1) { + switch (c) { + case 'i': + out.input = optarg; + break; + case 'o': + out.output = optarg; + break; + case 'h': + print_usage (argv[0]); + return false; + default: + print_usage (argv[0]); + return false; + } + } + + if (out.input.empty() || out.output.empty()) { + print_usage (argv[0]); + return false; + } + return true; +} + +// ------------------------------------------------------------ +// Convert +// ------------------------------------------------------------ + +static int convert (const std::string &in_path, const std::string &out_path) { + std::ifstream in (in_path); + if (!in.is_open()) { + std::cerr << "Failed to open input file: " << in_path + << " (" << std::strerror (errno) << ")\n"; + return 2; + } + + // Build factory + cache handlers once + TypeFactory factory = build_factory(); + + HandlerMap handlers; + try { + handlers = build_handlers_cache (factory); + } catch (const std::exception &e) { + std::cerr << "Internal error while building handler cache: " << e.what() << "\n"; + return 4; + } + + ParseContext ctx{}; + Stage stage = Stage::Title; + + std::string line; + while (std::getline (in, line)) { + line = rstrip_cr (std::move (line)); + + auto it = handlers.find (stage); + if (it == handlers.end() || !it->second) { + std::cerr << "Internal error: handler missing at runtime\n"; + return 4; + } + + Stage next = stage; + it->second->handle (line, ctx, next); + stage = next; + } + + // EOF: если файл не закончился "==========", всё равно зафиксируем последний блок + ctx.finalize_quote(); + + std::ofstream out (out_path, std::ios::trunc); + if (!out.is_open()) { + std::cerr << "Failed to open output file: " << out_path + << " (" << std::strerror (errno) << ")\n"; + return 3; + } + + for (std::size_t i = 0; i < ctx.order.size(); ++i) { + const auto &title = ctx.order[i]; + out << "\\section {" << latex_escape (title) << "}\n"; + + const auto it = ctx.by_title.find (title); + if (it == ctx.by_title.end()) + continue; + + for (const auto &q : it->second) { + out << " \\subsection {" << latex_escape (q.meta) << "}\n"; + for (const auto &tl : q.text_lines) + out << " " << latex_escape (tl) << "\n"; + out << " \\subsubsection{notes}\n\n"; + } + + if (i + 1 < ctx.order.size()) + out << "\n"; + } + + return 0; +} + +int main (int argc, char **argv) { + CliArgs args; + if (!parse_args (argc, argv, args)) + return 1; + return convert (args.input, args.output); +} diff --git a/typefactory/typefactory.h b/typefactory/typefactory.h new file mode 100644 index 0000000..4f8909f --- /dev/null +++ b/typefactory/typefactory.h @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 Dim Himro + +#ifndef TYPEFACTORY_H +#define TYPEFACTORY_H + +#include +#include +#include +#include // std::move + +/** + * @brief Registry-based factory for creating objects by runtime identifier. + * + * Allows registering derived types and instantiating them using a ClassId key. + * + * Notes: + * - Not thread-safe. Register types during initialization phase only. + * - Re-registering the same id overwrites the previous entry ("last wins"). + * - ClassId must be hashable (std::hash specialization must exist). + * + * @tparam ClassId -Unique class identifier type (e.g. std::string, int, enum). + * @tparam BaseClass -Base type for all created objects. + * @tparam Arg - Constructor arguments forwarded to registered derived types (by value). + * + * Usage example: + * @code + * #include + * + * struct Base { + * virtual ~Base() = default; + * virtual int get() = 0; + * }; + * + * struct Derived1 : Base { + * explicit Derived1(int start) : m_start(start) {} + * int get() override { return m_start + 1; } + * int m_start = 0; + * }; + * + * struct Derived2 : Base { + * explicit Derived2(int start) : m_start(start) {} + * int get() override { return m_start + 2; } + * int m_start = 0; + * }; + * + * TypeFactory factory; + * factory.registerType("one"); + * factory.registerType("two"); + * + * auto a = factory.create("one", 10); + * auto b = factory.creator("two")(10); // advanced API: get creator function + * + * (void)a->get(); + * (void)b->get(); + * @endcode + */ +template +class TypeFactory { +public: + using BasePtr = std::shared_ptr; + using CreatorFn = BasePtr (*)(Args...); + + TypeFactory() = default; + + /** + * @brief Registers a Derived type under the given id. + * + * Requirements (checked at compile-time): + * - Derived must inherit from BaseClass + * - Derived must be constructible from Args... + * + * Re-registering the same id overwrites the previous entry ("last wins"). + */ + template + void registerType(const ClassId& id) { + static_assert(std::is_base_of_v, + "Derived must inherit from BaseClass"); + static_assert(std::is_constructible_v, + "Derived must be constructible from Args..."); + + classes.insert_or_assign(id, &instantiate); + } + + /** + * @brief Returns creator function for the given id. + * @throws std::out_of_range when id is not registered. + */ + [[nodiscard]] CreatorFn creator(const ClassId& id) const { + return classes.at(id); + } + + /** + * @brief Creates an instance for the given id using provided arguments. + * @throws std::out_of_range when id is not registered. + */ + [[nodiscard]] BasePtr create(const ClassId& id, Args... args) const { + return creator(id)(std::move(args)...); + } + + /** + * @brief Checks whether an id is registered. + */ + [[nodiscard]] bool contains(const ClassId& id) const { + return classes.find(id) != classes.end(); + } + +protected: + // Protected by design: derived factories may extend/inspect the registry. + std::unordered_map classes; + +private: + template + static BasePtr instantiate(Args... args) { + return std::make_shared(std::move(args)...); + } +}; +#endif // TYPEFACTORY_H