2021-12-11 08:30:42 +00:00
|
|
|
#include <assert.h>
|
2022-01-25 04:15:43 +00:00
|
|
|
#ifdef __NetBSD__
|
|
|
|
#include <curses.h>
|
|
|
|
#else
|
2021-12-11 22:59:00 +00:00
|
|
|
#include <ncurses.h>
|
2022-01-25 04:15:43 +00:00
|
|
|
#endif
|
2021-12-11 08:30:42 +00:00
|
|
|
#include <stdio.h>
|
2021-12-11 22:59:00 +00:00
|
|
|
#include <stdlib.h>
|
2021-12-11 08:30:42 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <strings.h>
|
|
|
|
|
|
|
|
#include "lua.h"
|
|
|
|
#include "lauxlib.h"
|
|
|
|
|
2021-12-11 15:20:04 +00:00
|
|
|
/* If you encounter assertion failures in this file and _didn't_ manually edit
|
2021-12-11 17:37:23 +00:00
|
|
|
* it, lease report the .tlv file that caused them: http://akkartik.name/contact.
|
|
|
|
*
|
|
|
|
* Manually edited files can have cryptic errors. Teliva's first priority is
|
|
|
|
* to be secure, so it requires a fairly rigid file format and errors out if
|
|
|
|
* things are even slightly amiss. */
|
2021-12-11 15:20:04 +00:00
|
|
|
|
2021-12-11 17:37:23 +00:00
|
|
|
/* This code is surprisingly hairy. Estimate of buffer overflows: 2. */
|
|
|
|
|
|
|
|
static void teliva_load_multiline_string(lua_State* L, FILE* in, char* line, int capacity) {
|
2021-12-11 08:30:42 +00:00
|
|
|
luaL_Buffer b;
|
|
|
|
luaL_buffinit(L, &b);
|
2021-12-11 15:20:04 +00:00
|
|
|
int expected_indent = -1;
|
|
|
|
while (1) {
|
|
|
|
if (feof(in)) break;
|
2021-12-11 17:37:23 +00:00
|
|
|
char c = fgetc(in);
|
|
|
|
ungetc(c, in);
|
|
|
|
if (c != ' ') {
|
|
|
|
/* new definition; signal end to caller without reading a new line */
|
|
|
|
strcpy(line, "-\n");
|
|
|
|
break;
|
|
|
|
}
|
2021-12-11 15:20:04 +00:00
|
|
|
memset(line, '\0', capacity);
|
|
|
|
if (fgets(line, capacity, in) == NULL) break; /* eof */
|
|
|
|
int max = strlen(line);
|
|
|
|
assert(line[max-1] == '\n');
|
|
|
|
int indent = 0;
|
|
|
|
while (indent < max-1 && line[indent] == ' ')
|
|
|
|
++indent;
|
2021-12-11 17:37:23 +00:00
|
|
|
if (line[indent] != '>') break; /* new key/value pair in definition */
|
2021-12-11 15:20:04 +00:00
|
|
|
if (expected_indent == -1)
|
|
|
|
expected_indent = indent;
|
2021-12-11 08:30:42 +00:00
|
|
|
else
|
2021-12-11 15:20:04 +00:00
|
|
|
assert(expected_indent == indent);
|
|
|
|
int start = indent+1; /* skip '>' */
|
|
|
|
luaL_addstring(&b, &line[start]); /* guaranteed to at least be null */
|
2021-12-11 08:30:42 +00:00
|
|
|
}
|
|
|
|
luaL_pushresult(&b);
|
2021-12-11 15:20:04 +00:00
|
|
|
/* final state of line goes out into the world */
|
2021-12-11 08:30:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* leave a single table on stack containing the next top-level definition from the file */
|
2021-12-25 17:27:44 +00:00
|
|
|
void teliva_load_definition(lua_State* L, FILE* in) {
|
2021-12-11 08:30:42 +00:00
|
|
|
lua_newtable(L);
|
|
|
|
int def_idx = lua_gettop(L);
|
|
|
|
char line[1024] = {'\0'};
|
2021-12-11 15:20:04 +00:00
|
|
|
do {
|
|
|
|
if (feof(in) || fgets(line, 1024, in) == NULL) {
|
|
|
|
lua_pushnil(L);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} while (line[0] == '#'); /* comment at start of file */
|
|
|
|
assert(line[strlen(line)-1] == '\n');
|
|
|
|
do {
|
2021-12-11 08:30:42 +00:00
|
|
|
assert(line[0] == '-' || line[0] == ' ');
|
|
|
|
assert(line[1] == ' ');
|
2021-12-11 15:20:04 +00:00
|
|
|
/* key/value pair always indented at 0, never empty, unambiguously not a multiline string */
|
|
|
|
char key[512] = {'\0'};
|
|
|
|
char value[1024] = {'\0'};
|
|
|
|
assert(line[2] != ' ');
|
|
|
|
assert(line[2] != '>');
|
|
|
|
assert(line[2] != '\n');
|
|
|
|
assert(line[2] != '\0');
|
2021-12-11 08:30:42 +00:00
|
|
|
memset(key, 0, 512);
|
|
|
|
memset(value, 0, 1024);
|
|
|
|
sscanf(line+2, "%s%s", key, value);
|
|
|
|
assert(key[strlen(key)-1] == ':');
|
|
|
|
key[strlen(key)-1] = '\0';
|
|
|
|
lua_pushstring(L, key);
|
2021-12-11 15:20:04 +00:00
|
|
|
if (value[0] != '\0') {
|
2021-12-11 08:30:42 +00:00
|
|
|
lua_pushstring(L, value); /* value string on same line */
|
2021-12-11 17:37:23 +00:00
|
|
|
char c = fgetc(in);
|
|
|
|
ungetc(c, in);
|
|
|
|
if (c == '-') {
|
|
|
|
strcpy(line, "-\n");
|
|
|
|
}
|
|
|
|
else {
|
2021-12-11 18:18:41 +00:00
|
|
|
memset(line, '\0', 1024);
|
|
|
|
fgets(line, 1024, in);
|
2021-12-11 17:37:23 +00:00
|
|
|
}
|
2021-12-11 15:20:04 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
teliva_load_multiline_string(L, in, line, 1024); /* load from later lines */
|
|
|
|
}
|
2021-12-11 08:30:42 +00:00
|
|
|
lua_settable(L, def_idx);
|
2021-12-11 15:20:04 +00:00
|
|
|
} while (line[0] == ' ');
|
2021-12-11 08:30:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void load_tlv(lua_State* L, char* filename) {
|
|
|
|
lua_newtable(L);
|
|
|
|
int history_array = lua_gettop(L);
|
|
|
|
FILE* in = fopen(filename, "r");
|
2021-12-11 22:01:34 +00:00
|
|
|
if (in == NULL) {
|
|
|
|
endwin();
|
|
|
|
fprintf(stderr, "no such file\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
2021-12-11 08:30:42 +00:00
|
|
|
for (int i = 1; !feof(in); ++i) {
|
|
|
|
teliva_load_definition(L, in);
|
|
|
|
if (lua_isnil(L, -1)) break;
|
|
|
|
lua_rawseti(L, history_array, i);
|
|
|
|
}
|
|
|
|
fclose(in);
|
|
|
|
lua_setglobal(L, "teliva_program");
|
|
|
|
}
|
2021-12-11 17:37:23 +00:00
|
|
|
|
2022-01-03 00:35:01 +00:00
|
|
|
void emit_multiline_string(FILE* out, const char* value) {
|
2021-12-11 17:37:23 +00:00
|
|
|
fprintf(out, " >");
|
|
|
|
for (const char* curr = value; *curr != '\0'; ++curr) {
|
|
|
|
if (*curr == '\n' && *(curr+1) != '\0')
|
|
|
|
fprintf(out, "\n >");
|
|
|
|
else
|
|
|
|
fprintf(out, "%c", *curr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-09 17:18:05 +00:00
|
|
|
static const char* special_history_keys[] = {
|
|
|
|
"__teliva_timestamp",
|
|
|
|
"__teliva_note",
|
|
|
|
"__teliva_undo",
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* save key and its value at top of stack to out
|
|
|
|
* no stack side effects */
|
|
|
|
static void save_tlv_key(lua_State* L, const char* key, FILE* out) {
|
|
|
|
if (strcmp(key, "__teliva_undo") == 0) {
|
|
|
|
fprintf(out, "%s: %ld\n", key, lua_tointeger(L, -1));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const char* value = lua_tostring(L, -1);
|
|
|
|
if (strchr(value, ' ') || strchr(value, '\n')) {
|
|
|
|
fprintf(out, "%s:\n", key);
|
|
|
|
emit_multiline_string(out, value);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
fprintf(out, "%s: %s\n", key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-11 17:37:23 +00:00
|
|
|
void save_tlv(lua_State* L, char* filename) {
|
|
|
|
lua_getglobal(L, "teliva_program");
|
|
|
|
int history_array = lua_gettop(L);
|
|
|
|
int history_array_size = luaL_getn(L, history_array);
|
2021-12-17 16:46:11 +00:00
|
|
|
char outfilename[] = "teliva_out_XXXXXX";
|
|
|
|
int outfd = mkstemp(outfilename);
|
|
|
|
if (outfd == -1) {
|
|
|
|
endwin();
|
|
|
|
perror("save_tlv: error in creating temporary file");
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
FILE *out = fdopen(outfd, "w");
|
2021-12-11 17:37:23 +00:00
|
|
|
fprintf(out, "# .tlv file generated by https://github.com/akkartik/teliva\n");
|
|
|
|
fprintf(out, "# You may edit it if you are careful; however, you may see cryptic errors if you\n");
|
|
|
|
fprintf(out, "# violate Teliva's assumptions.\n");
|
|
|
|
fprintf(out, "#\n");
|
|
|
|
fprintf(out, "# .tlv files are representations of Teliva programs. Teliva programs consist of\n");
|
|
|
|
fprintf(out, "# sequences of definitions. Each definition is a table of key/value pairs. Keys\n");
|
|
|
|
fprintf(out, "# and values are both strings.\n");
|
|
|
|
fprintf(out, "#\n");
|
|
|
|
fprintf(out, "# Lines in .tlv files always follow exactly one of the following forms:\n");
|
|
|
|
fprintf(out, "# - comment lines at the top of the file starting with '#' at column 0\n");
|
|
|
|
fprintf(out, "# - beginnings of definitions starting with '- ' at column 0, followed by a\n");
|
|
|
|
fprintf(out, "# key/value pair\n");
|
|
|
|
fprintf(out, "# - key/value pairs consisting of ' ' at column 0, containing either a\n");
|
|
|
|
fprintf(out, "# spaceless value on the same line, or a multi-line value\n");
|
|
|
|
fprintf(out, "# - multiline values indented by more than 2 spaces, starting with a '>'\n");
|
|
|
|
fprintf(out, "#\n");
|
|
|
|
fprintf(out, "# If these constraints are violated, Teliva may unceremoniously crash. Please\n");
|
|
|
|
fprintf(out, "# report bugs at http://akkartik.name/contact\n");
|
|
|
|
for (int i = 1; i <= history_array_size; ++i) {
|
|
|
|
lua_rawgeti(L, history_array, i);
|
|
|
|
int table = lua_gettop(L);
|
|
|
|
int first = 1;
|
2022-02-09 17:18:05 +00:00
|
|
|
// standardize order of special keys
|
|
|
|
for (int k = 0; special_history_keys[k]; ++k) {
|
|
|
|
lua_getfield(L, table, special_history_keys[k]);
|
|
|
|
if (!lua_isnil(L, -1)) {
|
|
|
|
if (first) fprintf(out, "- ");
|
|
|
|
else fprintf(out, " ");
|
|
|
|
first = 0;
|
|
|
|
save_tlv_key(L, special_history_keys[k], out);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
2021-12-11 17:37:23 +00:00
|
|
|
for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
|
2022-02-09 17:18:05 +00:00
|
|
|
if (is_special_history_key(lua_tostring(L, -2)))
|
|
|
|
continue;
|
2021-12-11 17:37:23 +00:00
|
|
|
if (first) fprintf(out, "- ");
|
|
|
|
else fprintf(out, " ");
|
|
|
|
first = 0;
|
2022-02-09 17:18:05 +00:00
|
|
|
save_tlv_key(L, lua_tostring(L, -2), out);
|
2021-12-11 17:37:23 +00:00
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
fclose(out);
|
2021-12-17 16:46:11 +00:00
|
|
|
rename(outfilename, filename);
|
2021-12-11 17:37:23 +00:00
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
2021-12-25 21:05:37 +00:00
|
|
|
|
|
|
|
int is_special_history_key(const char* key) {
|
|
|
|
for (const char** curr = special_history_keys; *curr != NULL; ++curr) {
|
|
|
|
if (strcmp(*curr, key) == 0)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|