diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api.js | 6 | ||||
-rw-r--r-- | src/catalog.c | 501 | ||||
-rw-r--r-- | src/catalog.h | 33 | ||||
-rw-r--r-- | src/config.h.in | 12 | ||||
-rw-r--r-- | src/db.js | 45 | ||||
-rw-r--r-- | src/i18n.c | 68 | ||||
-rw-r--r-- | src/i18n.h | 55 | ||||
-rw-r--r-- | src/ircd.js | 4 | ||||
-rw-r--r-- | src/logerr.c | 301 | ||||
-rw-r--r-- | src/logerr.h | 23 | ||||
-rw-r--r-- | src/napi-sqlite.c | 103 | ||||
-rw-r--r-- | src/papo.en.msg | 70 | ||||
-rw-r--r-- | src/sql/config.sql | 34 |
13 files changed, 1229 insertions, 26 deletions
@@ -2,19 +2,19 @@ const { eq } = require("./utils.js"); const ircd = require("./ircd.js"); const web = require("./web.js"); -const main = () => { +const main = async () => { if (process.argv.length === 3 && process.argv[2] === "-V") { console.log("papo 1970-01-01 0.1.0"); return; } if (process.argv[2] === "ircd") { - ircd.app(process.argv[3]); + await ircd.app(process.argv[3]); return; } if (process.argv[2] === "web") { - web.app(process.argv[3]); + await web.app(process.argv[3]); return; } diff --git a/src/catalog.c b/src/catalog.c new file mode 100644 index 0000000..c3ec9b2 --- /dev/null +++ b/src/catalog.c @@ -0,0 +1,501 @@ +#include "catalog.h" +#include "logerr.h" + +#include <assert.h> +#include <errno.h> +#include <nl_types.h> +#include <stdlib.h> +#include <string.h> + +#ifdef TEST +#include "../tests/tests-lib.h" +#include "../tests/slurp.h" + +static const char *const +FNAME = __FILE__ ".txt"; + +enum TEST_MSGCATALOG_ID { + MSG_X_FIRST = 1, + MSG_X_1, + MSG_X_2, + MSG_X_LAST, + MSG_STANDALONE, +}; + +static const char *const +TEST_MSGS[] = { + "", + [MSG_X_FIRST]="First line\n", + [MSG_X_1]="a second\n", + [MSG_X_2]="a third\n", + [MSG_X_LAST]="and the last one\n", + [MSG_STANDALONE]="single line message\n", + NULL +}; +#endif + + + +static const char *const +CATALOG_NAME = NAME_MACRO_STRING; + +static nl_catd +catalog_descriptor = NULL; + +static const char *const +NLSPATH = LOCALEDIR_MACRO_STRING "/%l_%t/LC_MESSAGES/%N.cat" ":" + LOCALEDIR_MACRO_STRING "/%l/LC_MESSAGES/%N.cat"; + +static const char *const +NLSPATH_KEY = "NLSPATH"; + + +int +i18n_init(void) { + int rc = 0; + + static const int should_overwrite = 0; + if (setenv(NLSPATH_KEY, NLSPATH, should_overwrite)) { + logerr("setenv(\"%s\", \"%s\", 0): %s\n", NLSPATH_KEY, + NLSPATH, strerror(errno)); + rc = -1; + goto out; + } + + catalog_descriptor = catopen(CATALOG_NAME, 0); + if (catalog_descriptor && catalog_descriptor == (nl_catd)-1) { + logerr("catopen(\"%s\", 0): %s\n", CATALOG_NAME, strerror(errno)); + catalog_descriptor = NULL; + rc = -1; + goto out; + } + +out: + return rc; +} + +#ifdef TEST +static int +test_i18n_init(void) { + int rc = 0; + + test_start("i18n_init()"); + + { + testing("simple call without touching the environment"); + + static const int should_overwrite = 1; + if (setenv(NLSPATH_KEY, "src/%N.en.cat", should_overwrite)) { + logerr("setenv(\"%s\", \"src/%%N.en.cat\", 1): %s\n", + NLSPATH_KEY, strerror(errno)); + rc = -1; + goto out; + } + + if (i18n_init()) { + logerr("i18n_init()\n"); + rc = -1; + goto out; + } + + test_ok(); + } + +out: + if (i18n_destroy()) { + logerr("i18n_destroy()\n"); + rc = -1; + } + return rc; +} +#endif + + +int +i18n_destroy(void) { + int rc = 0; + + if (catalog_descriptor) { + if (catclose(catalog_descriptor)) { + logerr("catclose(...): %s\n", strerror(errno)); + rc = -1; + goto out; + } + } + +out: + if (catalog_descriptor) { + catalog_descriptor = NULL; + } + return rc; +} + +#ifdef TEST +static int +test_i18n_destroy(void) { + int rc = 0; + + test_start("i18n_destroy()"); + + { + testing("simple call without init first"); + + if (i18n_destroy()) { + logerr("i18n_destroy()\n"); + rc = -1; + goto out; + } + + test_ok(); + } + +out: + return rc; +} +#endif + + +/** + * Infallible: always returns a valid string, no matter what. + */ +const char * +s(const char* const MSGS[], const int msg_id) { + assert(msg_id > 0); + // FIXME: assert within bounds! + // printf("sizeof(MSGS): %ld\n", sizeof(MSGS)); + if (!catalog_descriptor) { + return MSGS[msg_id]; + } + + errno = 0; + const char *const ret = + catgets(catalog_descriptor, NL_SETD, msg_id, MSGS[msg_id]); + if (errno) { + logerr("catgets(%d): %s\n", msg_id, strerror(errno)); + } + + return ret; +} + +#ifdef TEST +static int +test_s(void) { + int rc = 0; + + test_start("_()"); + FILE *file = NULL; + char *str = NULL; + + { + testing("empty string"); + + file = fopen(FNAME, "w"); + if (!file) { + perror("fopen(FNAME, \"w\")"); + rc = -1; + goto out; + } + + // FIXME: implement correct test + + + + test_ok(); + } + +out: + if (str) { + free(str); + } + if (file) { + if (fclose(file)) { + logerr("fclose(file): %s\n", strerror(errno)); + rc = -1; + } + } + return rc; +} +#endif + + +int +s_print_msgs( + const char *const MSGS[], + FILE *restrict stream, + const int msg_begin, + const int msg_end +) { + int rc = 0; + + for (int i = msg_begin; i <= msg_end; i++) { + if (fprintf(stream, "%s", s(MSGS, i)) < 0) { + logerr("fprintf(stream, \"%%s\", _(%d)): %s\n", i, + strerror(errno)); + rc = -1; + goto out; + } + } + +out: + return rc; +} + +#ifdef TEST +static int +test_s_print_msgs(void) { + int rc = 0; + + test_start("s_print_msgs()"); + FILE *file = NULL; + char *str = NULL; + + { + testing("message in range"); + + file = fopen(FNAME, "w"); + if (!file) { + perror("fopen(FNAME, \"w\")"); + rc = -1; + goto out; + } + + if (s_print_msgs(TEST_MSGS, file, MSG_X_FIRST, MSG_X_LAST)) { + logerr("print_msgs(TEST_MSGS, file, MSG_X_FIRST, MSG_X_LAST)\n"); + rc = -1; + goto out; + } + + const int ret = fclose(file); + file = NULL; + if (ret) { + logerr("fclose(file): %s\n", strerror(errno)); + rc = -1; + goto out; + } + + if (slurp_for_tests(FNAME, &str)) { + logerr("slurp_for_tests(FNAME, &str)\n"); + rc = -1; + goto out; + } + + const char *const expected = + "First line\n" + "a second\n" + "a third\n" + "and the last one\n" + ; + + assert(strcmp(expected, str) == 0); + + free(str); + str = NULL; + + test_ok(); + } + { + testing("range begin and end is the same"); + + file = fopen(FNAME, "w"); + if (!file) { + logerr("fopen(FNAME, \"w\"): %s\n", strerror(errno)); + rc = -1; + goto out; + } + + if (s_print_msgs(TEST_MSGS, file, MSG_X_FIRST, MSG_X_FIRST)) { + logerr("s_print_msgs(TEST_MSGS, file, MSG_X_FIRST, MSG_X_FIRST)\n"); + rc = -1; + goto out; + } + + const int ret = fclose(file); + file = NULL; + if (ret) { + logerr("fclose(file): %s\n", strerror(errno)); + rc = -1; + goto out; + } + + if (slurp_for_tests(FNAME, &str)) { + logerr("slurp_for_tests(FNAME, &str)\n"); + rc = -1; + goto out; + } + + const char *const expected = + "First line\n"; + + assert(strcmp(expected, str) == 0); + + free(str); + str = NULL; + + test_ok(); + } + +out: + if (str) { + free(str); + } + if (file) { + if (fclose(file)) { + logerr("fclose(file): %s\n", strerror(errno)); + rc = -1; + } + } + return rc; +} +#endif + + +int +s_print_msg(const char *const MSGS[], FILE *const fd, const int msg_id) { + return s_print_msgs(MSGS, fd, msg_id, msg_id); +} + +#ifdef TEST +static int +test_s_print_msg(void) { + int rc = 0; + + test_start("s_print_msg()"); + FILE *file = NULL; + char *str = NULL; + + { + testing("simple individual message"); + + file = fopen(FNAME, "w"); + if (!file) { + logerr("fopen(FNAME, \"w\"): %s\n"); + rc = -1; + goto out; + } + + if (s_print_msg(TEST_MSGS, file, MSG_STANDALONE)) { + logerr("s_print_msg(TEST_MSGS, file, MSG_STANDALONE)\n"); + rc = -1; + goto out; + } + + const int ret = fclose(file); + file = NULL; + if (ret) { + logerr("fopen(file): %s\n", strerror(errno)); + rc = -1; + goto out; + } + + if (slurp_for_tests(FNAME, &str)) { + logerr("slurp_for_tests(FNAME, &str)\n"); + rc = -1; + goto out; + } + + const char *const expected = + "single line message\n"; + + assert(strcmp(expected, str) == 0); + + free(str); + str = NULL; + + test_ok(); + } + +out: + if (str) { + free(str); + } + if (file) { + if (fclose(file)) { + logerr("fclose(file): %s\n", strerror(errno)); + rc = -1; + } + } + return rc; +} +#endif + + +int +dump_translatable_strings(const char *const MSGS[]) { + int rc = 0; + + for (size_t i = 1; MSGS[i]; i++) { + if (printf("%ld ", i) < 0) { + logerr("printf(\"%%ld\", %d): %s\n", i); + rc = -1; + goto out; + } + + for (size_t j = 0; MSGS[i][j]; j++) { + if (MSGS[i][j] == '\n') { + if (printf("\\n") < 0) { + logerr("printf(\"\\\\n\"): %s\n", + strerror(errno)); + rc = -1; + goto out; + } + } else { + if (printf("%c", MSGS[i][j]) < 0) { + logerr("printf(\"%%c\", " + "MSGS[%ld][%ld]): %s\n", + i, j, strerror(errno)); + rc = -1; + goto out; + } + } + } + + if (printf("\n\n") < 0) { + logerr("printf(\"\\n\\n\"): %s\n", strerror(errno)); + rc = -1; + goto out; + } + } + +out: + return rc; +} + +#ifdef TEST +int +main(void) { + int rc = 0; + + if (test_i18n_init()) { + logerr("test_i18n_init()\n"); + rc = -1; + goto out; + } + + if (test_i18n_destroy()) { + logerr("test_i18n_destroy()\n"); + rc = -1; + goto out; + } + + if (test_s()) { + logerr("test_s()\n"); + rc = -1; + goto out; + } + + if (test_s_print_msgs()) { + logerr("test_s_print_msgs()\n"); + rc = -1; + goto out; + } + + if (test_s_print_msg()) { + logerr("test_s_print_msg()\n"); + rc = -1; + goto out; + } + +out: + return !!rc; +} +#endif diff --git a/src/catalog.h b/src/catalog.h new file mode 100644 index 0000000..9f237a9 --- /dev/null +++ b/src/catalog.h @@ -0,0 +1,33 @@ +#ifndef CATALOG_H +#define CATALOG_H + +#include "config.h" + +#include <stdio.h> + + +int +i18n_init(void); + +int +i18n_destroy(void); + +const char * +s(const char *const MSGS[], const int msg_id); + +int +s_print_msgs( + const char *const MSGS[], + FILE *restrict stream, + const int msg_begin, + const int msg_end +); + +int +s_print_msg(const char *const MSGS[], FILE *restrict stream, const int msg_id); + +int +dump_translatable_strings(const char *const MSGS[]); + + +#endif diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..4867dc0 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,12 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE 200809L + +#define VERSION_MACRO_STRING "@VERSION@" +#define DATE_MACRO_STRING "@DATE@" +#define NAME_MACRO_STRING "@NAME@" +#define LOCALEDIR_MACRO_STRING "@LOCALEDIR@" + +#endif diff --git a/src/db.js b/src/db.js new file mode 100644 index 0000000..e735ab1 --- /dev/null +++ b/src/db.js @@ -0,0 +1,45 @@ +const fs = require("node:fs"); +const sqlite = require("./napi-sqlite.node"); + +// const value = 8; +// console.log(`${value} time 2 =`, sqlite.my_function(value)); + +const CONFIG_FILE = __dirname + "/sql/config.sql"; +const MIGRATIONS_DIR = __dirname + "/sql/migrations/"; + +let db = null; +const init = async () => { + console.log({ + sqlite, + }); + console.log(`sqlite.myfn(2): ${sqlite.myfn(2)}`); + console.log(`sqlite.open(2): ${sqlite.open(2)}`); + /* + const config = fs.readFileSync(CONFIG_FILE, "UTF-8"); + const migrations = fs.readdirSync(MIGRATIONS_DIR, "UTF-8"); + + await exec(config); + await exec(` + CREATE TABLE IF NOT EXISTS migrations ( + filename TEXT PRIMARY KEY + ); + `); + const done = await run(` + SELECT filename FROM migrations; + `); + + // FIXME: sort + const pending = new Set(migrations).difference(new Set(done)); + + await exec("BEGIN TRANSACTION;"); + for (const p of pending) { + await exec(fs.readFileSync(MIGRATIONS_DIR + p, "UTF-8")); + await exec(`INSERT INTO migrations (filename) VALUES (?)`, p); + } + await exec("COMMIT TRANSACTION;"); + */ +}; + +module.exports = { + init, +}; diff --git a/src/i18n.c b/src/i18n.c new file mode 100644 index 0000000..245ad84 --- /dev/null +++ b/src/i18n.c @@ -0,0 +1,68 @@ +#include "i18n.h" + +#ifdef TEST +#include "logerr.h" +#include "catalog.h" + +#include <stdlib.h> +#endif + +const char *const +MSGS[] = { + "", + [MSG_USAGE_FIRST]="Usage:\n", + [MSG_USAGE_1]=" " NAME_MACRO_STRING " -p FILE [-o DIRECTORY]\n", + [MSG_USAGE_2]=" " NAME_MACRO_STRING " -l FILE [-o DIRECTORY]\n", + [MSG_USAGE_LAST]=" " NAME_MACRO_STRING " [-hV]\n", + [MSG_HELP_FIRST]="\n", + [MSG_HELP_1]="\n", + [MSG_HELP_2]="Options:\n", + [MSG_HELP_3]=" -p FILE parser file to be processed\n", + [MSG_HELP_4]=" -l FILE lexer file to be processed\n", + [MSG_HELP_5]=" -o DIRECTORY output where to place the\n", + [MSG_HELP_6]=" generated files (default .)\n", + [MSG_HELP_7]=" -h, --help show this help message\n", + [MSG_HELP_8]=" -V, --version print the version number\n", + [MSG_HELP_9]="\n", + [MSG_HELP_10]="\n", + [MSG_HELP_11]="Run the " NAME_MACRO_STRING "(1) parser program.\n", + [MSG_HELP_12]="\n", + [MSG_HELP_13]="Here is the explanation for what it does, and the synopsis\n", + [MSG_HELP_14]="of its usage.\n", + [MSG_HELP_15]="\n", + [MSG_HELP_16]="See \"man " NAME_MACRO_STRING "\" for usage information and\n", + [MSG_HELP_17]="\"man " NAME_MACRO_STRING ".tutorial\" for a beginner introduction.\n", + [MSG_HELP_18]="\n", + [MSG_HELP_19]="\n", + [MSG_HELP_20]="Examples:\n", + [MSG_HELP_21]="\n", + [MSG_HELP_22]=" Do a one-line parser:\n", + [MSG_HELP_23]="\n", + [MSG_HELP_24]=" $ " NAME_MACRO_STRING " run md.grammar < README.md\n", + [MSG_HELP_25]="\n", + [MSG_HELP_26]="\n", + [MSG_HELP_27]=" Compile the grammer:\n", + [MSG_HELP_28]="\n", + [MSG_HELP_LAST]=" $ " NAME_MACRO_STRING " build csv.grammar > dunno.alsodunno\n", + [MSG_VERSION]= NAME_MACRO_STRING " " VERSION_MACRO_STRING " " DATE_MACRO_STRING "\n", + NULL +}; + + +#ifdef TEST +int +main(void) { + int rc = 0; + + if (getenv("DUMP_TRANSLATABLE_STRINGS")) { + if (dump_translatable_strings(MSGS)) { + logerr("dump_translatable_strings(MSGS)\n"); + rc = -1; + goto out; + } + } + +out: + return !!rc; +} +#endif diff --git a/src/i18n.h b/src/i18n.h new file mode 100644 index 0000000..8245564 --- /dev/null +++ b/src/i18n.h @@ -0,0 +1,55 @@ +#ifndef I18N_H +#define I18N_H + +#include "config.h" +#include "catalog.h" + +#include <stdio.h> + + +enum MSGCATALOG_ID { + MSG_USAGE_FIRST = 1, + MSG_USAGE_1, + MSG_USAGE_2, + MSG_USAGE_LAST, + MSG_HELP_FIRST, + MSG_HELP_1, + MSG_HELP_2, + MSG_HELP_3, + MSG_HELP_4, + MSG_HELP_5, + MSG_HELP_6, + MSG_HELP_7, + MSG_HELP_8, + MSG_HELP_9, + MSG_HELP_10, + MSG_HELP_11, + MSG_HELP_12, + MSG_HELP_13, + MSG_HELP_14, + MSG_HELP_15, + MSG_HELP_16, + MSG_HELP_17, + MSG_HELP_18, + MSG_HELP_19, + MSG_HELP_20, + MSG_HELP_21, + MSG_HELP_22, + MSG_HELP_23, + MSG_HELP_24, + MSG_HELP_25, + MSG_HELP_26, + MSG_HELP_27, + MSG_HELP_28, + MSG_HELP_LAST, + MSG_VERSION, +}; + + +extern const char *const +MSGS[]; + +#define _(msg_id) s(MSGS, msg_id) + + +#endif diff --git a/src/ircd.js b/src/ircd.js index affc986..6a02bd6 100644 --- a/src/ircd.js +++ b/src/ircd.js @@ -1,11 +1,13 @@ const net = require("node:net"); +const db = require("./db.js"); const server = net.createServer(socket => { socket.write("olar\r\n"); socket.pipe(socket); }); -const app = udsPath => { +const app = async udsPath => { + await db.init(); server.listen(udsPath, () => { console.log("I'm ircd."); }); diff --git a/src/logerr.c b/src/logerr.c new file mode 100644 index 0000000..936bd28 --- /dev/null +++ b/src/logerr.c @@ -0,0 +1,301 @@ +#include "logerr.h" + +#include <stdarg.h> +#include <stdlib.h> + +#ifdef TEST +#include "../tests/tests-lib.h" +#include "../tests/slurp.h" +#include <assert.h> +#include <errno.h> +#include <string.h> +#endif + + +void +vlogerr( + const char *const file, + const char *const function, + const int lineno, + FILE *restrict stream, + const char *restrict format, + ... +) { + (void)fprintf(stream, "%s:%s:%d: ", file, function, lineno); + + va_list args; + va_start(args, format); + (void)vfprintf(stream, format, args); + va_end(args); +} + +#ifdef TEST +static const char *const +FNAME = __FILE__ ".txt"; + +static int +test_vlogerr(void) { + int rc = 0; + + test_start("vlogerr()"); + FILE *file = NULL; + char *str = NULL; + + { + testing("empty varargs"); + + file = fopen(FNAME, "w"); + if (!file) { + perror("fopen(FNAME, \"w\")"); + rc = -1; + goto out; + } + + vlogerr(__FILE__, __func__, __LINE__, file, + ""); + + const int ret = fclose(file); + file = NULL; + if (ret) { + perror("fclose(file)"); + rc = -1; + goto out; + } + + if (slurp_for_tests(FNAME, &str)) { + perror("slurp_for_tests(FNAME, &str)"); + rc = -1; + goto out; + } + + const char *const expected = + "src/logerr.c:test_vlogerr:54: "; + + assert(strcmp(expected, str) == 0); + + free(str); + str = NULL; + + test_ok(); + } + { + testing("a newline only"); + + file = fopen(FNAME, "w"); + if (!file) { + perror("fopen(FNAME, \"w\")"); + rc = -1; + goto out; + } + + vlogerr(__FILE__, __func__, __LINE__, file, + "\n"); + + const int ret = fclose(file); + file = NULL; + if (ret) { + perror("fclose(file)"); + rc = -1; + goto out; + } + + if (slurp_for_tests(FNAME, &str)) { + perror("slurp_for_tests(FNAME, &str)"); + rc = -1; + goto out; + } + + const char *const expected = + "src/logerr.c:test_vlogerr:91: \n"; + assert(strcmp(expected, str) == 0); + + free(str); + str = NULL; + + test_ok(); + } + { + testing("static format string"); + + file = fopen(FNAME, "w"); + if (!file) { + perror("fopen(FNAME, \"w\")"); + rc = -1; + goto out; + } + + vlogerr(__FILE__, __func__, __LINE__, file, + "some static string\n"); + + const int ret = fclose(file); + file = NULL; + if (ret) { + perror("fclose(file)"); + rc = -1; + goto out; + } + + if (slurp_for_tests(FNAME, &str)) { + perror("slurp_for_tests(FNAME, &str)"); + rc = -1; + goto out; + } + + const char *const expected = + "src/logerr.c:test_vlogerr:127: some static string\n"; + assert(strcmp(expected, str) == 0); + + free(str); + str = NULL; + + test_ok(); + } + { + testing("single arg format string"); + + file = fopen(FNAME, "w"); + if (!file) { + perror("fopen(FNAME, \"w\")"); + rc = -1; + goto out; + } + + vlogerr(__FILE__, __func__, __LINE__, file, + "fn(%s)\n", "an-arg"); + + const int ret = fclose(file); + file = NULL; + if (ret) { + perror("fclose(file)"); + rc = -1; + goto out; + } + + if (slurp_for_tests(FNAME, &str)) { + perror("slurp_for_tests(FNAME, &str)"); + rc = -1; + goto out; + } + + const char *const expected = + "src/logerr.c:test_vlogerr:163: fn(an-arg)\n"; + assert(strcmp(expected, str) == 0); + + free(str); + str = NULL; + + test_ok(); + } + { + testing("multiple format strings"); + + file = fopen(FNAME, "w"); + if (!file) { + perror("fopen(FNAME, \"w\")"); + rc = -1; + goto out; + } + + vlogerr(__FILE__, __func__, __LINE__, file, + "int (%d), string (%s) and char (%c)\n", + 123, + "another-str", + 'z'); + + const int ret = fclose(file); + file = NULL; + if (ret) { + perror("fclose(file)"); + rc = -1; + goto out; + } + + if (slurp_for_tests(FNAME, &str)) { + perror("slurp_for_tests(FNAME, &str)"); + rc = -1; + goto out; + } + + const char *const expected = + "src/logerr.c:test_vlogerr:199: " + "int (123), string (another-str) and char (z)\n"; + assert(strcmp(expected, str) == 0); + + free(str); + str = NULL; + + test_ok(); + } + +out: + if (str) { + free(str); + } + if (file) { + if (fclose(file)) { + perror("fclose(file)"); + rc = -1; + } + } + return rc; +} + +static int +test_logerr(void) { + int rc = 0; + + test_start("logerr()"); + + { + testing("can be called with an empty string"); + + logerr(""); + + test_ok(); + } + { + testing("can be called with a static string"); + + logerr("some err\n"); + + test_ok(); + } + { + testing("can be called with a formatted string"); + + logerr("some err: %s\n", strerror(errno)); + + test_ok(); + } + { + testing("can be called with formatting arguments"); + + logerr("int: %d\nstr: %s\n", 123, "an example string"); + + test_ok(); + } + + return rc; +} + + +int +main(void) { + int rc = 0; + + if (test_vlogerr()) { + perror("test_vlogerr()"); + rc = -1; + goto out; + } + + if (test_logerr()) { + perror("test_logerr()"); + rc = -1; + goto out; + } + +out: + return !!rc; +} +#endif diff --git a/src/logerr.h b/src/logerr.h new file mode 100644 index 0000000..77f98bd --- /dev/null +++ b/src/logerr.h @@ -0,0 +1,23 @@ +#ifndef LOGERR_H +#define LOGERR_H + +#include "config.h" + +#include <stdio.h> + + +void +vlogerr( + + const char *const file, + const char *const function, + const int lineno, + FILE *restrict stream, + const char *restrict format, + ... +); + +#define logerr(...) vlogerr(__FILE__, __func__, __LINE__, stderr, __VA_ARGS__) + + +#endif diff --git a/src/napi-sqlite.c b/src/napi-sqlite.c index 7c0b872..df3b042 100644 --- a/src/napi-sqlite.c +++ b/src/napi-sqlite.c @@ -1,7 +1,18 @@ +#include <stdio.h> + #include <node/node_api.h> +/* +FIXME +static const napi_type_tag SQLITE_DB_TYPE_TAG = { + 0x0e9614d459f746cc, 0x88b814a5dc5c4cf7 +}; +*/ + static napi_value -my_function(napi_env env, napi_callback_info info) { +myfn(napi_env env, napi_callback_info info) { + napi_value ret = NULL; + napi_status status; size_t argc = 1; int number; @@ -11,51 +22,99 @@ my_function(napi_env env, napi_callback_info info) { status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL); if (status != napi_ok) { napi_throw_error(env, NULL, "Failed to parse arguments FIXME i18n"); - // FIXME: does execution somehow halt here? Or is it missing a - // return? + goto out; } status = napi_get_value_int32(env, argv[0], &number); if (status != napi_ok) { napi_throw_error(env, NULL, "Invalid number was passed as argument FIXME i18n"); - // FIXME: return? + goto out; } number = number * 2; status = napi_create_int32(env, number, &my_number); if (status != napi_ok) { napi_throw_error(env, NULL, "Unable to create return value FIXME i18n"); - // FIXME: return? + goto out; } + ret = my_number; + +out: + return ret; +} - return my_number; +static napi_value +open(napi_env env, napi_callback_info info) { + (void)env; + (void)info; + return NULL; } static napi_value +close(napi_env env, napi_callback_info info) { + (void)env; + (void)info; + return NULL; +} + +static const struct { + const char *label; + napi_value(*const handle)(napi_env env, napi_callback_info info); +} fns[] = { + { .label = "myfn", .handle = myfn, }, + { .label = "open", .handle = open, }, + { .label = "close", .handle = close, }, + { NULL, NULL }, +}; + +static napi_value init(napi_env env, napi_value exports) { + napi_value ret = exports; + napi_status status; - napi_value fn; - status = napi_create_function(env, NULL, 0, my_function, NULL, &fn); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Unable to wrap native function FIXME i18n"); - // FIXME: return? - } + for (size_t i = 0; fns[i].label && fns[i].handle; i++) { + napi_value fn; + status = napi_create_function( + env, + fns[i].label, + NAPI_AUTO_LENGTH, + fns[i].handle, + "xucrutes", + &fn + ); + if (status != napi_ok) { + ret = NULL; + napi_throw_error( + env, + "SQLITE_FN_CREATE", + "Unable to wrap native function FIXME i18n" + ); + goto out; + } - status = napi_set_named_property(env, exports, "my_function", fn); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Unable to populate exports FIXME i18n"); - // FIXME: return? + status = napi_set_named_property( + env, + exports, + fns[i].label, + fn + ); + if (status != napi_ok) { + ret = NULL; + napi_throw_error( + env, + "SQLITE_FN_SETNAME", + "Unable to populate exports FIXME i18n" + ); + goto out; + } } - return exports; +out: + return ret; } -napi_value -sqlite_napi_init(napi_env env, napi_value exports) { +NAPI_MODULE_INIT() { return init(env, exports); } - -//NAPI_MODULE(NODE_GYP_MODULE_NAME, sqlite_napi_init) -NAPI_MODULE(FIXME_CAN_THIS_BE_ANYTHING, sqlite_napi_init) diff --git a/src/papo.en.msg b/src/papo.en.msg new file mode 100644 index 0000000..f1679a3 --- /dev/null +++ b/src/papo.en.msg @@ -0,0 +1,70 @@ +1 Usage:\n + +2 papo -p FILE [-o DIRECTORY]\n + +3 papo -l FILE [-o DIRECTORY]\n + +4 papo [-hV]\n + +5 \n + +6 \n + +7 Options:\n + +8 -p FILE parser file to be processed\n + +9 -l FILE lexer file to be processed\n + +10 -o DIRECTORY output where to place the\n + +11 generated files (default .)\n + +12 -h, --help show this help message\n + +13 -V, --version print the version number\n + +14 \n + +15 \n + +16 Run the papo(1) parser program.\n + +17 \n + +18 Here is the explanation for what it does, and the synopsis\n + +19 of its usage.\n + +20 \n + +21 See "man papo" for usage information and\n + +22 "man papo.tutorial" for a beginner introduction.\n + +23 \n + +24 \n + +25 Examples:\n + +26 \n + +27 Do a one-line parser:\n + +28 \n + +29 $ papo run md.grammar < README.md\n + +30 \n + +31 \n + +32 Compile the grammer:\n + +33 \n + +34 $ papo build csv.grammar > dunno.alsodunno\n + +35 papo 0.1.0 1970-01-01\n + diff --git a/src/sql/config.sql b/src/sql/config.sql new file mode 100644 index 0000000..53eb279 --- /dev/null +++ b/src/sql/config.sql @@ -0,0 +1,34 @@ +; "Litestream requires periodic but short write locks on the database when +; checkpointing occurs": +; https://litestream.io/tips/#busy-timeout +PRAGMA busy_timeout = 5000; + +; "Litestream only works with the SQLite WAL journaling mode": +; https://litestream.io/tips/#wal-journal-mode +PRAGMA journal_mode = WAL; + +; "(...) change the synchronous mode to NORMAL (it typically defaults to FULL)": +; https://litestream.io/tips/#synchronous-pragma +; "WAL mode is safe from corruption with synchronous=NORMAL": +; https://www.sqlite.org/pragma.html#pragma_synchronous +PRAGMA synchronous = NORMAL; + +; "(...) can perform a checkpoint in between Litestream-initiated checkpoints +; and cause Litestream to miss a WAL file": +; https://litestream.io/tips/#disable-autocheckpoints-for-high-write-load-servers +PRAGMA wal_autocheckpoint = 0; + +; "This pragma does a low-level formatting and consistency check of the +; database": +; https://www.sqlite.org/pragma.html#pragma_integrity_check +PRAGMA integrity_check; + +; "The foreign_key_check pragma checks the database, or the table called + \"table-name\", for foreign key constraints that are violated": +; https://www.sqlite.org/pragma.html#pragma_foreign_key_check +PRAGMA foreign_key_check; + + +CREATE TABLE IF NO EXISTS migrations ( + name TEXT PRIMARY KEY +); |