autogram/src/main.cpp
2023-10-05 16:41:01 +02:00

193 lines
6.9 KiB
C++

//
// main.cpp
// autogram
//
// Created by Bob Polis at 2020-11-19
// Copyright (c) 2020 SwiftCoder. All rights reserved.
//
#include <iostream>
#include <cstdlib>
#include <string>
#include <stdexcept>
#include <vector>
#include <memory>
#include <thread>
#include <getopt.h>
#include <signal.h>
#include <sqlite3.h>
#include <sc/integer_locale.hpp>
#include "robinsonizer_mode.hpp"
#include "engine.hpp"
void reset_term(int sig) {
std::cerr << "\nbreak\n\x1b[?25h"; // show cursor again
exit(0);
}
void print_help() {
std::cout << "usage: autogram [-h|--version] ";
std::cout << "[-a <relaxed|strict|pangram] ";
std::cout << "[-i <number>] ";
std::cout << "[-l <nl|en>] ";
std::cout << "[-s <text>] ";
std::cout << "[-v] ";
std::cout << "[path-to-numerals.db]\n";
std::cout << " -a, --autogram <relaxed|strict|pangram> autogram type, default relaxed\n";
std::cout << " -h, --help show this help text and exit\n";
std::cout << " -i, --max-iter <number> maximum iterations before restart, default 10\n";
std::cout << " -l, --lang <nl|en> language, default en\n";
std::cout << " -s, --start <text> start of sentence\n";
std::cout << " -v, --verbose increase verbosity\n";
std::cout << " --version show version number and exit\n";
}
void print_version() {
std::cout << "autogram version 1.0\n";
}
void read_nums_from_db(std::vector<std::string>& numerals, const std::string& db_path, int lang_id) {
sqlite3* db {nullptr};
int rc = sqlite3_open(db_path.c_str(), &db);
if (rc) {
sqlite3_close(db);
throw std::runtime_error("can't open database");
}
std::unique_ptr<sqlite3, int(*)(sqlite3*)> dbh {db, sqlite3_close};
const char* query = "SELECT num FROM numerals WHERE language_id = ? ORDER BY value";
sqlite3_stmt* stmt {nullptr};
sqlite3_prepare_v2(db, query, -1, &stmt, nullptr);
std::unique_ptr<sqlite3_stmt, int(*)(sqlite3_stmt*)> stmth {stmt, sqlite3_finalize};
sqlite3_bind_int(stmt, 1, lang_id);
while (true) {
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const unsigned char* data = sqlite3_column_text(stmt, 0);
std::string num {reinterpret_cast<const char*>(data)};
numerals.push_back(num);
} else {
break;
}
}
}
int main(int argc, const char * argv[]) {
try {
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = reset_term;
(void) sigaction(SIGINT, &sa, NULL);
robinsonizer_mode mode {robinsonizer_mode::lax_autogram};
std::string lang {"en"};
std::string start;
int maxiter {0};
std::string db_path {"/usr/local/share/autogram/numerals.db"};
unsigned int verbose {0};
int opt_char, opt_val;
struct option long_options[] = {
{"autogram", required_argument, nullptr, 'a'},
{"help", no_argument, nullptr, 'h'},
{"max-iter", required_argument, nullptr, 'i'},
{"lang", required_argument, nullptr, 'l'},
{"start", required_argument, nullptr, 's'},
{"verbose", required_argument, nullptr, 'v'},
{"version", no_argument, &opt_val, 1},
{nullptr, 0, nullptr, 0}
};
while ((opt_char = getopt_long(argc, const_cast<char* const *>(argv), "a:hi:l:s:v", long_options, nullptr)) != -1) {
std::string arg {optarg ? optarg : ""};
switch (opt_char) {
case 0: {
// handle long-only options here
switch (opt_val) {
case 1:
print_version();
return EXIT_SUCCESS;
}
break;
}
case 'a':
if (arg == "relaxed") {
mode = robinsonizer_mode::lax_autogram;
} else if (arg == "strict") {
mode = robinsonizer_mode::strict_autogram;
} else if (arg == "pangram") {
mode = robinsonizer_mode::pangram;
} else {
throw std::invalid_argument("invalid autogram type");
}
break;
case 'h':
print_help();
return EXIT_SUCCESS;
case 'i':
maxiter = atoi(optarg);
break;
case 'l':
lang = arg;
break;
case 's':
start = arg;
break;
case 'v':
verbose++;
break;
case '?':
throw std::runtime_error("unrecognized option");
}
}
if (optind == argc) {
// here when no file args
}
for (int i = optind; i < argc; ++i) {
try {
// process file argv[i]
db_path = argv[i];
break; // ignore other args
} catch (const std::runtime_error& ex) {
std::cerr << "autogram: " << ex.what() << '\n';
}
}
std::vector<std::string> numerals;
numerals.push_back(lang == "nl" ? "en" : "and");
read_nums_from_db(numerals, db_path, (lang == "nl" ? 1 : 2));
if (start.size() == 0) start = "This sentence contains";
if (maxiter == 0) maxiter = 10;
engine robinsonizer {start, std::move(numerals), maxiter, mode, verbose};
if (verbose) {
std::locale loc {std::locale {}, new sc::integer_locale};
std::cerr.imbue(loc);
std::cout.imbue(loc);
std::thread worker {[&robinsonizer](){robinsonizer.run();}};
unsigned long long cur_iter {robinsonizer.total_iterations()};
unsigned long long prev_iter {0};
// hide cursor
std::cerr << "\x1b[?25l";
while (!robinsonizer.found()) { // show progress info
prev_iter = cur_iter;
std::this_thread::sleep_for(std::chrono::seconds{1});
cur_iter = robinsonizer.total_iterations();
std::cerr << '\r' << cur_iter - prev_iter << " iterations per second, " << cur_iter << " total";
}
// show cursor
std::cerr << "\x1b[?25h";
worker.join();
std::cout << '\n';
} else {
robinsonizer.run();
}
std::cout << robinsonizer << '\n';
} catch (const std::exception& ex) {
std::cerr << "autogram: " << ex.what() << '\n';
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}