Extract typefactory converter from CLI
This commit is contained in:
1 parent
2535953b8b
commit
28c9752f18
5 files changed
+338
-314
No files matched your search
@@ -15,6 +15,8 @@ function(kindle2latex_enable_warnings target)
|
|||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
add_executable(kindle2latex_typefactory
|
add_executable(kindle2latex_typefactory
|
||||||
|
typefactory/converter.cpp
|
||||||
|
typefactory/converter.h
|
||||||
typefactory/main.cpp
|
typefactory/main.cpp
|
||||||
typefactory/typefactory.h
|
typefactory/typefactory.h
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,297 @@
|
|||||||
|
#include "converter.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "typefactory.h"
|
||||||
|
|
||||||
|
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<char> (ch));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Quote {
|
||||||
|
std::string meta;
|
||||||
|
std::vector<std::string> text_lines;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParseContext {
|
||||||
|
std::vector<std::string> order;
|
||||||
|
std::unordered_map<std::string, std::vector<Quote>> by_title;
|
||||||
|
std::unordered_map<std::string, std::size_t> seen;
|
||||||
|
|
||||||
|
std::string cur_title;
|
||||||
|
std::string cur_meta;
|
||||||
|
std::vector<std::string> cur_text;
|
||||||
|
|
||||||
|
void start_title (std::string title) {
|
||||||
|
cur_title = std::move (title);
|
||||||
|
cur_meta.clear();
|
||||||
|
cur_text.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_meta (std::string meta) {
|
||||||
|
cur_meta = std::move (meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Stage { Title, Meta, Body };
|
||||||
|
|
||||||
|
static constexpr std::array<Stage, 3> 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using HandlerPtr = std::shared_ptr<ILineHandler>;
|
||||||
|
using HandlerMap = std::unordered_map<Stage, HandlerPtr>;
|
||||||
|
|
||||||
|
static TypeFactory<Stage, ILineHandler> build_factory() {
|
||||||
|
TypeFactory<Stage, ILineHandler> factory;
|
||||||
|
|
||||||
|
factory.registerType<TitleHandler> (Stage::Title);
|
||||||
|
factory.registerType<MetaHandler> (Stage::Meta);
|
||||||
|
factory.registerType<BodyHandler> (Stage::Body);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HandlerMap build_handlers_cache (
|
||||||
|
const TypeFactory<Stage, ILineHandler> &factory) {
|
||||||
|
|
||||||
|
HandlerMap handlers;
|
||||||
|
handlers.reserve (kAllStages.size());
|
||||||
|
|
||||||
|
for (Stage stage : kAllStages) {
|
||||||
|
try {
|
||||||
|
auto handler = factory.create (stage);
|
||||||
|
if (!handler)
|
||||||
|
throw std::runtime_error ("Factory returned null handler");
|
||||||
|
handlers.emplace (stage, std::move (handler));
|
||||||
|
} catch (const std::out_of_range &) {
|
||||||
|
throw std::runtime_error ("Missing handler registration for Stage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handlers.size() != kAllStages.size())
|
||||||
|
throw std::runtime_error ("Handler cache size mismatch");
|
||||||
|
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Converter::Impl {
|
||||||
|
ParseContext context;
|
||||||
|
};
|
||||||
|
|
||||||
|
Converter::Converter (std::string input_file_name)
|
||||||
|
: input_file_name_ (std::move (input_file_name))
|
||||||
|
, impl_ (std::make_unique<Impl>()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Converter::~Converter() = default;
|
||||||
|
|
||||||
|
int Converter::error_code() const noexcept {
|
||||||
|
return error_code_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Converter::parse() {
|
||||||
|
std::ifstream input (input_file_name_);
|
||||||
|
if (!input.is_open()) {
|
||||||
|
std::cerr << "Failed to open input file: "
|
||||||
|
<< input_file_name_
|
||||||
|
<< " (" << std::strerror (errno) << ")\n";
|
||||||
|
error_code_ = 2;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFactory<Stage, ILineHandler> 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";
|
||||||
|
error_code_ = 4;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_->context = ParseContext{};
|
||||||
|
Stage stage = Stage::Title;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline (input, line)) {
|
||||||
|
line = rstrip_cr (std::move (line));
|
||||||
|
|
||||||
|
auto it = handlers.find (stage);
|
||||||
|
if (it == handlers.end() || !it->second) {
|
||||||
|
std::cerr << "Internal error: missing handler at runtime\n";
|
||||||
|
error_code_ = 4;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stage next = stage;
|
||||||
|
it->second->handle (line, impl_->context, next);
|
||||||
|
stage = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_->context.finalize_quote();
|
||||||
|
error_code_ = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Converter::save (const std::string &output_file_name) {
|
||||||
|
if (!parse())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::ofstream output (output_file_name, std::ios::trunc);
|
||||||
|
if (!output.is_open()) {
|
||||||
|
std::cerr << "Failed to open output file: "
|
||||||
|
<< output_file_name
|
||||||
|
<< " (" << std::strerror (errno) << ")\n";
|
||||||
|
error_code_ = 3;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ParseContext &ctx = impl_->context;
|
||||||
|
for (std::size_t i = 0; i < ctx.order.size(); ++i) {
|
||||||
|
const auto &title = ctx.order[i];
|
||||||
|
output << "\\section {" << latex_escape (title) << "}\n";
|
||||||
|
|
||||||
|
const auto it = ctx.by_title.find (title);
|
||||||
|
if (it == ctx.by_title.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (const auto "e : it->second) {
|
||||||
|
output << " \\subsection {" << latex_escape (quote.meta) << "}\n";
|
||||||
|
|
||||||
|
for (const auto &text_line : quote.text_lines)
|
||||||
|
output << " " << latex_escape (text_line) << "\n";
|
||||||
|
|
||||||
|
output << " \\subsubsection{notes}\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i + 1 < ctx.order.size())
|
||||||
|
output << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
error_code_ = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef CONVERTER_H
|
||||||
|
#define CONVERTER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Converter {
|
||||||
|
public:
|
||||||
|
explicit Converter (std::string input_file_name);
|
||||||
|
~Converter();
|
||||||
|
|
||||||
|
Converter (const Converter &) = delete;
|
||||||
|
Converter &operator= (const Converter &) = delete;
|
||||||
|
|
||||||
|
bool save (const std::string &output_file_name);
|
||||||
|
[[nodiscard]] int error_code() const noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool parse();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
|
||||||
|
std::string input_file_name_;
|
||||||
|
std::unique_ptr<Impl> impl_;
|
||||||
|
int error_code_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONVERTER_H
|
||||||
+8
-314
@@ -1,231 +1,8 @@
|
|||||||
#include <array>
|
|
||||||
#include <cerrno>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "typefactory.h"
|
#include "converter.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<char> (ch));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Data model
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
struct Quote {
|
|
||||||
std::string meta;
|
|
||||||
std::vector<std::string> text_lines;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ParseContext {
|
|
||||||
std::vector<std::string> order;
|
|
||||||
std::unordered_map<std::string, std::vector<Quote>> by_title;
|
|
||||||
std::unordered_map<std::string, std::size_t> seen;
|
|
||||||
|
|
||||||
std::string cur_title;
|
|
||||||
std::string cur_meta;
|
|
||||||
std::vector<std::string> 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() {
|
|
||||||
// Ignore incomplete entries
|
|
||||||
if (cur_title.empty() || cur_meta.empty()) {
|
|
||||||
cur_meta.clear();
|
|
||||||
cur_text.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preserve insertion order of sections
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Parsing stages
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
enum class Stage { Title, Meta, Body };
|
|
||||||
|
|
||||||
// Explicit list of all supported stages for runtime validation
|
|
||||||
static constexpr std::array<Stage, 3> kAllStages = {
|
|
||||||
Stage::Title, Stage::Meta, Stage::Body
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ILineHandler {
|
|
||||||
virtual ~ILineHandler() = default;
|
|
||||||
|
|
||||||
// Process a single line and decide the next stage
|
|
||||||
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 {
|
|
||||||
|
|
||||||
// Skip empty lines between entries
|
|
||||||
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 {
|
|
||||||
|
|
||||||
// Separator marks the end of a clipping block
|
|
||||||
if (line == "==========") {
|
|
||||||
ctx.finalize_quote();
|
|
||||||
next = Stage::Title;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep body lines as-is (including empty ones)
|
|
||||||
ctx.add_text (line);
|
|
||||||
next = Stage::Body;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Factory wiring + handler caching
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
using HandlerPtr = std::shared_ptr<ILineHandler>;
|
|
||||||
using HandlerMap = std::unordered_map<Stage, HandlerPtr>;
|
|
||||||
|
|
||||||
static TypeFactory<Stage, ILineHandler> build_factory() {
|
|
||||||
TypeFactory<Stage, ILineHandler> f;
|
|
||||||
|
|
||||||
f.registerType<TitleHandler> (Stage::Title);
|
|
||||||
f.registerType<MetaHandler> (Stage::Meta);
|
|
||||||
f.registerType<BodyHandler> (Stage::Body);
|
|
||||||
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
static HandlerMap build_handlers_cache (
|
|
||||||
const TypeFactory<Stage, ILineHandler> &factory) {
|
|
||||||
|
|
||||||
HandlerMap handlers;
|
|
||||||
handlers.reserve (kAllStages.size());
|
|
||||||
|
|
||||||
// Runtime validation: ensure each stage has a registered handler
|
|
||||||
for (Stage st : kAllStages) {
|
|
||||||
try {
|
|
||||||
auto h = factory.create (st);
|
|
||||||
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 Stage");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extra consistency check
|
|
||||||
if (handlers.size() != kAllStages.size())
|
|
||||||
throw std::runtime_error ("Handler cache size mismatch");
|
|
||||||
|
|
||||||
return handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// CLI handling
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
struct CliArgs {
|
struct CliArgs {
|
||||||
std::string input;
|
std::string input;
|
||||||
@@ -237,10 +14,7 @@ static void print_usage (const char *argv0) {
|
|||||||
<< " --input <file> --output <file>\n";
|
<< " --input <file> --output <file>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool parse_args (int argc,
|
static bool parse_args (int argc, char **argv, CliArgs &out) {
|
||||||
char **argv,
|
|
||||||
CliArgs &out) {
|
|
||||||
|
|
||||||
static option long_opts[] = {
|
static option long_opts[] = {
|
||||||
{"input", required_argument, nullptr, 'i'},
|
{"input", required_argument, nullptr, 'i'},
|
||||||
{"output", required_argument, nullptr, 'o'},
|
{"output", required_argument, nullptr, 'o'},
|
||||||
@@ -249,10 +23,7 @@ static bool parse_args (int argc,
|
|||||||
};
|
};
|
||||||
|
|
||||||
int c = 0;
|
int c = 0;
|
||||||
while ((c = ::getopt_long (argc, argv,
|
while ((c = ::getopt_long (argc, argv, "i:o:h", long_opts, nullptr)) != -1) {
|
||||||
"i:o:h",
|
|
||||||
long_opts,
|
|
||||||
nullptr)) != -1) {
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'i':
|
case 'i':
|
||||||
out.input = optarg;
|
out.input = optarg;
|
||||||
@@ -277,91 +48,14 @@ static bool parse_args (int argc,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Conversion logic
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
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 and cache handlers once
|
|
||||||
TypeFactory<Stage, ILineHandler> 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: missing handler at runtime\n";
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stage next = stage;
|
|
||||||
it->second->handle (line, ctx, next);
|
|
||||||
stage = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the last entry is flushed if file does not end with separator
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate LaTeX output
|
|
||||||
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) {
|
int main (int argc, char **argv) {
|
||||||
CliArgs args;
|
CliArgs args;
|
||||||
if (!parse_args (argc, argv, args))
|
if (!parse_args (argc, argv, args))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return convert (args.input, args.output);
|
Converter converter (args.input);
|
||||||
|
if (!converter.save (args.output))
|
||||||
|
return converter.error_code();
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,9 @@ CONFIG -= app_bundle
|
|||||||
CONFIG -= qt
|
CONFIG -= qt
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
converter.cpp \
|
||||||
main.cpp
|
main.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
converter.h \
|
||||||
typefactory.h
|
typefactory.h
|
||||||
Reference in new issue
Block a user